diff --git a/common/src/main/java/nl/matsv/viabackwards/protocol/protocol1_12_2to1_13/Protocol1_12_2To1_13.java b/common/src/main/java/nl/matsv/viabackwards/protocol/protocol1_12_2to1_13/Protocol1_12_2To1_13.java index 1ebb78a1..850499aa 100644 --- a/common/src/main/java/nl/matsv/viabackwards/protocol/protocol1_12_2to1_13/Protocol1_12_2To1_13.java +++ b/common/src/main/java/nl/matsv/viabackwards/protocol/protocol1_12_2to1_13/Protocol1_12_2To1_13.java @@ -81,7 +81,6 @@ public class Protocol1_12_2To1_13 extends BackwardsProtocol { + private final CommandRewriter commandRewriter = new CommandRewriter(protocol) {}; + public PlayerPacket1_13(Protocol1_12_2To1_13 protocol) { super(protocol); } @@ -301,6 +305,47 @@ public class PlayerPacket1_13 extends Rewriter { } }); + protocol.registerOutgoing(ClientboundPackets1_13.DECLARE_COMMANDS, null, new PacketRemapper() { + @Override + public void registerMap() { + handler(wrapper -> { + wrapper.cancel(); + + TabCompleteStorage storage = wrapper.user().get(TabCompleteStorage.class); + + if (!storage.commands.isEmpty()) { + storage.commands.clear(); + } + + int size = wrapper.read(Type.VAR_INT); + for (int i = 0; i < size; i++) { + byte flags = wrapper.read(Type.BYTE); + wrapper.read(Type.VAR_INT_ARRAY_PRIMITIVE); // Children indices + if ((flags & 0x08) != 0) { + wrapper.read(Type.VAR_INT); // Redirect node index + } + + byte nodeType = (byte) (flags & 0x03); + if (nodeType == 1 || nodeType == 2) { // Literal/argument node + String name = wrapper.read(Type.STRING); + + if (nodeType == 1) { + storage.commands.add('/' + name); + } + } + + if (nodeType == 2) { // Argument node + commandRewriter.handleArgument(wrapper, wrapper.read(Type.STRING)); + } + + if ((flags & 0x10) != 0) { + wrapper.read(Type.STRING); // Suggestion type + } + } + }); + } + }); + protocol.registerOutgoing(ClientboundPackets1_13.TAB_COMPLETE, new PacketRemapper() { @Override public void registerMap() { @@ -342,34 +387,49 @@ public class PlayerPacket1_13 extends Rewriter { public void registerMap() { handler(wrapper -> { TabCompleteStorage storage = wrapper.user().get(TabCompleteStorage.class); - int id = ThreadLocalRandom.current().nextInt(); - wrapper.write(Type.VAR_INT, id); + List suggestions = new ArrayList<>(); String command = wrapper.read(Type.STRING); boolean assumeCommand = wrapper.read(Type.BOOLEAN); wrapper.read(Type.OPTIONAL_POSITION); - if (!assumeCommand) { - if (command.startsWith("/")) { - command = command.substring(1); - } else { - wrapper.cancel(); - PacketWrapper response = wrapper.create(0xE); - List usernames = new ArrayList<>(); - for (String value : storage.usernames.values()) { - if (value.toLowerCase().startsWith(command.substring(command.lastIndexOf(' ') + 1).toLowerCase())) { - usernames.add(value); - } + if (!assumeCommand && !command.startsWith("/")) { + // Complete usernames for non-commands + String buffer = command.substring(command.lastIndexOf(' ') + 1); + for (String value : storage.usernames.values()) { + if (startsWithIgnoreCase(value, buffer)) { + suggestions.add(value); } - response.write(Type.VAR_INT, usernames.size()); - for (String value : usernames) { - response.write(Type.STRING, value); + } + } else if (!storage.commands.isEmpty() && !command.contains(" ")) { + // Complete commands names with values from 'Declare Commands' packet + for (String value : storage.commands) { + if (startsWithIgnoreCase(value, command)) { + suggestions.add(value); } - response.send(protocol.getClass()); } } + if (!suggestions.isEmpty()) { + wrapper.cancel(); + PacketWrapper response = wrapper.create(ClientboundPackets1_12_1.TAB_COMPLETE); + response.write(Type.VAR_INT, suggestions.size()); + for (String value : suggestions) { + response.write(Type.STRING, value); + } + response.send(Protocol1_12_2To1_13.class); + storage.lastRequest = null; + return; + } + + if (!assumeCommand && command.startsWith("/")) { + command = command.substring(1); + } + + int id = ThreadLocalRandom.current().nextInt(); + wrapper.write(Type.VAR_INT, id); wrapper.write(Type.STRING, command); + storage.lastId = id; storage.lastAssumeCommand = assumeCommand; storage.lastRequest = command; @@ -424,7 +484,6 @@ public class PlayerPacket1_13 extends Rewriter { wrapper.passthrough(Type.STRING); //Command - byte flags = 0; if (wrapper.read(Type.BOOLEAN)) flags |= 0x01; //Track Output @@ -579,4 +638,11 @@ public class PlayerPacket1_13 extends Rewriter { } }); } + + private static boolean startsWithIgnoreCase(String string, String prefix) { + if (string.length() < prefix.length()) { + return false; + } + return string.regionMatches(true, 0, prefix, 0, prefix.length()); + } } diff --git a/common/src/main/java/nl/matsv/viabackwards/protocol/protocol1_12_2to1_13/storage/TabCompleteStorage.java b/common/src/main/java/nl/matsv/viabackwards/protocol/protocol1_12_2to1_13/storage/TabCompleteStorage.java index 6d521cd0..59779579 100644 --- a/common/src/main/java/nl/matsv/viabackwards/protocol/protocol1_12_2to1_13/storage/TabCompleteStorage.java +++ b/common/src/main/java/nl/matsv/viabackwards/protocol/protocol1_12_2to1_13/storage/TabCompleteStorage.java @@ -21,7 +21,9 @@ import us.myles.ViaVersion.api.data.StoredObject; import us.myles.ViaVersion.api.data.UserConnection; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; +import java.util.Set; import java.util.UUID; public class TabCompleteStorage extends StoredObject { @@ -29,6 +31,7 @@ public class TabCompleteStorage extends StoredObject { public String lastRequest; public boolean lastAssumeCommand; public Map usernames = new HashMap<>(); + public Set commands = new HashSet<>(); public TabCompleteStorage(UserConnection user) { super(user);