3
0
Mirror von https://github.com/PaperMC/Velocity.git synchronisiert 2024-09-29 06:30:16 +02:00

[WIP] Inject commands for Minecraft 1.13+

Dieser Commit ist enthalten in:
Andrew Steinborn 2018-12-29 09:28:24 -05:00
Ursprung b6944bbec2
Commit 2cedb457ce
21 geänderte Dateien mit 1034 neuen und 46 gelöschten Zeilen

Datei anzeigen

@ -33,9 +33,16 @@ allprojects {
repositories {
mavenLocal()
mavenCentral()
// for kyoripowered dependencies
maven {
url 'https://oss.sonatype.org/content/groups/public/'
}
// Brigadier
maven {
url "https://libraries.minecraft.net"
}
}
test {

Datei anzeigen

@ -47,6 +47,8 @@ dependencies {
compile 'it.unimi.dsi:fastutil:8.2.1'
compile 'net.kyori:event-method-asm:3.0.0'
compile 'com.mojang:brigadier:1.0.15'
testCompile "org.junit.jupiter:junit-jupiter-api:${junitVersion}"
testCompile "org.junit.jupiter:junit-jupiter-engine:${junitVersion}"
}

Datei anzeigen

@ -2,6 +2,7 @@ package com.velocitypowered.proxy.command;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.velocitypowered.api.command.Command;
import com.velocitypowered.api.command.CommandManager;
import com.velocitypowered.api.command.CommandSource;
@ -11,6 +12,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import org.checkerframework.checker.nullness.qual.NonNull;
public class VelocityCommandManager implements CommandManager {
@ -68,6 +70,10 @@ public class VelocityCommandManager implements CommandManager {
return commands.containsKey(command);
}
public Set<String> getAllRegisteredCommands() {
return ImmutableSet.copyOf(commands.keySet());
}
/**
* Offer suggestions to fill in the command.
* @param source the source for the command
@ -116,4 +122,31 @@ public class VelocityCommandManager implements CommandManager {
"Unable to invoke suggestions for command " + alias + " for " + source, e);
}
}
public boolean hasPermission(CommandSource source, String cmdLine) {
Preconditions.checkNotNull(source, "source");
Preconditions.checkNotNull(cmdLine, "cmdLine");
String[] split = cmdLine.split(" ", -1);
if (split.length == 0) {
// No command available.
return false;
}
String alias = split[0];
@SuppressWarnings("nullness")
String[] actualArgs = Arrays.copyOfRange(split, 1, split.length);
Command command = commands.get(alias.toLowerCase(Locale.ENGLISH));
if (command == null) {
// No such command.
return false;
}
try {
return command.hasPermission(source, actualArgs);
} catch (Exception e) {
throw new RuntimeException(
"Unable to invoke suggestions for command " + alias + " for " + source, e);
}
}
}

Datei anzeigen

@ -1,6 +1,7 @@
package com.velocitypowered.proxy.connection;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.packet.AvailableCommands;
import com.velocitypowered.proxy.protocol.packet.BossBar;
import com.velocitypowered.proxy.protocol.packet.Chat;
import com.velocitypowered.proxy.protocol.packet.ClientSettings;
@ -67,6 +68,10 @@ public interface MinecraftSessionHandler {
}
default boolean handle(AvailableCommands commands) {
return false;
}
default boolean handle(BossBar packet) {
return false;
}

Datei anzeigen

@ -1,5 +1,9 @@
package com.velocitypowered.proxy.connection.backend;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
import com.mojang.brigadier.tree.LiteralCommandNode;
import com.velocitypowered.api.event.connection.PluginMessageEvent;
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
import com.velocitypowered.api.network.ProtocolVersion;
@ -10,6 +14,8 @@ import com.velocitypowered.proxy.connection.client.ClientPlaySessionHandler;
import com.velocitypowered.proxy.connection.forge.legacy.LegacyForgeConstants;
import com.velocitypowered.proxy.connection.util.ConnectionMessages;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.packet.AvailableCommands;
import com.velocitypowered.proxy.protocol.packet.AvailableCommands.ProtocolSuggestionProvider;
import com.velocitypowered.proxy.protocol.packet.BossBar;
import com.velocitypowered.proxy.protocol.packet.Disconnect;
import com.velocitypowered.proxy.protocol.packet.JoinGame;
@ -128,6 +134,25 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
return false; //Forward packet to player
}
@Override
public boolean handle(AvailableCommands commands) {
// Inject commands from the proxy.
for (String command : server.getCommandManager().getAllRegisteredCommands()) {
if (!server.getCommandManager().hasPermission(serverConn.getPlayer(), command)) {
continue;
}
LiteralCommandNode<Object> root = LiteralArgumentBuilder.literal(command)
.then(RequiredArgumentBuilder.argument("args", StringArgumentType.greedyString())
.suggests(new ProtocolSuggestionProvider("minecraft:ask_server"))
.build())
.executes((ctx) -> 0)
.build();
commands.getRootNode().addChild(root);
}
return false;
}
@Override
public void handleGeneric(MinecraftPacket packet) {
serverConn.getPlayer().getConnection().write(packet);

Datei anzeigen

@ -1,5 +1,7 @@
package com.velocitypowered.proxy.connection.client;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_13;
import com.velocitypowered.api.event.connection.PluginMessageEvent;
import com.velocitypowered.api.event.player.PlayerChatEvent;
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
@ -19,6 +21,7 @@ import com.velocitypowered.proxy.protocol.packet.PluginMessage;
import com.velocitypowered.proxy.protocol.packet.Respawn;
import com.velocitypowered.proxy.protocol.packet.TabCompleteRequest;
import com.velocitypowered.proxy.protocol.packet.TabCompleteResponse;
import com.velocitypowered.proxy.protocol.packet.TabCompleteResponse.Offer;
import com.velocitypowered.proxy.protocol.packet.TitlePacket;
import com.velocitypowered.proxy.protocol.util.PluginMessageUtil;
import com.velocitypowered.proxy.util.ThrowableUtils;
@ -133,23 +136,39 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
@Override
public boolean handle(TabCompleteRequest packet) {
// Record the request so that the outstanding request can be augmented later.
if (!packet.isAssumeCommand() && packet.getCommand().startsWith("/")) {
int spacePos = packet.getCommand().indexOf(' ');
if (spacePos > 0) {
String cmd = packet.getCommand().substring(1, spacePos);
if (server.getCommandManager().hasCommand(cmd)) {
List<String> suggestions = server.getCommandManager()
.offerSuggestions(player, packet.getCommand().substring(1));
if (!suggestions.isEmpty()) {
TabCompleteResponse resp = new TabCompleteResponse();
resp.getOffers().addAll(suggestions);
player.getConnection().write(resp);
return true;
boolean is113 = player.getProtocolVersion().compareTo(MINECRAFT_1_13) >= 0;
boolean isCommand = is113 || (!packet.isAssumeCommand() && packet.getCommand().startsWith("/"));
if (!isCommand) {
// 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;
return false;
}
int spacePos = packet.getCommand().indexOf(' ');
if (spacePos > 0) {
String command = is113 ? packet.getCommand() : packet.getCommand().substring(1);
String commandLabel = command.substring(0, spacePos);
if (server.getCommandManager().hasCommand(commandLabel)) {
List<String> suggestions = server.getCommandManager().offerSuggestions(player, command);
if (!suggestions.isEmpty()) {
List<Offer> offers = new ArrayList<>();
for (String suggestion : suggestions) {
offers.add(new Offer(suggestion, null));
}
TabCompleteResponse resp = new TabCompleteResponse();
resp.setTransactionId(packet.getTransactionId());
resp.setStart(spacePos);
resp.setLength(command.length() - spacePos);
resp.getOffers().addAll(offers);
player.getConnection().write(resp);
return true;
}
}
}
outstandingTabComplete = packet;
return false;
}
@ -323,7 +342,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
// Tell the server about this client's plugin message channels.
ProtocolVersion serverVersion = serverMc.getProtocolVersion();
Collection<String> toRegister = new HashSet<>(knownChannels);
if (serverVersion.compareTo(ProtocolVersion.MINECRAFT_1_13) >= 0) {
if (serverVersion.compareTo(MINECRAFT_1_13) >= 0) {
toRegister.addAll(server.getChannelRegistrar().getModernChannelIds());
} else {
toRegister.addAll(server.getChannelRegistrar().getIdsForLegacyConnections());
@ -362,7 +381,11 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
&& outstandingTabComplete.getCommand().startsWith("/")) {
String command = outstandingTabComplete.getCommand().substring(1);
try {
response.getOffers().addAll(server.getCommandManager().offerSuggestions(player, command));
List<String> 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(),

Datei anzeigen

@ -109,6 +109,22 @@ public enum ProtocolUtils {
buf.writeBytes(array);
}
public static int[] readIntegerArray(ByteBuf buf) {
int len = readVarInt(buf);
int[] array = new int[len];
for (int i = 0; i < len; i++) {
array[i] = readVarInt(buf);
}
return array;
}
public static void writeIntegerArray(ByteBuf buf, int[] array) {
writeVarInt(buf, array.length);
for (int i : array) {
writeVarInt(buf, i);
}
}
public static UUID readUuid(ByteBuf buf) {
long msb = buf.readLong();
long lsb = buf.readLong();

Datei anzeigen

@ -19,6 +19,7 @@ import static com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
import com.google.common.collect.ImmutableList;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.protocol.packet.AvailableCommands;
import com.velocitypowered.proxy.protocol.packet.BossBar;
import com.velocitypowered.proxy.protocol.packet.Chat;
import com.velocitypowered.proxy.protocol.packet.ClientSettings;
@ -83,7 +84,8 @@ public enum StateRegistry {
map(0x14, MINECRAFT_1_8, false),
map(0x01, MINECRAFT_1_9, false),
map(0x02, MINECRAFT_1_12, false),
map(0x01, MINECRAFT_1_12_1, false));
map(0x01, MINECRAFT_1_12_1, false),
map(0x05, MINECRAFT_1_13, false));
serverbound.register(Chat.class, Chat::new,
map(0x01, MINECRAFT_1_8, false),
map(0x02, MINECRAFT_1_9, false),
@ -121,7 +123,10 @@ public enum StateRegistry {
clientbound.register(TabCompleteResponse.class, TabCompleteResponse::new,
map(0x3A, MINECRAFT_1_8, false),
map(0x0E, MINECRAFT_1_9, false),
map(0x0E, MINECRAFT_1_12, false));
map(0x0E, MINECRAFT_1_12, false),
map(0x10, MINECRAFT_1_13, false));
clientbound.register(AvailableCommands.class, AvailableCommands::new,
map(0x11, MINECRAFT_1_13, false));
clientbound.register(PluginMessage.class, PluginMessage::new,
map(0x3F, MINECRAFT_1_8, false),
map(0x18, MINECRAFT_1_9, false),

Datei anzeigen

@ -0,0 +1,320 @@
package com.velocitypowered.proxy.protocol.packet;
import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableList;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.arguments.ArgumentType;
import com.mojang.brigadier.builder.ArgumentBuilder;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.suggestion.SuggestionProvider;
import com.mojang.brigadier.suggestion.Suggestions;
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import com.mojang.brigadier.tree.ArgumentCommandNode;
import com.mojang.brigadier.tree.CommandNode;
import com.mojang.brigadier.tree.LiteralCommandNode;
import com.mojang.brigadier.tree.RootCommandNode;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
import com.velocitypowered.proxy.protocol.packet.brigadier.ArgumentPropertyRegistry;
import io.netty.buffer.ByteBuf;
import it.unimi.dsi.fastutil.objects.Object2IntLinkedOpenHashMap;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Deque;
import java.util.Iterator;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.CompletableFuture;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
public class AvailableCommands implements MinecraftPacket {
private static final byte NODE_TYPE_ROOT = 0x00;
private static final byte NODE_TYPE_LITERAL = 0x01;
private static final byte NODE_TYPE_ARGUMENT = 0x02;
private static final byte FLAG_NODE_TYPE = 0x03;
private static final byte FLAG_EXECUTABLE = 0x04;
private static final byte FLAG_IS_REDIRECT = 0x08;
private static final byte FLAG_HAS_SUGGESTIONS = 0x10;
// Note: Velocity doesn't use Brigadier for command handling. This may change in Velocity 2.0.0.
@MonotonicNonNull
private RootCommandNode<Object> rootNode;
public RootCommandNode<Object> getRootNode() {
if (rootNode == null) {
throw new IllegalStateException("Packet not yet deserialized");
}
return rootNode;
}
@Override
public void decode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) {
int commands = ProtocolUtils.readVarInt(buf);
WireNode[] wireNodes = new WireNode[commands];
for (int i = 0; i < commands; i++) {
WireNode node = deserializeNode(buf, i);
wireNodes[i] = node;
}
// Iterate over the deserialized nodes and attempt to form a graph. We also resolve any cycles
// that exist.
Queue<WireNode> nodeQueue = new ArrayDeque<>(Arrays.asList(wireNodes));
while (!nodeQueue.isEmpty()) {
boolean cycling = false;
for (Iterator<WireNode> it = nodeQueue.iterator(); it.hasNext(); ) {
WireNode node = it.next();
if (node.toNode(wireNodes)) {
cycling = true;
it.remove();
}
}
if (!cycling) {
// Uh-oh. We can't cycle. This is bad.
throw new IllegalStateException("Stopped cycling; the root node can't be built.");
}
}
int rootIdx = ProtocolUtils.readVarInt(buf);
rootNode = (RootCommandNode<Object>) wireNodes[rootIdx].built;
}
@Override
public void encode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) {
// Assign all the children an index.
Deque<CommandNode<Object>> childrenQueue = new ArrayDeque<>(ImmutableList.of(rootNode));
Object2IntMap<CommandNode<Object>> idMappings = new Object2IntLinkedOpenHashMap<>();
while (!childrenQueue.isEmpty()) {
CommandNode<Object> child = childrenQueue.poll();
if (!idMappings.containsKey(child)) {
idMappings.put(child, idMappings.size());
childrenQueue.addAll(child.getChildren());
}
}
// Now serialize the children
ProtocolUtils.writeVarInt(buf, idMappings.size());
for (CommandNode<Object> child : idMappings.keySet()) {
serializeNode(child, buf, idMappings);
}
ProtocolUtils.writeVarInt(buf, idMappings.getInt(rootNode));
}
private static void serializeNode(CommandNode<Object> node, ByteBuf buf,
Object2IntMap<CommandNode<Object>> idMappings) {
byte flags = 0;
if (node.getRedirect() != null) {
flags |= FLAG_IS_REDIRECT;
}
if (node.getCommand() != null) {
flags |= FLAG_EXECUTABLE;
}
if (node instanceof RootCommandNode<?>) {
flags |= NODE_TYPE_ROOT;
} else if (node instanceof LiteralCommandNode<?>) {
flags |= NODE_TYPE_LITERAL;
} else if (node instanceof ArgumentCommandNode<?, ?>) {
flags |= NODE_TYPE_ARGUMENT;
if (((ArgumentCommandNode) node).getCustomSuggestions() != null) {
flags |= FLAG_HAS_SUGGESTIONS;
}
} else {
throw new IllegalArgumentException("Unknown node type " + node.getClass().getName());
}
buf.writeByte(flags);
ProtocolUtils.writeVarInt(buf, node.getChildren().size());
for (CommandNode<Object> child : node.getChildren()) {
ProtocolUtils.writeVarInt(buf, idMappings.getInt(child));
}
if (node.getRedirect() != null) {
ProtocolUtils.writeVarInt(buf, idMappings.getInt(node.getRedirect()));
}
if (node instanceof ArgumentCommandNode<?, ?>) {
ProtocolUtils.writeString(buf, node.getName());
ArgumentPropertyRegistry.serialize(buf, ((ArgumentCommandNode) node).getType());
if (((ArgumentCommandNode) node).getCustomSuggestions() != null) {
// The unchecked cast is required, but it is not particularly relevant because we check for
// a more specific type later. (Even then, we only pull out one field.)
@SuppressWarnings("unchecked")
SuggestionProvider<Object> provider = ((ArgumentCommandNode) node).getCustomSuggestions();
if (!(provider instanceof ProtocolSuggestionProvider)) {
throw new IllegalArgumentException("Suggestion provider " + provider.getClass().getName()
+ " is not valid.");
}
ProtocolUtils.writeString(buf, ((ProtocolSuggestionProvider) provider).name);
}
} else if (node instanceof LiteralCommandNode<?>) {
ProtocolUtils.writeString(buf, node.getName());
}
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
private static WireNode deserializeNode(ByteBuf buf, int idx) {
byte flags = buf.readByte();
int[] children = ProtocolUtils.readIntegerArray(buf);
int redirectTo = -1;
if ((flags & FLAG_IS_REDIRECT) > 0) {
redirectTo = ProtocolUtils.readVarInt(buf);
}
switch (flags & FLAG_NODE_TYPE) {
case NODE_TYPE_ROOT:
return new WireNode(idx, flags, children, redirectTo, null);
case NODE_TYPE_LITERAL:
return new WireNode(idx, flags, children, redirectTo, LiteralArgumentBuilder
.literal(ProtocolUtils.readString(buf)));
case NODE_TYPE_ARGUMENT:
String name = ProtocolUtils.readString(buf);
ArgumentType<?> argumentType = ArgumentPropertyRegistry.deserialize(buf);
RequiredArgumentBuilder<Object, ?> argumentBuilder = RequiredArgumentBuilder
.argument(name, argumentType);
if ((flags & FLAG_HAS_SUGGESTIONS) != 0) {
argumentBuilder.suggests(new ProtocolSuggestionProvider(ProtocolUtils.readString(buf)));
}
return new WireNode(idx, flags, children, redirectTo, argumentBuilder);
default:
throw new IllegalArgumentException("Unknown node type " + (flags & FLAG_NODE_TYPE));
}
}
private static class WireNode {
private final int idx;
private final byte flags;
private final int[] children;
private final int redirectTo;
@Nullable
private final ArgumentBuilder<Object, ?> args;
@MonotonicNonNull
private CommandNode<Object> built;
private WireNode(int idx, byte flags, int[] children, int redirectTo,
@Nullable ArgumentBuilder<Object, ?> args) {
this.idx = idx;
this.flags = flags;
this.children = children;
this.redirectTo = redirectTo;
this.args = args;
}
public boolean toNode(WireNode[] wireNodes) {
if (this.built == null) {
// Ensure all children exist. Note that we delay checking if the node has been built yet;
// that needs to come after this node is built.
for (int child : children) {
if (child >= wireNodes.length) {
throw new IllegalStateException("Node points to non-existent index " + redirectTo);
}
}
int type = flags & FLAG_NODE_TYPE;
if (type == NODE_TYPE_ROOT) {
this.built = new RootCommandNode<>();
} else {
if (args == null) {
throw new IllegalStateException("Non-root node without args builder!");
}
// Add any redirects
if (redirectTo != -1) {
if (redirectTo >= wireNodes.length) {
throw new IllegalStateException("Node points to non-existent index " + redirectTo);
}
if (wireNodes[redirectTo].built != null) {
args.redirect(wireNodes[redirectTo].built);
} else {
// Redirect node does not yet exist
return false;
}
}
// If executable, add a dummy command
if ((flags & FLAG_EXECUTABLE) != 0) {
args.executes((Command<Object>) context -> 0);
}
this.built = args.build();
}
}
for (int child : children) {
if (wireNodes[child].built == null) {
// The child is not yet deserialized. The node can't be built now.
return false;
}
}
// Associate children with nodes
for (int child : children) {
CommandNode<Object> childNode = wireNodes[child].built;
if (!(childNode instanceof RootCommandNode)) {
built.addChild(childNode);
}
}
return true;
}
@Override
public String toString() {
MoreObjects.ToStringHelper helper = MoreObjects.toStringHelper(this)
.add("idx", idx)
.add("flags", flags)
.add("children", children)
.add("redirectTo", redirectTo);
if (args != null) {
if (args instanceof LiteralArgumentBuilder) {
helper.add("argsLabel", ((LiteralArgumentBuilder) args).getLiteral());
} else if (args instanceof RequiredArgumentBuilder) {
helper.add("argsName", ((RequiredArgumentBuilder) args).getName());
}
}
return helper.toString();
}
}
/**
* A placeholder {@link SuggestionProvider} used internally to preserve the suggestion provider
* name.
*/
public static class ProtocolSuggestionProvider implements SuggestionProvider<Object> {
private final String name;
public ProtocolSuggestionProvider(String name) {
this.name = name;
}
@Override
public CompletableFuture<Suggestions> getSuggestions(CommandContext<Object> context,
SuggestionsBuilder builder) throws CommandSyntaxException {
return builder.buildFuture();
}
}
}

Datei anzeigen

@ -1,5 +1,9 @@
package com.velocitypowered.proxy.protocol.packet;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_13;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_9;
import com.google.common.base.MoreObjects;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
@ -10,6 +14,7 @@ import org.checkerframework.checker.nullness.qual.Nullable;
public class TabCompleteRequest implements MinecraftPacket {
private @Nullable String command;
private int transactionId;
private boolean assumeCommand;
private boolean hasPosition;
private long position;
@ -49,25 +54,39 @@ public class TabCompleteRequest implements MinecraftPacket {
this.position = position;
}
public int getTransactionId() {
return transactionId;
}
public void setTransactionId(int transactionId) {
this.transactionId = transactionId;
}
@Override
public String toString() {
return "TabCompleteRequest{"
+ "command='" + command + '\''
+ ", assumeCommand=" + assumeCommand
+ ", hasPosition=" + hasPosition
+ ", position=" + position
+ '}';
return MoreObjects.toStringHelper(this)
.add("command", command)
.add("transactionId", transactionId)
.add("assumeCommand", assumeCommand)
.add("hasPosition", hasPosition)
.add("position", position)
.toString();
}
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
this.command = ProtocolUtils.readString(buf);
if (version.compareTo(ProtocolVersion.MINECRAFT_1_9) >= 0) {
this.assumeCommand = buf.readBoolean();
}
this.hasPosition = buf.readBoolean();
if (hasPosition) {
this.position = buf.readLong();
if (version.compareTo(MINECRAFT_1_13) >= 0) {
this.transactionId = ProtocolUtils.readVarInt(buf);
this.command = ProtocolUtils.readString(buf);
} else {
this.command = ProtocolUtils.readString(buf);
if (version.compareTo(MINECRAFT_1_9) >= 0) {
this.assumeCommand = buf.readBoolean();
}
this.hasPosition = buf.readBoolean();
if (hasPosition) {
this.position = buf.readLong();
}
}
}
@ -76,13 +95,19 @@ public class TabCompleteRequest implements MinecraftPacket {
if (command == null) {
throw new IllegalStateException("Command is not specified");
}
ProtocolUtils.writeString(buf, command);
if (version.compareTo(ProtocolVersion.MINECRAFT_1_9) >= 0) {
buf.writeBoolean(assumeCommand);
}
buf.writeBoolean(hasPosition);
if (hasPosition) {
buf.writeLong(position);
if (version.compareTo(MINECRAFT_1_13) >= 0) {
ProtocolUtils.writeVarInt(buf, transactionId);
ProtocolUtils.writeString(buf, command);
} else {
ProtocolUtils.writeString(buf, command);
if (version.compareTo(MINECRAFT_1_9) >= 0) {
buf.writeBoolean(assumeCommand);
}
buf.writeBoolean(hasPosition);
if (hasPosition) {
buf.writeLong(position);
}
}
}

Datei anzeigen

@ -1,5 +1,8 @@
package com.velocitypowered.proxy.protocol.packet;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_13;
import com.google.common.base.MoreObjects;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
@ -7,35 +10,95 @@ import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
import java.util.ArrayList;
import java.util.List;
import net.kyori.text.Component;
import net.kyori.text.serializer.ComponentSerializers;
import org.checkerframework.checker.nullness.qual.Nullable;
public class TabCompleteResponse implements MinecraftPacket {
private final List<String> offers = new ArrayList<>();
private int transactionId;
private int start;
private int length;
private final List<Offer> offers = new ArrayList<>();
public List<String> getOffers() {
public int getTransactionId() {
return transactionId;
}
public void setTransactionId(int transactionId) {
this.transactionId = transactionId;
}
public int getStart() {
return start;
}
public void setStart(int start) {
this.start = start;
}
public int getLength() {
return length;
}
public void setLength(int length) {
this.length = length;
}
public List<Offer> getOffers() {
return offers;
}
@Override
public String toString() {
return "TabCompleteResponse{"
+ "offers=" + offers
+ "transactionId=" + transactionId
+ ", start=" + start
+ ", length=" + length
+ ", offers=" + offers
+ '}';
}
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
int offersAvailable = ProtocolUtils.readVarInt(buf);
for (int i = 0; i < offersAvailable; i++) {
offers.add(ProtocolUtils.readString(buf));
if (version.compareTo(MINECRAFT_1_13) >= 0) {
this.transactionId = ProtocolUtils.readVarInt(buf);
this.start = ProtocolUtils.readVarInt(buf);
this.length = ProtocolUtils.readVarInt(buf);
int offersAvailable = ProtocolUtils.readVarInt(buf);
for (int i = 0; i < offersAvailable; i++) {
String offer = ProtocolUtils.readString(buf);
Component tooltip = buf.readBoolean() ? ComponentSerializers.JSON.deserialize(
ProtocolUtils.readString(buf)) : null;
offers.add(new Offer(offer, tooltip));
}
} else {
int offersAvailable = ProtocolUtils.readVarInt(buf);
for (int i = 0; i < offersAvailable; i++) {
offers.add(new Offer(ProtocolUtils.readString(buf), null));
}
}
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
ProtocolUtils.writeVarInt(buf, offers.size());
for (String offer : offers) {
ProtocolUtils.writeString(buf, offer);
if (version.compareTo(MINECRAFT_1_13) >= 0) {
ProtocolUtils.writeVarInt(buf, this.transactionId);
ProtocolUtils.writeVarInt(buf, this.start);
ProtocolUtils.writeVarInt(buf, this.length);
ProtocolUtils.writeVarInt(buf, offers.size());
for (Offer offer : offers) {
ProtocolUtils.writeString(buf, offer.text);
buf.writeBoolean(offer.tooltip != null);
if (offer.tooltip != null) {
ProtocolUtils.writeString(buf, ComponentSerializers.JSON.serialize(offer.tooltip));
}
}
} else {
ProtocolUtils.writeVarInt(buf, offers.size());
for (Offer offer : offers) {
ProtocolUtils.writeString(buf, offer.text);
}
}
}
@ -43,4 +106,29 @@ public class TabCompleteResponse implements MinecraftPacket {
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
public static class Offer implements Comparable<Offer> {
private final String text;
@Nullable
private final Component tooltip;
public Offer(String text,
@Nullable Component tooltip) {
this.text = text;
this.tooltip = tooltip;
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("text", text)
.add("tooltip", tooltip)
.toString();
}
@Override
public int compareTo(Offer o) {
return this.text.compareTo(o.text);
}
}
}

Datei anzeigen

@ -0,0 +1,122 @@
package com.velocitypowered.proxy.protocol.packet.brigadier;
import static com.velocitypowered.proxy.protocol.packet.brigadier.DoubleArgumentPropertySerializer.DOUBLE;
import static com.velocitypowered.proxy.protocol.packet.brigadier.DummyVoidArgumentPropertySerializer.DUMMY;
import static com.velocitypowered.proxy.protocol.packet.brigadier.FloatArgumentPropertySerializer.FLOAT;
import static com.velocitypowered.proxy.protocol.packet.brigadier.IntegerArgumentPropertySerializer.INTEGER;
import static com.velocitypowered.proxy.protocol.packet.brigadier.StringArgumentPropertySerializer.STRING;
import com.mojang.brigadier.arguments.ArgumentType;
import com.mojang.brigadier.arguments.BoolArgumentType;
import com.mojang.brigadier.arguments.DoubleArgumentType;
import com.mojang.brigadier.arguments.FloatArgumentType;
import com.mojang.brigadier.arguments.IntegerArgumentType;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
import java.util.HashMap;
import java.util.Map;
public class ArgumentPropertyRegistry {
private ArgumentPropertyRegistry() {
throw new AssertionError();
}
private static final Map<String, ArgumentPropertySerializer<?>> byId = new HashMap<>();
private static final Map<Class<? extends ArgumentType>,
ArgumentPropertySerializer<?>> byClass = new HashMap<>();
private static final Map<Class<? extends ArgumentType>, String> classToId = new HashMap<>();
private static <T extends ArgumentType<?>> void register(String identifier, Class<T> klazz,
ArgumentPropertySerializer<T> serializer) {
byId.put(identifier, serializer);
byClass.put(klazz, serializer);
classToId.put(klazz, identifier);
}
private static <T> void dummy(String identifier, ArgumentPropertySerializer<T> serializer) {
byId.put(identifier, serializer);
}
public static ArgumentType<?> deserialize(ByteBuf buf) {
String identifier = ProtocolUtils.readString(buf);
ArgumentPropertySerializer<?> serializer = byId.get(identifier);
if (serializer == null) {
throw new IllegalArgumentException("Argument type identifier " + identifier + " unknown.");
}
Object result = serializer.deserialize(buf);
if (result instanceof ArgumentType) {
return (ArgumentType<?>) result;
} else {
return new DummyProperty(identifier, serializer, result);
}
}
public static void serialize(ByteBuf buf, ArgumentType<?> type) {
if (type instanceof DummyProperty) {
DummyProperty property = (DummyProperty) type;
ProtocolUtils.writeString(buf, property.getIdentifier());
if (property.getResult() != null) {
property.getSerializer().serialize(property.getResult(), buf);
}
} else {
ArgumentPropertySerializer serializer = byClass.get(type.getClass());
String id = classToId.get(type.getClass());
if (serializer == null || id == null) {
throw new IllegalArgumentException("Don't know how to serialize "
+ type.getClass().getName());
}
ProtocolUtils.writeString(buf, id);
serializer.serialize(type, buf);
}
}
static {
// Base Brigadier argument types
register("brigadier:string", StringArgumentType.class, STRING);
register("brigadier:integer", IntegerArgumentType.class, INTEGER);
register("brigadier:float", FloatArgumentType.class, FLOAT);
register("brigadier:double", DoubleArgumentType.class, DOUBLE);
register("brigadier:bool", BoolArgumentType.class,
VoidArgumentPropertySerializer.create(BoolArgumentType::bool));
// Minecraft argument types with extra properties
dummy("minecraft:entity", ByteArgumentPropertySerializer.BYTE);
dummy("minecraft:score_holder", ByteArgumentPropertySerializer.BYTE);
// Minecraft argument types
dummy("minecraft:game_profile", DUMMY);
dummy("minecraft:block_pos", DUMMY);
dummy("minecraft:column_pos", DUMMY);
dummy("minecraft:vec3", DUMMY);
dummy("minecraft:vec2", DUMMY);
dummy("minecraft:block_state", DUMMY);
dummy("minecraft:block_predicate", DUMMY);
dummy("minecraft:item_stack", DUMMY);
dummy("minecraft:item_predicate", DUMMY);
dummy("minecraft:color", DUMMY);
dummy("minecraft:component", DUMMY);
dummy("minecraft:message", DUMMY);
dummy("minecraft:nbt", DUMMY);
dummy("minecraft:nbt_path", DUMMY);
dummy("minecraft:objective", DUMMY);
dummy("minecraft:objective_criteria", DUMMY);
dummy("minecraft:operation", DUMMY);
dummy("minecraft:particle", DUMMY);
dummy("minecraft:rotation", DUMMY);
dummy("minecraft:scoreboard_slot", DUMMY);
dummy("minecraft:swizzle", DUMMY);
dummy("minecraft:team", DUMMY);
dummy("minecraft:item_slot", DUMMY);
dummy("minecraft:resource_location", DUMMY);
dummy("minecraft:mob_effect", DUMMY);
dummy("minecraft:function", DUMMY);
dummy("minecraft:entity_anchor", DUMMY);
dummy("minecraft:item_enchantment", DUMMY);
dummy("minecraft:entity_summon", DUMMY);
dummy("minecraft:dimension", DUMMY);
dummy("minecraft:int_range", DUMMY);
dummy("minecraft:float_range", DUMMY);
}
}

Datei anzeigen

@ -0,0 +1,10 @@
package com.velocitypowered.proxy.protocol.packet.brigadier;
import io.netty.buffer.ByteBuf;
import org.checkerframework.checker.nullness.qual.Nullable;
public interface ArgumentPropertySerializer<T> {
@Nullable T deserialize(ByteBuf buf);
void serialize(T object, ByteBuf buf);
}

Datei anzeigen

@ -0,0 +1,24 @@
package com.velocitypowered.proxy.protocol.packet.brigadier;
import io.netty.buffer.ByteBuf;
import org.checkerframework.checker.nullness.qual.Nullable;
class ByteArgumentPropertySerializer implements ArgumentPropertySerializer<Byte> {
static final ByteArgumentPropertySerializer BYTE = new ByteArgumentPropertySerializer();
private ByteArgumentPropertySerializer() {
}
@Nullable
@Override
public Byte deserialize(ByteBuf buf) {
return buf.readByte();
}
@Override
public void serialize(Byte object, ByteBuf buf) {
buf.writeByte(object);
}
}

Datei anzeigen

@ -0,0 +1,41 @@
package com.velocitypowered.proxy.protocol.packet.brigadier;
import static com.velocitypowered.proxy.protocol.packet.brigadier.IntegerArgumentPropertySerializer.HAS_MAXIMUM;
import static com.velocitypowered.proxy.protocol.packet.brigadier.IntegerArgumentPropertySerializer.HAS_MINIMUM;
import static com.velocitypowered.proxy.protocol.packet.brigadier.IntegerArgumentPropertySerializer.getFlags;
import com.mojang.brigadier.arguments.DoubleArgumentType;
import io.netty.buffer.ByteBuf;
import org.checkerframework.checker.nullness.qual.Nullable;
class DoubleArgumentPropertySerializer implements ArgumentPropertySerializer<DoubleArgumentType> {
static final DoubleArgumentPropertySerializer DOUBLE = new DoubleArgumentPropertySerializer();
private DoubleArgumentPropertySerializer() {
}
@Nullable
@Override
public DoubleArgumentType deserialize(ByteBuf buf) {
byte flags = buf.readByte();
double minimum = (flags & HAS_MINIMUM) != 0 ? buf.readDouble() : Double.MIN_VALUE;
double maximum = (flags & HAS_MAXIMUM) != 0 ? buf.readDouble() : Double.MAX_VALUE;
return DoubleArgumentType.doubleArg(minimum, maximum);
}
@Override
public void serialize(DoubleArgumentType object, ByteBuf buf) {
boolean hasMinimum = object.getMinimum() != Double.MIN_VALUE;
boolean hasMaximum = object.getMaximum() != Double.MAX_VALUE;
byte flag = getFlags(hasMinimum, hasMaximum);
buf.writeByte(flag);
if (hasMinimum) {
buf.writeDouble(object.getMinimum());
}
if (hasMaximum) {
buf.writeDouble(object.getMaximum());
}
}
}

Datei anzeigen

@ -0,0 +1,37 @@
package com.velocitypowered.proxy.protocol.packet.brigadier;
import com.mojang.brigadier.StringReader;
import com.mojang.brigadier.arguments.ArgumentType;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import org.checkerframework.checker.nullness.qual.Nullable;
class DummyProperty<T> implements ArgumentType<T> {
private final String identifier;
private final ArgumentPropertySerializer<T> serializer;
@Nullable
private final T result;
DummyProperty(String identifier, ArgumentPropertySerializer<T> serializer, @Nullable T result) {
this.identifier = identifier;
this.serializer = serializer;
this.result = result;
}
@Override
public <S> T parse(StringReader reader) throws CommandSyntaxException {
throw new UnsupportedOperationException();
}
public String getIdentifier() {
return identifier;
}
public ArgumentPropertySerializer<T> getSerializer() {
return serializer;
}
public @Nullable T getResult() {
return result;
}
}

Datei anzeigen

@ -0,0 +1,27 @@
package com.velocitypowered.proxy.protocol.packet.brigadier;
import io.netty.buffer.ByteBuf;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* An argument property serializer that will serialize and deserialize nothing.
*/
class DummyVoidArgumentPropertySerializer implements ArgumentPropertySerializer<Void> {
static final ArgumentPropertySerializer<Void> DUMMY =
new DummyVoidArgumentPropertySerializer();
private DummyVoidArgumentPropertySerializer() {
}
@Nullable
@Override
public Void deserialize(ByteBuf buf) {
return null;
}
@Override
public void serialize(Void object, ByteBuf buf) {
}
}

Datei anzeigen

@ -0,0 +1,42 @@
package com.velocitypowered.proxy.protocol.packet.brigadier;
import static com.velocitypowered.proxy.protocol.packet.brigadier.IntegerArgumentPropertySerializer.HAS_MAXIMUM;
import static com.velocitypowered.proxy.protocol.packet.brigadier.IntegerArgumentPropertySerializer.HAS_MINIMUM;
import static com.velocitypowered.proxy.protocol.packet.brigadier.IntegerArgumentPropertySerializer.getFlags;
import com.mojang.brigadier.arguments.FloatArgumentType;
import io.netty.buffer.ByteBuf;
import org.checkerframework.checker.nullness.qual.Nullable;
class FloatArgumentPropertySerializer implements ArgumentPropertySerializer<FloatArgumentType> {
static FloatArgumentPropertySerializer FLOAT = new FloatArgumentPropertySerializer();
private FloatArgumentPropertySerializer() {
}
@Nullable
@Override
public FloatArgumentType deserialize(ByteBuf buf) {
byte flags = buf.readByte();
float minimum = (flags & HAS_MINIMUM) != 0 ? buf.readFloat() : Float.MIN_VALUE;
float maximum = (flags & HAS_MAXIMUM) != 0 ? buf.readFloat() : Float.MAX_VALUE;
return FloatArgumentType.floatArg(minimum, maximum);
}
@Override
public void serialize(FloatArgumentType object, ByteBuf buf) {
boolean hasMinimum = object.getMinimum() != Float.MIN_VALUE;
boolean hasMaximum = object.getMaximum() != Float.MAX_VALUE;
byte flag = getFlags(hasMinimum, hasMaximum);
buf.writeByte(flag);
if (hasMinimum) {
buf.writeFloat(object.getMinimum());
}
if (hasMaximum) {
buf.writeFloat(object.getMaximum());
}
}
}

Datei anzeigen

@ -0,0 +1,52 @@
package com.velocitypowered.proxy.protocol.packet.brigadier;
import com.mojang.brigadier.arguments.IntegerArgumentType;
import io.netty.buffer.ByteBuf;
import org.checkerframework.checker.nullness.qual.Nullable;
class IntegerArgumentPropertySerializer implements ArgumentPropertySerializer<IntegerArgumentType> {
static final IntegerArgumentPropertySerializer INTEGER = new IntegerArgumentPropertySerializer();
static final byte HAS_MINIMUM = 0x01;
static final byte HAS_MAXIMUM = 0x02;
private IntegerArgumentPropertySerializer() {
}
@Nullable
@Override
public IntegerArgumentType deserialize(ByteBuf buf) {
byte flags = buf.readByte();
int minimum = (flags & HAS_MINIMUM) != 0 ? buf.readInt() : Integer.MIN_VALUE;
int maximum = (flags & HAS_MAXIMUM) != 0 ? buf.readInt() : Integer.MAX_VALUE;
return IntegerArgumentType.integer(minimum, maximum);
}
@Override
public void serialize(IntegerArgumentType object, ByteBuf buf) {
boolean hasMinimum = object.getMinimum() != Integer.MIN_VALUE;
boolean hasMaximum = object.getMaximum() != Integer.MAX_VALUE;
byte flag = getFlags(hasMinimum, hasMaximum);
buf.writeByte(flag);
if (hasMinimum) {
buf.writeInt(object.getMinimum());
}
if (hasMaximum) {
buf.writeInt(object.getMaximum());
}
}
static byte getFlags(boolean hasMinimum, boolean hasMaximum) {
byte flags = 0;
if (hasMinimum) {
flags |= HAS_MINIMUM;
}
if (hasMaximum) {
flags |= HAS_MAXIMUM;
}
return flags;
}
}

Datei anzeigen

@ -0,0 +1,52 @@
package com.velocitypowered.proxy.protocol.packet.brigadier;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* Serializes properties for {@link StringArgumentType}.
*/
class StringArgumentPropertySerializer implements ArgumentPropertySerializer<StringArgumentType> {
public static final ArgumentPropertySerializer<StringArgumentType> STRING =
new StringArgumentPropertySerializer();
private StringArgumentPropertySerializer() {
}
@Nullable
@Override
public StringArgumentType deserialize(ByteBuf buf) {
int type = ProtocolUtils.readVarInt(buf);
switch (type) {
case 0:
return StringArgumentType.word();
case 1:
return StringArgumentType.string();
case 2:
return StringArgumentType.greedyString();
default:
throw new IllegalArgumentException("Invalid string argument type " + type);
}
}
@Override
public void serialize(StringArgumentType object, ByteBuf buf) {
switch (object.getType()) {
case SINGLE_WORD:
ProtocolUtils.writeVarInt(buf, 0);
break;
case QUOTABLE_PHRASE:
ProtocolUtils.writeVarInt(buf, 1);
break;
case GREEDY_PHRASE:
ProtocolUtils.writeVarInt(buf, 2);
break;
default:
throw new IllegalArgumentException("Invalid string argument type " + object.getType());
}
}
}

Datei anzeigen

@ -0,0 +1,32 @@
package com.velocitypowered.proxy.protocol.packet.brigadier;
import com.mojang.brigadier.arguments.ArgumentType;
import io.netty.buffer.ByteBuf;
import java.util.function.Supplier;
import org.checkerframework.checker.nullness.qual.Nullable;
class VoidArgumentPropertySerializer<T extends ArgumentType<?>>
implements ArgumentPropertySerializer<T> {
private final Supplier<T> argumentSupplier;
public VoidArgumentPropertySerializer(Supplier<T> argumentSupplier) {
this.argumentSupplier = argumentSupplier;
}
public static <T extends ArgumentType<?>> ArgumentPropertySerializer<T> create(
Supplier<T> supplier) {
return new VoidArgumentPropertySerializer<T>(supplier);
}
@Nullable
@Override
public T deserialize(ByteBuf buf) {
return argumentSupplier.get();
}
@Override
public void serialize(T object, ByteBuf buf) {
}
}