From fdf88c3a11167e1d543fbfd449f57c9dfac5af89 Mon Sep 17 00:00:00 2001 From: RacoonDog <32882447+RacoonDog@users.noreply.github.com> Date: Mon, 6 May 2024 23:35:21 -0400 Subject: [PATCH 1/2] port .nbt to 1.20.5+ --- .../arguments/ComponentMapArgumentType.java | 53 +++++ .../commands/commands/NbtCommand.java | 198 ++++++++++-------- .../utils/misc/ComponentMapReader.java | 189 +++++++++++++++++ 3 files changed, 354 insertions(+), 86 deletions(-) create mode 100644 src/main/java/meteordevelopment/meteorclient/commands/arguments/ComponentMapArgumentType.java create mode 100644 src/main/java/meteordevelopment/meteorclient/utils/misc/ComponentMapReader.java diff --git a/src/main/java/meteordevelopment/meteorclient/commands/arguments/ComponentMapArgumentType.java b/src/main/java/meteordevelopment/meteorclient/commands/arguments/ComponentMapArgumentType.java new file mode 100644 index 0000000000..4ec8c2648a --- /dev/null +++ b/src/main/java/meteordevelopment/meteorclient/commands/arguments/ComponentMapArgumentType.java @@ -0,0 +1,53 @@ +/* + * This file is part of the Meteor Client distribution (https://github.com/MeteorDevelopment/meteor-client). + * Copyright (c) Meteor Development. + */ + +package meteordevelopment.meteorclient.commands.arguments; + +import com.mojang.brigadier.StringReader; +import com.mojang.brigadier.arguments.ArgumentType; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.suggestion.Suggestions; +import com.mojang.brigadier.suggestion.SuggestionsBuilder; +import meteordevelopment.meteorclient.utils.misc.ComponentMapReader; +import net.minecraft.command.CommandRegistryAccess; +import net.minecraft.command.CommandSource; +import net.minecraft.component.ComponentMap; + +import java.util.Collection; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +public class ComponentMapArgumentType implements ArgumentType { + private static final Collection EXAMPLES = List.of("{foo=bar}"); + private final ComponentMapReader reader; + + public ComponentMapArgumentType(CommandRegistryAccess commandRegistryAccess) { + this.reader = new ComponentMapReader(commandRegistryAccess); + } + + public static ComponentMapArgumentType componentMap(CommandRegistryAccess commandRegistryAccess) { + return new ComponentMapArgumentType(commandRegistryAccess); + } + + public static ComponentMap getComponentMap(CommandContext context, String name) { + return context.getArgument(name, ComponentMap.class); + } + + @Override + public ComponentMap parse(StringReader reader) throws CommandSyntaxException { + return this.reader.consume(reader); + } + + @Override + public CompletableFuture listSuggestions(CommandContext context, SuggestionsBuilder builder) { + return this.reader.getSuggestions(builder); + } + + @Override + public Collection getExamples() { + return EXAMPLES; + } +} diff --git a/src/main/java/meteordevelopment/meteorclient/commands/commands/NbtCommand.java b/src/main/java/meteordevelopment/meteorclient/commands/commands/NbtCommand.java index 9bbb3b39fd..3acda1c180 100644 --- a/src/main/java/meteordevelopment/meteorclient/commands/commands/NbtCommand.java +++ b/src/main/java/meteordevelopment/meteorclient/commands/commands/NbtCommand.java @@ -5,150 +5,176 @@ package meteordevelopment.meteorclient.commands.commands; -import com.mojang.brigadier.StringReader; import com.mojang.brigadier.arguments.IntegerArgumentType; import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.exceptions.DynamicCommandExceptionType; +import com.mojang.serialization.DataResult; import meteordevelopment.meteorclient.commands.Command; -import meteordevelopment.meteorclient.commands.arguments.CompoundNbtTagArgumentType; -import meteordevelopment.meteorclient.systems.config.Config; +import meteordevelopment.meteorclient.commands.arguments.ComponentMapArgumentType; import meteordevelopment.meteorclient.utils.misc.text.MeteorClickEvent; import net.minecraft.command.CommandSource; +import net.minecraft.command.DataCommandObject; +import net.minecraft.command.EntityDataObject; import net.minecraft.command.argument.NbtPathArgumentType; -import net.minecraft.component.ComponentMap; +import net.minecraft.command.argument.RegistryKeyArgumentType; +import net.minecraft.component.*; import net.minecraft.item.ItemStack; -import net.minecraft.nbt.NbtCompound; +import net.minecraft.nbt.NbtElement; +import net.minecraft.nbt.NbtHelper; import net.minecraft.network.packet.c2s.play.CreativeInventoryActionC2SPacket; -import net.minecraft.text.ClickEvent; -import net.minecraft.text.HoverEvent; -import net.minecraft.text.MutableText; -import net.minecraft.text.Text; +import net.minecraft.registry.Registries; +import net.minecraft.registry.RegistryKey; +import net.minecraft.registry.RegistryKeys; +import net.minecraft.registry.entry.RegistryEntry; +import net.minecraft.text.*; import net.minecraft.util.Formatting; +import net.minecraft.util.Unit; + +import java.util.List; +import java.util.Set; import static com.mojang.brigadier.Command.SINGLE_SUCCESS; import static meteordevelopment.meteorclient.MeteorClient.mc; public class NbtCommand extends Command { + private static final DynamicCommandExceptionType MALFORMED_ITEM_EXCEPTION = new DynamicCommandExceptionType( + error -> Text.stringifiedTranslatable("arguments.item.malformed", error) + ); + private final Text copyButton = Text.literal("NBT").setStyle(Style.EMPTY + .withFormatting(Formatting.UNDERLINE) + .withClickEvent(new MeteorClickEvent( + ClickEvent.Action.RUN_COMMAND, + this.toString("copy") + )) + .withHoverEvent(new HoverEvent( + HoverEvent.Action.SHOW_TEXT, + Text.literal("Copy the NBT data to your clipboard.") + ))); + public NbtCommand() { super("nbt", "Modifies NBT data for an item, example: .nbt add {display:{Name:'{\"text\":\"$cRed Name\"}'}}"); } @Override public void build(LiteralArgumentBuilder builder) { - builder.executes(context -> { - error("This command is not yet updated for 1.20.5 and above!"); - return SINGLE_SUCCESS; - }); - - // TODO: Update using Components over NBT - /*builder.then(literal("add").then(argument("nbt", CompoundNbtTagArgumentType.create()).executes(s -> { + builder.then(literal("add").then(argument("component", ComponentMapArgumentType.componentMap(REGISTRY_ACCESS)).executes(ctx -> { ItemStack stack = mc.player.getInventory().getMainHandStack(); if (validBasic(stack)) { - NbtCompound tag = CompoundNbtTagArgumentType.get(s); + ComponentMap itemComponents = stack.getComponents(); + ComponentMap newComponents = ComponentMapArgumentType.getComponentMap(ctx, "component"); - if (tag != null) { - ItemStack newStack = ItemStack.fromNbtOrEmpty(mc.world.getRegistryManager(), tag); - newStack.applyComponentsFrom(stack.getComponents()); + ComponentMap testComponents = ComponentMap.of(itemComponents, newComponents); + DataResult dataResult = ItemStack.validateComponents(testComponents); + dataResult.getOrThrow(MALFORMED_ITEM_EXCEPTION::create); - setStack(newStack); - } else { - error("Some of the NBT data could not be found, try using: " + Config.get().prefix.get() + "nbt set {nbt}"); - } + stack.applyComponentsFrom(testComponents); + + setStack(stack); } return SINGLE_SUCCESS; }))); - builder.then(literal("set").then(argument("nbt", CompoundNbtTagArgumentType.create()).executes(context -> { + builder.then(literal("set").then(argument("component", ComponentMapArgumentType.componentMap(REGISTRY_ACCESS)).executes(ctx -> { ItemStack stack = mc.player.getInventory().getMainHandStack(); if (validBasic(stack)) { - stack = ItemStack.fromNbtOrEmpty(mc.world.getRegistryManager(), CompoundNbtTagArgumentType.get(context)); + ComponentMap components = ComponentMapArgumentType.getComponentMap(ctx, "component"); + ComponentMapImpl stackComponents = (ComponentMapImpl) stack.getComponents(); + + DataResult dataResult = ItemStack.validateComponents(components); + dataResult.getOrThrow(MALFORMED_ITEM_EXCEPTION::create); + + ComponentChanges.Builder changesBuilder = ComponentChanges.builder(); + Set> types = stackComponents.getTypes(); + + //set changes + for (Component entry : components) { + changesBuilder.add(entry); + types.remove(entry.type()); + } + + //remove the rest + for (DataComponentType type : types) { + changesBuilder.remove(type); + } + + stackComponents.applyChanges(changesBuilder.build()); + setStack(stack); } return SINGLE_SUCCESS; }))); - builder.then(literal("remove").then(argument("nbt_path", NbtPathArgumentType.nbtPath()).executes(context -> { + builder.then(literal("remove").then(argument("component", RegistryKeyArgumentType.registryKey(RegistryKeys.DATA_COMPONENT_TYPE)).executes(ctx -> { ItemStack stack = mc.player.getInventory().getMainHandStack(); if (validBasic(stack)) { - NbtPathArgumentType.NbtPath path = context.getArgument("nbt_path", NbtPathArgumentType.NbtPath.class); - path.remove(stack.encode(mc.world.getRegistryManager())); + @SuppressWarnings("unchecked") + RegistryKey> componentTypeKey = (RegistryKey>) ctx.getArgument("component", RegistryKey.class); + + DataComponentType componentType = Registries.DATA_COMPONENT_TYPE.get(componentTypeKey); + + ComponentMapImpl components = (ComponentMapImpl) stack.getComponents(); + components.applyChanges(ComponentChanges.builder().remove(componentType).build()); + + setStack(stack); } return SINGLE_SUCCESS; + }).suggests((ctx, suggestionsBuilder) -> { + ItemStack stack = mc.player.getInventory().getMainHandStack(); + if (stack != ItemStack.EMPTY) { + ComponentMap components = stack.getComponents(); + return CommandSource.suggestMatching(components.getTypes().stream().map(Registries.DATA_COMPONENT_TYPE::getEntry).map(RegistryEntry::getIdAsString), suggestionsBuilder); + } + return suggestionsBuilder.buildFuture(); }))); builder.then(literal("get").executes(context -> { - ItemStack stack = mc.player.getInventory().getMainHandStack(); + DataCommandObject dataCommandObject = new EntityDataObject(mc.player); + NbtPathArgumentType.NbtPath handPath = NbtPathArgumentType.NbtPath.parse("SelectedItem"); - if (stack == null) { - error("You must hold an item in your main hand."); - } else { - ComponentMap components = stack.getComponents(); + MutableText text = Text.empty().append(copyButton); - MutableText copyButton = Text.literal("NBT"); - copyButton.setStyle(copyButton.getStyle() - .withFormatting(Formatting.UNDERLINE) - .withClickEvent(new MeteorClickEvent( - ClickEvent.Action.RUN_COMMAND, - this.toString("copy") - )) - .withHoverEvent(new HoverEvent( - HoverEvent.Action.SHOW_TEXT, - Text.literal("Copy the NBT data to your clipboard.") - ))); - - MutableText text = Text.literal(""); - text.append(copyButton); - - if (components == null) text.append("{}"); - else text.append(" ").append(Text.of(components.toString())); - - info(text); + try { + List nbtElement = handPath.get(dataCommandObject.getNbt()); + if (!nbtElement.isEmpty()) { + text.append(" ").append(NbtHelper.toPrettyPrintedText(nbtElement.getFirst())); + } + } catch (CommandSyntaxException e) { + text.append("{}"); } + info(text); + return SINGLE_SUCCESS; })); builder.then(literal("copy").executes(context -> { - ItemStack stack = mc.player.getInventory().getMainHandStack(); - - if (stack == null) { - error("You must hold an item in your main hand."); - } else { - ComponentMap components = stack.getComponents(); - mc.keyboard.setClipboard(components.toString()); - MutableText nbt = Text.literal("NBT"); - nbt.setStyle(nbt.getStyle() - .withFormatting(Formatting.UNDERLINE) - .withHoverEvent(new HoverEvent( - HoverEvent.Action.SHOW_TEXT, - Text.of(components.toString()) - ))); - - MutableText text = Text.literal(""); - text.append(nbt); - text.append(Text.literal(" data copied!")); - - info(text); - } + DataCommandObject dataCommandObject = new EntityDataObject(mc.player); + NbtPathArgumentType.NbtPath handPath = NbtPathArgumentType.NbtPath.parse("SelectedItem"); - return SINGLE_SUCCESS; - })); + MutableText text = Text.empty().append(copyButton); + String nbt = "{}"; - builder.then(literal("paste").executes(context -> { - ItemStack stack = mc.player.getInventory().getMainHandStack(); - - if (validBasic(stack)) { - NbtCompound nbt = CompoundNbtTagArgumentType.create().parse(new StringReader(mc.keyboard.getClipboard())); + try { + List nbtElement = handPath.get(dataCommandObject.getNbt()); + if (!nbtElement.isEmpty()) { + text.append(" ").append(NbtHelper.toPrettyPrintedText(nbtElement.getFirst())); + nbt = nbtElement.getFirst().toString(); + } + } catch (CommandSyntaxException e) { + text.append("{}"); + } - stack = ItemStack.fromNbtOrEmpty(mc.world.getRegistryManager(), nbt); + mc.keyboard.setClipboard(nbt); - setStack(stack); - } + text.append(" data copied!"); + info(text); return SINGLE_SUCCESS; })); @@ -164,7 +190,7 @@ public void build(LiteralArgumentBuilder builder) { } return SINGLE_SUCCESS; - })));*/ + }))); } private void setStack(ItemStack stack) { @@ -177,7 +203,7 @@ private boolean validBasic(ItemStack stack) { return false; } - if (stack == null) { + if (stack == ItemStack.EMPTY) { error("You must hold an item in your main hand."); return false; } diff --git a/src/main/java/meteordevelopment/meteorclient/utils/misc/ComponentMapReader.java b/src/main/java/meteordevelopment/meteorclient/utils/misc/ComponentMapReader.java new file mode 100644 index 0000000000..2ed916302c --- /dev/null +++ b/src/main/java/meteordevelopment/meteorclient/utils/misc/ComponentMapReader.java @@ -0,0 +1,189 @@ +/* + * This file is part of the Meteor Client distribution (https://github.com/MeteorDevelopment/meteor-client). + * Copyright (c) Meteor Development. + */ + +package meteordevelopment.meteorclient.utils.misc; + +import com.mojang.brigadier.StringReader; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.exceptions.Dynamic2CommandExceptionType; +import com.mojang.brigadier.exceptions.DynamicCommandExceptionType; +import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; +import com.mojang.brigadier.suggestion.Suggestions; +import com.mojang.brigadier.suggestion.SuggestionsBuilder; +import com.mojang.serialization.DataResult; +import com.mojang.serialization.DynamicOps; +import it.unimi.dsi.fastutil.objects.ReferenceArraySet; +import net.minecraft.command.CommandRegistryAccess; +import net.minecraft.command.CommandSource; +import net.minecraft.component.ComponentMap; +import net.minecraft.component.DataComponentType; +import net.minecraft.nbt.NbtElement; +import net.minecraft.nbt.NbtOps; +import net.minecraft.nbt.StringNbtReader; +import net.minecraft.registry.Registries; +import net.minecraft.text.Text; +import net.minecraft.util.Identifier; + +import java.util.Locale; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.function.Function; + +public class ComponentMapReader { + private static final DynamicCommandExceptionType UNKNOWN_COMPONENT_EXCEPTION = new DynamicCommandExceptionType( + id -> Text.stringifiedTranslatable("arguments.item.component.unknown", id) + ); + private static final SimpleCommandExceptionType COMPONENT_EXPECTED_EXCEPTION = new SimpleCommandExceptionType(Text.translatable("arguments.item.component.expected")); + private static final DynamicCommandExceptionType REPEATED_COMPONENT_EXCEPTION = new DynamicCommandExceptionType( + type -> Text.stringifiedTranslatable("arguments.item.component.repeated", type) + ); + private static final Dynamic2CommandExceptionType MALFORMED_COMPONENT_EXCEPTION = new Dynamic2CommandExceptionType( + (type, error) -> Text.stringifiedTranslatable("arguments.item.component.malformed", type, error) + ); + private final DynamicOps nbtOps; + + public ComponentMapReader(CommandRegistryAccess commandRegistryAccess) { + this.nbtOps = commandRegistryAccess.getOps(NbtOps.INSTANCE); + } + + public ComponentMap consume(StringReader reader) throws CommandSyntaxException { + int cursor = reader.getCursor(); + + try { + return new Reader(reader, nbtOps).read(); + } catch (CommandSyntaxException e) { + reader.setCursor(cursor); + throw e; + } + } + + public CompletableFuture getSuggestions(SuggestionsBuilder builder) { + StringReader stringReader = new StringReader(builder.getInput()); + stringReader.setCursor(builder.getStart()); + Reader reader = new Reader(stringReader, nbtOps); + + try { + reader.read(); + } catch (CommandSyntaxException ignored) { + } + + return reader.suggestor.apply(builder.createOffset(stringReader.getCursor())); + } + + private static class Reader { + private static final Function> SUGGEST_DEFAULT = SuggestionsBuilder::buildFuture; + private final StringReader reader; + private final DynamicOps nbtOps; + public Function> suggestor = this::suggestBracket; + + public Reader(StringReader reader, DynamicOps nbtOps) { + this.reader = reader; + this.nbtOps = nbtOps; + } + + public ComponentMap read() throws CommandSyntaxException { + ComponentMap.Builder builder = ComponentMap.builder(); + + reader.expect('['); + suggestor = this::suggestComponentType; + Set> set = new ReferenceArraySet<>(); + + while(reader.canRead() && reader.peek() != ']') { + reader.skipWhitespace(); + DataComponentType dataComponentType = readComponentType(reader); + if (!set.add(dataComponentType)) { + throw REPEATED_COMPONENT_EXCEPTION.create(dataComponentType); + } + + suggestor = this::suggestEqual; + reader.skipWhitespace(); + reader.expect('='); + suggestor = SUGGEST_DEFAULT; + reader.skipWhitespace(); + this.readComponentValue(reader, builder, dataComponentType); + reader.skipWhitespace(); + suggestor = this::suggestEndOfComponent; + if (!reader.canRead() || reader.peek() != ',') { + break; + } + + reader.skip(); + reader.skipWhitespace(); + suggestor = this::suggestComponentType; + if (!reader.canRead()) { + throw COMPONENT_EXPECTED_EXCEPTION.createWithContext(reader); + } + } + + reader.expect(']'); + suggestor = SUGGEST_DEFAULT; + + return builder.build(); + } + + public static DataComponentType readComponentType(StringReader reader) throws CommandSyntaxException { + if (!reader.canRead()) { + throw COMPONENT_EXPECTED_EXCEPTION.createWithContext(reader); + } else { + int i = reader.getCursor(); + Identifier identifier = Identifier.fromCommandInput(reader); + DataComponentType dataComponentType = Registries.DATA_COMPONENT_TYPE.get(identifier); + if (dataComponentType != null && !dataComponentType.shouldSkipSerialization()) { + return dataComponentType; + } else { + reader.setCursor(i); + throw UNKNOWN_COMPONENT_EXCEPTION.createWithContext(reader, identifier); + } + } + } + + private CompletableFuture suggestComponentType(SuggestionsBuilder builder) { + String string = builder.getRemaining().toLowerCase(Locale.ROOT); + CommandSource.forEachMatching(Registries.DATA_COMPONENT_TYPE.getEntrySet(), string, entry -> entry.getKey().getValue(), entry -> { + DataComponentType dataComponentType = entry.getValue(); + if (dataComponentType.getCodec() != null) { + Identifier identifier = entry.getKey().getValue(); + builder.suggest(identifier.toString() + "="); + } + }); + return builder.buildFuture(); + } + + private void readComponentValue(StringReader reader, ComponentMap.Builder builder, DataComponentType type) throws CommandSyntaxException { + int i = reader.getCursor(); + NbtElement nbtElement = new StringNbtReader(reader).parseElement(); + DataResult dataResult = type.getCodecOrThrow().parse(this.nbtOps, nbtElement); + builder.add(type, dataResult.getOrThrow(error -> { + reader.setCursor(i); + return MALFORMED_COMPONENT_EXCEPTION.createWithContext(reader, type.toString(), error); + })); + } + + private CompletableFuture suggestBracket(SuggestionsBuilder builder) { + if (builder.getRemaining().isEmpty()) { + builder.suggest(String.valueOf('[')); + } + + return builder.buildFuture(); + } + + private CompletableFuture suggestEndOfComponent(SuggestionsBuilder builder) { + if (builder.getRemaining().isEmpty()) { + builder.suggest(String.valueOf(',')); + builder.suggest(String.valueOf(']')); + } + + return builder.buildFuture(); + } + + private CompletableFuture suggestEqual(SuggestionsBuilder builder) { + if (builder.getRemaining().isEmpty()) { + builder.suggest(String.valueOf('=')); + } + + return builder.buildFuture(); + } + } +} From 073835b7060a175c26d245504eb4ef0c3e26dfac Mon Sep 17 00:00:00 2001 From: Wide-Cat Date: Thu, 20 Jun 2024 21:35:28 +0100 Subject: [PATCH 2/2] Fix suggestions for remove argument --- .../commands/commands/NbtCommand.java | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/main/java/meteordevelopment/meteorclient/commands/commands/NbtCommand.java b/src/main/java/meteordevelopment/meteorclient/commands/commands/NbtCommand.java index 3acda1c180..6fd7280ba6 100644 --- a/src/main/java/meteordevelopment/meteorclient/commands/commands/NbtCommand.java +++ b/src/main/java/meteordevelopment/meteorclient/commands/commands/NbtCommand.java @@ -26,12 +26,12 @@ import net.minecraft.registry.Registries; import net.minecraft.registry.RegistryKey; import net.minecraft.registry.RegistryKeys; -import net.minecraft.registry.entry.RegistryEntry; import net.minecraft.text.*; import net.minecraft.util.Formatting; import net.minecraft.util.Unit; import java.util.List; +import java.util.Locale; import java.util.Set; import static com.mojang.brigadier.Command.SINGLE_SUCCESS; @@ -129,8 +129,21 @@ public void build(LiteralArgumentBuilder builder) { ItemStack stack = mc.player.getInventory().getMainHandStack(); if (stack != ItemStack.EMPTY) { ComponentMap components = stack.getComponents(); - return CommandSource.suggestMatching(components.getTypes().stream().map(Registries.DATA_COMPONENT_TYPE::getEntry).map(RegistryEntry::getIdAsString), suggestionsBuilder); + String remaining = suggestionsBuilder.getRemaining().toLowerCase(Locale.ROOT); + + CommandSource.forEachMatching(components.getTypes().stream().map(Registries.DATA_COMPONENT_TYPE::getEntry).toList(), remaining, entry -> { + if (entry.getKey().isPresent()) return entry.getKey().get().getValue(); + return null; + }, entry -> { + DataComponentType dataComponentType = entry.value(); + if (dataComponentType.getCodec() != null) { + if (entry.getKey().isPresent()) { + suggestionsBuilder.suggest(entry.getKey().get().getValue().toString()); + } + } + }); } + return suggestionsBuilder.buildFuture(); })));