350 lines
12 KiB
Java
350 lines
12 KiB
Java
package net.minecraft.network.protocol.game;
|
|
|
|
import com.mojang.brigadier.arguments.ArgumentType;
|
|
import com.mojang.brigadier.builder.ArgumentBuilder;
|
|
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 it.unimi.dsi.fastutil.ints.IntOpenHashSet;
|
|
import it.unimi.dsi.fastutil.ints.IntSet;
|
|
import it.unimi.dsi.fastutil.ints.IntSets;
|
|
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
|
import it.unimi.dsi.fastutil.objects.Object2IntMaps;
|
|
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
|
|
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
|
import java.util.ArrayDeque;
|
|
import java.util.List;
|
|
import java.util.Queue;
|
|
import java.util.function.BiPredicate;
|
|
import net.minecraft.commands.CommandBuildContext;
|
|
import net.minecraft.commands.synchronization.ArgumentTypeInfo;
|
|
import net.minecraft.commands.synchronization.ArgumentTypeInfos;
|
|
import net.minecraft.core.registries.BuiltInRegistries;
|
|
import net.minecraft.network.FriendlyByteBuf;
|
|
import net.minecraft.network.codec.StreamCodec;
|
|
import net.minecraft.network.protocol.Packet;
|
|
import net.minecraft.network.protocol.PacketType;
|
|
import net.minecraft.resources.ResourceLocation;
|
|
import org.jetbrains.annotations.Nullable;
|
|
|
|
public class ClientboundCommandsPacket implements Packet<ClientGamePacketListener> {
|
|
public static final StreamCodec<FriendlyByteBuf, ClientboundCommandsPacket> STREAM_CODEC = Packet.codec(
|
|
ClientboundCommandsPacket::write, ClientboundCommandsPacket::new
|
|
);
|
|
private static final byte MASK_TYPE = 3;
|
|
private static final byte FLAG_EXECUTABLE = 4;
|
|
private static final byte FLAG_REDIRECT = 8;
|
|
private static final byte FLAG_CUSTOM_SUGGESTIONS = 16;
|
|
private static final byte FLAG_RESTRICTED = 32;
|
|
private static final byte TYPE_ROOT = 0;
|
|
private static final byte TYPE_LITERAL = 1;
|
|
private static final byte TYPE_ARGUMENT = 2;
|
|
private final int rootIndex;
|
|
private final List<ClientboundCommandsPacket.Entry> entries;
|
|
|
|
public <S> ClientboundCommandsPacket(RootCommandNode<S> root, ClientboundCommandsPacket.NodeInspector<S> nodeInspector) {
|
|
Object2IntMap<CommandNode<S>> object2IntMap = enumerateNodes(root);
|
|
this.entries = createEntries(object2IntMap, nodeInspector);
|
|
this.rootIndex = object2IntMap.getInt(root);
|
|
}
|
|
|
|
private ClientboundCommandsPacket(FriendlyByteBuf buffer) {
|
|
this.entries = buffer.readList(ClientboundCommandsPacket::readNode);
|
|
this.rootIndex = buffer.readVarInt();
|
|
validateEntries(this.entries);
|
|
}
|
|
|
|
/**
|
|
* Writes the raw packet data to the data stream.
|
|
*/
|
|
private void write(FriendlyByteBuf buffer) {
|
|
buffer.writeCollection(this.entries, (friendlyByteBuf, entry) -> entry.write(friendlyByteBuf));
|
|
buffer.writeVarInt(this.rootIndex);
|
|
}
|
|
|
|
private static void validateEntries(List<ClientboundCommandsPacket.Entry> entries, BiPredicate<ClientboundCommandsPacket.Entry, IntSet> validator) {
|
|
IntSet intSet = new IntOpenHashSet(IntSets.fromTo(0, entries.size()));
|
|
|
|
while (!intSet.isEmpty()) {
|
|
boolean bl = intSet.removeIf(i -> validator.test((ClientboundCommandsPacket.Entry)entries.get(i), intSet));
|
|
if (!bl) {
|
|
throw new IllegalStateException("Server sent an impossible command tree");
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void validateEntries(List<ClientboundCommandsPacket.Entry> entries) {
|
|
validateEntries(entries, ClientboundCommandsPacket.Entry::canBuild);
|
|
validateEntries(entries, ClientboundCommandsPacket.Entry::canResolve);
|
|
}
|
|
|
|
private static <S> Object2IntMap<CommandNode<S>> enumerateNodes(RootCommandNode<S> rootNode) {
|
|
Object2IntMap<CommandNode<S>> object2IntMap = new Object2IntOpenHashMap<>();
|
|
Queue<CommandNode<S>> queue = new ArrayDeque();
|
|
queue.add(rootNode);
|
|
|
|
CommandNode<S> commandNode;
|
|
while ((commandNode = (CommandNode<S>)queue.poll()) != null) {
|
|
if (!object2IntMap.containsKey(commandNode)) {
|
|
int i = object2IntMap.size();
|
|
object2IntMap.put(commandNode, i);
|
|
queue.addAll(commandNode.getChildren());
|
|
if (commandNode.getRedirect() != null) {
|
|
queue.add(commandNode.getRedirect());
|
|
}
|
|
}
|
|
}
|
|
|
|
return object2IntMap;
|
|
}
|
|
|
|
private static <S> List<ClientboundCommandsPacket.Entry> createEntries(
|
|
Object2IntMap<CommandNode<S>> nodes, ClientboundCommandsPacket.NodeInspector<S> nodeInspector
|
|
) {
|
|
ObjectArrayList<ClientboundCommandsPacket.Entry> objectArrayList = new ObjectArrayList<>(nodes.size());
|
|
objectArrayList.size(nodes.size());
|
|
|
|
for (Object2IntMap.Entry<CommandNode<S>> entry : Object2IntMaps.fastIterable(nodes)) {
|
|
objectArrayList.set(entry.getIntValue(), createEntry((CommandNode<S>)entry.getKey(), nodeInspector, nodes));
|
|
}
|
|
|
|
return objectArrayList;
|
|
}
|
|
|
|
private static ClientboundCommandsPacket.Entry readNode(FriendlyByteBuf buffer) {
|
|
byte b = buffer.readByte();
|
|
int[] is = buffer.readVarIntArray();
|
|
int i = (b & 8) != 0 ? buffer.readVarInt() : 0;
|
|
ClientboundCommandsPacket.NodeStub nodeStub = read(buffer, b);
|
|
return new ClientboundCommandsPacket.Entry(nodeStub, b, i, is);
|
|
}
|
|
|
|
@Nullable
|
|
private static ClientboundCommandsPacket.NodeStub read(FriendlyByteBuf buffer, byte flags) {
|
|
int i = flags & 3;
|
|
if (i == 2) {
|
|
String string = buffer.readUtf();
|
|
int j = buffer.readVarInt();
|
|
ArgumentTypeInfo<?, ?> argumentTypeInfo = BuiltInRegistries.COMMAND_ARGUMENT_TYPE.byId(j);
|
|
if (argumentTypeInfo == null) {
|
|
return null;
|
|
} else {
|
|
ArgumentTypeInfo.Template<?> template = argumentTypeInfo.deserializeFromNetwork(buffer);
|
|
ResourceLocation resourceLocation = (flags & 16) != 0 ? buffer.readResourceLocation() : null;
|
|
return new ClientboundCommandsPacket.ArgumentNodeStub(string, template, resourceLocation);
|
|
}
|
|
} else if (i == 1) {
|
|
String string = buffer.readUtf();
|
|
return new ClientboundCommandsPacket.LiteralNodeStub(string);
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private static <S> ClientboundCommandsPacket.Entry createEntry(
|
|
CommandNode<S> node, ClientboundCommandsPacket.NodeInspector<S> nodeInspector, Object2IntMap<CommandNode<S>> nodes
|
|
) {
|
|
int i = 0;
|
|
int j;
|
|
if (node.getRedirect() != null) {
|
|
i |= 8;
|
|
j = nodes.getInt(node.getRedirect());
|
|
} else {
|
|
j = 0;
|
|
}
|
|
|
|
if (nodeInspector.isExecutable(node)) {
|
|
i |= 4;
|
|
}
|
|
|
|
if (nodeInspector.isRestricted(node)) {
|
|
i |= 32;
|
|
}
|
|
|
|
ClientboundCommandsPacket.NodeStub nodeStub;
|
|
switch (node) {
|
|
case RootCommandNode<S> rootCommandNode:
|
|
i |= 0;
|
|
nodeStub = null;
|
|
break;
|
|
case ArgumentCommandNode<S, ?> argumentCommandNode:
|
|
ResourceLocation resourceLocation = nodeInspector.suggestionId(argumentCommandNode);
|
|
nodeStub = new ClientboundCommandsPacket.ArgumentNodeStub(
|
|
argumentCommandNode.getName(), ArgumentTypeInfos.unpack(argumentCommandNode.getType()), resourceLocation
|
|
);
|
|
i |= 2;
|
|
if (resourceLocation != null) {
|
|
i |= 16;
|
|
}
|
|
break;
|
|
case LiteralCommandNode<S> literalCommandNode:
|
|
nodeStub = new ClientboundCommandsPacket.LiteralNodeStub(literalCommandNode.getLiteral());
|
|
i |= 1;
|
|
break;
|
|
default:
|
|
throw new UnsupportedOperationException("Unknown node type " + node);
|
|
}
|
|
|
|
int[] is = node.getChildren().stream().mapToInt(nodes::getInt).toArray();
|
|
return new ClientboundCommandsPacket.Entry(nodeStub, i, j, is);
|
|
}
|
|
|
|
@Override
|
|
public PacketType<ClientboundCommandsPacket> type() {
|
|
return GamePacketTypes.CLIENTBOUND_COMMANDS;
|
|
}
|
|
|
|
/**
|
|
* Passes this Packet on to the NetHandler for processing.
|
|
*/
|
|
public void handle(ClientGamePacketListener handler) {
|
|
handler.handleCommands(this);
|
|
}
|
|
|
|
public <S> RootCommandNode<S> getRoot(CommandBuildContext context, ClientboundCommandsPacket.NodeBuilder<S> nodeBuilder) {
|
|
return (RootCommandNode<S>)new ClientboundCommandsPacket.NodeResolver<>(context, nodeBuilder, this.entries).resolve(this.rootIndex);
|
|
}
|
|
|
|
record ArgumentNodeStub(String id, ArgumentTypeInfo.Template<?> argumentType, @Nullable ResourceLocation suggestionId)
|
|
implements ClientboundCommandsPacket.NodeStub {
|
|
@Override
|
|
public <S> ArgumentBuilder<S, ?> build(CommandBuildContext context, ClientboundCommandsPacket.NodeBuilder<S> nodeBuilder) {
|
|
ArgumentType<?> argumentType = this.argumentType.instantiate(context);
|
|
return nodeBuilder.createArgument(this.id, argumentType, this.suggestionId);
|
|
}
|
|
|
|
@Override
|
|
public void write(FriendlyByteBuf buffer) {
|
|
buffer.writeUtf(this.id);
|
|
serializeCap(buffer, this.argumentType);
|
|
if (this.suggestionId != null) {
|
|
buffer.writeResourceLocation(this.suggestionId);
|
|
}
|
|
}
|
|
|
|
private static <A extends ArgumentType<?>> void serializeCap(FriendlyByteBuf buffer, ArgumentTypeInfo.Template<A> argumentInfoTemplate) {
|
|
serializeCap(buffer, argumentInfoTemplate.type(), argumentInfoTemplate);
|
|
}
|
|
|
|
private static <A extends ArgumentType<?>, T extends ArgumentTypeInfo.Template<A>> void serializeCap(
|
|
FriendlyByteBuf buffer, ArgumentTypeInfo<A, T> argumentInfo, ArgumentTypeInfo.Template<A> argumentInfoTemplate
|
|
) {
|
|
buffer.writeVarInt(BuiltInRegistries.COMMAND_ARGUMENT_TYPE.getId(argumentInfo));
|
|
argumentInfo.serializeToNetwork((T)argumentInfoTemplate, buffer);
|
|
}
|
|
}
|
|
|
|
record Entry(@Nullable ClientboundCommandsPacket.NodeStub stub, int flags, int redirect, int[] children) {
|
|
|
|
public void write(FriendlyByteBuf buffer) {
|
|
buffer.writeByte(this.flags);
|
|
buffer.writeVarIntArray(this.children);
|
|
if ((this.flags & 8) != 0) {
|
|
buffer.writeVarInt(this.redirect);
|
|
}
|
|
|
|
if (this.stub != null) {
|
|
this.stub.write(buffer);
|
|
}
|
|
}
|
|
|
|
public boolean canBuild(IntSet children) {
|
|
return (this.flags & 8) != 0 ? !children.contains(this.redirect) : true;
|
|
}
|
|
|
|
public boolean canResolve(IntSet children) {
|
|
for (int i : this.children) {
|
|
if (children.contains(i)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
record LiteralNodeStub(String id) implements ClientboundCommandsPacket.NodeStub {
|
|
@Override
|
|
public <S> ArgumentBuilder<S, ?> build(CommandBuildContext context, ClientboundCommandsPacket.NodeBuilder<S> nodeBuilder) {
|
|
return nodeBuilder.createLiteral(this.id);
|
|
}
|
|
|
|
@Override
|
|
public void write(FriendlyByteBuf buffer) {
|
|
buffer.writeUtf(this.id);
|
|
}
|
|
}
|
|
|
|
public interface NodeBuilder<S> {
|
|
ArgumentBuilder<S, ?> createLiteral(String id);
|
|
|
|
ArgumentBuilder<S, ?> createArgument(String id, ArgumentType<?> type, @Nullable ResourceLocation suggestionId);
|
|
|
|
ArgumentBuilder<S, ?> configure(ArgumentBuilder<S, ?> argumentBuilder, boolean executable, boolean restricted);
|
|
}
|
|
|
|
public interface NodeInspector<S> {
|
|
@Nullable
|
|
ResourceLocation suggestionId(ArgumentCommandNode<S, ?> node);
|
|
|
|
boolean isExecutable(CommandNode<S> node);
|
|
|
|
boolean isRestricted(CommandNode<S> node);
|
|
}
|
|
|
|
static class NodeResolver<S> {
|
|
private final CommandBuildContext context;
|
|
private final ClientboundCommandsPacket.NodeBuilder<S> builder;
|
|
private final List<ClientboundCommandsPacket.Entry> entries;
|
|
private final List<CommandNode<S>> nodes;
|
|
|
|
NodeResolver(CommandBuildContext context, ClientboundCommandsPacket.NodeBuilder<S> builder, List<ClientboundCommandsPacket.Entry> entries) {
|
|
this.context = context;
|
|
this.builder = builder;
|
|
this.entries = entries;
|
|
ObjectArrayList<CommandNode<S>> objectArrayList = new ObjectArrayList<>();
|
|
objectArrayList.size(entries.size());
|
|
this.nodes = objectArrayList;
|
|
}
|
|
|
|
public CommandNode<S> resolve(int index) {
|
|
CommandNode<S> commandNode = (CommandNode<S>)this.nodes.get(index);
|
|
if (commandNode != null) {
|
|
return commandNode;
|
|
} else {
|
|
ClientboundCommandsPacket.Entry entry = (ClientboundCommandsPacket.Entry)this.entries.get(index);
|
|
CommandNode<S> commandNode2;
|
|
if (entry.stub == null) {
|
|
commandNode2 = new RootCommandNode<>();
|
|
} else {
|
|
ArgumentBuilder<S, ?> argumentBuilder = entry.stub.build(this.context, this.builder);
|
|
if ((entry.flags & 8) != 0) {
|
|
argumentBuilder.redirect(this.resolve(entry.redirect));
|
|
}
|
|
|
|
boolean bl = (entry.flags & 4) != 0;
|
|
boolean bl2 = (entry.flags & 32) != 0;
|
|
commandNode2 = this.builder.configure(argumentBuilder, bl, bl2).build();
|
|
}
|
|
|
|
this.nodes.set(index, commandNode2);
|
|
|
|
for (int i : entry.children) {
|
|
CommandNode<S> commandNode3 = this.resolve(i);
|
|
if (!(commandNode3 instanceof RootCommandNode)) {
|
|
commandNode2.addChild(commandNode3);
|
|
}
|
|
}
|
|
|
|
return commandNode2;
|
|
}
|
|
}
|
|
}
|
|
|
|
interface NodeStub {
|
|
<S> ArgumentBuilder<S, ?> build(CommandBuildContext context, ClientboundCommandsPacket.NodeBuilder<S> nodeBuilder);
|
|
|
|
void write(FriendlyByteBuf buffer);
|
|
}
|
|
}
|