From 02a725035c527f0c64fc75094ad71fbd18fb2466 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Fri, 15 Feb 2019 16:03:15 -0500 Subject: [PATCH] Another round of improvements to tab complete. Fix fallback servers. --- .../client/ClientPlaySessionHandler.java | 107 ++++++++++++------ .../connection/client/ConnectedPlayer.java | 39 +++++-- .../protocol/packet/TabCompleteRequest.java | 2 +- .../protocol/packet/TabCompleteResponse.java | 4 + 4 files changed, 111 insertions(+), 41 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java index bf6524fe7..a70f2952c 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java @@ -57,7 +57,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { private final Set knownChannels = new HashSet<>(); private final Queue loginPluginMessages = new ArrayDeque<>(); private final VelocityServer server; - private @Nullable TabCompleteRequest outstandingTabComplete; + private @Nullable TabCompleteRequest legacyCommandTabComplete; public ClientPlaySessionHandler(VelocityServer server, ConnectedPlayer player) { this.player = player; @@ -143,7 +143,63 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { return false; } - // See if this is a proxy command. + if (player.getProtocolVersion().compareTo(MINECRAFT_1_13) >= 0) { + return handleTabCompleteModern(packet); + } else { + return handleTabCompleteLegacy(packet); + } + } + + private boolean handleTabCompleteModern(TabCompleteRequest packet) { + // In 1.13+, we need to do additional work for the richer suggestions available. + String command = packet.getCommand().substring(1); + int spacePos = command.indexOf(' '); + if (spacePos == -1) { + return false; + } + + String commandLabel = command.substring(0, spacePos); + if (!server.getCommandManager().hasCommand(commandLabel)) { + return false; + } + + + List suggestions = server.getCommandManager().offerSuggestions(player, command); + if (suggestions.isEmpty()) { + return false; + } + + List offers = new ArrayList<>(); + int longestLength = 0; + for (String suggestion : suggestions) { + offers.add(new Offer(suggestion)); + if (suggestion.length() > longestLength) { + longestLength = suggestion.length(); + } + } + + TabCompleteResponse resp = new TabCompleteResponse(); + resp.setTransactionId(packet.getTransactionId()); + + int startPos = packet.getCommand().lastIndexOf(' ') + 1; + int length; + if (startPos == 0) { + startPos = packet.getCommand().length() + 1; + length = longestLength; + } else { + length = packet.getCommand().substring(startPos).indexOf(' ') + 1; + } + + resp.setStart(startPos); + resp.setLength(length); + resp.getOffers().addAll(offers); + + player.getMinecraftConnection().write(resp); + return true; + } + + private boolean handleTabCompleteLegacy(TabCompleteRequest packet) { + // Let us check for a possible proxy command. String command = packet.getCommand().substring(1); int spacePos = command.indexOf(' '); if (spacePos >= 0) { @@ -151,18 +207,11 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { if (server.getCommandManager().hasCommand(commandLabel)) { List suggestions = server.getCommandManager().offerSuggestions(player, command); if (!suggestions.isEmpty()) { - int longestLength = 0; List offers = new ArrayList<>(); for (String suggestion : suggestions) { - offers.add(new Offer(suggestion, null)); - if (suggestion.length() > longestLength) { - longestLength = suggestion.length(); - } + offers.add(new Offer(suggestion)); } TabCompleteResponse resp = new TabCompleteResponse(); - resp.setTransactionId(packet.getTransactionId()); - resp.setStart(command.lastIndexOf(' ') + 2); - resp.setLength(longestLength); resp.getOffers().addAll(offers); player.getMinecraftConnection().write(resp); @@ -171,13 +220,10 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { } } - boolean is113 = player.getProtocolVersion().compareTo(MINECRAFT_1_13) >= 0; - if (!is113) { - // Outstanding tab completes are recorded for use with 1.12 clients and below to provide - // tab list completion support for command names. In 1.13, Brigadier handles everything for - // us. - outstandingTabComplete = packet; - } + // Outstanding tab completes are recorded for use with 1.12 clients and below to provide + // tab list completion support for command names. In 1.13, Brigadier handles everything for + // us. + legacyCommandTabComplete = packet; return false; } @@ -398,23 +444,20 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { * @param response the tab complete response from the backend */ public void handleTabCompleteResponse(TabCompleteResponse response) { - if (outstandingTabComplete != null) { - if (!outstandingTabComplete.isAssumeCommand() - && outstandingTabComplete.getCommand().startsWith("/")) { - String command = outstandingTabComplete.getCommand().substring(1); - try { - List offers = server.getCommandManager().offerSuggestions(player, command); - for (String offer : offers) { - response.getOffers().add(new Offer(offer, null)); - } - response.getOffers().sort(null); - } catch (Exception e) { - logger.error("Unable to provide tab list completions for {} for command '{}'", - player.getUsername(), - command, e); + if (legacyCommandTabComplete != null) { + String command = legacyCommandTabComplete.getCommand().substring(1); + try { + List offers = server.getCommandManager().offerSuggestions(player, command); + for (String offer : offers) { + response.getOffers().add(new Offer(offer, null)); } - outstandingTabComplete = null; + response.getOffers().sort(null); + } catch (Exception e) { + logger.error("Unable to provide tab list completions for {} for command '{}'", + player.getUsername(), + command, e); } + legacyCommandTabComplete = null; } player.getMinecraftConnection().write(response); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java index 0ab45a4f3..9122fbe38 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java @@ -357,13 +357,13 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { private void handleConnectionException(RegisteredServer rs, @Nullable Component kickReason, Component friendlyReason) { - // There can't be any connection in flight now. - connectionInFlight = null; - if (connectedServer == null) { // The player isn't yet connected to a server. - Optional nextServer = getNextServerToTry(); + Optional nextServer = getNextServerToTry(rs); if (nextServer.isPresent()) { + // There can't be any connection in flight now. + connectionInFlight = null; + createConnectionRequest(nextServer.get()).fireAndForget(); } else { disconnect(friendlyReason); @@ -372,7 +372,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { boolean kickedFromCurrent = connectedServer.getServer().equals(rs); ServerKickResult result; if (kickedFromCurrent) { - Optional next = getNextServerToTry(); + Optional next = getNextServerToTry(rs); result = next.map(RedirectPlayer::create) .orElseGet(() -> DisconnectPlayer.create(friendlyReason)); } else { @@ -392,6 +392,9 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { DisconnectPlayer res = (DisconnectPlayer) event.getResult(); disconnect(res.getReason()); } else if (event.getResult() instanceof RedirectPlayer) { + // There can't be any connection in flight now. + connectionInFlight = null; + RedirectPlayer res = (RedirectPlayer) event.getResult(); createConnectionRequest(res.getServer()) .connectWithIndication() @@ -419,9 +422,22 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { /** * Finds another server to attempt to log into, if we were unexpectedly disconnected from the * server. + * * @return the next server to try */ public Optional getNextServerToTry() { + return this.getNextServerToTry(null); + } + + /** + * Finds another server to attempt to log into, if we were unexpectedly disconnected from the + * server. + * + * @param current the "current" server that the player is on, useful as an override + * + * @return the next server to try + */ + private Optional getNextServerToTry(@Nullable RegisteredServer current) { if (serversToTry == null) { String virtualHostStr = getVirtualHost().map(InetSocketAddress::getHostString).orElse(""); serversToTry = server.getConfiguration().getForcedHosts().getOrDefault(virtualHostStr, @@ -432,17 +448,24 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { serversToTry = server.getConfiguration().getAttemptConnectionOrder(); } - for (; tryIndex < serversToTry.size(); tryIndex++) { - String toTryName = serversToTry.get(tryIndex); - if (connectedServer != null && toTryName.equals(connectedServer.getServerInfo().getName())) { + for (int i = tryIndex; i < serversToTry.size(); i++) { + String toTryName = serversToTry.get(i); + if ((connectedServer != null && hasSameName(connectedServer.getServer(), toTryName)) + || (connectionInFlight != null && hasSameName(connectionInFlight.getServer(), toTryName)) + || (current != null && hasSameName(current, toTryName))) { continue; } + tryIndex = i; return server.getServer(toTryName); } return Optional.empty(); } + private static boolean hasSameName(RegisteredServer server, String name) { + return server.getServerInfo().getName().equalsIgnoreCase(name); + } + /** * Sets the player's new connected server and clears the in-flight connection. * diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/TabCompleteRequest.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/TabCompleteRequest.java index d334e8db3..27afacaf9 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/TabCompleteRequest.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/TabCompleteRequest.java @@ -38,7 +38,7 @@ public class TabCompleteRequest implements MinecraftPacket { this.assumeCommand = assumeCommand; } - public boolean isHasPosition() { + public boolean hasPosition() { return hasPosition; } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/TabCompleteResponse.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/TabCompleteResponse.java index b6e274bb8..5fcb3a7bd 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/TabCompleteResponse.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/TabCompleteResponse.java @@ -112,6 +112,10 @@ public class TabCompleteResponse implements MinecraftPacket { @Nullable private final Component tooltip; + public Offer(String text) { + this(text, null); + } + public Offer(String text, @Nullable Component tooltip) { this.text = text;