minecraft-src/net/minecraft/network/protocol/game/ClientboundCommandsPacket.java
2025-07-04 01:41:11 +03:00

368 lines
13 KiB
Java

package net.minecraft.network.protocol.game;
import com.google.common.collect.Queues;
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.suggestion.SuggestionProvider;
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.List;
import java.util.Queue;
import java.util.function.BiPredicate;
import net.minecraft.commands.CommandBuildContext;
import net.minecraft.commands.SharedSuggestionProvider;
import net.minecraft.commands.synchronization.ArgumentTypeInfo;
import net.minecraft.commands.synchronization.ArgumentTypeInfos;
import net.minecraft.commands.synchronization.SuggestionProviders;
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 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 ClientboundCommandsPacket(RootCommandNode<SharedSuggestionProvider> root) {
Object2IntMap<CommandNode<SharedSuggestionProvider>> object2IntMap = enumerateNodes(root);
this.entries = createEntries(object2IntMap);
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 Object2IntMap<CommandNode<SharedSuggestionProvider>> enumerateNodes(RootCommandNode<SharedSuggestionProvider> rootNode) {
Object2IntMap<CommandNode<SharedSuggestionProvider>> object2IntMap = new Object2IntOpenHashMap<>();
Queue<CommandNode<SharedSuggestionProvider>> queue = Queues.<CommandNode<SharedSuggestionProvider>>newArrayDeque();
queue.add(rootNode);
CommandNode<SharedSuggestionProvider> commandNode;
while ((commandNode = (CommandNode<SharedSuggestionProvider>)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 List<ClientboundCommandsPacket.Entry> createEntries(Object2IntMap<CommandNode<SharedSuggestionProvider>> nodes) {
ObjectArrayList<ClientboundCommandsPacket.Entry> objectArrayList = new ObjectArrayList<>(nodes.size());
objectArrayList.size(nodes.size());
for (Object2IntMap.Entry<CommandNode<SharedSuggestionProvider>> entry : Object2IntMaps.fastIterable(nodes)) {
objectArrayList.set(entry.getIntValue(), createEntry((CommandNode<SharedSuggestionProvider>)entry.getKey(), 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 ClientboundCommandsPacket.Entry createEntry(
CommandNode<SharedSuggestionProvider> node, Object2IntMap<CommandNode<SharedSuggestionProvider>> nodes
) {
int i = 0;
int j;
if (node.getRedirect() != null) {
i |= 8;
j = nodes.getInt(node.getRedirect());
} else {
j = 0;
}
if (node.getCommand() != null) {
i |= 4;
}
ClientboundCommandsPacket.NodeStub nodeStub;
if (node instanceof RootCommandNode) {
i |= 0;
nodeStub = null;
} else if (node instanceof ArgumentCommandNode<SharedSuggestionProvider, ?> argumentCommandNode) {
nodeStub = new ClientboundCommandsPacket.ArgumentNodeStub(argumentCommandNode);
i |= 2;
if (argumentCommandNode.getCustomSuggestions() != null) {
i |= 16;
}
} else {
if (!(node instanceof LiteralCommandNode literalCommandNode)) {
throw new UnsupportedOperationException("Unknown node type " + node);
}
nodeStub = new ClientboundCommandsPacket.LiteralNodeStub(literalCommandNode.getLiteral());
i |= 1;
}
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 RootCommandNode<SharedSuggestionProvider> getRoot(CommandBuildContext context) {
return (RootCommandNode<SharedSuggestionProvider>)new ClientboundCommandsPacket.NodeResolver(context, this.entries).resolve(this.rootIndex);
}
static class ArgumentNodeStub implements ClientboundCommandsPacket.NodeStub {
private final String id;
private final ArgumentTypeInfo.Template<?> argumentType;
@Nullable
private final ResourceLocation suggestionId;
@Nullable
private static ResourceLocation getSuggestionId(@Nullable SuggestionProvider<SharedSuggestionProvider> provider) {
return provider != null ? SuggestionProviders.getName(provider) : null;
}
ArgumentNodeStub(String id, ArgumentTypeInfo.Template<?> argumentType, @Nullable ResourceLocation suggestionId) {
this.id = id;
this.argumentType = argumentType;
this.suggestionId = suggestionId;
}
public ArgumentNodeStub(ArgumentCommandNode<SharedSuggestionProvider, ?> argumentNode) {
this(argumentNode.getName(), ArgumentTypeInfos.unpack(argumentNode.getType()), getSuggestionId(argumentNode.getCustomSuggestions()));
}
@Override
public ArgumentBuilder<SharedSuggestionProvider, ?> build(CommandBuildContext context) {
ArgumentType<?> argumentType = this.argumentType.instantiate(context);
RequiredArgumentBuilder<SharedSuggestionProvider, ?> requiredArgumentBuilder = RequiredArgumentBuilder.argument(this.id, argumentType);
if (this.suggestionId != null) {
requiredArgumentBuilder.suggests(SuggestionProviders.getProvider(this.suggestionId));
}
return requiredArgumentBuilder;
}
@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);
}
}
static class Entry {
@Nullable
final ClientboundCommandsPacket.NodeStub stub;
final int flags;
final int redirect;
final int[] children;
Entry(@Nullable ClientboundCommandsPacket.NodeStub stub, int flags, int redirect, int[] children) {
this.stub = stub;
this.flags = flags;
this.redirect = redirect;
this.children = 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;
}
}
static class LiteralNodeStub implements ClientboundCommandsPacket.NodeStub {
private final String id;
LiteralNodeStub(String id) {
this.id = id;
}
@Override
public ArgumentBuilder<SharedSuggestionProvider, ?> build(CommandBuildContext context) {
return LiteralArgumentBuilder.literal(this.id);
}
@Override
public void write(FriendlyByteBuf buffer) {
buffer.writeUtf(this.id);
}
}
static class NodeResolver {
private final CommandBuildContext context;
private final List<ClientboundCommandsPacket.Entry> entries;
private final List<CommandNode<SharedSuggestionProvider>> nodes;
NodeResolver(CommandBuildContext context, List<ClientboundCommandsPacket.Entry> entries) {
this.context = context;
this.entries = entries;
ObjectArrayList<CommandNode<SharedSuggestionProvider>> objectArrayList = new ObjectArrayList<>();
objectArrayList.size(entries.size());
this.nodes = objectArrayList;
}
public CommandNode<SharedSuggestionProvider> resolve(int index) {
CommandNode<SharedSuggestionProvider> commandNode = (CommandNode<SharedSuggestionProvider>)this.nodes.get(index);
if (commandNode != null) {
return commandNode;
} else {
ClientboundCommandsPacket.Entry entry = (ClientboundCommandsPacket.Entry)this.entries.get(index);
CommandNode<SharedSuggestionProvider> commandNode2;
if (entry.stub == null) {
commandNode2 = new RootCommandNode<>();
} else {
ArgumentBuilder<SharedSuggestionProvider, ?> argumentBuilder = entry.stub.build(this.context);
if ((entry.flags & 8) != 0) {
argumentBuilder.redirect(this.resolve(entry.redirect));
}
if ((entry.flags & 4) != 0) {
argumentBuilder.executes(commandContext -> 0);
}
commandNode2 = argumentBuilder.build();
}
this.nodes.set(index, commandNode2);
for (int i : entry.children) {
CommandNode<SharedSuggestionProvider> commandNode3 = this.resolve(i);
if (!(commandNode3 instanceof RootCommandNode)) {
commandNode2.addChild(commandNode3);
}
}
return commandNode2;
}
}
}
interface NodeStub {
ArgumentBuilder<SharedSuggestionProvider, ?> build(CommandBuildContext context);
void write(FriendlyByteBuf buffer);
}
}