diff --git a/src/main/java/meteordevelopment/meteorclient/commands/Command.java b/src/main/java/meteordevelopment/meteorclient/commands/Command.java index 10c63e133a..54c59d4824 100644 --- a/src/main/java/meteordevelopment/meteorclient/commands/Command.java +++ b/src/main/java/meteordevelopment/meteorclient/commands/Command.java @@ -9,6 +9,7 @@ import com.mojang.brigadier.arguments.ArgumentType; import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.builder.RequiredArgumentBuilder; +import com.mojang.brigadier.tree.LiteralCommandNode; import meteordevelopment.meteorclient.MeteorClient; import meteordevelopment.meteorclient.systems.config.Config; import meteordevelopment.meteorclient.utils.Utils; @@ -23,7 +24,7 @@ import java.util.List; public abstract class Command { - protected static final CommandRegistryAccess REGISTRY_ACCESS = CommandManager.createRegistryAccess(BuiltinRegistries.createWrapperLookup()); + protected static CommandRegistryAccess REGISTRY_ACCESS = CommandManager.createRegistryAccess(BuiltinRegistries.createWrapperLookup()); protected static final int SINGLE_SUCCESS = com.mojang.brigadier.Command.SINGLE_SUCCESS; protected static final MinecraftClient mc = MeteorClient.mc; @@ -49,14 +50,21 @@ protected static LiteralArgumentBuilder literal(final String name } public final void registerTo(CommandDispatcher dispatcher) { - register(dispatcher, name); - for (String alias : aliases) register(dispatcher, alias); + LiteralArgumentBuilder builder = literal(name); + build(builder); + LiteralCommandNode node = dispatcher.register(builder); + + for (String alias : aliases) registerAlias(dispatcher, node, alias); } - public void register(CommandDispatcher dispatcher, String name) { - LiteralArgumentBuilder builder = LiteralArgumentBuilder.literal(name); - build(builder); - dispatcher.register(builder); + private void registerAlias(CommandDispatcher dispatcher, LiteralCommandNode node, String alias) { + LiteralArgumentBuilder aliasBuilder = literal(alias); + if (node.getChildren().isEmpty()) { // apparently redirect nodes break when the target node has no children :/ + build(aliasBuilder); + } else { + aliasBuilder.redirect(node); + } + dispatcher.register(aliasBuilder); } public abstract void build(LiteralArgumentBuilder builder); diff --git a/src/main/java/meteordevelopment/meteorclient/commands/Commands.java b/src/main/java/meteordevelopment/meteorclient/commands/Commands.java index de498a625c..bba563ca66 100644 --- a/src/main/java/meteordevelopment/meteorclient/commands/Commands.java +++ b/src/main/java/meteordevelopment/meteorclient/commands/Commands.java @@ -7,9 +7,14 @@ import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.exceptions.CommandSyntaxException; +import meteordevelopment.meteorclient.MeteorClient; import meteordevelopment.meteorclient.commands.commands.*; +import meteordevelopment.meteorclient.events.game.GameJoinedEvent; import meteordevelopment.meteorclient.pathing.PathManagers; import meteordevelopment.meteorclient.utils.PostInit; +import meteordevelopment.orbit.EventHandler; +import net.minecraft.client.network.ClientPlayNetworkHandler; +import net.minecraft.command.CommandRegistryAccess; import net.minecraft.command.CommandSource; import java.util.ArrayList; @@ -19,56 +24,61 @@ import static meteordevelopment.meteorclient.MeteorClient.mc; public class Commands { - public static final CommandDispatcher DISPATCHER = new CommandDispatcher<>(); public static final List COMMANDS = new ArrayList<>(); + public static CommandDispatcher DISPATCHER = new CommandDispatcher<>(); @PostInit(dependencies = PathManagers.class) public static void init() { - add(new VClipCommand()); - add(new HClipCommand()); - add(new DismountCommand()); - add(new DisconnectCommand()); + add(new BindCommand()); + add(new BindsCommand()); + add(new CommandsCommand()); + add(new ComponentsCommand()); + add(new ConfigCommand()); add(new DamageCommand()); + add(new DisconnectCommand()); + add(new DismountCommand()); add(new DropCommand()); add(new EnchantCommand()); + add(new EnderChestCommand()); add(new FakePlayerCommand()); + add(new FovCommand()); add(new FriendsCommand()); - add(new CommandsCommand()); + add(new GamemodeCommand()); + add(new GiveCommand()); + add(new HClipCommand()); + add(new InputCommand()); add(new InventoryCommand()); + add(new LocateCommand()); + add(new MacroCommand()); + add(new ModulesCommand()); + add(new MultitoolCommand()); + add(new NameHistoryCommand()); add(new NbtCommand()); add(new NotebotCommand()); add(new PeekCommand()); - add(new EnderChestCommand()); add(new ProfilesCommand()); add(new ReloadCommand()); add(new ResetCommand()); + add(new RotationCommand()); + add(new SaveMapCommand()); add(new SayCommand()); add(new ServerCommand()); - add(new SwarmCommand()); - add(new ToggleCommand()); add(new SettingCommand()); add(new SpectateCommand()); - add(new GamemodeCommand()); - add(new SaveMapCommand()); - add(new MacroCommand()); - add(new ModulesCommand()); - add(new BindsCommand()); - add(new GiveCommand()); - add(new NameHistoryCommand()); - add(new BindCommand()); - add(new FovCommand()); - add(new RotationCommand()); - add(new WaypointCommand()); - add(new InputCommand()); + add(new SwarmCommand()); + add(new ToggleCommand()); + add(new TransmogrifyCommand()); + add(new VClipCommand()); add(new WaspCommand()); - add(new LocateCommand()); + add(new WaypointCommand()); COMMANDS.sort(Comparator.comparing(Command::getName)); + + MeteorClient.EVENT_BUS.subscribe(Commands.class); } public static void add(Command command) { COMMANDS.removeIf(existing -> existing.getName().equals(command.getName())); - command.registerTo(DISPATCHER); COMMANDS.add(command); } @@ -85,4 +95,38 @@ public static Command get(String name) { return null; } + + /** + * Argument types that rely on Minecraft registries access those registries through a {@link CommandRegistryAccess} + * object. + * Since dynamic registries are specific to each server, we need to make a new {@link CommandRegistryAccess} object + * every time we join a server. + *

+ * Annoyingly, we also have to create a new {@link CommandDispatcher} because the command tree needs to be rebuilt + * from scratch. This is mainly due to two reasons: + *

    + *
  1. Argument types that access registries do so through a registry wrapper object that is created and cached + * when the argument type objects are created, that is to say when the command tree is built. + * This would cause the registry wrapper objects to become stale after joining another server. + *
  2. We can't re-register command nodes to the same {@link CommandDispatcher}, because the node merging that + * happens only registers missing children, it doesn't replace existing ones so the stale argument type + * objects wouldn't get replaced. + *
+ *

+ * Ensuring dynamic registry entries match up perfectly is important, because even if a stale registry has identical + * contents to an up-to-date one, registry entries and keys are compared using referential equality, so their + * contents would not match. + * + * @author Crosby + */ + @EventHandler + private static void onJoin(GameJoinedEvent event) { + ClientPlayNetworkHandler networkHandler = mc.getNetworkHandler(); + Command.REGISTRY_ACCESS = CommandRegistryAccess.of(networkHandler.getRegistryManager(), networkHandler.getEnabledFeatures()); + + DISPATCHER = new CommandDispatcher<>(); + for (Command command : COMMANDS) { + command.registerTo(DISPATCHER); + } + } } diff --git a/src/main/java/meteordevelopment/meteorclient/commands/arguments/BlockPosArgumentType.java b/src/main/java/meteordevelopment/meteorclient/commands/arguments/BlockPosArgumentType.java new file mode 100644 index 0000000000..72725bbd5f --- /dev/null +++ b/src/main/java/meteordevelopment/meteorclient/commands/arguments/BlockPosArgumentType.java @@ -0,0 +1,341 @@ +/* + * 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.exceptions.SimpleCommandExceptionType; +import com.mojang.brigadier.suggestion.Suggestions; +import com.mojang.brigadier.suggestion.SuggestionsBuilder; +import net.minecraft.client.world.ClientWorld; +import net.minecraft.command.CommandSource; +import net.minecraft.command.argument.CoordinateArgument; +import net.minecraft.command.argument.EntityAnchorArgumentType; +import net.minecraft.command.argument.Vec3ArgumentType; +import net.minecraft.server.command.CommandManager; +import net.minecraft.text.Text; +import net.minecraft.util.math.*; +import net.minecraft.world.World; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; + +import static meteordevelopment.meteorclient.MeteorClient.mc; + +/** + * Taken from clientarguments + *

+ * The MIT License (MIT) + *

+ * Copyright (c) 2021 xpple + *

+ * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + *

+ * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + *

+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author Xpple + * @see CBlockPosArgument.java + * @see CCoordinates.java + * @see CWorldCoordinates.java + * @see CLocalCoordinates.java + */ +public class BlockPosArgumentType implements ArgumentType { + private static final BlockPosArgumentType INSTANCE = new BlockPosArgumentType(); + private static final Collection EXAMPLES = Arrays.asList("0 0 0", "~ ~ ~", "^ ^ ^", "^1 ^ ^-5", "~0.5 ~1 ~-5"); + public static final SimpleCommandExceptionType UNLOADED_EXCEPTION = new SimpleCommandExceptionType(Text.translatable("argument.pos.unloaded")); + public static final SimpleCommandExceptionType OUT_OF_WORLD_EXCEPTION = new SimpleCommandExceptionType(Text.translatable("argument.pos.outofworld")); + public static final SimpleCommandExceptionType OUT_OF_BOUNDS_EXCEPTION = new SimpleCommandExceptionType(Text.translatable("argument.pos.outofbounds")); + + private BlockPosArgumentType() {} + + public static BlockPosArgumentType blockPos() { + return INSTANCE; + } + + public static BlockPos getLoadedBlockPos(CommandContext context, String name) throws CommandSyntaxException { + ClientWorld clientLevel = mc.world; + return getLoadedBlockPos(context, clientLevel, name); + } + + public static BlockPos getLoadedBlockPos(CommandContext context, ClientWorld level, String name) throws CommandSyntaxException { + BlockPos blockPos = getBlockPos(context, name); + ChunkPos chunkPos = new ChunkPos(blockPos); + if (!level.getChunkManager().isChunkLoaded(chunkPos.x, chunkPos.z)) { + throw UNLOADED_EXCEPTION.create(); + } else if (!level.isInBuildLimit(blockPos)) { + throw OUT_OF_WORLD_EXCEPTION.create(); + } else { + return blockPos; + } + } + + public static BlockPos getBlockPos(CommandContext context, String name) { + return context.getArgument(name, PosArgument.class).getBlockPos(context.getSource()); + } + + public static BlockPos getValidBlockPos(CommandContext context, String name) throws CommandSyntaxException { + BlockPos blockPos = getBlockPos(context, name); + if (!World.isValid(blockPos)) { + throw OUT_OF_BOUNDS_EXCEPTION.create(); + } else { + return blockPos; + } + } + + public PosArgument parse(StringReader stringReader) throws CommandSyntaxException { + return stringReader.canRead() && stringReader.peek() == '^' ? LookingPosArgument.parse(stringReader) : DefaultPosArgument.parse(stringReader); + } + + public CompletableFuture listSuggestions(CommandContext context, SuggestionsBuilder builder) { + if (!(context.getSource() instanceof CommandSource)) { + return Suggestions.empty(); + } else { + String string = builder.getRemaining(); + Collection collection; + if (!string.isEmpty() && string.charAt(0) == '^') { + collection = Collections.singleton(CommandSource.RelativePosition.ZERO_LOCAL); + } else { + collection = ((CommandSource) context.getSource()).getBlockPositionSuggestions(); + } + + return CommandSource.suggestPositions(string, collection, builder, CommandManager.getCommandValidator(this::parse)); + } + } + + public Collection getExamples() { + return EXAMPLES; + } + + public interface PosArgument { + Vec3d getPosition(S var1); + + Vec2f getRotation(S var1); + + default BlockPos getBlockPos(S source) { + return BlockPos.ofFloored(this.getPosition(source)); + } + + boolean isXRelative(); + + boolean isYRelative(); + + boolean isZRelative(); + } + + public static class DefaultPosArgument implements PosArgument { + private final CoordinateArgument x; + private final CoordinateArgument y; + private final CoordinateArgument z; + + public DefaultPosArgument(CoordinateArgument x, CoordinateArgument y, CoordinateArgument z) { + this.x = x; + this.y = y; + this.z = z; + } + + @Override + public Vec3d getPosition(S source) { + Vec3d vec3 = mc.player.getPos(); + return new Vec3d(this.x.toAbsoluteCoordinate(vec3.x), this.y.toAbsoluteCoordinate(vec3.y), this.z.toAbsoluteCoordinate(vec3.z)); + } + + @Override + public Vec2f getRotation(S source) { + Vec2f vec2 = mc.player.getRotationClient(); + return new Vec2f((float) this.x.toAbsoluteCoordinate(vec2.x), (float) this.y.toAbsoluteCoordinate(vec2.y)); + } + + @Override + public boolean isXRelative() { + return this.x.isRelative(); + } + + @Override + public boolean isYRelative() { + return this.y.isRelative(); + } + + @Override + public boolean isZRelative() { + return this.z.isRelative(); + } + + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof DefaultPosArgument defaultPosArgument)) { + return false; + } + return this.x.equals(defaultPosArgument.x) && this.y.equals(defaultPosArgument.y) && this.z.equals(defaultPosArgument.z); + } + + public static DefaultPosArgument parse(StringReader reader) throws CommandSyntaxException { + int cursor = reader.getCursor(); + CoordinateArgument worldCoordinate = CoordinateArgument.parse(reader); + if (reader.canRead() && reader.peek() == ' ') { + reader.skip(); + CoordinateArgument worldCoordinate2 = CoordinateArgument.parse(reader); + if (reader.canRead() && reader.peek() == ' ') { + reader.skip(); + CoordinateArgument worldCoordinate3 = CoordinateArgument.parse(reader); + return new DefaultPosArgument(worldCoordinate, worldCoordinate2, worldCoordinate3); + } + } + reader.setCursor(cursor); + throw Vec3ArgumentType.INCOMPLETE_EXCEPTION.createWithContext(reader); + } + + public static DefaultPosArgument parse(StringReader reader, boolean centerIntegers) throws CommandSyntaxException { + int cursor = reader.getCursor(); + CoordinateArgument worldCoordinate = CoordinateArgument.parse(reader, centerIntegers); + if (reader.canRead() && reader.peek() == ' ') { + reader.skip(); + CoordinateArgument worldCoordinate2 = CoordinateArgument.parse(reader, false); + if (reader.canRead() && reader.peek() == ' ') { + reader.skip(); + CoordinateArgument worldCoordinate3 = CoordinateArgument.parse(reader, centerIntegers); + return new DefaultPosArgument(worldCoordinate, worldCoordinate2, worldCoordinate3); + } + } + reader.setCursor(cursor); + throw Vec3ArgumentType.INCOMPLETE_EXCEPTION.createWithContext(reader); + } + + public static DefaultPosArgument absolute(double x, double y, double z) { + return new DefaultPosArgument(new CoordinateArgument(false, x), new CoordinateArgument(false, y), new CoordinateArgument(false, z)); + } + + public static DefaultPosArgument absolute(Vec2f vec) { + return new DefaultPosArgument(new CoordinateArgument(false, vec.x), new CoordinateArgument(false, vec.y), new CoordinateArgument(true, 0.0)); + } + + public static DefaultPosArgument current() { + return new DefaultPosArgument(new CoordinateArgument(true, 0.0), new CoordinateArgument(true, 0.0), new CoordinateArgument(true, 0.0)); + } + + @Override + public int hashCode() { + int i = this.x.hashCode(); + i = 31 * i + this.y.hashCode(); + return 31 * i + this.z.hashCode(); + } + } + + public static class LookingPosArgument implements PosArgument { + private final double x; + private final double y; + private final double z; + + public LookingPosArgument(double x, double y, double z) { + this.x = x; + this.y = y; + this.z = z; + } + + @Override + public Vec3d getPosition(S source) { + Vec2f vec2 = mc.player.getRotationClient(); + Vec3d vec3 = EntityAnchorArgumentType.EntityAnchor.FEET.positionAt(mc.player); + float f = MathHelper.cos((vec2.y + 90.0F) * (float) (Math.PI / 180.0)); + float g = MathHelper.sin((vec2.y + 90.0F) * (float) (Math.PI / 180.0)); + float h = MathHelper.cos(-vec2.x * (float) (Math.PI / 180.0)); + float i = MathHelper.sin(-vec2.x * (float) (Math.PI / 180.0)); + float j = MathHelper.cos((-vec2.x + 90.0F) * (float) (Math.PI / 180.0)); + float k = MathHelper.sin((-vec2.x + 90.0F) * (float) (Math.PI / 180.0)); + Vec3d vec32 = new Vec3d(f * h, i, g * h); + Vec3d vec33 = new Vec3d(f * j, k, g * j); + Vec3d vec34 = vec32.crossProduct(vec33).multiply(-1.0); + double d = vec32.x * this.z + vec33.x * this.y + vec34.x * this.x; + double e = vec32.y * this.z + vec33.y * this.y + vec34.y * this.x; + double l = vec32.z * this.z + vec33.z * this.y + vec34.z * this.x; + return new Vec3d(vec3.x + d, vec3.y + e, vec3.z + l); + } + + @Override + public Vec2f getRotation(S source) { + return Vec2f.ZERO; + } + + @Override + public boolean isXRelative() { + return true; + } + + @Override + public boolean isYRelative() { + return true; + } + + @Override + public boolean isZRelative() { + return true; + } + + public static LookingPosArgument parse(StringReader reader) throws CommandSyntaxException { + int cursor = reader.getCursor(); + double d = readCoordinate(reader, cursor); + if (!reader.canRead() || reader.peek() != ' ') { + reader.setCursor(cursor); + throw Vec3ArgumentType.INCOMPLETE_EXCEPTION.createWithContext(reader); + } + reader.skip(); + double e = readCoordinate(reader, cursor); + if (!reader.canRead() || reader.peek() != ' ') { + reader.setCursor(cursor); + throw Vec3ArgumentType.INCOMPLETE_EXCEPTION.createWithContext(reader); + } + reader.skip(); + double f = readCoordinate(reader, cursor); + return new LookingPosArgument(d, e, f); + } + + private static double readCoordinate(StringReader reader, int startingCursorPos) throws CommandSyntaxException { + if (!reader.canRead()) { + throw CoordinateArgument.MISSING_COORDINATE.createWithContext(reader); + } + if (reader.peek() != '^') { + reader.setCursor(startingCursorPos); + throw Vec3ArgumentType.MIXED_COORDINATE_EXCEPTION.createWithContext(reader); + } + reader.skip(); + return reader.canRead() && reader.peek() != ' ' ? reader.readDouble() : 0.0; + } + + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof LookingPosArgument lookingPosArgument)) { + return false; + } + return this.x == lookingPosArgument.x && this.y == lookingPosArgument.y && this.z == lookingPosArgument.z; + } + + public int hashCode() { + return Objects.hash(this.x, this.y, this.z); + } + } +} diff --git a/src/main/java/meteordevelopment/meteorclient/commands/arguments/ComponentMapArgumentType.java b/src/main/java/meteordevelopment/meteorclient/commands/arguments/ComponentMapArgumentType.java index 4ec8c2648a..c986fd66ed 100644 --- a/src/main/java/meteordevelopment/meteorclient/commands/arguments/ComponentMapArgumentType.java +++ b/src/main/java/meteordevelopment/meteorclient/commands/arguments/ComponentMapArgumentType.java @@ -11,9 +11,8 @@ 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 meteordevelopment.meteorclient.utils.commands.ComponentMapReader; import net.minecraft.command.CommandRegistryAccess; -import net.minecraft.command.CommandSource; import net.minecraft.component.ComponentMap; import java.util.Collection; @@ -32,7 +31,7 @@ public static ComponentMapArgumentType componentMap(CommandRegistryAccess comman return new ComponentMapArgumentType(commandRegistryAccess); } - public static ComponentMap getComponentMap(CommandContext context, String name) { + public static ComponentMap getComponentMap(CommandContext context, String name) { return context.getArgument(name, ComponentMap.class); } diff --git a/src/main/java/meteordevelopment/meteorclient/commands/arguments/CompoundNbtTagArgumentType.java b/src/main/java/meteordevelopment/meteorclient/commands/arguments/CompoundNbtTagArgumentType.java index 23d761922d..d4460c1974 100644 --- a/src/main/java/meteordevelopment/meteorclient/commands/arguments/CompoundNbtTagArgumentType.java +++ b/src/main/java/meteordevelopment/meteorclient/commands/arguments/CompoundNbtTagArgumentType.java @@ -37,22 +37,7 @@ public NbtCompound parse(StringReader reader) throws CommandSyntaxException { if (!reader.canRead()) { throw EXPECTED_VALUE.createWithContext(reader); } - StringBuilder b = new StringBuilder(); - int open = 0; - while (reader.canRead()) { - if (reader.peek() == '{') { - open++; - } - else if (reader.peek() == '}') { - open--; - } - if (open == 0) - break; - b.append(reader.read()); - } - reader.expect('}'); - b.append('}'); - return StringNbtReader.parse(b.toString() + return StringNbtReader.parse(reader.getRemaining() .replace("$", "§") .replace("§§", "$") ); diff --git a/src/main/java/meteordevelopment/meteorclient/commands/arguments/DirectionArgumentType.java b/src/main/java/meteordevelopment/meteorclient/commands/arguments/DirectionArgumentType.java index d50efe6797..7c5d41bcaa 100644 --- a/src/main/java/meteordevelopment/meteorclient/commands/arguments/DirectionArgumentType.java +++ b/src/main/java/meteordevelopment/meteorclient/commands/arguments/DirectionArgumentType.java @@ -5,6 +5,7 @@ package meteordevelopment.meteorclient.commands.arguments; +import com.mojang.brigadier.context.CommandContext; import net.minecraft.command.argument.EnumArgumentType; import net.minecraft.util.math.Direction; @@ -15,6 +16,14 @@ private DirectionArgumentType() { super(Direction.CODEC, Direction::values); } + public static Direction getDirection(CommandContext context) { + return context.getArgument("direction", Direction.class); + } + + public static Direction getDirection(CommandContext context, String name) { + return context.getArgument(name, Direction.class); + } + public static DirectionArgumentType create() { return INSTANCE; } diff --git a/src/main/java/meteordevelopment/meteorclient/commands/arguments/EntityArgumentType.java b/src/main/java/meteordevelopment/meteorclient/commands/arguments/EntityArgumentType.java new file mode 100644 index 0000000000..eb3105af4a --- /dev/null +++ b/src/main/java/meteordevelopment/meteorclient/commands/arguments/EntityArgumentType.java @@ -0,0 +1,1406 @@ +/* + * 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.google.common.collect.Iterables; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.Streams; +import com.google.common.primitives.Doubles; +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.exceptions.DynamicCommandExceptionType; +import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; +import com.mojang.brigadier.suggestion.Suggestions; +import com.mojang.brigadier.suggestion.SuggestionsBuilder; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import meteordevelopment.meteorclient.mixin.WorldAccessor; +import net.minecraft.advancement.AdvancementProgress; +import net.minecraft.advancement.criterion.CriterionProgress; +import net.minecraft.client.network.AbstractClientPlayerEntity; +import net.minecraft.client.network.PlayerListEntry; +import net.minecraft.client.world.ClientWorld; +import net.minecraft.command.CommandSource; +import net.minecraft.command.FloatRangeArgument; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityType; +import net.minecraft.entity.LivingEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.nbt.NbtHelper; +import net.minecraft.nbt.StringNbtReader; +import net.minecraft.predicate.NumberRange; +import net.minecraft.registry.Registries; +import net.minecraft.registry.RegistryKeys; +import net.minecraft.registry.tag.TagKey; +import net.minecraft.resource.featuretoggle.FeatureSet; +import net.minecraft.scoreboard.ReadableScoreboardScore; +import net.minecraft.scoreboard.Scoreboard; +import net.minecraft.scoreboard.ScoreboardObjective; +import net.minecraft.scoreboard.Team; +import net.minecraft.text.Text; +import net.minecraft.text.Texts; +import net.minecraft.util.Identifier; +import net.minecraft.util.TypeFilter; +import net.minecraft.util.Util; +import net.minecraft.util.function.LazyIterationConsumer; +import net.minecraft.util.math.Box; +import net.minecraft.util.math.MathHelper; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.GameMode; +import net.minecraft.world.World; +import org.jetbrains.annotations.Nullable; + +import java.io.IOException; +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.function.*; +import java.util.stream.Collectors; + +import static meteordevelopment.meteorclient.MeteorClient.mc; + +/** + * Taken from clientarguments + *

+ * The MIT License (MIT) + *

+ * Copyright (c) 2021 xpple + *

+ * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + *

+ * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + *

+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author Xpple + * @see CEntityArgument.java + * @see CEntitySelector.java + * @see CEntitySelectorOptions.java + * @see CEntitySelectorParser.java + */ +public class EntityArgumentType implements ArgumentType { + private static final Collection EXAMPLES = Arrays.asList("Player", "0123", "@e", "@e[type=foo]", "dd12be42-52a9-4a91-a8a1-11c01849e498"); + private static final SimpleCommandExceptionType TOO_MANY_ENTITIES_EXCEPTION = new SimpleCommandExceptionType(Text.translatable("argument.entity.toomany")); + private static final SimpleCommandExceptionType TOO_MANY_PLAYERS_EXCEPTION = new SimpleCommandExceptionType(Text.translatable("argument.player.toomany")); + private static final SimpleCommandExceptionType PLAYER_SELECTOR_HAS_ENTITIES_EXCEPTION = new SimpleCommandExceptionType(Text.translatable("argument.player.entities")); + private static final SimpleCommandExceptionType ENTITY_NOT_FOUND_EXCEPTION = new SimpleCommandExceptionType(Text.translatable("argument.entity.notfound.entity")); + private static final SimpleCommandExceptionType PLAYER_NOT_FOUND_EXCEPTION = new SimpleCommandExceptionType(Text.translatable("argument.entity.notfound.player")); + final boolean singleTarget; + final boolean playersOnly; + + protected EntityArgumentType(boolean singleTarget, boolean playersOnly) { + this.singleTarget = singleTarget; + this.playersOnly = playersOnly; + } + + public static EntityArgumentType entity() { + return new EntityArgumentType(true, false); + } + + public static Entity getEntity(final CommandContext context, final String name) throws CommandSyntaxException { + return context.getArgument(name, EntitySelector.class).findSingleEntity(context.getSource()); + } + + public static EntityArgumentType entities() { + return new EntityArgumentType(false, false); + } + + public static Collection getEntities(final CommandContext context, final String name) throws CommandSyntaxException { + Collection collection = getOptionalEntities(context, name); + if (collection.isEmpty()) { + throw ENTITY_NOT_FOUND_EXCEPTION.create(); + } + return collection; + } + + public static Collection getOptionalEntities(final CommandContext context, final String name) throws CommandSyntaxException { + return context.getArgument(name, EntitySelector.class).findEntities(context.getSource()); + } + + public static Collection getOptionalPlayers(final CommandContext context, final String name) throws CommandSyntaxException { + return context.getArgument(name, EntitySelector.class).findPlayers(context.getSource()); + } + + public static EntityArgumentType player() { + return new EntityArgumentType(true, true); + } + + public static AbstractClientPlayerEntity getPlayer(final CommandContext context, final String name) throws CommandSyntaxException { + return context.getArgument(name, EntitySelector.class).findSinglePlayer(context.getSource()); + } + + public static EntityArgumentType players() { + return new EntityArgumentType(false, true); + } + + public static Collection getPlayers(final CommandContext context, final String name) throws CommandSyntaxException { + List list = context.getArgument(name, EntitySelector.class).findPlayers(context.getSource()); + if (list.isEmpty()) { + throw PLAYER_NOT_FOUND_EXCEPTION.create(); + } + return list; + } + + @Override + public EntitySelector parse(final StringReader stringReader) throws CommandSyntaxException { + return this.parse(stringReader, true); + } + + public EntitySelector parse(final StringReader stringReader, final S source) throws CommandSyntaxException { + return this.parse(stringReader, EntitySelectorParser.allowSelectors(source)); + } + + private EntitySelector parse(StringReader stringReader, boolean allowSelectors) throws CommandSyntaxException { + EntitySelectorParser entitySelectorParser = new EntitySelectorParser(stringReader, allowSelectors); + EntitySelector entitySelector = entitySelectorParser.parse(); + if (entitySelector.getMaxResults() > 1 && this.singleTarget) { + if (this.playersOnly) { + stringReader.setCursor(0); + throw TOO_MANY_PLAYERS_EXCEPTION.createWithContext(stringReader); + } else { + stringReader.setCursor(0); + throw TOO_MANY_ENTITIES_EXCEPTION.createWithContext(stringReader); + } + } + if (entitySelector.includesEntities() && this.playersOnly && !entitySelector.isSelfSelector()) { + stringReader.setCursor(0); + throw PLAYER_SELECTOR_HAS_ENTITIES_EXCEPTION.createWithContext(stringReader); + } + return entitySelector; + } + + @Override + public CompletableFuture listSuggestions(final CommandContext context, final SuggestionsBuilder builder) { + if (context.getSource() instanceof CommandSource commandSource) { + StringReader stringReader = new StringReader(builder.getInput()); + stringReader.setCursor(builder.getStart()); + EntitySelectorParser entitySelectorParser = new EntitySelectorParser(stringReader, EntitySelectorParser.allowSelectors(commandSource)); + + try { + entitySelectorParser.parse(); + } catch (CommandSyntaxException ignored) { + } + + return entitySelectorParser.fillSuggestions(builder, builderx -> { + Collection collection = commandSource.getPlayerNames(); + Iterable iterable = this.playersOnly ? collection : Iterables.concat(collection, commandSource.getEntitySuggestions()); + CommandSource.suggestMatching(iterable, builderx); + }); + } + return Suggestions.empty(); + } + + @Override + public Collection getExamples() { + return EXAMPLES; + } + + public static class EntitySelectorOptions { + + private static final Map OPTIONS = Maps.newHashMap(); + public static final DynamicCommandExceptionType UNKNOWN_OPTION_EXCEPTION = new DynamicCommandExceptionType(option -> Text.stringifiedTranslatable("argument.entity.options.unknown", option)); + public static final DynamicCommandExceptionType INAPPLICABLE_OPTION_EXCEPTION = new DynamicCommandExceptionType(option -> Text.stringifiedTranslatable("argument.entity.options.inapplicable", option)); + public static final SimpleCommandExceptionType NEGATIVE_DISTANCE_EXCEPTION = new SimpleCommandExceptionType(Text.translatable("argument.entity.options.distance.negative")); + public static final SimpleCommandExceptionType NEGATIVE_LEVEL_EXCEPTION = new SimpleCommandExceptionType(Text.translatable("argument.entity.options.level.negative")); + public static final SimpleCommandExceptionType TOO_SMALL_LEVEL_EXCEPTION = new SimpleCommandExceptionType(Text.translatable("argument.entity.options.limit.toosmall")); + public static final DynamicCommandExceptionType IRREVERSIBLE_SORT_EXCEPTION = new DynamicCommandExceptionType(sortType -> Text.stringifiedTranslatable("argument.entity.options.sort.irreversible", sortType)); + public static final DynamicCommandExceptionType INVALID_MODE_EXCEPTION = new DynamicCommandExceptionType(gameMode -> Text.stringifiedTranslatable("argument.entity.options.mode.invalid", gameMode)); + public static final DynamicCommandExceptionType INVALID_TYPE_EXCEPTION = new DynamicCommandExceptionType(entity -> Text.stringifiedTranslatable("argument.entity.options.type.invalid", entity)); + + private static void putOption(String id, SelectorHandler handler, Predicate condition, Text description) { + OPTIONS.put(id, new SelectorOption(handler, condition, description)); + } + + public static void register() { + if (!OPTIONS.isEmpty()) { + return; + } + putOption("name", reader -> { + int cursor = reader.getReader().getCursor(); + boolean bl = reader.shouldInvertValue(); + String string = reader.getReader().readString(); + if (reader.hasNameNotEquals() && !bl) { + reader.getReader().setCursor(cursor); + throw INAPPLICABLE_OPTION_EXCEPTION.createWithContext(reader.getReader(), "name"); + } + if (bl) { + reader.setHasNameNotEquals(true); + } else { + reader.setHasNameEquals(true); + } + + reader.addPredicate(entity -> entity.getName().getString().equals(string) != bl); + }, reader -> !reader.hasNameEquals(), Text.translatable("argument.entity.options.name.description")); + putOption("distance", reader -> { + int cursor = reader.getReader().getCursor(); + NumberRange.DoubleRange doubleRange = NumberRange.DoubleRange.parse(reader.getReader()); + if ((doubleRange.min().isEmpty() || !(doubleRange.min().get() < 0.0)) && (doubleRange.max().isEmpty() || !(doubleRange.max().get() < 0.0))) { + reader.setDistance(doubleRange); + } else { + reader.getReader().setCursor(cursor); + throw NEGATIVE_DISTANCE_EXCEPTION.createWithContext(reader.getReader()); + } + }, reader -> reader.getDistance().isDummy(), Text.translatable("argument.entity.options.distance.description")); + putOption("level", reader -> { + int cursor = reader.getReader().getCursor(); + NumberRange.IntRange intRange = NumberRange.IntRange.parse(reader.getReader()); + if ((intRange.min().isEmpty() || intRange.min().get() >= 0) && (intRange.max().isEmpty() || intRange.max().get() >= 0)) { + reader.setLevel(intRange); + reader.setIncludesEntities(false); + } else { + reader.getReader().setCursor(cursor); + throw NEGATIVE_LEVEL_EXCEPTION.createWithContext(reader.getReader()); + } + }, reader -> reader.getLevel().isDummy(), Text.translatable("argument.entity.options.level.description")); + putOption("x", reader -> reader.setX(reader.getReader().readDouble()), reader -> reader.getX() == null, Text.translatable("argument.entity.options.x.description")); + putOption("y", reader -> reader.setY(reader.getReader().readDouble()), reader -> reader.getY() == null, Text.translatable("argument.entity.options.y.description")); + putOption("z", reader -> reader.setZ(reader.getReader().readDouble()), reader -> reader.getZ() == null, Text.translatable("argument.entity.options.z.description")); + putOption("dx", reader -> reader.setDeltaX(reader.getReader().readDouble()), reader -> reader.getDeltaX() == null, Text.translatable("argument.entity.options.dx.description")); + putOption("dy", reader -> reader.setDeltaY(reader.getReader().readDouble()), reader -> reader.getDeltaY() == null, Text.translatable("argument.entity.options.dy.description")); + putOption("dz", reader -> reader.setDeltaZ(reader.getReader().readDouble()), reader -> reader.getDeltaZ() == null, Text.translatable("argument.entity.options.dz.description")); + putOption("x_rotation", reader -> reader.setRotX(FloatRangeArgument.parse(reader.getReader(), true, MathHelper::wrapDegrees)), reader -> reader.getRotX() == FloatRangeArgument.ANY, Text.translatable("argument.entity.options.x_rotation.description")); + putOption("y_rotation", reader -> reader.setRotY(FloatRangeArgument.parse(reader.getReader(), true, MathHelper::wrapDegrees)), reader -> reader.getRotY() == FloatRangeArgument.ANY, Text.translatable("argument.entity.options.y_rotation.description")); + putOption("limit", reader -> { + int cursor = reader.getReader().getCursor(); + int j = reader.getReader().readInt(); + if (j < 1) { + reader.getReader().setCursor(cursor); + throw TOO_SMALL_LEVEL_EXCEPTION.createWithContext(reader.getReader()); + } + reader.setMaxResults(j); + reader.setLimited(true); + }, reader -> !reader.isCurrentEntity() && !reader.isLimited(), Text.translatable("argument.entity.options.limit.description")); + putOption("sort", reader -> { + int cursor = reader.getReader().getCursor(); + String string = reader.getReader().readUnquotedString(); + reader.setSuggestions((builder, consumer) -> CommandSource.suggestMatching(Arrays.asList("nearest", "furthest", "random", "arbitrary"), builder)); + + reader.setOrder(switch (string) { + case "nearest" -> EntitySelectorParser.ORDER_NEAREST; + case "furthest" -> EntitySelectorParser.ORDER_FURTHEST; + case "random" -> EntitySelectorParser.ORDER_RANDOM; + case "arbitrary" -> EntitySelector.ORDER_ARBITRARY; + default -> { + reader.getReader().setCursor(cursor); + throw IRREVERSIBLE_SORT_EXCEPTION.createWithContext(reader.getReader(), string); + } + }); + reader.setSorted(true); + }, reader -> !reader.isCurrentEntity() && !reader.isSorted(), Text.translatable("argument.entity.options.sort.description")); + putOption("gamemode", reader -> { + reader.setSuggestions((builder, consumer) -> { + String stringxx = builder.getRemaining().toLowerCase(Locale.ROOT); + boolean blxx = !reader.hasGamemodeNotEquals(); + boolean bl2 = true; + if (!stringxx.isEmpty()) { + if (stringxx.charAt(0) == EntitySelectorParser.SYNTAX_NOT) { + blxx = false; + stringxx = stringxx.substring(1); + } else { + bl2 = false; + } + } + + for (GameMode gameType : GameMode.values()) { + if (gameType.getName().toLowerCase(Locale.ROOT).startsWith(stringxx)) { + if (bl2) { + builder.suggest("!" + gameType.getName()); + } + + if (blxx) { + builder.suggest(gameType.getName()); + } + } + } + + return builder.buildFuture(); + }); + int cursor = reader.getReader().getCursor(); + boolean bl = reader.shouldInvertValue(); + if (reader.hasGamemodeNotEquals() && !bl) { + reader.getReader().setCursor(cursor); + throw INAPPLICABLE_OPTION_EXCEPTION.createWithContext(reader.getReader(), "gamemode"); + } + String string = reader.getReader().readUnquotedString(); + GameMode gameType = GameMode.byName(string, null); + if (gameType == null) { + reader.getReader().setCursor(cursor); + throw INVALID_MODE_EXCEPTION.createWithContext(reader.getReader(), string); + } + reader.setIncludesEntities(false); + reader.addPredicate(entity -> { + if (!(entity instanceof AbstractClientPlayerEntity abstractClientPlayerEntity)) { + return false; + } + PlayerListEntry playerListEntry = mc.player.networkHandler.getPlayerListEntry(abstractClientPlayerEntity.getUuid()); + if (playerListEntry == null) { + return false; + } + GameMode gameType2 = playerListEntry.getGameMode(); + return bl == (gameType2 != gameType); + }); + if (bl) { + reader.setHasGamemodeNotEquals(true); + } else { + reader.setHasGamemodeEquals(true); + } + }, reader -> !reader.hasGamemodeEquals(), Text.translatable("argument.entity.options.gamemode.description")); + putOption("team", reader -> { + boolean bl = reader.shouldInvertValue(); + String string = reader.getReader().readUnquotedString(); + reader.addPredicate(entity -> { + if (!(entity instanceof LivingEntity)) { + return false; + } + Team team = entity.getScoreboardTeam(); + String string2 = team == null ? "" : team.getName(); + return string2.equals(string) != bl; + }); + if (bl) { + reader.setHasTeamNotEquals(true); + } else { + reader.setHasTeamEquals(true); + } + }, reader -> !reader.hasTeamEquals(), Text.translatable("argument.entity.options.team.description")); + putOption("type", reader -> { + reader.setSuggestions((builder, consumer) -> { + CommandSource.suggestIdentifiers(Registries.ENTITY_TYPE.getIds(), builder, String.valueOf(EntitySelectorParser.SYNTAX_NOT)); + CommandSource.suggestIdentifiers(Registries.ENTITY_TYPE.getTags().map(named -> named.getTag().id()), builder, "!#"); + if (!reader.isTypeLimitedInversely()) { + CommandSource.suggestIdentifiers(Registries.ENTITY_TYPE.getIds(), builder); + CommandSource.suggestIdentifiers(Registries.ENTITY_TYPE.getTags().map(named -> named.getTag().id()), builder, String.valueOf(EntitySelectorParser.SYNTAX_TAG)); + } + + return builder.buildFuture(); + }); + int cursor = reader.getReader().getCursor(); + boolean bl = reader.shouldInvertValue(); + if (reader.isTypeLimitedInversely() && !bl) { + reader.getReader().setCursor(cursor); + throw INAPPLICABLE_OPTION_EXCEPTION.createWithContext(reader.getReader(), "type"); + } + if (bl) { + reader.setTypeLimitedInversely(); + } + + if (reader.isTag()) { + TagKey> tagKey = TagKey.of(RegistryKeys.ENTITY_TYPE, Identifier.fromCommandInput(reader.getReader())); + reader.addPredicate(entity -> entity.getType().isIn(tagKey) != bl); + } else { + Identifier resourceLocation = Identifier.fromCommandInput(reader.getReader()); + EntityType entityType = Registries.ENTITY_TYPE.getOptionalValue(resourceLocation).orElseThrow(() -> { + reader.getReader().setCursor(cursor); + return INVALID_TYPE_EXCEPTION.createWithContext(reader.getReader(), resourceLocation.toString()); + }); + if (Objects.equals(EntityType.PLAYER, entityType) && !bl) { + reader.setIncludesEntities(false); + } + + reader.addPredicate(entity -> Objects.equals(entityType, entity.getType()) != bl); + if (!bl) { + reader.limitToType(entityType); + } + } + }, reader -> !reader.isTypeLimited(), Text.translatable("argument.entity.options.type.description")); + putOption("tag", reader -> { + boolean bl = reader.shouldInvertValue(); + String string = reader.getReader().readUnquotedString(); + reader.addPredicate(entity -> { + if ("".equals(string)) { + return entity.getCommandTags().isEmpty() != bl; + } else { + return entity.getCommandTags().contains(string) != bl; + } + }); + }, reader -> true, Text.translatable("argument.entity.options.tag.description")); + putOption("nbt", reader -> { + boolean bl = reader.shouldInvertValue(); + NbtCompound compoundTag = new StringNbtReader(reader.getReader()).parseCompound(); + reader.addPredicate(entity -> { + NbtCompound compoundTag2 = entity.writeNbt(new NbtCompound()); + if (entity instanceof AbstractClientPlayerEntity abstractClientPlayer) { + ItemStack itemStack = abstractClientPlayer.getInventory().getMainHandStack(); + if (!itemStack.isEmpty()) { + compoundTag2.put("SelectedItem", itemStack.toNbt(abstractClientPlayer.getRegistryManager())); + } + } + + return NbtHelper.matches(compoundTag, compoundTag2, true) != bl; + }); + }, reader -> true, Text.translatable("argument.entity.options.nbt.description")); + putOption("scores", reader -> { + StringReader stringReader = reader.getReader(); + Map map = Maps.newHashMap(); + stringReader.expect('{'); + stringReader.skipWhitespace(); + + while (stringReader.canRead() && stringReader.peek() != '}') { + stringReader.skipWhitespace(); + String string = stringReader.readUnquotedString(); + stringReader.skipWhitespace(); + stringReader.expect(EntitySelectorParser.SYNTAX_OPTIONS_KEY_VALUE_SEPARATOR); + stringReader.skipWhitespace(); + NumberRange.IntRange intRange = NumberRange.IntRange.parse(stringReader); + map.put(string, intRange); + stringReader.skipWhitespace(); + if (stringReader.canRead() && stringReader.peek() == ',') { + stringReader.skip(); + } + } + + stringReader.expect('}'); + if (!map.isEmpty()) { + reader.addPredicate(entity -> { + try (World level = entity.getWorld()) { + Scoreboard scoreboard = level.getScoreboard(); + + for (Map.Entry entry : map.entrySet()) { + ScoreboardObjective objective = scoreboard.getNullableObjective(entry.getKey()); + if (objective == null) { + return false; + } + + ReadableScoreboardScore readOnlyScoreInfo = scoreboard.getScore(entity, objective); + if (readOnlyScoreInfo == null) { + return false; + } + + if (!entry.getValue().test(readOnlyScoreInfo.getScore())) { + return false; + } + } + + return true; + } catch (IOException e) { + return false; + } + }); + } + + reader.setHasScores(true); + }, reader -> !reader.hasScores(), Text.translatable("argument.entity.options.scores.description")); + putOption("advancements", reader -> { + StringReader stringReader = reader.getReader(); + Map> map = Maps.newHashMap(); + stringReader.expect('{'); + stringReader.skipWhitespace(); + + while (stringReader.canRead() && stringReader.peek() != '}') { + stringReader.skipWhitespace(); + Identifier resourceLocation = Identifier.fromCommandInput(stringReader); + stringReader.skipWhitespace(); + stringReader.expect(EntitySelectorParser.SYNTAX_OPTIONS_KEY_VALUE_SEPARATOR); + stringReader.skipWhitespace(); + if (stringReader.canRead() && stringReader.peek() == '{') { + Map> map2 = Maps.newHashMap(); + stringReader.skipWhitespace(); + stringReader.expect('{'); + stringReader.skipWhitespace(); + + while (stringReader.canRead() && stringReader.peek() != '}') { + stringReader.skipWhitespace(); + String string = stringReader.readUnquotedString(); + stringReader.skipWhitespace(); + stringReader.expect(EntitySelectorParser.SYNTAX_OPTIONS_KEY_VALUE_SEPARATOR); + stringReader.skipWhitespace(); + boolean bl = stringReader.readBoolean(); + map2.put(string, criterionProgress -> criterionProgress.isObtained() == bl); + stringReader.skipWhitespace(); + if (stringReader.canRead() && stringReader.peek() == ',') { + stringReader.skip(); + } + } + + stringReader.skipWhitespace(); + stringReader.expect('}'); + stringReader.skipWhitespace(); + map.put(resourceLocation, advancementProgress -> { + for (Map.Entry> entry : map2.entrySet()) { + CriterionProgress criterionProgress = advancementProgress.getCriterionProgress(entry.getKey()); + if (criterionProgress == null || !entry.getValue().test(criterionProgress)) { + return false; + } + } + + return true; + }); + } else { + boolean bl2 = stringReader.readBoolean(); + map.put(resourceLocation, advancementProgress -> advancementProgress.isDone() == bl2); + } + + stringReader.skipWhitespace(); + if (stringReader.canRead() && stringReader.peek() == ',') { + stringReader.skip(); + } + } + + stringReader.expect('}'); + if (!map.isEmpty()) { + reader.addPredicate(entity -> false); + reader.setIncludesEntities(false); + } + + reader.setHasAdvancements(true); + }, reader -> !reader.hasAdvancements(), Text.translatable("argument.entity.options.advancements.description")); + putOption("predicate", reader -> reader.addPredicate(entity -> false), reader -> true, Text.translatable("argument.entity.options.predicate.description")); + } + + public static SelectorHandler getHandler(EntitySelectorParser reader, String option, int restoreCursor) throws CommandSyntaxException { + SelectorOption selectorOption = OPTIONS.get(option); + if (selectorOption != null) { + if (selectorOption.condition.test(reader)) { + return selectorOption.handler; + } + throw INAPPLICABLE_OPTION_EXCEPTION.createWithContext(reader.getReader(), option); + } + reader.getReader().setCursor(restoreCursor); + throw UNKNOWN_OPTION_EXCEPTION.createWithContext(reader.getReader(), option); + } + + public static void suggestOptions(EntitySelectorParser reader, SuggestionsBuilder suggestionBuilder) { + String string = suggestionBuilder.getRemaining().toLowerCase(Locale.ROOT); + + for (Map.Entry entry : OPTIONS.entrySet()) { + if (entry.getValue().condition.test(reader) && entry.getKey().toLowerCase(Locale.ROOT).startsWith(string)) { + suggestionBuilder.suggest(entry.getKey() + "=", entry.getValue().description); + } + } + } + + public interface SelectorHandler { + void handle(EntitySelectorParser reader) throws CommandSyntaxException; + } + + record SelectorOption(SelectorHandler handler, Predicate condition, Text description) {} + } + + public static class EntitySelectorParser { + public static final char SYNTAX_SELECTOR_START = '@'; + private static final char SYNTAX_OPTIONS_START = '['; + private static final char SYNTAX_OPTIONS_END = ']'; + public static final char SYNTAX_OPTIONS_KEY_VALUE_SEPARATOR = '='; + private static final char SYNTAX_OPTIONS_SEPARATOR = ','; + public static final char SYNTAX_NOT = '!'; + public static final char SYNTAX_TAG = '#'; + private static final char SELECTOR_NEAREST_PLAYER = 'p'; + private static final char SELECTOR_ALL_PLAYERS = 'a'; + private static final char SELECTOR_RANDOM_PLAYERS = 'r'; + private static final char SELECTOR_CURRENT_ENTITY = 's'; + private static final char SELECTOR_ALL_ENTITIES = 'e'; + private static final char SELECTOR_NEAREST_ENTITY = 'n'; + public static final SimpleCommandExceptionType ERROR_INVALID_NAME_OR_UUID = new SimpleCommandExceptionType(Text.translatable("argument.entity.invalid")); + public static final DynamicCommandExceptionType ERROR_UNKNOWN_SELECTOR_TYPE = new DynamicCommandExceptionType(type -> Text.stringifiedTranslatable("argument.entity.selector.unknown", type)); + public static final SimpleCommandExceptionType ERROR_SELECTORS_NOT_ALLOWED = new SimpleCommandExceptionType(Text.translatable("argument.entity.selector.not_allowed")); + public static final SimpleCommandExceptionType ERROR_MISSING_SELECTOR_TYPE = new SimpleCommandExceptionType(Text.translatable("argument.entity.selector.missing")); + public static final SimpleCommandExceptionType ERROR_EXPECTED_END_OF_OPTIONS = new SimpleCommandExceptionType(Text.translatable("argument.entity.options.unterminated")); + public static final DynamicCommandExceptionType ERROR_EXPECTED_OPTION_VALUE = new DynamicCommandExceptionType(value -> Text.stringifiedTranslatable("argument.entity.options.valueless", value)); + public static final BiConsumer> ORDER_NEAREST = (pos, entities) -> entities.sort((entity1, entity2) -> Doubles.compare(entity1.squaredDistanceTo(pos), entity2.squaredDistanceTo(pos))); + public static final BiConsumer> ORDER_FURTHEST = (pos, entities) -> entities.sort((entity1, entity2) -> Doubles.compare(entity2.squaredDistanceTo(pos), entity1.squaredDistanceTo(pos))); + public static final BiConsumer> ORDER_RANDOM = (pos, entities) -> Collections.shuffle(entities); + public static final BiFunction, CompletableFuture> SUGGEST_NOTHING = (builder, consumer) -> builder.buildFuture(); + private final StringReader reader; + private final boolean allowSelectors; + private int maxResults; + private boolean includesEntities; + private boolean worldLimited; + private NumberRange.DoubleRange distance = NumberRange.DoubleRange.ANY; + private NumberRange.IntRange level = NumberRange.IntRange.ANY; + @Nullable + private Double x; + @Nullable + private Double y; + @Nullable + private Double z; + @Nullable + private Double deltaX; + @Nullable + private Double deltaY; + @Nullable + private Double deltaZ; + private FloatRangeArgument rotX = FloatRangeArgument.ANY; + private FloatRangeArgument rotY = FloatRangeArgument.ANY; + private final List> predicates = new ArrayList<>(); + private BiConsumer> order = EntitySelector.ORDER_ARBITRARY; + private boolean currentEntity; + @Nullable + private String playerName; + private int startPosition; + @Nullable + private UUID entityUUID; + private BiFunction, CompletableFuture> suggestions = SUGGEST_NOTHING; + private boolean hasNameEquals; + private boolean hasNameNotEquals; + private boolean isLimited; + private boolean isSorted; + private boolean hasGamemodeEquals; + private boolean hasGamemodeNotEquals; + private boolean hasTeamEquals; + private boolean hasTeamNotEquals; + @Nullable + private EntityType type; + private boolean typeInverse; + private boolean hasScores; + private boolean hasAdvancements; + private boolean usesSelectors; + + public EntitySelectorParser(StringReader reader, boolean allowSelectors) { + this.reader = reader; + this.allowSelectors = allowSelectors; + } + + public static boolean allowSelectors(S source) { + return source instanceof CommandSource; + } + + public EntitySelector getSelector() { + Box box; + if (this.deltaX == null && this.deltaY == null && this.deltaZ == null) { + if (this.distance.max().isPresent()) { + double d = this.distance.max().get(); + box = new Box(-d, -d, -d, d + 1.0, d + 1.0, d + 1.0); + } else { + box = null; + } + } else { + box = this.createBox(this.deltaX == null ? 0.0 : this.deltaX, this.deltaY == null ? 0.0 : this.deltaY, this.deltaZ == null ? 0.0 : this.deltaZ); + } + + Function function; + if (this.x == null && this.y == null && this.z == null) { + function = vec3 -> vec3; + } else { + function = vec3 -> new Vec3d(this.x == null ? vec3.x : this.x, this.y == null ? vec3.y : this.y, this.z == null ? vec3.z : this.z); + } + + return new EntitySelector(this.maxResults, this.includesEntities, this.worldLimited, List.copyOf(this.predicates), this.distance, function, box, this.order, this.currentEntity, this.playerName, this.entityUUID, this.type, this.usesSelectors); + } + + private Box createBox(double sizeX, double sizeY, double sizeZ) { + boolean bl = sizeX < 0.0; + boolean bl2 = sizeY < 0.0; + boolean bl3 = sizeZ < 0.0; + double d = bl ? sizeX : 0.0; + double e = bl2 ? sizeY : 0.0; + double f = bl3 ? sizeZ : 0.0; + double g = (bl ? 0.0 : sizeX) + 1.0; + double h = (bl2 ? 0.0 : sizeY) + 1.0; + double i = (bl3 ? 0.0 : sizeZ) + 1.0; + return new Box(d, e, f, g, h, i); + } + + private void finalizePredicates() { + if (this.rotX != FloatRangeArgument.ANY) { + this.predicates.add(this.createRotationPredicate(this.rotX, Entity::getPitch)); + } + + if (this.rotY != FloatRangeArgument.ANY) { + this.predicates.add(this.createRotationPredicate(this.rotY, Entity::getYaw)); + } + + if (!this.level.isDummy()) { + this.predicates.add(entity -> entity instanceof AbstractClientPlayerEntity abstractClientPlayer && this.level.test(abstractClientPlayer.experienceLevel)); + } + } + + private Predicate createRotationPredicate(FloatRangeArgument angleBounds, ToDoubleFunction angleFunction) { + double d = MathHelper.wrapDegrees(angleBounds.min() == null ? 0.0F : angleBounds.min()); + double e = MathHelper.wrapDegrees(angleBounds.max() == null ? 359.0F : angleBounds.max()); + return entity -> { + double f = MathHelper.wrapDegrees(angleFunction.applyAsDouble(entity)); + return d > e ? f >= d || f <= e : f >= d && f <= e; + }; + } + + protected void parseSelector() throws CommandSyntaxException { + this.usesSelectors = true; + this.suggestions = this::suggestSelector; + if (!this.reader.canRead()) { + throw ERROR_MISSING_SELECTOR_TYPE.createWithContext(this.reader); + } + int cursor = this.reader.getCursor(); + char c = this.reader.read(); + + if (switch (c) { + case SELECTOR_ALL_PLAYERS -> { + this.maxResults = Integer.MAX_VALUE; + this.includesEntities = false; + this.order = EntitySelector.ORDER_ARBITRARY; + this.limitToType(EntityType.PLAYER); + yield false; + } + case SELECTOR_ALL_ENTITIES -> { + this.maxResults = Integer.MAX_VALUE; + this.includesEntities = true; + this.order = EntitySelector.ORDER_ARBITRARY; + yield true; + } + case SELECTOR_NEAREST_ENTITY -> { + this.maxResults = 1; + this.includesEntities = true; + this.order = ORDER_NEAREST; + yield true; + } + case SELECTOR_NEAREST_PLAYER -> { + this.maxResults = 1; + this.includesEntities = false; + this.order = ORDER_NEAREST; + this.limitToType(EntityType.PLAYER); + yield false; + } + case SELECTOR_RANDOM_PLAYERS -> { + this.maxResults = 1; + this.includesEntities = false; + this.order = ORDER_RANDOM; + this.limitToType(EntityType.PLAYER); + yield false; + } + case SELECTOR_CURRENT_ENTITY -> { + this.maxResults = 1; + this.includesEntities = true; + this.currentEntity = true; + yield false; + } + default -> { + this.reader.setCursor(cursor); + throw ERROR_UNKNOWN_SELECTOR_TYPE.createWithContext(this.reader, "@" + c); + } + }) { + this.predicates.add(Entity::isAlive); + } + + this.suggestions = this::suggestOpenOptions; + if (this.reader.canRead() && this.reader.peek() == SYNTAX_OPTIONS_START) { + this.reader.skip(); + this.suggestions = this::suggestOptionsKeyOrClose; + this.parseOptions(); + } + + } + + protected void parseNameOrUUID() throws CommandSyntaxException { + if (this.reader.canRead()) { + this.suggestions = this::suggestName; + } + + int cursor = this.reader.getCursor(); + String string = this.reader.readString(); + + try { + this.entityUUID = UUID.fromString(string); + this.includesEntities = true; + } catch (IllegalArgumentException var4) { + if (string.isEmpty() || string.length() > 16) { + this.reader.setCursor(cursor); + throw ERROR_INVALID_NAME_OR_UUID.createWithContext(this.reader); + } + + this.includesEntities = false; + this.playerName = string; + } + + this.maxResults = 1; + } + + protected void parseOptions() throws CommandSyntaxException { + this.suggestions = this::suggestOptionsKey; + this.reader.skipWhitespace(); + + while (this.reader.canRead() && this.reader.peek() != SYNTAX_OPTIONS_END) { + this.reader.skipWhitespace(); + int i = this.reader.getCursor(); + String string = this.reader.readString(); + EntitySelectorOptions.SelectorHandler handler = EntitySelectorOptions.getHandler(this, string, i); + this.reader.skipWhitespace(); + if (!this.reader.canRead() || this.reader.peek() != SYNTAX_OPTIONS_KEY_VALUE_SEPARATOR) { + this.reader.setCursor(i); + throw ERROR_EXPECTED_OPTION_VALUE.createWithContext(this.reader, string); + } + + this.reader.skip(); + this.reader.skipWhitespace(); + this.suggestions = SUGGEST_NOTHING; + handler.handle(this); + this.reader.skipWhitespace(); + this.suggestions = this::suggestOptionsNextOrClose; + if (this.reader.canRead()) { + if (this.reader.peek() != SYNTAX_OPTIONS_SEPARATOR) { + if (this.reader.peek() != SYNTAX_OPTIONS_END) { + throw ERROR_EXPECTED_END_OF_OPTIONS.createWithContext(this.reader); + } + break; + } + + this.reader.skip(); + this.suggestions = this::suggestOptionsKey; + } + } + + if (!this.reader.canRead()) { + throw ERROR_EXPECTED_END_OF_OPTIONS.createWithContext(this.reader); + } + this.reader.skip(); + this.suggestions = SUGGEST_NOTHING; + } + + public boolean shouldInvertValue() { + this.reader.skipWhitespace(); + if (this.reader.canRead() && this.reader.peek() == SYNTAX_NOT) { + this.reader.skip(); + this.reader.skipWhitespace(); + return true; + } + return false; + } + + public boolean isTag() { + this.reader.skipWhitespace(); + if (this.reader.canRead() && this.reader.peek() == SYNTAX_TAG) { + this.reader.skip(); + this.reader.skipWhitespace(); + return true; + } + return false; + } + + public StringReader getReader() { + return this.reader; + } + + public void addPredicate(Predicate predicate) { + this.predicates.add(predicate); + } + + public void setWorldLimited() { + this.worldLimited = true; + } + + public NumberRange.DoubleRange getDistance() { + return this.distance; + } + + public void setDistance(NumberRange.DoubleRange distance) { + this.distance = distance; + } + + public NumberRange.IntRange getLevel() { + return this.level; + } + + public void setLevel(NumberRange.IntRange level) { + this.level = level; + } + + public FloatRangeArgument getRotX() { + return this.rotX; + } + + public void setRotX(FloatRangeArgument rotX) { + this.rotX = rotX; + } + + public FloatRangeArgument getRotY() { + return this.rotY; + } + + public void setRotY(FloatRangeArgument rotY) { + this.rotY = rotY; + } + + @Nullable + public Double getX() { + return this.x; + } + + @Nullable + public Double getY() { + return this.y; + } + + @Nullable + public Double getZ() { + return this.z; + } + + public void setX(double x) { + this.x = x; + } + + public void setY(double y) { + this.y = y; + } + + public void setZ(double z) { + this.z = z; + } + + public void setDeltaX(double deltaX) { + this.deltaX = deltaX; + } + + public void setDeltaY(double deltaY) { + this.deltaY = deltaY; + } + + public void setDeltaZ(double deltaZ) { + this.deltaZ = deltaZ; + } + + @Nullable + public Double getDeltaX() { + return this.deltaX; + } + + @Nullable + public Double getDeltaY() { + return this.deltaY; + } + + @Nullable + public Double getDeltaZ() { + return this.deltaZ; + } + + public void setMaxResults(int maxResults) { + this.maxResults = maxResults; + } + + public void setIncludesEntities(boolean includesEntities) { + this.includesEntities = includesEntities; + } + + public BiConsumer> getOrder() { + return this.order; + } + + public void setOrder(BiConsumer> order) { + this.order = order; + } + + public EntitySelector parse() throws CommandSyntaxException { + this.startPosition = this.reader.getCursor(); + this.suggestions = this::suggestNameOrSelector; + if (this.reader.canRead() && this.reader.peek() == SYNTAX_SELECTOR_START) { + if (!this.allowSelectors) { + throw ERROR_SELECTORS_NOT_ALLOWED.createWithContext(this.reader); + } + + this.reader.skip(); + this.parseSelector(); + } else { + this.parseNameOrUUID(); + } + + this.finalizePredicates(); + return this.getSelector(); + } + + private static void fillSelectorSuggestions(SuggestionsBuilder builder) { + builder.suggest("@p", Text.translatable("argument.entity.selector.nearestPlayer")); + builder.suggest("@a", Text.translatable("argument.entity.selector.allPlayers")); + builder.suggest("@r", Text.translatable("argument.entity.selector.randomPlayer")); + builder.suggest("@s", Text.translatable("argument.entity.selector.self")); + builder.suggest("@e", Text.translatable("argument.entity.selector.allEntities")); + builder.suggest("@n", Text.translatable("argument.entity.selector.nearestEntity")); + } + + private CompletableFuture suggestNameOrSelector(SuggestionsBuilder builder, Consumer consumer) { + consumer.accept(builder); + if (this.allowSelectors) { + fillSelectorSuggestions(builder); + } + + return builder.buildFuture(); + } + + private CompletableFuture suggestName(SuggestionsBuilder builder, Consumer consumer) { + SuggestionsBuilder suggestionsBuilder = builder.createOffset(this.startPosition); + consumer.accept(suggestionsBuilder); + return builder.add(suggestionsBuilder).buildFuture(); + } + + private CompletableFuture suggestSelector(SuggestionsBuilder builder, Consumer consumer) { + SuggestionsBuilder suggestionsBuilder = builder.createOffset(builder.getStart() - 1); + fillSelectorSuggestions(suggestionsBuilder); + builder.add(suggestionsBuilder); + return builder.buildFuture(); + } + + private CompletableFuture suggestOpenOptions(SuggestionsBuilder builder, Consumer consumer) { + builder.suggest(String.valueOf(SYNTAX_OPTIONS_START)); + return builder.buildFuture(); + } + + private CompletableFuture suggestOptionsKeyOrClose(SuggestionsBuilder builder, Consumer consumer) { + builder.suggest(String.valueOf(SYNTAX_OPTIONS_END)); + EntitySelectorOptions.suggestOptions(this, builder); + return builder.buildFuture(); + } + + private CompletableFuture suggestOptionsKey(SuggestionsBuilder builder, Consumer consumer) { + EntitySelectorOptions.suggestOptions(this, builder); + return builder.buildFuture(); + } + + private CompletableFuture suggestOptionsNextOrClose(SuggestionsBuilder builder, Consumer consumer) { + builder.suggest(String.valueOf(SYNTAX_OPTIONS_SEPARATOR)); + builder.suggest(String.valueOf(SYNTAX_OPTIONS_END)); + return builder.buildFuture(); + } + + private CompletableFuture suggestEquals(SuggestionsBuilder builder, Consumer consumer) { + builder.suggest(String.valueOf(SYNTAX_OPTIONS_KEY_VALUE_SEPARATOR)); + return builder.buildFuture(); + } + + public boolean isCurrentEntity() { + return this.currentEntity; + } + + public void setSuggestions(BiFunction, CompletableFuture> suggestionHandler) { + this.suggestions = suggestionHandler; + } + + public CompletableFuture fillSuggestions(SuggestionsBuilder builder, Consumer consumer) { + return this.suggestions.apply(builder.createOffset(this.reader.getCursor()), consumer); + } + + public boolean hasNameEquals() { + return this.hasNameEquals; + } + + public void setHasNameEquals(boolean hasNameEquals) { + this.hasNameEquals = hasNameEquals; + } + + public boolean hasNameNotEquals() { + return this.hasNameNotEquals; + } + + public void setHasNameNotEquals(boolean hasNameNotEquals) { + this.hasNameNotEquals = hasNameNotEquals; + } + + public boolean isLimited() { + return this.isLimited; + } + + public void setLimited(boolean isLimited) { + this.isLimited = isLimited; + } + + public boolean isSorted() { + return this.isSorted; + } + + public void setSorted(boolean isSorted) { + this.isSorted = isSorted; + } + + public boolean hasGamemodeEquals() { + return this.hasGamemodeEquals; + } + + public void setHasGamemodeEquals(boolean hasGamemodeEquals) { + this.hasGamemodeEquals = hasGamemodeEquals; + } + + public boolean hasGamemodeNotEquals() { + return this.hasGamemodeNotEquals; + } + + public void setHasGamemodeNotEquals(boolean hasGamemodeNotEquals) { + this.hasGamemodeNotEquals = hasGamemodeNotEquals; + } + + public boolean hasTeamEquals() { + return this.hasTeamEquals; + } + + public void setHasTeamEquals(boolean hasTeamEquals) { + this.hasTeamEquals = hasTeamEquals; + } + + public boolean hasTeamNotEquals() { + return this.hasTeamNotEquals; + } + + public void setHasTeamNotEquals(boolean hasTeamNotEquals) { + this.hasTeamNotEquals = hasTeamNotEquals; + } + + public void limitToType(EntityType type) { + this.type = type; + } + + public void setTypeLimitedInversely() { + this.typeInverse = true; + } + + public boolean isTypeLimited() { + return this.type != null; + } + + public boolean isTypeLimitedInversely() { + return this.typeInverse; + } + + public boolean hasScores() { + return this.hasScores; + } + + public void setHasScores(boolean hasScores) { + this.hasScores = hasScores; + } + + public boolean hasAdvancements() { + return this.hasAdvancements; + } + + public void setHasAdvancements(boolean hasAdvancements) { + this.hasAdvancements = hasAdvancements; + } + } + + public static class EntitySelector { + + public static final int INFINITE = Integer.MAX_VALUE; + public static final BiConsumer> ORDER_ARBITRARY = (center, entityList) -> {}; + private static final TypeFilter ANY_TYPE = new TypeFilter<>() { + @Override + public Entity downcast(Entity entity) { + return entity; + } + + @Override + public Class getBaseClass() { + return Entity.class; + } + }; + private final int maxResults; + private final boolean includesEntities; + private final boolean worldLimited; + private final List> contextFreePredicates; + private final NumberRange.DoubleRange range; + private final Function position; + @Nullable + private final Box Box; + private final BiConsumer> order; + private final boolean currentEntity; + @Nullable + private final String playerName; + @Nullable + private final UUID entityUUID; + private final TypeFilter type; + private final boolean usesSelector; + + public EntitySelector(int maxResults, boolean includesEntities, boolean worldLimited, List> contextFreePredicates, NumberRange.DoubleRange range, Function position, @Nullable Box Box, BiConsumer> order, boolean currentEntity, @Nullable String playerName, @Nullable UUID entityUUID, @Nullable EntityType type, boolean usesSelector) { + this.maxResults = maxResults; + this.includesEntities = includesEntities; + this.worldLimited = worldLimited; + this.contextFreePredicates = contextFreePredicates; + this.range = range; + this.position = position; + this.Box = Box; + this.order = order; + this.currentEntity = currentEntity; + this.playerName = playerName; + this.entityUUID = entityUUID; + this.type = type == null ? ANY_TYPE : type; + this.usesSelector = usesSelector; + } + + public int getMaxResults() { + return this.maxResults; + } + + public boolean includesEntities() { + return this.includesEntities; + } + + public boolean isSelfSelector() { + return this.currentEntity; + } + + public boolean isWorldLimited() { + return this.worldLimited; + } + + public boolean usesSelector() { + return this.usesSelector; + } + + public Entity findSingleEntity(S source) throws CommandSyntaxException { + List list = this.findEntities(source); + if (list.isEmpty()) { + throw EntityArgumentType.ENTITY_NOT_FOUND_EXCEPTION.create(); + } + if (list.size() > 1) { + throw EntityArgumentType.TOO_MANY_ENTITIES_EXCEPTION.create(); + } + return list.getFirst(); + } + + public List findEntities(S source) throws CommandSyntaxException { + if (!this.includesEntities) { + return this.findPlayers(source); + } + if (this.playerName != null) { + AbstractClientPlayerEntity abstractClientPlayer = Streams.stream(mc.world.getEntities()) + .filter(entity -> entity instanceof AbstractClientPlayerEntity) + .map(entity -> (AbstractClientPlayerEntity) entity) + .filter(abstractPlayer -> abstractPlayer.getName().getString().equals(this.playerName)) + .findAny().orElse(null); + return abstractClientPlayer == null ? Collections.emptyList() : Lists.newArrayList(abstractClientPlayer); + } + if (this.entityUUID != null) { + Entity foundEntity = Streams.stream(mc.world.getEntities()) + .filter(entity -> entity.getUuid().equals(this.entityUUID)) + .findAny().orElse(null); + return foundEntity == null ? Collections.emptyList() : Lists.newArrayList(foundEntity); + } + + Vec3d Vec3d = this.position.apply(mc.player.getPos()); + Box Box = this.getAbsoluteBox(Vec3d); + Predicate predicate = this.getPredicate(Vec3d, Box, null); + if (this.currentEntity) { + return mc.player != null && predicate.test(mc.player) + ? Lists.newArrayList(mc.player) + : Collections.emptyList(); + } + List list = Lists.newArrayList(); + this.addEntities(list, mc.world, Box, predicate); + return list; + } + + private void addEntities(List entities, ClientWorld world, @Nullable Box box, Predicate predicate) { + int resultLimit = this.getResultLimit(); + if (entities.size() < resultLimit) { + if (box != null) { + world.collectEntitiesByType(this.type, box, predicate, entities, resultLimit); + } else { + ((WorldAccessor) world).getEntityLookup().forEach(this.type, entity -> { + if (predicate.test(entity)) { + entities.add(entity); + if (entities.size() >= maxResults) { + return LazyIterationConsumer.NextIteration.ABORT; + } + } + + return LazyIterationConsumer.NextIteration.CONTINUE; + }); + } + } + } + + private int getResultLimit() { + return this.order == ORDER_ARBITRARY ? this.maxResults : INFINITE; + } + + public AbstractClientPlayerEntity findSinglePlayer(S source) throws CommandSyntaxException { + List list = this.findPlayers(source); + if (list.size() != 1) { + throw EntityArgumentType.PLAYER_NOT_FOUND_EXCEPTION.create(); + } + return list.getFirst(); + } + + public List findPlayers(S source) throws CommandSyntaxException { + AbstractClientPlayerEntity abstractClientPlayer; + if (this.playerName != null) { + abstractClientPlayer = Streams.stream(mc.world.getEntities()) + .filter(entity -> entity instanceof AbstractClientPlayerEntity) + .map(entity -> (AbstractClientPlayerEntity) entity) + .filter(abstractPlayer -> abstractPlayer.getName().getString().equals(this.playerName)) + .findAny().orElse(null); + return abstractClientPlayer == null ? Collections.emptyList() : Lists.newArrayList(abstractClientPlayer); + } + if (this.entityUUID != null) { + abstractClientPlayer = Streams.stream(mc.world.getEntities()) + .filter(entity -> entity instanceof AbstractClientPlayerEntity) + .map(entity -> (AbstractClientPlayerEntity) entity) + .filter(entity -> entity.getUuid().equals(this.entityUUID)) + .findAny().orElse(null); + return abstractClientPlayer == null ? Collections.emptyList() : Lists.newArrayList(abstractClientPlayer); + } + Vec3d Vec3dd = this.position.apply(mc.player.getPos()); + Predicate predicate = this.getPredicate(Vec3dd, this.getAbsoluteBox(Vec3dd), null); + if (this.currentEntity) { + if (mc.player instanceof AbstractClientPlayerEntity player && predicate.test(player)) { + return Lists.newArrayList(player); + } + + return Collections.emptyList(); + } + List entities = mc.world.getPlayers().stream() + .filter(predicate) + .limit(this.getResultLimit()) + .collect(Collectors.toList()); + + return this.sortAndLimit(Vec3dd, entities); + } + + @Nullable + private Box getAbsoluteBox(Vec3d pos) { + return this.Box != null ? this.Box.offset(pos) : null; + } + + private Predicate getPredicate(Vec3d pos, @Nullable Box box, @Nullable FeatureSet enabledFeatures) { + boolean bl = enabledFeatures != null; + boolean bl2 = box != null; + boolean bl3 = !this.range.isDummy(); + int i = (bl ? 1 : 0) + (bl2 ? 1 : 0) + (bl3 ? 1 : 0); + List> list; + if (i == 0) { + list = this.contextFreePredicates; + } else { + List> list2 = new ObjectArrayList<>(this.contextFreePredicates.size() + i); + list2.addAll(this.contextFreePredicates); + if (bl) { + list2.add(entity -> entity.getType().isEnabled(enabledFeatures)); + } + + if (bl2) { + list2.add(entity -> box.intersects(entity.getBoundingBox())); + } + + if (bl3) { + list2.add(entity -> this.range.testSqrt(entity.squaredDistanceTo(pos))); + } + + list = list2; + } + + return Util.allOf(list); + } + + private List sortAndLimit(Vec3d pos, List entities) { + if (entities.size() > 1) { + this.order.accept(pos, entities); + } + + return entities.subList(0, Math.min(this.maxResults, entities.size())); + } + + public static Text joinNames(List names) { + return Texts.join(names, Entity::getDisplayName); + } + } +} diff --git a/src/main/java/meteordevelopment/meteorclient/commands/arguments/FakePlayerArgumentType.java b/src/main/java/meteordevelopment/meteorclient/commands/arguments/FakePlayerArgumentType.java index 948447377d..ef8f62dafb 100644 --- a/src/main/java/meteordevelopment/meteorclient/commands/arguments/FakePlayerArgumentType.java +++ b/src/main/java/meteordevelopment/meteorclient/commands/arguments/FakePlayerArgumentType.java @@ -5,10 +5,12 @@ package meteordevelopment.meteorclient.commands.arguments; +import com.mojang.brigadier.LiteralMessage; 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.exceptions.DynamicCommandExceptionType; import com.mojang.brigadier.suggestion.Suggestions; import com.mojang.brigadier.suggestion.SuggestionsBuilder; import meteordevelopment.meteorclient.utils.entity.fakeplayer.FakePlayerEntity; @@ -20,23 +22,34 @@ import static net.minecraft.command.CommandSource.suggestMatching; -public class FakePlayerArgumentType implements ArgumentType { +public class FakePlayerArgumentType implements ArgumentType { private static final FakePlayerArgumentType INSTANCE = new FakePlayerArgumentType(); private static final Collection EXAMPLES = List.of("seasnail8169", "MineGame159"); + private static final DynamicCommandExceptionType NO_SUCH_FAKEPLAYER_EXCEPTION = new DynamicCommandExceptionType(name -> new LiteralMessage("Fake player with name " + name + " doesn't exist.")); public static FakePlayerArgumentType create() { return INSTANCE; } - public static FakePlayerEntity get(CommandContext context) { - return FakePlayerManager.get(context.getArgument("fp", String.class)); + public static FakePlayerEntity get(CommandContext context) { + return context.getArgument("fp", FakePlayerEntity.class); + } + + public static FakePlayerEntity get(CommandContext context, String name) { + return context.getArgument(name, FakePlayerEntity.class); } private FakePlayerArgumentType() {} @Override - public String parse(StringReader reader) throws CommandSyntaxException { - return reader.readString(); + public FakePlayerEntity parse(StringReader reader) throws CommandSyntaxException { + String name = reader.readString(); + FakePlayerEntity entity = FakePlayerManager.get(name); + if (entity == null) { + throw NO_SUCH_FAKEPLAYER_EXCEPTION.create(name); + } + + return entity; } @Override diff --git a/src/main/java/meteordevelopment/meteorclient/commands/arguments/FriendArgumentType.java b/src/main/java/meteordevelopment/meteorclient/commands/arguments/FriendArgumentType.java index e236bdf789..6efc817e33 100644 --- a/src/main/java/meteordevelopment/meteorclient/commands/arguments/FriendArgumentType.java +++ b/src/main/java/meteordevelopment/meteorclient/commands/arguments/FriendArgumentType.java @@ -6,10 +6,12 @@ package meteordevelopment.meteorclient.commands.arguments; import com.google.common.collect.Streams; +import com.mojang.brigadier.LiteralMessage; 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.exceptions.DynamicCommandExceptionType; import com.mojang.brigadier.suggestion.Suggestions; import com.mojang.brigadier.suggestion.SuggestionsBuilder; import meteordevelopment.meteorclient.systems.friends.Friend; @@ -21,23 +23,34 @@ import static net.minecraft.command.CommandSource.suggestMatching; -public class FriendArgumentType implements ArgumentType { +public class FriendArgumentType implements ArgumentType { private static final FriendArgumentType INSTANCE = new FriendArgumentType(); private static final Collection EXAMPLES = List.of("seasnail8169", "MineGame159"); + private static final DynamicCommandExceptionType NO_SUCH_FRIEND_EXCEPTION = new DynamicCommandExceptionType(name -> new LiteralMessage("Friend with name " + name + " doesn't exist.")); public static FriendArgumentType create() { return INSTANCE; } - public static Friend get(CommandContext context) { - return Friends.get().get(context.getArgument("friend", String.class)); + public static Friend get(CommandContext context) { + return context.getArgument("friend", Friend.class); + } + + public static Friend get(CommandContext context, String name) { + return context.getArgument(name, Friend.class); } private FriendArgumentType() {} @Override - public String parse(StringReader reader) throws CommandSyntaxException { - return reader.readString(); + public Friend parse(StringReader reader) throws CommandSyntaxException { + String name = reader.readString(); + Friend friend = Friends.get().get(name); + if (friend == null) { + throw NO_SUCH_FRIEND_EXCEPTION.create(name); + } + + return friend; } @Override diff --git a/src/main/java/meteordevelopment/meteorclient/commands/arguments/ItemSlotArgumentType.java b/src/main/java/meteordevelopment/meteorclient/commands/arguments/ItemSlotArgumentType.java new file mode 100644 index 0000000000..b925ffee2d --- /dev/null +++ b/src/main/java/meteordevelopment/meteorclient/commands/arguments/ItemSlotArgumentType.java @@ -0,0 +1,139 @@ +/* + * 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.exceptions.DynamicCommandExceptionType; +import com.mojang.brigadier.suggestion.Suggestions; +import com.mojang.brigadier.suggestion.SuggestionsBuilder; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import meteordevelopment.meteorclient.utils.player.EChestMemory; +import net.minecraft.command.CommandSource; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EquipmentSlot; +import net.minecraft.inventory.SlotRange; +import net.minecraft.inventory.SlotRanges; +import net.minecraft.text.Text; +import net.minecraft.util.Util; +import org.jetbrains.annotations.Nullable; + +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.function.Function; +import java.util.stream.Collectors; + +import static meteordevelopment.meteorclient.MeteorClient.mc; + +public class ItemSlotArgumentType implements ArgumentType { + private static final ItemSlotArgumentType ONLY_MODIFIABLE = new ItemSlotArgumentType(Mode.ONLY_MODIFIABLE); + private static final ItemSlotArgumentType SELF_SLOTS = new ItemSlotArgumentType(Mode.SELF_SLOTS); + private static final ItemSlotArgumentType ALL_SLOTS = new ItemSlotArgumentType(Mode.ALL_SLOTS); + private static final Collection EXAMPLES = Arrays.asList("container.5", "weapon"); + private static final DynamicCommandExceptionType UNKNOWN_SLOT_EXCEPTION = new DynamicCommandExceptionType(name -> Text.translatable("slot.unknown", name)); + public static final int MAINHAND_SLOT_INDEX = EquipmentSlot.MAINHAND.getOffsetEntitySlotId(98); + public static final int OFFHAND_SLOT_INDEX = EquipmentSlot.OFFHAND.getOffsetEntitySlotId(98); + + private final Mode mode; + + private ItemSlotArgumentType(Mode mode) { + this.mode = mode; + } + + public static ItemSlotArgumentType modifiableSlot() { + return ONLY_MODIFIABLE; + } + + public static ItemSlotArgumentType selfSlot() { + return SELF_SLOTS; + } + + public static ItemSlotArgumentType itemSlot() { + return ALL_SLOTS; + } + + public static int getItemSlot(CommandContext context) { + return context.getArgument("slot", int.class); + } + + public static int getItemSlot(CommandContext context, String name) { + return context.getArgument(name, int.class); + } + + @Override + public Integer parse(StringReader stringReader) throws CommandSyntaxException { + String string = stringReader.readUnquotedString(); + + Set allowedValues = this.mode.contextAwareAllowedValues.apply(mc.player); + + if (allowedValues.contains(string)) { + @Nullable SlotRange range = SlotRanges.fromName(string); + if (range != null) { + return range.getSlotIds().getInt(0); + } + } + + throw UNKNOWN_SLOT_EXCEPTION.create(string); + } + + @Override + public CompletableFuture listSuggestions(final CommandContext context, final SuggestionsBuilder builder) { + return CommandSource.suggestMatching(this.mode.contextAwareAllowedValues.apply(mc.player), builder); + } + + @Override + public Collection getExamples() { + return EXAMPLES; + } + + private enum Mode { + ONLY_MODIFIABLE(new ObjectOpenHashSet<>(Util.make(new ArrayList<>(), list -> { + for (int i = 0; i < 9; i++) list.add("hotbar." + i); + for (int i = 0; i < 27; i++) list.add("inventory." + i); + list.add("weapon"); + list.add("weapon.mainhand"); + list.add("weapon.offhand"); + list.add("armor.head"); + list.add("armor.chest"); + list.add("armor.legs"); + list.add("armor.feet"); + list.add("armor.body"); + list.add("player.cursor"); + for (int i = 0; i < 4; i++) list.add("player.crafting." + i); + }))), + SELF_SLOTS( + Util.make(new ObjectOpenHashSet<>(ONLY_MODIFIABLE.allowedValues), set -> { + for (int i = 0; i < 27; i++) { + set.add("enderchest." + i); + } + }), + e -> Util.make(new ObjectOpenHashSet<>(ONLY_MODIFIABLE.allowedValues), set -> { + if (EChestMemory.isKnown()) { + for (int i = 0; i < 27; i++) { + set.add("enderchest." + i); + } + } + }) + ), + ALL_SLOTS( + SlotRanges.streamSingleSlotNames().collect(Collectors.toSet()) + ); + + private final Set allowedValues; + private final Function> contextAwareAllowedValues; + + Mode(Set allowedValues) { + this(allowedValues, e -> allowedValues); + } + + Mode(Set allowedValues, Function> contextAwareAllowedValues) { + this.allowedValues = allowedValues; + this.contextAwareAllowedValues = contextAwareAllowedValues; + } + } +} diff --git a/src/main/java/meteordevelopment/meteorclient/commands/arguments/MacroArgumentType.java b/src/main/java/meteordevelopment/meteorclient/commands/arguments/MacroArgumentType.java index 09a3d6ee58..d61fd35a65 100644 --- a/src/main/java/meteordevelopment/meteorclient/commands/arguments/MacroArgumentType.java +++ b/src/main/java/meteordevelopment/meteorclient/commands/arguments/MacroArgumentType.java @@ -5,6 +5,7 @@ package meteordevelopment.meteorclient.commands.arguments; +import com.mojang.brigadier.LiteralMessage; import com.mojang.brigadier.StringReader; import com.mojang.brigadier.arguments.ArgumentType; import com.mojang.brigadier.context.CommandContext; @@ -15,7 +16,6 @@ import meteordevelopment.meteorclient.systems.macros.Macro; import meteordevelopment.meteorclient.systems.macros.Macros; import net.minecraft.command.CommandSource; -import net.minecraft.text.Text; import java.util.Collection; import java.util.concurrent.CompletableFuture; @@ -23,16 +23,20 @@ public class MacroArgumentType implements ArgumentType { private static final MacroArgumentType INSTANCE = new MacroArgumentType(); - private static final DynamicCommandExceptionType NO_SUCH_MACRO = new DynamicCommandExceptionType(name -> Text.literal("Macro with name " + name + " doesn't exist.")); + private static final DynamicCommandExceptionType NO_SUCH_MACRO = new DynamicCommandExceptionType(name -> new LiteralMessage("Macro with name " + name + " doesn't exist.")); public static MacroArgumentType create() { return INSTANCE; } - public static Macro get(CommandContext context) { + public static Macro get(CommandContext context) { return context.getArgument("macro", Macro.class); } + public static Macro get(CommandContext context, String name) { + return context.getArgument(name, Macro.class); + } + private MacroArgumentType() {} @Override diff --git a/src/main/java/meteordevelopment/meteorclient/commands/arguments/ModuleArgumentType.java b/src/main/java/meteordevelopment/meteorclient/commands/arguments/ModuleArgumentType.java index 727cedcdcc..f147878285 100644 --- a/src/main/java/meteordevelopment/meteorclient/commands/arguments/ModuleArgumentType.java +++ b/src/main/java/meteordevelopment/meteorclient/commands/arguments/ModuleArgumentType.java @@ -5,6 +5,7 @@ package meteordevelopment.meteorclient.commands.arguments; +import com.mojang.brigadier.LiteralMessage; import com.mojang.brigadier.StringReader; import com.mojang.brigadier.arguments.ArgumentType; import com.mojang.brigadier.context.CommandContext; @@ -15,7 +16,6 @@ import meteordevelopment.meteorclient.systems.modules.Module; import meteordevelopment.meteorclient.systems.modules.Modules; import net.minecraft.command.CommandSource; -import net.minecraft.text.Text; import java.util.Collection; import java.util.concurrent.CompletableFuture; @@ -23,7 +23,7 @@ public class ModuleArgumentType implements ArgumentType { private static final ModuleArgumentType INSTANCE = new ModuleArgumentType(); - private static final DynamicCommandExceptionType NO_SUCH_MODULE = new DynamicCommandExceptionType(name -> Text.literal("Module with name " + name + " doesn't exist.")); + private static final DynamicCommandExceptionType NO_SUCH_MODULE = new DynamicCommandExceptionType(name -> new LiteralMessage("Module with name " + name + " doesn't exist.")); private static final Collection EXAMPLES = Modules.get().getAll() .stream() @@ -35,10 +35,14 @@ public static ModuleArgumentType create() { return INSTANCE; } - public static Module get(CommandContext context) { + public static Module get(CommandContext context) { return context.getArgument("module", Module.class); } + public static Module get(CommandContext context, String name) { + return context.getArgument(name, Module.class); + } + private ModuleArgumentType() {} @Override diff --git a/src/main/java/meteordevelopment/meteorclient/commands/arguments/NotebotSongArgumentType.java b/src/main/java/meteordevelopment/meteorclient/commands/arguments/NotebotSongArgumentType.java index 370689917c..c25eb389f2 100644 --- a/src/main/java/meteordevelopment/meteorclient/commands/arguments/NotebotSongArgumentType.java +++ b/src/main/java/meteordevelopment/meteorclient/commands/arguments/NotebotSongArgumentType.java @@ -5,15 +5,19 @@ package meteordevelopment.meteorclient.commands.arguments; +import com.mojang.brigadier.LiteralMessage; 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.exceptions.DynamicCommandExceptionType; import com.mojang.brigadier.suggestion.Suggestions; import com.mojang.brigadier.suggestion.SuggestionsBuilder; import meteordevelopment.meteorclient.MeteorClient; +import meteordevelopment.meteorclient.utils.commands.PathReader; import meteordevelopment.meteorclient.utils.notebot.decoder.SongDecoders; import net.minecraft.command.CommandSource; +import net.minecraft.text.Text; import java.io.IOException; import java.nio.file.Files; @@ -22,6 +26,7 @@ public class NotebotSongArgumentType implements ArgumentType { private static final NotebotSongArgumentType INSTANCE = new NotebotSongArgumentType(); + private static final DynamicCommandExceptionType INVALID_SONG_EXCEPTION = new DynamicCommandExceptionType(path -> new LiteralMessage("Path " + path + " is not a valid song.")); public static NotebotSongArgumentType create() { return INSTANCE; @@ -29,11 +34,24 @@ public static NotebotSongArgumentType create() { private NotebotSongArgumentType() {} + public static Path get(CommandContext context) { + return context.getArgument("song", Path.class); + } + + public static Path get(CommandContext context, String name) { + return context.getArgument(name, Path.class); + } + @Override public Path parse(StringReader reader) throws CommandSyntaxException { - final String text = reader.getRemaining(); - reader.setCursor(reader.getTotalLength()); - return MeteorClient.FOLDER.toPath().resolve("notebot/" + text); + Path pathArgument = PathReader.readPath(reader); + Path path = MeteorClient.FOLDER.toPath().resolve("notebot").resolve(pathArgument); + + if (!Files.exists(path)) { + throw INVALID_SONG_EXCEPTION.create(pathArgument.toString()); + } + + return path; } @Override diff --git a/src/main/java/meteordevelopment/meteorclient/commands/arguments/PlayerArgumentType.java b/src/main/java/meteordevelopment/meteorclient/commands/arguments/PlayerArgumentType.java index d579ac27bc..32a62f148f 100644 --- a/src/main/java/meteordevelopment/meteorclient/commands/arguments/PlayerArgumentType.java +++ b/src/main/java/meteordevelopment/meteorclient/commands/arguments/PlayerArgumentType.java @@ -5,6 +5,7 @@ package meteordevelopment.meteorclient.commands.arguments; +import com.mojang.brigadier.LiteralMessage; import com.mojang.brigadier.StringReader; import com.mojang.brigadier.arguments.ArgumentType; import com.mojang.brigadier.context.CommandContext; @@ -14,7 +15,6 @@ import com.mojang.brigadier.suggestion.SuggestionsBuilder; import net.minecraft.command.CommandSource; import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.text.Text; import java.util.Collection; import java.util.List; @@ -24,7 +24,7 @@ public class PlayerArgumentType implements ArgumentType { private static final PlayerArgumentType INSTANCE = new PlayerArgumentType(); - private static final DynamicCommandExceptionType NO_SUCH_PLAYER = new DynamicCommandExceptionType(name -> Text.literal("Player with name " + name + " doesn't exist.")); + private static final DynamicCommandExceptionType NO_SUCH_PLAYER = new DynamicCommandExceptionType(name -> new LiteralMessage("Player with name " + name + " doesn't exist.")); private static final Collection EXAMPLES = List.of("seasnail8169", "MineGame159"); @@ -32,10 +32,14 @@ public static PlayerArgumentType create() { return INSTANCE; } - public static PlayerEntity get(CommandContext context) { + public static PlayerEntity get(CommandContext context) { return context.getArgument("player", PlayerEntity.class); } + public static PlayerEntity get(CommandContext context, String name) { + return context.getArgument(name, PlayerEntity.class); + } + private PlayerArgumentType() {} @Override diff --git a/src/main/java/meteordevelopment/meteorclient/commands/arguments/PlayerListEntryArgumentType.java b/src/main/java/meteordevelopment/meteorclient/commands/arguments/PlayerListEntryArgumentType.java index db7c3eae36..a83d618d59 100644 --- a/src/main/java/meteordevelopment/meteorclient/commands/arguments/PlayerListEntryArgumentType.java +++ b/src/main/java/meteordevelopment/meteorclient/commands/arguments/PlayerListEntryArgumentType.java @@ -5,6 +5,7 @@ package meteordevelopment.meteorclient.commands.arguments; +import com.mojang.brigadier.LiteralMessage; import com.mojang.brigadier.StringReader; import com.mojang.brigadier.arguments.ArgumentType; import com.mojang.brigadier.context.CommandContext; @@ -14,7 +15,6 @@ import com.mojang.brigadier.suggestion.SuggestionsBuilder; import net.minecraft.client.network.PlayerListEntry; import net.minecraft.command.CommandSource; -import net.minecraft.text.Text; import java.util.Collection; import java.util.List; @@ -24,7 +24,7 @@ public class PlayerListEntryArgumentType implements ArgumentType { private static final PlayerListEntryArgumentType INSTANCE = new PlayerListEntryArgumentType(); - private static final DynamicCommandExceptionType NO_SUCH_PLAYER = new DynamicCommandExceptionType(name -> Text.literal("Player list entry with name " + name + " doesn't exist.")); + private static final DynamicCommandExceptionType NO_SUCH_PLAYER = new DynamicCommandExceptionType(name -> new LiteralMessage("Player list entry with name " + name + " doesn't exist.")); private static final Collection EXAMPLES = List.of("seasnail8169", "MineGame159"); @@ -32,10 +32,14 @@ public static PlayerListEntryArgumentType create() { return INSTANCE; } - public static PlayerListEntry get(CommandContext context) { + public static PlayerListEntry get(CommandContext context) { return context.getArgument("player", PlayerListEntry.class); } + public static PlayerListEntry get(CommandContext context, String name) { + return context.getArgument(name, PlayerListEntry.class); + } + private PlayerListEntryArgumentType() {} @Override diff --git a/src/main/java/meteordevelopment/meteorclient/commands/arguments/ProfileArgumentType.java b/src/main/java/meteordevelopment/meteorclient/commands/arguments/ProfileArgumentType.java index 56fb09e8dd..8afe93e8de 100644 --- a/src/main/java/meteordevelopment/meteorclient/commands/arguments/ProfileArgumentType.java +++ b/src/main/java/meteordevelopment/meteorclient/commands/arguments/ProfileArgumentType.java @@ -6,6 +6,7 @@ package meteordevelopment.meteorclient.commands.arguments; import com.google.common.collect.Streams; +import com.mojang.brigadier.LiteralMessage; import com.mojang.brigadier.StringReader; import com.mojang.brigadier.arguments.ArgumentType; import com.mojang.brigadier.context.CommandContext; @@ -15,7 +16,6 @@ import com.mojang.brigadier.suggestion.SuggestionsBuilder; import meteordevelopment.meteorclient.systems.profiles.Profile; import meteordevelopment.meteorclient.systems.profiles.Profiles; -import net.minecraft.text.Text; import java.util.Collection; import java.util.List; @@ -23,9 +23,9 @@ import static net.minecraft.command.CommandSource.suggestMatching; -public class ProfileArgumentType implements ArgumentType { +public class ProfileArgumentType implements ArgumentType { private static final ProfileArgumentType INSTANCE = new ProfileArgumentType(); - private static final DynamicCommandExceptionType NO_SUCH_PROFILE = new DynamicCommandExceptionType(name -> Text.literal("Profile with name " + name + " doesn't exist.")); + private static final DynamicCommandExceptionType NO_SUCH_PROFILE = new DynamicCommandExceptionType(name -> new LiteralMessage("Profile with name " + name + " doesn't exist.")); private static final Collection EXAMPLES = List.of("pvp.meteorclient.com", "anarchy"); @@ -33,19 +33,23 @@ public static ProfileArgumentType create() { return INSTANCE; } - public static Profile get(CommandContext context) { - return Profiles.get().get(context.getArgument("profile", String.class)); + public static Profile get(CommandContext context) { + return context.getArgument("profile", Profile.class); + } + + public static Profile get(CommandContext context, String name) { + return context.getArgument(name, Profile.class); } private ProfileArgumentType() {} @Override - public String parse(StringReader reader) throws CommandSyntaxException { - String argument = reader.getRemaining(); - reader.setCursor(reader.getTotalLength()); - if (Profiles.get().get(argument) == null) throw NO_SUCH_PROFILE.create(argument); + public Profile parse(StringReader reader) throws CommandSyntaxException { + String argument = reader.readString(); + Profile profile = Profiles.get().get(argument); + if (profile == null) throw NO_SUCH_PROFILE.create(argument); - return argument; + return profile; } @Override diff --git a/src/main/java/meteordevelopment/meteorclient/commands/arguments/RegistryEntryReferenceArgumentType.java b/src/main/java/meteordevelopment/meteorclient/commands/arguments/RegistryEntryReferenceArgumentType.java index 7db6039a51..2e2b757213 100644 --- a/src/main/java/meteordevelopment/meteorclient/commands/arguments/RegistryEntryReferenceArgumentType.java +++ b/src/main/java/meteordevelopment/meteorclient/commands/arguments/RegistryEntryReferenceArgumentType.java @@ -71,27 +71,27 @@ public static RegistryEntryReferenceArgumentType statusEffect() { return STATUS_EFFECT; } - public static RegistryEntry.Reference getEnchantment(CommandContext context, String name) throws CommandSyntaxException { + public static RegistryEntry.Reference getEnchantment(CommandContext context, String name) throws CommandSyntaxException { return getRegistryEntry(context, name, RegistryKeys.ENCHANTMENT); } - public static RegistryEntry.Reference getEntityAttribute(CommandContext context, String name) throws CommandSyntaxException { + public static RegistryEntry.Reference getEntityAttribute(CommandContext context, String name) throws CommandSyntaxException { return getRegistryEntry(context, name, RegistryKeys.ATTRIBUTE); } - public static RegistryEntry.Reference getStructure(CommandContext context, String name) throws CommandSyntaxException { + public static RegistryEntry.Reference getStructure(CommandContext context, String name) throws CommandSyntaxException { return getRegistryEntry(context, name, RegistryKeys.STRUCTURE); } - public static RegistryEntry.Reference> getEntityType(CommandContext context, String name) throws CommandSyntaxException { + public static RegistryEntry.Reference> getEntityType(CommandContext context, String name) throws CommandSyntaxException { return getRegistryEntry(context, name, RegistryKeys.ENTITY_TYPE); } - public static RegistryEntry.Reference getStatusEffect(CommandContext context, String name) throws CommandSyntaxException { + public static RegistryEntry.Reference getStatusEffect(CommandContext context, String name) throws CommandSyntaxException { return getRegistryEntry(context, name, RegistryKeys.STATUS_EFFECT); } - private static RegistryEntry.Reference getRegistryEntry(CommandContext context, String name, RegistryKey> registryRef) throws CommandSyntaxException { + private static RegistryEntry.Reference getRegistryEntry(CommandContext context, String name, RegistryKey> registryRef) throws CommandSyntaxException { RegistryEntry.Reference reference = context.getArgument(name, RegistryEntry.Reference.class); RegistryKey registryKey = reference.registryKey(); if (registryKey.isOf(registryRef)) { diff --git a/src/main/java/meteordevelopment/meteorclient/commands/arguments/SettingArgumentType.java b/src/main/java/meteordevelopment/meteorclient/commands/arguments/SettingArgumentType.java index 2bfc08c3a6..cd0fffa158 100644 --- a/src/main/java/meteordevelopment/meteorclient/commands/arguments/SettingArgumentType.java +++ b/src/main/java/meteordevelopment/meteorclient/commands/arguments/SettingArgumentType.java @@ -14,44 +14,65 @@ import com.mojang.brigadier.suggestion.Suggestions; import com.mojang.brigadier.suggestion.SuggestionsBuilder; import meteordevelopment.meteorclient.settings.Setting; +import meteordevelopment.meteorclient.settings.Settings; import meteordevelopment.meteorclient.systems.modules.Module; +import meteordevelopment.meteorclient.utils.commands.ArgumentFunction; import net.minecraft.command.CommandSource; import net.minecraft.text.Text; +import org.jetbrains.annotations.Nullable; import java.util.concurrent.CompletableFuture; -import java.util.stream.Stream; -public class SettingArgumentType implements ArgumentType { - private static final SettingArgumentType INSTANCE = new SettingArgumentType(); +public class SettingArgumentType implements ArgumentType { private static final DynamicCommandExceptionType NO_SUCH_SETTING = new DynamicCommandExceptionType(name -> Text.literal("No such setting '" + name + "'.")); - public static SettingArgumentType create() { - return INSTANCE; + private final ArgumentFunction settingsArgumentFunction; + + private SettingArgumentType(ArgumentFunction settingsArgumentFunction) { + this.settingsArgumentFunction = settingsArgumentFunction; } - public static Setting get(CommandContext context) throws CommandSyntaxException { - Module module = context.getArgument("module", Module.class); - String settingName = context.getArgument("setting", String.class); + public static SettingArgumentType create(ArgumentFunction settingsArgumentFunction) { + return new SettingArgumentType(settingsArgumentFunction); + } - Setting setting = module.settings.get(settingName); - if (setting == null) throw NO_SUCH_SETTING.create(settingName); + public static SettingArgumentType createModule(ArgumentFunction moduleArgumentFunction) { + return new SettingArgumentType(moduleArgumentFunction.andThen(module -> module.settings)); + } - return setting; + public static Setting get(CommandContext context) throws CommandSyntaxException { + return context.getArgument("setting", SettingArgument.class).getSetting(context); } - private SettingArgumentType() {} + public static Setting get(CommandContext context, String name) throws CommandSyntaxException { + return context.getArgument(name, SettingArgument.class).getSetting(context); + } @Override - public String parse(StringReader reader) throws CommandSyntaxException { - return reader.readString(); + public SettingArgument parse(StringReader reader) throws CommandSyntaxException { + return new SettingArgument(reader.readString(), this.settingsArgumentFunction); } @Override public CompletableFuture listSuggestions(CommandContext context, SuggestionsBuilder builder) { - Stream stream = Streams.stream(context.getArgument("module", Module.class).settings.iterator()) + try { + return CommandSource.suggestMatching(Streams.stream(this.settingsArgumentFunction.uncheckedApply(context).iterator()) .flatMap(settings -> Streams.stream(settings.iterator())) - .map(setting -> setting.name); + .filter(Setting::isVisible) + .map(setting -> setting.name), builder); + } catch (CommandSyntaxException e) { + return Suggestions.empty(); + } + } - return CommandSource.suggestMatching(stream, builder); + public record SettingArgument(String settingName, ArgumentFunction settingsArgumentFunction) { + public Setting getSetting(CommandContext context) throws CommandSyntaxException { + Settings settings = this.settingsArgumentFunction().uncheckedApply(context); + @Nullable Setting setting = settings.get(this.settingName()); + if (setting == null) { + throw NO_SUCH_SETTING.create(this.settingName()); + } + return setting; + } } } diff --git a/src/main/java/meteordevelopment/meteorclient/commands/arguments/SettingValueArgumentType.java b/src/main/java/meteordevelopment/meteorclient/commands/arguments/SettingValueArgumentType.java index e7267dce71..2cd897e5a4 100644 --- a/src/main/java/meteordevelopment/meteorclient/commands/arguments/SettingValueArgumentType.java +++ b/src/main/java/meteordevelopment/meteorclient/commands/arguments/SettingValueArgumentType.java @@ -7,51 +7,57 @@ import com.mojang.brigadier.StringReader; import com.mojang.brigadier.arguments.ArgumentType; +import com.mojang.brigadier.arguments.StringArgumentType; 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.settings.Setting; +import meteordevelopment.meteorclient.utils.commands.ArgumentFunction; import net.minecraft.command.CommandSource; import net.minecraft.util.Identifier; import java.util.concurrent.CompletableFuture; public class SettingValueArgumentType implements ArgumentType { - private static final SettingValueArgumentType INSTANCE = new SettingValueArgumentType(); + private final ArgumentFunction> settingArgumentFunction; - public static SettingValueArgumentType create() { - return INSTANCE; + private SettingValueArgumentType(ArgumentFunction> settingArgumentFunction) { + this.settingArgumentFunction = settingArgumentFunction; } - public static String get(CommandContext context) { + public static SettingValueArgumentType create(ArgumentFunction> settingArgumentFunction) { + return new SettingValueArgumentType(settingArgumentFunction); + } + + public static String get(CommandContext context) { return context.getArgument("value", String.class); } - private SettingValueArgumentType() {} + public static String get(CommandContext context, String name) { + return context.getArgument(name, String.class); + } @Override public String parse(StringReader reader) throws CommandSyntaxException { - String text = reader.getRemaining(); + String settingValue = reader.getRemaining(); reader.setCursor(reader.getTotalLength()); - return text; + return settingValue; } @Override public CompletableFuture listSuggestions(CommandContext context, SuggestionsBuilder builder) { - Setting setting; - try { - setting = SettingArgumentType.get(context); - } catch (CommandSyntaxException ignored) { - return Suggestions.empty(); - } + Setting setting = this.settingArgumentFunction.uncheckedApply(context); - Iterable identifiers = setting.getIdentifierSuggestions(); - if (identifiers != null) { - return CommandSource.suggestIdentifiers(identifiers, builder); - } + Iterable identifiers = setting.getIdentifierSuggestions(); + if (identifiers != null) { + return CommandSource.suggestIdentifiers(identifiers, builder); + } - return CommandSource.suggestMatching(setting.getSuggestions(), builder); + return CommandSource.suggestMatching(setting.getSuggestions(), builder); + } catch (CommandSyntaxException e) { + return Suggestions.empty(); + } } } diff --git a/src/main/java/meteordevelopment/meteorclient/commands/arguments/WaypointArgumentType.java b/src/main/java/meteordevelopment/meteorclient/commands/arguments/WaypointArgumentType.java index ee5d2cbce5..42710ec47d 100644 --- a/src/main/java/meteordevelopment/meteorclient/commands/arguments/WaypointArgumentType.java +++ b/src/main/java/meteordevelopment/meteorclient/commands/arguments/WaypointArgumentType.java @@ -22,42 +22,31 @@ import java.util.List; import java.util.concurrent.CompletableFuture; -public class WaypointArgumentType implements ArgumentType { - private static final WaypointArgumentType GREEDY = new WaypointArgumentType(true); - private static final WaypointArgumentType QUOTED = new WaypointArgumentType(false); +public class WaypointArgumentType implements ArgumentType { + private static final WaypointArgumentType INSTANCE = new WaypointArgumentType(); private static final DynamicCommandExceptionType NO_SUCH_WAYPOINT = new DynamicCommandExceptionType(name -> Text.literal("Waypoint with name '" + name + "' doesn't exist.")); - private final boolean greedyString; - private WaypointArgumentType(boolean greedyString) { - this.greedyString = greedyString; - } + private WaypointArgumentType() {} public static WaypointArgumentType create() { - return GREEDY; - } - - public static WaypointArgumentType create(boolean greedy) { - return greedy ? GREEDY : QUOTED; + return INSTANCE; } - public static Waypoint get(CommandContext context) { - return Waypoints.get().get(context.getArgument("waypoint", String.class)); + public static Waypoint get(CommandContext context) { + return context.getArgument("waypoint", Waypoint.class); } - public static Waypoint get(CommandContext context, String name) { - return Waypoints.get().get(context.getArgument(name, String.class)); + public static Waypoint get(CommandContext context, String name) { + return context.getArgument(name, Waypoint.class); } @Override - public String parse(StringReader reader) throws CommandSyntaxException { - String argument; - if (greedyString) { - argument = reader.getRemaining(); - reader.setCursor(reader.getTotalLength()); - } else argument = reader.readString(); + public Waypoint parse(StringReader reader) throws CommandSyntaxException { + String argument = reader.readString(); + Waypoint waypoint = Waypoints.get().get(argument); + if (waypoint == null) throw NO_SUCH_WAYPOINT.create(argument); - if (Waypoints.get().get(argument) == null) throw NO_SUCH_WAYPOINT.create(argument); - return argument; + return waypoint; } @Override diff --git a/src/main/java/meteordevelopment/meteorclient/commands/commands/BindsCommand.java b/src/main/java/meteordevelopment/meteorclient/commands/commands/BindsCommand.java index ee2fb642c5..c6ff27af88 100644 --- a/src/main/java/meteordevelopment/meteorclient/commands/commands/BindsCommand.java +++ b/src/main/java/meteordevelopment/meteorclient/commands/commands/BindsCommand.java @@ -9,7 +9,6 @@ import meteordevelopment.meteorclient.commands.Command; import meteordevelopment.meteorclient.systems.modules.Module; import meteordevelopment.meteorclient.systems.modules.Modules; -import meteordevelopment.meteorclient.utils.Utils; import meteordevelopment.meteorclient.utils.player.ChatUtils; import net.minecraft.command.CommandSource; import net.minecraft.text.HoverEvent; @@ -56,7 +55,7 @@ public void build(LiteralArgumentBuilder builder) { } private MutableText getTooltip(Module module) { - MutableText tooltip = Text.literal(Utils.nameToTitle(module.title)).formatted(Formatting.BLUE, Formatting.BOLD).append("\n\n"); + MutableText tooltip = Text.literal(module.title).formatted(Formatting.BLUE, Formatting.BOLD).append("\n\n"); tooltip.append(Text.literal(module.description).formatted(Formatting.WHITE)); return tooltip; } diff --git a/src/main/java/meteordevelopment/meteorclient/commands/commands/CommandsCommand.java b/src/main/java/meteordevelopment/meteorclient/commands/commands/CommandsCommand.java index 63d6fb3ac2..de34f82be2 100644 --- a/src/main/java/meteordevelopment/meteorclient/commands/commands/CommandsCommand.java +++ b/src/main/java/meteordevelopment/meteorclient/commands/commands/CommandsCommand.java @@ -12,10 +12,8 @@ import meteordevelopment.meteorclient.utils.Utils; import meteordevelopment.meteorclient.utils.player.ChatUtils; import net.minecraft.command.CommandSource; -import net.minecraft.text.ClickEvent; -import net.minecraft.text.HoverEvent; -import net.minecraft.text.MutableText; -import net.minecraft.text.Text; +import net.minecraft.screen.ScreenTexts; +import net.minecraft.text.*; import net.minecraft.util.Formatting; public class CommandsCommand extends Command { @@ -28,7 +26,7 @@ public void build(LiteralArgumentBuilder builder) { builder.executes(context -> { ChatUtils.info("--- Commands ((highlight)%d(default)) ---", Commands.COMMANDS.size()); - MutableText commands = Text.literal(""); + MutableText commands = Text.empty(); Commands.COMMANDS.forEach(command -> commands.append(getCommandText(command))); ChatUtils.sendMsg(commands); @@ -38,17 +36,17 @@ public void build(LiteralArgumentBuilder builder) { private MutableText getCommandText(Command command) { // Hover tooltip - MutableText tooltip = Text.literal(""); + MutableText tooltip = Text.empty(); - tooltip.append(Text.literal(Utils.nameToTitle(command.getName())).formatted(Formatting.BLUE, Formatting.BOLD)).append("\n"); + tooltip.append(Text.literal(Utils.nameToTitle(command.getName())).formatted(Formatting.BLUE, Formatting.BOLD)).append(ScreenTexts.LINE_BREAK); MutableText aliases = Text.literal(Config.get().prefix.get() + command.getName()); if (!command.getAliases().isEmpty()) { - aliases.append(", "); + aliases.append(Texts.DEFAULT_SEPARATOR_TEXT); for (String alias : command.getAliases()) { if (alias.isEmpty()) continue; aliases.append(Config.get().prefix.get() + alias); - if (!alias.equals(command.getAliases().getLast())) aliases.append(", "); + if (!alias.equals(command.getAliases().getLast())) aliases.append(Texts.DEFAULT_SEPARATOR_TEXT); } } tooltip.append(aliases.formatted(Formatting.GRAY)).append("\n\n"); @@ -58,7 +56,7 @@ private MutableText getCommandText(Command command) { // Text MutableText text = Text.literal(Utils.nameToTitle(command.getName())); if (command != Commands.COMMANDS.getLast()) - text.append(Text.literal(", ").formatted(Formatting.GRAY)); + text.append(Texts.GRAY_DEFAULT_SEPARATOR_TEXT); text.setStyle(text .getStyle() .withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, tooltip)) diff --git a/src/main/java/meteordevelopment/meteorclient/commands/commands/ComponentsCommand.java b/src/main/java/meteordevelopment/meteorclient/commands/commands/ComponentsCommand.java new file mode 100644 index 0000000000..2b0e75b050 --- /dev/null +++ b/src/main/java/meteordevelopment/meteorclient/commands/commands/ComponentsCommand.java @@ -0,0 +1,410 @@ +/* + * This file is part of the Meteor Client distribution (https://github.com/MeteorDevelopment/meteor-client). + * Copyright (c) Meteor Development. + */ + +package meteordevelopment.meteorclient.commands.commands; + +import com.mojang.brigadier.StringReader; +import com.mojang.brigadier.arguments.IntegerArgumentType; +import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.exceptions.DynamicCommandExceptionType; +import com.mojang.brigadier.suggestion.SuggestionProvider; +import com.mojang.serialization.DataResult; +import meteordevelopment.meteorclient.commands.Command; +import meteordevelopment.meteorclient.commands.arguments.ComponentMapArgumentType; +import meteordevelopment.meteorclient.commands.arguments.EntityArgumentType; +import meteordevelopment.meteorclient.commands.arguments.ItemSlotArgumentType; +import meteordevelopment.meteorclient.utils.commands.ArgumentFunction; +import meteordevelopment.meteorclient.utils.commands.ComponentMapReader; +import meteordevelopment.meteorclient.utils.commands.ComponentMapWriter; +import meteordevelopment.meteorclient.utils.commands.CreativeCommandHelper; +import net.minecraft.command.CommandSource; +import net.minecraft.command.argument.RegistryKeyArgumentType; +import net.minecraft.component.*; +import net.minecraft.entity.Entity; +import net.minecraft.item.ItemStack; +import net.minecraft.registry.Registries; +import net.minecraft.registry.RegistryKey; +import net.minecraft.registry.RegistryKeys; +import net.minecraft.screen.ScreenTexts; +import net.minecraft.text.*; +import net.minecraft.util.Formatting; +import net.minecraft.util.Unit; + +import java.util.Locale; +import java.util.Set; + +public class ComponentsCommand extends Command { + private static final DynamicCommandExceptionType MALFORMED_ITEM_EXCEPTION = new DynamicCommandExceptionType( + error -> Text.stringifiedTranslatable("arguments.item.malformed", error) + ); + + private static final Text COPY_BUTTON = Text.literal("Data").setStyle(Style.EMPTY + .withFormatting(Formatting.UNDERLINE) + .withHoverEvent(new HoverEvent( + HoverEvent.Action.SHOW_TEXT, + Text.literal("Copy the component data to your clipboard.") + )) + ); + + public ComponentsCommand() { + super("components", "View and modify data components for an item, example: .components add [minecraft:item_name='{\"color\":\"red\",\"text\":\"Red Name\"}']"); + } + + public static MutableText createCopyButton(String toCopy) { + return Text.empty().append(COPY_BUTTON).setStyle(Style.EMPTY + .withClickEvent(new ClickEvent( + ClickEvent.Action.COPY_TO_CLIPBOARD, + toCopy + ))); + } + + @Override + public void build(LiteralArgumentBuilder builder) { + builder.then(literal("reset") + .executes(context -> executeReset(mc.player.getInventory().selectedSlot)) + .then(argument("slot", ItemSlotArgumentType.modifiableSlot()).executes(context -> + executeReset(ItemSlotArgumentType.getItemSlot(context)) + )) + ); + + builder.then(literal("add") + .then(argument("component", ComponentMapArgumentType.componentMap(REGISTRY_ACCESS)).executes(context -> + executeAdd(context, mc.player.getInventory().selectedSlot) + )) + .then(argument("slot", ItemSlotArgumentType.modifiableSlot()).then(argument("component", ComponentMapArgumentType.componentMap(REGISTRY_ACCESS)).executes(context -> + executeAdd(context, ItemSlotArgumentType.getItemSlot(context)) + ))) + ); + + builder.then(literal("set") + .then(argument("component", ComponentMapArgumentType.componentMap(REGISTRY_ACCESS)).executes(context -> + executeSet(context, mc.player.getInventory().selectedSlot) + )) + .then(argument("slot", ItemSlotArgumentType.modifiableSlot()).then(argument("component", ComponentMapArgumentType.componentMap(REGISTRY_ACCESS)).executes(context -> + executeSet(context, ItemSlotArgumentType.getItemSlot(context)) + ))) + ); + + builder.then(literal("remove") + .then(argument("component", RegistryKeyArgumentType.registryKey(RegistryKeys.DATA_COMPONENT_TYPE)) + .executes(context -> executeRemove(context, mc.player.getInventory().selectedSlot)) + .suggests(getComponentSuggestionProvider(context -> mc.player, context -> mc.player.getInventory().selectedSlot)) + ) + .then(argument("slot", ItemSlotArgumentType.modifiableSlot()).then(argument("component", RegistryKeyArgumentType.registryKey(RegistryKeys.DATA_COMPONENT_TYPE)) + .executes(context -> executeRemove(context, ItemSlotArgumentType.getItemSlot(context))) + .suggests(getComponentSuggestionProvider(context -> mc.player, ItemSlotArgumentType::getItemSlot)) + )) + ); + + builder.then(literal("get") + .executes(context -> executeGet(mc.player, mc.player.getInventory().selectedSlot)) + .then(literal("full").executes(context -> executeGetFull(mc.player, mc.player.getInventory().selectedSlot))) + .then(argument("component", RegistryKeyArgumentType.registryKey(RegistryKeys.DATA_COMPONENT_TYPE)) + .executes(context -> executeGet(context, mc.player, mc.player.getInventory().selectedSlot)) + .suggests(getComponentSuggestionProvider(context -> mc.player, context -> mc.player.getInventory().selectedSlot)) + ) + .then(argument("slot", ItemSlotArgumentType.selfSlot()) + .executes(context -> executeGet(mc.player, ItemSlotArgumentType.getItemSlot(context))) + .then(literal("full").executes(context -> executeGetFull(mc.player, ItemSlotArgumentType.getItemSlot(context)))) + .then(argument("component", RegistryKeyArgumentType.registryKey(RegistryKeys.DATA_COMPONENT_TYPE)) + .executes(context -> executeGet(context, mc.player, ItemSlotArgumentType.getItemSlot(context))) + .suggests(getComponentSuggestionProvider(context -> mc.player, ItemSlotArgumentType::getItemSlot)) + ) + ) + .then(argument("entity", EntityArgumentType.entity()) + .executes(context -> executeGet( + EntityArgumentType.getEntity(context, "entity"), + ItemSlotArgumentType.MAINHAND_SLOT_INDEX + )) + .then(literal("full").executes(context -> executeGetFull( + EntityArgumentType.getEntity(context, "entity"), + ItemSlotArgumentType.MAINHAND_SLOT_INDEX + ))) + .then(argument("component", RegistryKeyArgumentType.registryKey(RegistryKeys.DATA_COMPONENT_TYPE)) + .executes(context -> executeGet(context, EntityArgumentType.getEntity(context, "entity"), ItemSlotArgumentType.MAINHAND_SLOT_INDEX)) + .suggests(getComponentSuggestionProvider(context -> EntityArgumentType.getEntity(context, "entity"), context -> ItemSlotArgumentType.MAINHAND_SLOT_INDEX)) + ) + .then(argument("slot", ItemSlotArgumentType.itemSlot()) + .executes(context -> executeGet( + EntityArgumentType.getEntity(context, "entity"), + ItemSlotArgumentType.getItemSlot(context) + )) + .then(literal("full").executes(context -> executeGetFull( + EntityArgumentType.getEntity(context, "entity"), + ItemSlotArgumentType.getItemSlot(context) + ))) + .then(argument("component", RegistryKeyArgumentType.registryKey(RegistryKeys.DATA_COMPONENT_TYPE)) + .executes(context -> executeGet(context, EntityArgumentType.getEntity(context, "entity"), ItemSlotArgumentType.getItemSlot(context))) + .suggests(getComponentSuggestionProvider(context -> EntityArgumentType.getEntity(context, "entity"), ItemSlotArgumentType::getItemSlot)) + ) + ) + ) + ); + + builder.then(literal("copy") + .executes(context -> executeCopy(mc.player, mc.player.getInventory().selectedSlot)) + .then(literal("full").executes(context -> executeCopyFull(mc.player, mc.player.getInventory().selectedSlot))) + .then(argument("slot", ItemSlotArgumentType.selfSlot()) + .executes(context -> executeCopy(mc.player, ItemSlotArgumentType.getItemSlot(context))) + .then(literal("full").executes(context -> executeCopyFull(mc.player, ItemSlotArgumentType.getItemSlot(context)))) + ) + .then(argument("entity", EntityArgumentType.entity()) + .executes(context -> executeCopy( + EntityArgumentType.getEntity(context, "entity"), + ItemSlotArgumentType.MAINHAND_SLOT_INDEX + )) + .then(literal("full").executes(context -> executeCopyFull( + EntityArgumentType.getEntity(context, "entity"), + ItemSlotArgumentType.MAINHAND_SLOT_INDEX + ))) + .then(argument("slot", ItemSlotArgumentType.itemSlot()) + .executes(context -> executeCopy( + EntityArgumentType.getEntity(context, "entity"), + ItemSlotArgumentType.getItemSlot(context) + )) + .then(literal("full").executes(context -> executeCopyFull( + EntityArgumentType.getEntity(context, "entity"), + ItemSlotArgumentType.getItemSlot(context) + ))) + ) + ) + ); + + builder.then(literal("paste") + .executes(context -> executePaste(mc.player.getInventory().selectedSlot)) + .then(argument("slot", ItemSlotArgumentType.modifiableSlot()).executes(context -> + executePaste(ItemSlotArgumentType.getItemSlot(context)) + )) + ); + + builder.then(literal("count").then(argument("count", IntegerArgumentType.integer(1, 99)) + .executes(context -> executeCount( + mc.player.getInventory().selectedSlot, + IntegerArgumentType.getInteger(context, "count") + )) + .then(argument("slot", ItemSlotArgumentType.modifiableSlot()).executes(context -> executeCount( + ItemSlotArgumentType.getItemSlot(context), + IntegerArgumentType.getInteger(context, "count") + ))) + )); + + builder.then(literal("rename").then(argument("name", StringArgumentType.greedyString()).executes(context -> { + ItemStack stack = mc.player.getMainHandStack(); + CreativeCommandHelper.assertValid(stack); + + String name = StringArgumentType.getString(context, "name"); + stack.set(DataComponentTypes.ITEM_NAME, Text.literal(name)); + CreativeCommandHelper.setStack(stack); + + return SINGLE_SUCCESS; + }))); + } + + private int executeReset(int slot) throws CommandSyntaxException { + ItemStack stack = mc.player.getInventory().getStack(slot); + CreativeCommandHelper.assertValid(stack); + + ((MergedComponentMap) stack.getComponents()).clearChanges(); + + CreativeCommandHelper.setStack(stack, slot); + info("Reset components."); + + return SINGLE_SUCCESS; + } + + private int executeAdd(CommandContext context, int slot) throws CommandSyntaxException { + ItemStack stack = mc.player.getInventory().getStack(slot); + CreativeCommandHelper.assertValid(stack); + + ComponentMap itemComponents = stack.getComponents(); + ComponentMap newComponents = ComponentMapArgumentType.getComponentMap(context, "component"); + + ComponentMap testComponents = ComponentMap.of(itemComponents, newComponents); + DataResult dataResult = ItemStack.validateComponents(testComponents); + dataResult.getOrThrow(MALFORMED_ITEM_EXCEPTION::create); + + stack.applyComponentsFrom(testComponents); + + CreativeCommandHelper.setStack(stack, slot); + info("Added components."); + + return SINGLE_SUCCESS; + } + + private int executeSet(CommandContext context, int slot) throws CommandSyntaxException { + ItemStack stack = mc.player.getInventory().getStack(slot); + CreativeCommandHelper.assertValid(stack); + + ComponentMap components = ComponentMapArgumentType.getComponentMap(context, "component"); + MergedComponentMap stackComponents = (MergedComponentMap) 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 (ComponentType type : types) { + changesBuilder.remove(type); + } + + stackComponents.applyChanges(changesBuilder.build()); + + CreativeCommandHelper.setStack(stack, slot); + info("Set components."); + + return SINGLE_SUCCESS; + } + + private int executeRemove(CommandContext context, int slot) throws CommandSyntaxException { + ItemStack stack = mc.player.getInventory().getStack(slot); + CreativeCommandHelper.assertValid(stack); + + @SuppressWarnings("unchecked") + RegistryKey> componentTypeKey = (RegistryKey>) context.getArgument("component", RegistryKey.class); + + ComponentType componentType = Registries.DATA_COMPONENT_TYPE.get(componentTypeKey); + stack.remove(componentType); + + CreativeCommandHelper.setStack(stack, slot); + info("Removed (highlight)%s(default).", componentType); + + return SINGLE_SUCCESS; + } + + private SuggestionProvider getComponentSuggestionProvider(ArgumentFunction entityArgumentFunction, ArgumentFunction slotArgumentFunction) { + return (context, suggestionsBuilder) -> { + ItemStack stack = entityArgumentFunction.apply(context).getStackReference(slotArgumentFunction.apply(context)).get(); + CreativeCommandHelper.assertValid(stack); + + ComponentMap components = stack.getComponents(); + String remaining = suggestionsBuilder.getRemaining().toLowerCase(Locale.ROOT); + + CommandSource.forEachMatching(components.getTypes().stream().map(Registries.DATA_COMPONENT_TYPE::getEntry).filter(entry -> entry.getKey().isPresent()).toList(), remaining, entry -> + entry.getKey().orElseThrow().getValue(), + entry -> { + ComponentType dataComponentType = entry.value(); + if (dataComponentType.getCodec() != null) suggestionsBuilder.suggest(entry.getKey().orElseThrow().getValue().toString()); + } + ); + + return suggestionsBuilder.buildFuture(); + }; + } + + private int executeGet(Entity entity, int slot) throws CommandSyntaxException { + ItemStack stack = entity.getStackReference(slot).get(); + CreativeCommandHelper.assertValid(stack); + + SerializedComponents serialized = SerializedComponents.serialize(stack.getComponentChanges()); + info(serialized.formatted()); + + return SINGLE_SUCCESS; + } + + private int executeGetFull(Entity entity, int slot) throws CommandSyntaxException { + ItemStack stack = entity.getStackReference(slot).get(); + CreativeCommandHelper.assertValid(stack); + + SerializedComponents serialized = SerializedComponents.serialize(stack.getComponents()); + info(serialized.formatted()); + + return SINGLE_SUCCESS; + } + + private int executeGet(CommandContext context, Entity entity, int slot) throws CommandSyntaxException { + ItemStack stack = entity.getStackReference(slot).get(); + CreativeCommandHelper.assertValid(stack); + + @SuppressWarnings("unchecked") + RegistryKey> componentTypeKey = (RegistryKey>) context.getArgument("component", RegistryKey.class); + + ComponentType componentType = Registries.DATA_COMPONENT_TYPE.get(componentTypeKey); + + if (stack.contains(componentType)) { + SerializedComponents serialized = SerializedComponents.serialize(stack, componentType); + info(serialized.formatted()); + } else { + info("Item does not have component '(highlight)%s(default)'.", componentTypeKey.getValue()); + } + + return SINGLE_SUCCESS; + } + + private int executeCopy(Entity entity, int slot) throws CommandSyntaxException { + ItemStack stack = entity.getStackReference(slot).get(); + CreativeCommandHelper.assertValid(stack); + + SerializedComponents serialized = SerializedComponents.serialize(stack.getComponentChanges()); + + mc.keyboard.setClipboard(serialized.stringified()); + info(serialized.formatted().append(" data copied!")); + + return SINGLE_SUCCESS; + } + + private int executeCopyFull(Entity entity, int slot) throws CommandSyntaxException { + ItemStack stack = entity.getStackReference(slot).get(); + CreativeCommandHelper.assertValid(stack); + + SerializedComponents serialized = SerializedComponents.serialize(stack.getComponents()); + + mc.keyboard.setClipboard(serialized.stringified()); + info(serialized.formatted().append(" data copied!")); + + return SINGLE_SUCCESS; + } + + private int executePaste(int slot) throws CommandSyntaxException { + ItemStack stack = mc.player.getInventory().getStack(slot); + CreativeCommandHelper.assertValid(stack); + + stack.applyComponentsFrom(new ComponentMapReader(REGISTRY_ACCESS).consume(new StringReader(mc.keyboard.getClipboard()))); + CreativeCommandHelper.setStack(stack, slot); + + return SINGLE_SUCCESS; + } + + private int executeCount(int slot, int count) throws CommandSyntaxException { + ItemStack stack = mc.player.getInventory().getStack(slot); + CreativeCommandHelper.assertValid(stack); + + stack.setCount(count); + CreativeCommandHelper.setStack(stack, slot); + + return SINGLE_SUCCESS; + } + + private record SerializedComponents(String stringified, MutableText formatted) { + public static SerializedComponents serialize(ComponentMap componentMap) { + ComponentMapWriter writer = new ComponentMapWriter(REGISTRY_ACCESS); + String stringified = writer.write(componentMap).resultOrPartial().orElseThrow(); + return new SerializedComponents(stringified, createCopyButton(stringified).append(ScreenTexts.SPACE).append(writer.writePrettyPrinted(componentMap).resultOrPartial().orElseThrow())); + } + + public static SerializedComponents serialize(ComponentChanges componentChanges) { + ComponentMapWriter writer = new ComponentMapWriter(REGISTRY_ACCESS); + String stringified = writer.write(componentChanges).resultOrPartial().orElseThrow(); + return new SerializedComponents(stringified, createCopyButton(stringified).append(ScreenTexts.SPACE).append(writer.writePrettyPrinted(componentChanges).resultOrPartial().orElseThrow())); + } + + public static SerializedComponents serialize(ItemStack stack, ComponentType componentType) { + T componentValue = stack.get(componentType); + ComponentMapWriter writer = new ComponentMapWriter(REGISTRY_ACCESS); + String stringified = writer.write(componentType, componentValue).resultOrPartial().orElseThrow(); + return new SerializedComponents(stringified, createCopyButton(stringified).append(ScreenTexts.SPACE).append(writer.writePrettyPrinted(componentType, componentValue).resultOrPartial().orElseThrow())); + } + } +} diff --git a/src/main/java/meteordevelopment/meteorclient/commands/commands/ConfigCommand.java b/src/main/java/meteordevelopment/meteorclient/commands/commands/ConfigCommand.java new file mode 100644 index 0000000000..c49f05b74b --- /dev/null +++ b/src/main/java/meteordevelopment/meteorclient/commands/commands/ConfigCommand.java @@ -0,0 +1,55 @@ +/* + * This file is part of the Meteor Client distribution (https://github.com/MeteorDevelopment/meteor-client). + * Copyright (c) Meteor Development. + */ + +package meteordevelopment.meteorclient.commands.commands; + +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import meteordevelopment.meteorclient.commands.Command; +import meteordevelopment.meteorclient.commands.arguments.SettingArgumentType; +import meteordevelopment.meteorclient.commands.arguments.SettingValueArgumentType; +import meteordevelopment.meteorclient.gui.GuiThemes; +import meteordevelopment.meteorclient.gui.tabs.Tabs; +import meteordevelopment.meteorclient.gui.tabs.builtin.ConfigTab; +import meteordevelopment.meteorclient.settings.Setting; +import meteordevelopment.meteorclient.systems.config.Config; +import meteordevelopment.meteorclient.utils.Utils; +import net.minecraft.command.CommandSource; + +public class ConfigCommand extends Command { + public ConfigCommand() { + super("config", "Allows you to view and change meteor configurations."); + } + + @Override + public void build(LiteralArgumentBuilder builder) { + builder.executes(context -> { + Tabs.get().stream().filter(ConfigTab.class::isInstance).map(tab -> tab.createScreen(GuiThemes.get())).findAny().ifPresent(screen -> { + screen.parent = null; + Utils.screenToOpen = screen; + }); + return SINGLE_SUCCESS; + }); + + builder.then(argument("setting", SettingArgumentType.create(ctx -> Config.get().settings)) + .executes(context -> { + Setting setting = SettingArgumentType.get(context); + + info("Setting (highlight)%s(default) is (highlight)%s(default).", setting.title, setting.get()); + + return SINGLE_SUCCESS; + }) + .then(argument("value", SettingValueArgumentType.create(SettingArgumentType::get)).executes(context -> { + Setting setting = SettingArgumentType.get(context); + String value = SettingValueArgumentType.get(context); + + if (setting.parse(value)) { + info("Setting (highlight)%s(default) changed to (highlight)%s(default).", setting.title, value); + } + + return SINGLE_SUCCESS; + })) + ); + } +} diff --git a/src/main/java/meteordevelopment/meteorclient/commands/commands/DamageCommand.java b/src/main/java/meteordevelopment/meteorclient/commands/commands/DamageCommand.java index 89f48f34d8..21a09233eb 100644 --- a/src/main/java/meteordevelopment/meteorclient/commands/commands/DamageCommand.java +++ b/src/main/java/meteordevelopment/meteorclient/commands/commands/DamageCommand.java @@ -5,6 +5,7 @@ package meteordevelopment.meteorclient.commands.commands; +import com.mojang.brigadier.LiteralMessage; import com.mojang.brigadier.arguments.IntegerArgumentType; import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; @@ -14,11 +15,10 @@ import meteordevelopment.meteorclient.systems.modules.player.AntiHunger; import net.minecraft.command.CommandSource; import net.minecraft.network.packet.c2s.play.PlayerMoveC2SPacket; -import net.minecraft.text.Text; import net.minecraft.util.math.Vec3d; public class DamageCommand extends Command { - private final static SimpleCommandExceptionType INVULNERABLE = new SimpleCommandExceptionType(Text.literal("You are invulnerable.")); + private final static SimpleCommandExceptionType INVULNERABLE = new SimpleCommandExceptionType(new LiteralMessage("You are invulnerable.")); public DamageCommand() { super("damage", "Damages self", "dmg"); diff --git a/src/main/java/meteordevelopment/meteorclient/commands/commands/DisconnectCommand.java b/src/main/java/meteordevelopment/meteorclient/commands/commands/DisconnectCommand.java index f43585276a..b70d818d5a 100644 --- a/src/main/java/meteordevelopment/meteorclient/commands/commands/DisconnectCommand.java +++ b/src/main/java/meteordevelopment/meteorclient/commands/commands/DisconnectCommand.java @@ -11,6 +11,7 @@ import net.minecraft.command.CommandSource; import net.minecraft.network.packet.s2c.common.DisconnectS2CPacket; import net.minecraft.text.Text; +import net.minecraft.text.Texts; import net.minecraft.util.Formatting; public class DisconnectCommand extends Command { @@ -21,7 +22,7 @@ public DisconnectCommand() { @Override public void build(LiteralArgumentBuilder builder) { builder.executes(context -> { - mc.player.networkHandler.onDisconnect(new DisconnectS2CPacket(Text.literal("%s[%sDisconnectCommand%s] Disconnected by user.".formatted(Formatting.GRAY, Formatting.BLUE, Formatting.GRAY)))); + mc.player.networkHandler.onDisconnect(new DisconnectS2CPacket(Texts.bracketed(Text.literal("DisconnectCommand").formatted(Formatting.BLUE)).append(" Disconnected by user.").formatted(Formatting.GRAY))); return SINGLE_SUCCESS; }); diff --git a/src/main/java/meteordevelopment/meteorclient/commands/commands/DropCommand.java b/src/main/java/meteordevelopment/meteorclient/commands/commands/DropCommand.java index b0eed195f6..ddbe891f8b 100644 --- a/src/main/java/meteordevelopment/meteorclient/commands/commands/DropCommand.java +++ b/src/main/java/meteordevelopment/meteorclient/commands/commands/DropCommand.java @@ -5,6 +5,7 @@ package meteordevelopment.meteorclient.commands.commands; +import com.mojang.brigadier.LiteralMessage; import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; @@ -15,11 +16,10 @@ import net.minecraft.command.argument.ItemStackArgumentType; import net.minecraft.item.ItemStack; import net.minecraft.item.Items; -import net.minecraft.text.Text; public class DropCommand extends Command { - private static final SimpleCommandExceptionType NOT_SPECTATOR = new SimpleCommandExceptionType(Text.literal("Can't drop items while in spectator.")); - private static final SimpleCommandExceptionType NO_SUCH_ITEM = new SimpleCommandExceptionType(Text.literal("Could not find an item with that name!")); + private static final SimpleCommandExceptionType NOT_SPECTATOR = new SimpleCommandExceptionType(new LiteralMessage("Can't drop items while in spectator.")); + private static final SimpleCommandExceptionType NO_SUCH_ITEM = new SimpleCommandExceptionType(new LiteralMessage("Could not find an item with that name!")); public DropCommand() { super("drop", "Automatically drops specified items."); diff --git a/src/main/java/meteordevelopment/meteorclient/commands/commands/EnchantCommand.java b/src/main/java/meteordevelopment/meteorclient/commands/commands/EnchantCommand.java index 7eb9e6b584..ba812487ed 100644 --- a/src/main/java/meteordevelopment/meteorclient/commands/commands/EnchantCommand.java +++ b/src/main/java/meteordevelopment/meteorclient/commands/commands/EnchantCommand.java @@ -5,6 +5,7 @@ package meteordevelopment.meteorclient.commands.commands; +import com.mojang.brigadier.LiteralMessage; import com.mojang.brigadier.arguments.IntegerArgumentType; import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.context.CommandContext; @@ -24,8 +25,8 @@ import java.util.function.ToIntFunction; public class EnchantCommand extends Command { - private static final SimpleCommandExceptionType NOT_IN_CREATIVE = new SimpleCommandExceptionType(Text.literal("You must be in creative mode to use this.")); - private static final SimpleCommandExceptionType NOT_HOLDING_ITEM = new SimpleCommandExceptionType(Text.literal("You need to hold some item to enchant.")); + private static final SimpleCommandExceptionType NOT_IN_CREATIVE = new SimpleCommandExceptionType(new LiteralMessage("You must be in creative mode to use this.")); + private static final SimpleCommandExceptionType NOT_HOLDING_ITEM = new SimpleCommandExceptionType(new LiteralMessage("You need to hold some item to enchant.")); public EnchantCommand() { super("enchant", "Enchants the item in your hand. REQUIRES Creative mode."); diff --git a/src/main/java/meteordevelopment/meteorclient/commands/commands/EnderChestCommand.java b/src/main/java/meteordevelopment/meteorclient/commands/commands/EnderChestCommand.java index 818bf8bb27..074a5295df 100644 --- a/src/main/java/meteordevelopment/meteorclient/commands/commands/EnderChestCommand.java +++ b/src/main/java/meteordevelopment/meteorclient/commands/commands/EnderChestCommand.java @@ -5,14 +5,19 @@ package meteordevelopment.meteorclient.commands.commands; +import com.mojang.brigadier.LiteralMessage; import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; import meteordevelopment.meteorclient.commands.Command; import meteordevelopment.meteorclient.utils.Utils; +import meteordevelopment.meteorclient.utils.player.EChestMemory; import net.minecraft.command.CommandSource; import net.minecraft.item.ItemStack; import net.minecraft.item.Items; public class EnderChestCommand extends Command { + private static final SimpleCommandExceptionType NO_MEMORY_EXCEPTION = new SimpleCommandExceptionType(new LiteralMessage("No saved ender chest memory.")); + public EnderChestCommand() { super("ender-chest", "Allows you to preview memory of your ender chest.", "ec", "echest"); } @@ -20,6 +25,10 @@ public EnderChestCommand() { @Override public void build(LiteralArgumentBuilder builder) { builder.executes(context -> { + if (!EChestMemory.isKnown()) { + throw NO_MEMORY_EXCEPTION.create(); + } + Utils.openContainer(Items.ENDER_CHEST.getDefaultStack(), new ItemStack[27], true); return SINGLE_SUCCESS; }); diff --git a/src/main/java/meteordevelopment/meteorclient/commands/commands/FakePlayerCommand.java b/src/main/java/meteordevelopment/meteorclient/commands/commands/FakePlayerCommand.java index 5374e1f503..f7f08ce8a5 100644 --- a/src/main/java/meteordevelopment/meteorclient/commands/commands/FakePlayerCommand.java +++ b/src/main/java/meteordevelopment/meteorclient/commands/commands/FakePlayerCommand.java @@ -42,10 +42,6 @@ public void build(LiteralArgumentBuilder builder) { .then(argument("fp", FakePlayerArgumentType.create()) .executes(context -> { FakePlayerEntity fp = FakePlayerArgumentType.get(context); - if (fp == null || !FakePlayerManager.contains(fp)) { - error("Couldn't find a Fake Player with that name."); - return SINGLE_SUCCESS; - } FakePlayerManager.remove(fp); info("Removed Fake Player %s.".formatted(fp.getName().getString())); diff --git a/src/main/java/meteordevelopment/meteorclient/commands/commands/FovCommand.java b/src/main/java/meteordevelopment/meteorclient/commands/commands/FovCommand.java index f758d5bfc5..7716149fdd 100644 --- a/src/main/java/meteordevelopment/meteorclient/commands/commands/FovCommand.java +++ b/src/main/java/meteordevelopment/meteorclient/commands/commands/FovCommand.java @@ -18,7 +18,7 @@ public FovCommand() { @Override public void build(LiteralArgumentBuilder builder) { - builder.then(argument("fov", IntegerArgumentType.integer(0, 180)).executes(context -> { + builder.then(argument("fov", IntegerArgumentType.integer(2, 180)).executes(context -> { ((ISimpleOption) (Object) mc.options.getFov()).meteor$set(context.getArgument("fov", Integer.class)); return SINGLE_SUCCESS; })); diff --git a/src/main/java/meteordevelopment/meteorclient/commands/commands/FriendsCommand.java b/src/main/java/meteordevelopment/meteorclient/commands/commands/FriendsCommand.java index 20c2996fbc..85c92d0128 100644 --- a/src/main/java/meteordevelopment/meteorclient/commands/commands/FriendsCommand.java +++ b/src/main/java/meteordevelopment/meteorclient/commands/commands/FriendsCommand.java @@ -43,10 +43,6 @@ public void build(LiteralArgumentBuilder builder) { .then(argument("friend", FriendArgumentType.create()) .executes(context -> { Friend friend = FriendArgumentType.get(context); - if (friend == null) { - error("Not friends with that player."); - return SINGLE_SUCCESS; - } if (Friends.get().remove(friend)) { ChatUtils.sendMsg(friend.hashCode(), Formatting.GRAY, "Removed (highlight)%s (default)from friends.".formatted(friend.getName())); diff --git a/src/main/java/meteordevelopment/meteorclient/commands/commands/GiveCommand.java b/src/main/java/meteordevelopment/meteorclient/commands/commands/GiveCommand.java index 94150a58b7..38674267cb 100644 --- a/src/main/java/meteordevelopment/meteorclient/commands/commands/GiveCommand.java +++ b/src/main/java/meteordevelopment/meteorclient/commands/commands/GiveCommand.java @@ -7,20 +7,21 @@ import com.mojang.brigadier.arguments.IntegerArgumentType; import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; import meteordevelopment.meteorclient.commands.Command; +import meteordevelopment.meteorclient.commands.arguments.ItemSlotArgumentType; +import meteordevelopment.meteorclient.utils.commands.CreativeCommandHelper; import meteordevelopment.meteorclient.utils.player.FindItemResult; import meteordevelopment.meteorclient.utils.player.InvUtils; import net.minecraft.command.CommandSource; import net.minecraft.command.argument.ItemStackArgumentType; import net.minecraft.item.ItemStack; -import net.minecraft.network.packet.c2s.play.CreativeInventoryActionC2SPacket; import net.minecraft.text.Text; public class GiveCommand extends Command { - private final static SimpleCommandExceptionType NOT_IN_CREATIVE = new SimpleCommandExceptionType(Text.literal("You must be in creative mode to use this.")); - private final static SimpleCommandExceptionType NO_SPACE = new SimpleCommandExceptionType(Text.literal("No space in hotbar.")); + private final static SimpleCommandExceptionType NO_FREE_SPACE = new SimpleCommandExceptionType(Text.literal("No free space in hotbar.")); public GiveCommand() { super("give", "Gives you any item."); @@ -28,28 +29,30 @@ public GiveCommand() { @Override public void build(LiteralArgumentBuilder builder) { - builder.then(argument("item", ItemStackArgumentType.itemStack(REGISTRY_ACCESS)).executes(context -> { - if (!mc.player.getAbilities().creativeMode) throw NOT_IN_CREATIVE.create(); - - ItemStack item = ItemStackArgumentType.getItemStackArgument(context, "item").createStack(1, false); - giveItem(item); - - return SINGLE_SUCCESS; - }).then(argument("number", IntegerArgumentType.integer(1, 99)).executes(context -> { - if (!mc.player.getAbilities().creativeMode) throw NOT_IN_CREATIVE.create(); - - ItemStack item = ItemStackArgumentType.getItemStackArgument(context, "item").createStack(IntegerArgumentType.getInteger(context, "number"), true); - giveItem(item); + builder.then(argument("item", ItemStackArgumentType.itemStack(REGISTRY_ACCESS)) + .executes(context -> executeGive(context, 1, findEmptySlot())) + .then(literal("slot").then(argument("slot", ItemSlotArgumentType.modifiableSlot()).executes(context -> + executeGive(context, 1, ItemSlotArgumentType.getItemSlot(context)) + ))) + .then(argument("count", IntegerArgumentType.integer(1, 99)) + .executes(context -> executeGive(context, IntegerArgumentType.getInteger(context, "count"), findEmptySlot())) + .then(literal("slot").then(argument("slot", ItemSlotArgumentType.modifiableSlot()).executes(context -> + executeGive(context, IntegerArgumentType.getInteger(context, "count"), ItemSlotArgumentType.getItemSlot(context)) + ))) + ) + ); + } - return SINGLE_SUCCESS; - }))); + private int findEmptySlot() throws CommandSyntaxException { + FindItemResult fir = InvUtils.findInHotbar(ItemStack::isEmpty); + if (!fir.found()) throw NO_FREE_SPACE.create(); + return fir.slot(); } - private void giveItem(ItemStack item) throws CommandSyntaxException { - FindItemResult fir = InvUtils.find(ItemStack::isEmpty, 0, 8); - if (!fir.found()) throw NO_SPACE.create(); + private int executeGive(CommandContext context, int count, int slot) throws CommandSyntaxException { + ItemStack stack = ItemStackArgumentType.getItemStackArgument(context, "item").createStack(count, false); + CreativeCommandHelper.setStack(stack, slot); - mc.getNetworkHandler().sendPacket(new CreativeInventoryActionC2SPacket(36 + fir.slot(), item)); - mc.player.playerScreenHandler.getSlot(36 + fir.slot()).setStack(item); + return SINGLE_SUCCESS; } } diff --git a/src/main/java/meteordevelopment/meteorclient/commands/commands/InputCommand.java b/src/main/java/meteordevelopment/meteorclient/commands/commands/InputCommand.java index 8852feccce..620c6093ac 100644 --- a/src/main/java/meteordevelopment/meteorclient/commands/commands/InputCommand.java +++ b/src/main/java/meteordevelopment/meteorclient/commands/commands/InputCommand.java @@ -5,8 +5,10 @@ package meteordevelopment.meteorclient.commands.commands; +import com.mojang.brigadier.LiteralMessage; import com.mojang.brigadier.arguments.IntegerArgumentType; import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; import com.mojang.datafixers.util.Pair; import meteordevelopment.meteorclient.MeteorClient; import meteordevelopment.meteorclient.commands.Command; @@ -21,6 +23,8 @@ import java.util.List; public class InputCommand extends Command { + private static final SimpleCommandExceptionType OUT_OF_RANGE_EXCEPTION = new SimpleCommandExceptionType(new LiteralMessage("Index out of range.")); + private static final SimpleCommandExceptionType NO_ACTIVE_HANDLERS_EXCEPTION = new SimpleCommandExceptionType(new LiteralMessage("No active keypres handlers.")); private static final List activeHandlers = new ArrayList<>(); private static final List> holdKeys = List.of( @@ -76,7 +80,7 @@ public void build(LiteralArgumentBuilder builder) { } builder.then(literal("clear").executes(ctx -> { - if (activeHandlers.isEmpty()) warning("No active keypress handlers."); + if (activeHandlers.isEmpty()) throw NO_ACTIVE_HANDLERS_EXCEPTION.create(); else { info("Cleared all keypress handlers."); activeHandlers.forEach(MeteorClient.EVENT_BUS::unsubscribe); @@ -99,7 +103,7 @@ public void build(LiteralArgumentBuilder builder) { builder.then(literal("remove").then(argument("index", IntegerArgumentType.integer(0)).executes(ctx -> { int index = IntegerArgumentType.getInteger(ctx, "index"); - if (index >= activeHandlers.size()) warning("Index out of range."); + if (index >= activeHandlers.size()) throw OUT_OF_RANGE_EXCEPTION.create(); else { info("Removed keypress handler."); MeteorClient.EVENT_BUS.unsubscribe(activeHandlers.get(index)); diff --git a/src/main/java/meteordevelopment/meteorclient/commands/commands/MacroCommand.java b/src/main/java/meteordevelopment/meteorclient/commands/commands/MacroCommand.java index 564656c747..eb7a8e65c6 100644 --- a/src/main/java/meteordevelopment/meteorclient/commands/commands/MacroCommand.java +++ b/src/main/java/meteordevelopment/meteorclient/commands/commands/MacroCommand.java @@ -5,23 +5,168 @@ package meteordevelopment.meteorclient.commands.commands; +import com.mojang.brigadier.LiteralMessage; +import com.mojang.brigadier.arguments.IntegerArgumentType; +import com.mojang.brigadier.arguments.StringArgumentType; import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; +import meteordevelopment.meteorclient.MeteorClient; import meteordevelopment.meteorclient.commands.Command; import meteordevelopment.meteorclient.commands.arguments.MacroArgumentType; +import meteordevelopment.meteorclient.events.meteor.KeyEvent; +import meteordevelopment.meteorclient.events.meteor.MouseButtonEvent; +import meteordevelopment.meteorclient.systems.config.Config; import meteordevelopment.meteorclient.systems.macros.Macro; +import meteordevelopment.meteorclient.systems.macros.Macros; +import meteordevelopment.meteorclient.utils.misc.Keybind; +import meteordevelopment.meteorclient.utils.misc.input.KeyAction; +import meteordevelopment.meteorclient.utils.player.ChatUtils; +import meteordevelopment.orbit.EventHandler; +import meteordevelopment.orbit.EventPriority; import net.minecraft.command.CommandSource; +import net.minecraft.screen.ScreenTexts; +import net.minecraft.text.HoverEvent; +import net.minecraft.text.MutableText; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import org.lwjgl.glfw.GLFW; public class MacroCommand extends Command { + private static final SimpleCommandExceptionType ADD_EMPTY_EXCEPTION = new SimpleCommandExceptionType(new LiteralMessage("Cannot add empty line.")); + private Macro macroToBind; + private boolean awaitRelease; + public MacroCommand() { super("macro", "Allows you to execute macros."); } @Override public void build(LiteralArgumentBuilder builder) { - builder.then(argument("macro", MacroArgumentType.create()).executes(context -> { - Macro macro = MacroArgumentType.get(context); - macro.onAction(); + builder.then(literal("list").executes(context -> { + info("--- Macros ((highlight)%s(default)) ---", Macros.get().getAll().size()); + + for (Macro macro : Macros.get()) { + HoverEvent hoverEvent = new HoverEvent(HoverEvent.Action.SHOW_TEXT, getTooltip(macro)); + + MutableText text = Text.literal(macro.name.get()).formatted(Formatting.WHITE); + text.setStyle(text.getStyle().withHoverEvent(hoverEvent)); + + MutableText sep = Text.literal(" - "); + sep.setStyle(sep.getStyle().withHoverEvent(hoverEvent)); + text.append(sep.formatted(Formatting.GRAY)); + + MutableText key = Text.literal(macro.keybind.toString()); + key.setStyle(key.getStyle().withHoverEvent(hoverEvent)); + text.append(key.formatted(Formatting.GRAY)); + + ChatUtils.sendMsg(text); + } + return SINGLE_SUCCESS; })); + + builder.then(argument("macro", MacroArgumentType.create()) + .executes(context -> { + Macro macro = MacroArgumentType.get(context); + macro.onAction(); + return SINGLE_SUCCESS; + }) + + .then(literal("bind").executes(context -> { + info("Press a key to bind the macro to."); + Macro macro = MacroArgumentType.get(context); + macroToBind = macro; + awaitRelease = true; + MeteorClient.EVENT_BUS.subscribe(this); + return SINGLE_SUCCESS; + })) + + .then(literal("edit").then(argument("line", IntegerArgumentType.integer(0)) + .then(argument("content", StringArgumentType.greedyString()).executes(context -> { + Macro macro = MacroArgumentType.get(context); + int line = IntegerArgumentType.getInteger(context, "line"); + String content = StringArgumentType.getString(context, "content"); + boolean shouldAppend = line >= macro.messages.get().size(); + boolean shouldRemove = content.isEmpty(); + + if (shouldAppend && shouldRemove) throw ADD_EMPTY_EXCEPTION.create(); + + if (shouldAppend) { + macro.messages.get().add(content); + info("Added message to macro."); + } else if (shouldRemove) { + macro.messages.get().remove(line); + info("Removed line from macro messages."); + } else { + macro.messages.get().set(line, content); + info("Changed line from macro messages."); + } + + return SINGLE_SUCCESS; + }))) + ) + + .then(literal("delete").executes(context -> { + Macro macro = MacroArgumentType.get(context); + Macros.get().remove(macro); + info("Successfully removed macro."); + return SINGLE_SUCCESS; + })) + ); + + builder.then(literal("create").then(argument("name", StringArgumentType.string()).executes(context -> { + String name = StringArgumentType.getString(context, "name"); + Macro macro = new Macro(); + macro.name.set(name); + Macros.get().add(macro); + info("Macro created."); + info("Run '%smacro %s bind' to set a keybind.", Config.get().prefix.get(), name); + info("Run '%smacro %s edit {line} {content}' to edit the macro's content.", Config.get().prefix.get(), name); + return SINGLE_SUCCESS; + }))); + } + + private MutableText getTooltip(Macro macro) { + MutableText tooltip = Text.literal(macro.name.get()).formatted(Formatting.BLUE, Formatting.GOLD).append(ScreenTexts.LINE_BREAK); + for (String line : macro.messages.get()) { + tooltip.append(ScreenTexts.LINE_BREAK).append(Text.literal(line).formatted(Formatting.WHITE)); + } + return tooltip; + } + + @EventHandler(priority = EventPriority.HIGHEST) + private void onKeyBinding(KeyEvent event) { + if (event.action == KeyAction.Release && onBinding(true, event.key, event.modifiers)) event.cancel(); + } + + @EventHandler(priority = EventPriority.HIGHEST) + private void onButtonBinding(MouseButtonEvent event) { + if (event.action == KeyAction.Release && onBinding(false, event.button, 0)) event.cancel(); + } + + private boolean onBinding(boolean isKey, int value, int modifiers) { + if (macroToBind == null) return false; + + if (awaitRelease) { + if (!isKey || (value != GLFW.GLFW_KEY_ENTER && value != GLFW.GLFW_KEY_KP_ENTER)) return false; + + awaitRelease = false; + return false; + } + + if (macroToBind.keybind.get().canBindTo(isKey, value, modifiers)) { + macroToBind.keybind.get().set(isKey, value, modifiers); + info("Bound to (highlight)%s(default).", macroToBind.keybind.get()); + } + else if (value == GLFW.GLFW_KEY_ESCAPE) { + macroToBind.keybind.set(Keybind.none()); + info("Removed bind."); + } + else return false; + + macroToBind = null; + MeteorClient.EVENT_BUS.unsubscribe(this); + + return true; } } diff --git a/src/main/java/meteordevelopment/meteorclient/commands/commands/ModulesCommand.java b/src/main/java/meteordevelopment/meteorclient/commands/commands/ModulesCommand.java index 3be1156fd1..b5360d3a98 100644 --- a/src/main/java/meteordevelopment/meteorclient/commands/commands/ModulesCommand.java +++ b/src/main/java/meteordevelopment/meteorclient/commands/commands/ModulesCommand.java @@ -27,7 +27,7 @@ public void build(LiteralArgumentBuilder builder) { ChatUtils.info("--- Modules ((highlight)%d(default)) ---", Modules.get().getCount()); Modules.loopCategories().forEach(category -> { - MutableText categoryMessage = Text.literal(""); + MutableText categoryMessage = Text.empty(); Modules.get().getGroup(category).forEach(module -> categoryMessage.append(getModuleText(module))); ChatUtils.sendMsg(category.name, categoryMessage); }); diff --git a/src/main/java/meteordevelopment/meteorclient/commands/commands/MultitoolCommand.java b/src/main/java/meteordevelopment/meteorclient/commands/commands/MultitoolCommand.java new file mode 100644 index 0000000000..2dc4ffbdc2 --- /dev/null +++ b/src/main/java/meteordevelopment/meteorclient/commands/commands/MultitoolCommand.java @@ -0,0 +1,60 @@ +/* + * This file is part of the Meteor Client distribution (https://github.com/MeteorDevelopment/meteor-client). + * Copyright (c) Meteor Development. + */ + +package meteordevelopment.meteorclient.commands.commands; + +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import meteordevelopment.meteorclient.commands.Command; +import meteordevelopment.meteorclient.utils.commands.CreativeCommandHelper; +import net.minecraft.block.Blocks; +import net.minecraft.command.CommandSource; +import net.minecraft.component.DataComponentTypes; +import net.minecraft.component.type.ToolComponent; +import net.minecraft.item.BlockPredicatesChecker; +import net.minecraft.item.ItemStack; +import net.minecraft.predicate.BlockPredicate; +import net.minecraft.registry.Registries; +import net.minecraft.registry.entry.RegistryEntryList; +import net.minecraft.registry.tag.BlockTags; +import net.minecraft.text.Text; +import net.minecraft.util.Identifier; + +import java.util.List; + +public class MultitoolCommand extends Command { + public MultitoolCommand() { + super("multitool", "Makes your held item able to mine (almost) anything."); + } + + @Override + public void build(LiteralArgumentBuilder builder) { + builder.executes(context -> { + ItemStack stack = mc.player.getMainHandStack(); + CreativeCommandHelper.assertValid(stack); + + stack.set(DataComponentTypes.ITEM_MODEL, Identifier.of("meteor-client", "multitool")); + stack.set(DataComponentTypes.ITEM_NAME, Text.literal("Multi Tool")); + stack.set(DataComponentTypes.TOOL, new ToolComponent(List.of( + ToolComponent.Rule.ofAlwaysDropping(Registries.BLOCK.getOrThrow(BlockTags.AXE_MINEABLE), 8f), + ToolComponent.Rule.ofAlwaysDropping(Registries.BLOCK.getOrThrow(BlockTags.HOE_MINEABLE), 8f), + ToolComponent.Rule.ofAlwaysDropping(Registries.BLOCK.getOrThrow(BlockTags.PICKAXE_MINEABLE), 8f), + ToolComponent.Rule.ofAlwaysDropping(Registries.BLOCK.getOrThrow(BlockTags.SHOVEL_MINEABLE), 8f), + ToolComponent.Rule.ofAlwaysDropping(Registries.BLOCK.getOrThrow(BlockTags.SWORD_EFFICIENT), 8f), + ToolComponent.Rule.ofAlwaysDropping(RegistryEntryList.of(Registries.BLOCK.getEntry(Blocks.COBWEB)), 16f) + ), 10f, 1)); + stack.set(DataComponentTypes.CAN_BREAK, new BlockPredicatesChecker(List.of( + BlockPredicate.Builder.create().tag(Registries.BLOCK, BlockTags.AXE_MINEABLE).build(), + BlockPredicate.Builder.create().tag(Registries.BLOCK, BlockTags.HOE_MINEABLE).build(), + BlockPredicate.Builder.create().tag(Registries.BLOCK, BlockTags.PICKAXE_MINEABLE).build(), + BlockPredicate.Builder.create().tag(Registries.BLOCK, BlockTags.SHOVEL_MINEABLE).build(), + BlockPredicate.Builder.create().tag(Registries.BLOCK, BlockTags.SWORD_EFFICIENT).build() + ), false)); + + CreativeCommandHelper.setStack(stack); + + return SINGLE_SUCCESS; + }); + } +} diff --git a/src/main/java/meteordevelopment/meteorclient/commands/commands/NbtCommand.java b/src/main/java/meteordevelopment/meteorclient/commands/commands/NbtCommand.java index 4e4cbf9e64..05af8b59e2 100644 --- a/src/main/java/meteordevelopment/meteorclient/commands/commands/NbtCommand.java +++ b/src/main/java/meteordevelopment/meteorclient/commands/commands/NbtCommand.java @@ -5,218 +5,198 @@ package meteordevelopment.meteorclient.commands.commands; -import com.mojang.brigadier.arguments.IntegerArgumentType; +import com.mojang.brigadier.builder.ArgumentBuilder; import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.exceptions.CommandSyntaxException; -import com.mojang.brigadier.exceptions.DynamicCommandExceptionType; -import com.mojang.serialization.DataResult; +import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; import meteordevelopment.meteorclient.commands.Command; -import meteordevelopment.meteorclient.commands.arguments.ComponentMapArgumentType; -import meteordevelopment.meteorclient.utils.misc.text.MeteorClickEvent; +import meteordevelopment.meteorclient.commands.arguments.BlockPosArgumentType; +import meteordevelopment.meteorclient.commands.arguments.EntityArgumentType; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.command.BlockDataObject; import net.minecraft.command.CommandSource; import net.minecraft.command.DataCommandObject; import net.minecraft.command.EntityDataObject; import net.minecraft.command.argument.NbtPathArgumentType; -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.registry.Registries; -import net.minecraft.registry.RegistryKey; -import net.minecraft.registry.RegistryKeys; import net.minecraft.text.*; import net.minecraft.util.Formatting; -import net.minecraft.util.Unit; +import net.minecraft.util.math.BlockPos; +import java.util.Collection; +import java.util.Iterator; import java.util.List; -import java.util.Locale; -import java.util.Set; +import java.util.function.Function; 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 + private static final SimpleCommandExceptionType GET_MULTIPLE_EXCEPTION = new SimpleCommandExceptionType(Text.translatable("commands.data.get.multiple")); + + private static 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.") ))); + private final List OBJECT_TYPES = List.of( + BLOCK_DATA_OBJECT_TYPE, + ENTITY_DATA_OBJECT_TYPE + ); + public NbtCommand() { - super("nbt", "Modifies NBT data for an item, example: .nbt add {display:{Name:'{\"text\":\"$cRed Name\"}'}}"); + super("nbt", "View NBT data for entities and block entities."); + } + + public static MutableText createCopyButton(String toCopy) { + return Text.empty().append(copyButton).setStyle(Style.EMPTY + .withClickEvent(new ClickEvent( + ClickEvent.Action.COPY_TO_CLIPBOARD, + toCopy + ))); } @Override public void build(LiteralArgumentBuilder builder) { - builder.then(literal("add").then(argument("component", ComponentMapArgumentType.componentMap(REGISTRY_ACCESS)).executes(ctx -> { - ItemStack stack = mc.player.getInventory().getMainHandStack(); - - if (validBasic(stack)) { - ComponentMap itemComponents = stack.getComponents(); - ComponentMap newComponents = ComponentMapArgumentType.getComponentMap(ctx, "component"); - - ComponentMap testComponents = ComponentMap.of(itemComponents, newComponents); - DataResult dataResult = ItemStack.validateComponents(testComponents); - dataResult.getOrThrow(MALFORMED_ITEM_EXCEPTION::create); - - stack.applyComponentsFrom(testComponents); - - setStack(stack); - } - - return SINGLE_SUCCESS; - }))); - - builder.then(literal("set").then(argument("component", ComponentMapArgumentType.componentMap(REGISTRY_ACCESS)).executes(ctx -> { - ItemStack stack = mc.player.getInventory().getMainHandStack(); - - if (validBasic(stack)) { - ComponentMap components = ComponentMapArgumentType.getComponentMap(ctx, "component"); - MergedComponentMap stackComponents = (MergedComponentMap) 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 (ComponentType type : types) { - changesBuilder.remove(type); - } - - stackComponents.applyChanges(changesBuilder.build()); - - setStack(stack); - } - - return SINGLE_SUCCESS; - }))); + for (DataObjectType objectType : OBJECT_TYPES) { + builder.then(objectType.addArgumentsToBuilder( + literal("get"), + innerBuilder -> innerBuilder.executes(context -> executeGet(objectType.getObject(context))) + .then(argument("path", NbtPathArgumentType.nbtPath()).executes(context -> executeGet( + objectType.getObject(context), + context.getArgument("path", NbtPathArgumentType.NbtPath.class) + ))) + )); + + builder.then(objectType.addArgumentsToBuilder( + literal("copy"), + innerBuilder -> innerBuilder.executes(context -> executeCopy(objectType.getObject(context))) + .then(argument("path", NbtPathArgumentType.nbtPath()).executes(context -> executeCopy( + objectType.getObject(context), + context.getArgument("path", NbtPathArgumentType.NbtPath.class) + ))) + )); + } - builder.then(literal("remove").then(argument("component", RegistryKeyArgumentType.registryKey(RegistryKeys.DATA_COMPONENT_TYPE)).executes(ctx -> { - ItemStack stack = mc.player.getInventory().getMainHandStack(); + builder.then(literal("get") + .executes(context -> executeGet(new EntityDataObject(mc.player))) + .then(argument("path", NbtPathArgumentType.nbtPath()).executes(context -> executeGet( + new EntityDataObject(mc.player), + context.getArgument("path", NbtPathArgumentType.NbtPath.class) + ))) + ); + builder.then(literal("copy") + .executes(context -> executeCopy(new EntityDataObject(mc.player))) + .then(argument("path", NbtPathArgumentType.nbtPath()).executes(context -> executeCopy( + new EntityDataObject(mc.player), + context.getArgument("path", NbtPathArgumentType.NbtPath.class) + ))) + ); + } - if (validBasic(stack)) { - @SuppressWarnings("unchecked") - RegistryKey> componentTypeKey = (RegistryKey>) ctx.getArgument("component", RegistryKey.class); + private static NbtElement get(DataCommandObject object, NbtPathArgumentType.NbtPath path) throws CommandSyntaxException { + Collection collection = path.get(object.getNbt()); + Iterator iterator = collection.iterator(); + NbtElement element = iterator.next(); + if (iterator.hasNext()) { + throw GET_MULTIPLE_EXCEPTION.create(); + } + return element; + } - ComponentType componentType = Registries.DATA_COMPONENT_TYPE.get(componentTypeKey); + private int executeGet(DataCommandObject object, NbtPathArgumentType.NbtPath path) throws CommandSyntaxException { + NbtElement element = get(object, path); - MergedComponentMap components = (MergedComponentMap) stack.getComponents(); - components.applyChanges(ComponentChanges.builder().remove(componentType).build()); + Text text = Text.empty() + .append(createCopyButton(element.toString())) + .append(NbtHelper.toPrettyPrintedText(element)); - setStack(stack); - } + info(text); - return SINGLE_SUCCESS; - }).suggests((ctx, suggestionsBuilder) -> { - ItemStack stack = mc.player.getInventory().getMainHandStack(); - if (stack != ItemStack.EMPTY) { - ComponentMap components = stack.getComponents(); - 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 -> { - ComponentType dataComponentType = entry.value(); - if (dataComponentType.getCodec() != null) { - if (entry.getKey().isPresent()) { - suggestionsBuilder.suggest(entry.getKey().get().getValue().toString()); - } - } - }); - } + return SINGLE_SUCCESS; + } - return suggestionsBuilder.buildFuture(); - }))); + private int executeGet(DataCommandObject object) throws CommandSyntaxException { + NbtCompound compound = object.getNbt(); - builder.then(literal("get").executes(context -> { - DataCommandObject dataCommandObject = new EntityDataObject(mc.player); - NbtPathArgumentType.NbtPath handPath = NbtPathArgumentType.NbtPath.parse("SelectedItem"); + Text text = Text.empty() + .append(createCopyButton(compound.toString())) + .append(NbtHelper.toPrettyPrintedText(compound)); - MutableText text = Text.empty().append(copyButton); + 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; + } - return SINGLE_SUCCESS; - })); + private int executeCopy(DataCommandObject object, NbtPathArgumentType.NbtPath path) throws CommandSyntaxException { + NbtElement element = get(object, path); + String elementAsString = element.toString(); - builder.then(literal("copy").executes(context -> { - DataCommandObject dataCommandObject = new EntityDataObject(mc.player); - NbtPathArgumentType.NbtPath handPath = NbtPathArgumentType.NbtPath.parse("SelectedItem"); + Text text = Text.empty() + .append(createCopyButton(elementAsString)) + .append(NbtHelper.toPrettyPrintedText(element)); - MutableText text = Text.empty().append(copyButton); - String nbt = "{}"; + info(text); + mc.keyboard.setClipboard(elementAsString); - 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("{}"); - } + return SINGLE_SUCCESS; + } - mc.keyboard.setClipboard(nbt); + private int executeCopy(DataCommandObject object) throws CommandSyntaxException { + NbtCompound compound = object.getNbt(); + String compoundAsString = compound.toString(); - text.append(" data copied!"); - info(text); + Text text = Text.empty() + .append(createCopyButton(compoundAsString)) + .append(NbtHelper.toPrettyPrintedText(compound)); - return SINGLE_SUCCESS; - })); + info(text); + mc.keyboard.setClipboard(compoundAsString); - builder.then(literal("count").then(argument("count", IntegerArgumentType.integer(-127, 127)).executes(context -> { - ItemStack stack = mc.player.getInventory().getMainHandStack(); + return SINGLE_SUCCESS; + } - if (validBasic(stack)) { - int count = IntegerArgumentType.getInteger(context, "count"); - stack.setCount(count); - setStack(stack); - info("Set mainhand stack count to %s.", count); + public static final DataObjectType BLOCK_DATA_OBJECT_TYPE = new DataObjectType() { + private static final SimpleCommandExceptionType INVALID_BLOCK_EXCEPTION = new SimpleCommandExceptionType(Text.translatable("commands.data.block.invalid")); + + @Override + public DataCommandObject getObject(CommandContext context) throws CommandSyntaxException { + BlockPos blockPos = BlockPosArgumentType.getLoadedBlockPos(context, "sourcePos"); + BlockEntity blockEntity = mc.world.getBlockEntity(blockPos); + if (blockEntity == null) { + throw INVALID_BLOCK_EXCEPTION.create(); + } else { + return new BlockDataObject(blockEntity, blockPos); } + } - return SINGLE_SUCCESS; - }))); - } - - private void setStack(ItemStack stack) { - mc.player.networkHandler.sendPacket(new CreativeInventoryActionC2SPacket(36 + mc.player.getInventory().selectedSlot, stack)); - } + @Override + public ArgumentBuilder addArgumentsToBuilder(ArgumentBuilder argument, Function, ArgumentBuilder> argumentAdder) { + return argument.then(literal("block").then( + argumentAdder.apply(argument("sourcePos", BlockPosArgumentType.blockPos())) + )); + } + }; - private boolean validBasic(ItemStack stack) { - if (!mc.player.getAbilities().creativeMode) { - error("Creative mode only."); - return false; + public static final DataObjectType ENTITY_DATA_OBJECT_TYPE = new DataObjectType() { + @Override + public DataCommandObject getObject(CommandContext context) throws CommandSyntaxException { + return new EntityDataObject(EntityArgumentType.getEntity(context, "source")); } - if (stack == ItemStack.EMPTY) { - error("You must hold an item in your main hand."); - return false; + @Override + public ArgumentBuilder addArgumentsToBuilder(ArgumentBuilder argument, Function, ArgumentBuilder> argumentAdder) { + return argument.then(literal("entity").then( + argumentAdder.apply(argument("source", EntityArgumentType.entity())) + )); } - return true; + }; + + public interface DataObjectType { + DataCommandObject getObject(CommandContext context) throws CommandSyntaxException; + + ArgumentBuilder addArgumentsToBuilder(ArgumentBuilder argument, Function, ArgumentBuilder> argumentAdder); } } diff --git a/src/main/java/meteordevelopment/meteorclient/commands/commands/NotebotCommand.java b/src/main/java/meteordevelopment/meteorclient/commands/commands/NotebotCommand.java index ee791ce2fc..88edff30d0 100644 --- a/src/main/java/meteordevelopment/meteorclient/commands/commands/NotebotCommand.java +++ b/src/main/java/meteordevelopment/meteorclient/commands/commands/NotebotCommand.java @@ -5,10 +5,10 @@ package meteordevelopment.meteorclient.commands.commands; +import com.mojang.brigadier.LiteralMessage; import com.mojang.brigadier.arguments.StringArgumentType; import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.exceptions.DynamicCommandExceptionType; -import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; import meteordevelopment.meteorclient.MeteorClient; import meteordevelopment.meteorclient.commands.Command; import meteordevelopment.meteorclient.commands.arguments.NotebotSongArgumentType; @@ -22,7 +22,6 @@ import net.minecraft.command.CommandSource; import net.minecraft.network.packet.s2c.play.PlaySoundS2CPacket; import net.minecraft.sound.SoundEvent; -import net.minecraft.text.Text; import net.minecraft.util.Util; import java.io.FileWriter; @@ -34,8 +33,7 @@ import java.util.Map; public class NotebotCommand extends Command { - private static final SimpleCommandExceptionType INVALID_SONG = new SimpleCommandExceptionType(Text.literal("Invalid song.")); - private static final DynamicCommandExceptionType INVALID_PATH = new DynamicCommandExceptionType(object -> Text.literal("'%s' is not a valid path.".formatted(object))); + private static final DynamicCommandExceptionType INVALID_PATH = new DynamicCommandExceptionType(object -> new LiteralMessage("'%s' is not a valid path.".formatted(object))); int ticks = -1; private final Map> song = new HashMap<>(); // tick -> notes @@ -85,10 +83,8 @@ public void build(LiteralArgumentBuilder builder) { literal("play").then( argument("song", NotebotSongArgumentType.create()).executes(ctx -> { Notebot notebot = Modules.get().get(Notebot.class); - Path songPath = ctx.getArgument("song", Path.class); - if (songPath == null || !songPath.toFile().exists()) { - throw INVALID_SONG.create(); - } + Path songPath = NotebotSongArgumentType.get(ctx); + notebot.loadSong(songPath.toFile()); return SINGLE_SUCCESS; }) @@ -99,10 +95,8 @@ public void build(LiteralArgumentBuilder builder) { literal("preview").then( argument("song", NotebotSongArgumentType.create()).executes(ctx -> { Notebot notebot = Modules.get().get(Notebot.class); - Path songPath = ctx.getArgument("song", Path.class); - if (songPath == null || !songPath.toFile().exists()) { - throw INVALID_SONG.create(); - } + Path songPath = NotebotSongArgumentType.get(ctx); + notebot.previewSong(songPath.toFile()); return SINGLE_SUCCESS; }))); diff --git a/src/main/java/meteordevelopment/meteorclient/commands/commands/PeekCommand.java b/src/main/java/meteordevelopment/meteorclient/commands/commands/PeekCommand.java index 52e9761622..dd81e728b8 100644 --- a/src/main/java/meteordevelopment/meteorclient/commands/commands/PeekCommand.java +++ b/src/main/java/meteordevelopment/meteorclient/commands/commands/PeekCommand.java @@ -5,6 +5,7 @@ package meteordevelopment.meteorclient.commands.commands; +import com.mojang.brigadier.LiteralMessage; import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; import meteordevelopment.meteorclient.commands.Command; @@ -12,11 +13,10 @@ import net.minecraft.command.CommandSource; import net.minecraft.entity.decoration.ItemFrameEntity; import net.minecraft.item.ItemStack; -import net.minecraft.text.Text; public class PeekCommand extends Command { private static final ItemStack[] ITEMS = new ItemStack[27]; - private static final SimpleCommandExceptionType CANT_PEEK = new SimpleCommandExceptionType(Text.literal("You must be holding a storage block or looking at an item frame.")); + private static final SimpleCommandExceptionType CANT_PEEK = new SimpleCommandExceptionType(new LiteralMessage("You must be holding a storage block or looking at an item frame.")); public PeekCommand() { super("peek", "Lets you see what's inside storage block items."); diff --git a/src/main/java/meteordevelopment/meteorclient/commands/commands/ProfilesCommand.java b/src/main/java/meteordevelopment/meteorclient/commands/commands/ProfilesCommand.java index 43c816f1c1..405c715cc5 100644 --- a/src/main/java/meteordevelopment/meteorclient/commands/commands/ProfilesCommand.java +++ b/src/main/java/meteordevelopment/meteorclient/commands/commands/ProfilesCommand.java @@ -23,10 +23,8 @@ public void build(LiteralArgumentBuilder builder) { builder.then(literal("load").then(argument("profile", ProfileArgumentType.create()).executes(context -> { Profile profile = ProfileArgumentType.get(context); - if (profile != null) { - profile.load(); - info("Loaded profile (highlight)%s(default).", profile.name.get()); - } + profile.load(); + info("Loaded profile (highlight)%s(default).", profile.name.get()); return SINGLE_SUCCESS; }))); @@ -34,10 +32,8 @@ public void build(LiteralArgumentBuilder builder) { builder.then(literal("save").then(argument("profile", ProfileArgumentType.create()).executes(context -> { Profile profile = ProfileArgumentType.get(context); - if (profile != null) { - profile.save(); - info("Saved profile (highlight)%s(default).", profile.name.get()); - } + profile.save(); + info("Saved profile (highlight)%s(default).", profile.name.get()); return SINGLE_SUCCESS; }))); @@ -45,10 +41,8 @@ public void build(LiteralArgumentBuilder builder) { builder.then(literal("delete").then(argument("profile", ProfileArgumentType.create()).executes(context -> { Profile profile = ProfileArgumentType.get(context); - if (profile != null) { - Profiles.get().remove(profile); - info("Deleted profile (highlight)%s(default).", profile.name.get()); - } + Profiles.get().remove(profile); + info("Deleted profile (highlight)%s(default).", profile.name.get()); return SINGLE_SUCCESS; }))); diff --git a/src/main/java/meteordevelopment/meteorclient/commands/commands/RotationCommand.java b/src/main/java/meteordevelopment/meteorclient/commands/commands/RotationCommand.java index d9b3be2622..37ac87e3d6 100644 --- a/src/main/java/meteordevelopment/meteorclient/commands/commands/RotationCommand.java +++ b/src/main/java/meteordevelopment/meteorclient/commands/commands/RotationCommand.java @@ -24,8 +24,9 @@ public void build(LiteralArgumentBuilder builder) { .then(literal("set") .then(argument("direction", DirectionArgumentType.create()) .executes(context -> { - mc.player.setPitch(context.getArgument("direction", Direction.class).getVector().getY() * -90); - mc.player.setYaw(context.getArgument("direction", Direction.class).getPositiveHorizontalDegrees()); + Direction direction = DirectionArgumentType.getDirection(context); + mc.player.setPitch(direction.getVector().getY() * -90); + mc.player.setYaw(direction.getPositiveHorizontalDegrees()); return SINGLE_SUCCESS; })) diff --git a/src/main/java/meteordevelopment/meteorclient/commands/commands/SaveMapCommand.java b/src/main/java/meteordevelopment/meteorclient/commands/commands/SaveMapCommand.java index 8d5ba1fed4..03afa13819 100644 --- a/src/main/java/meteordevelopment/meteorclient/commands/commands/SaveMapCommand.java +++ b/src/main/java/meteordevelopment/meteorclient/commands/commands/SaveMapCommand.java @@ -5,6 +5,7 @@ package meteordevelopment.meteorclient.commands.commands; +import com.mojang.brigadier.LiteralMessage; import com.mojang.brigadier.arguments.IntegerArgumentType; import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.exceptions.CommandSyntaxException; @@ -20,7 +21,6 @@ import net.minecraft.item.ItemStack; import net.minecraft.item.Items; import net.minecraft.item.map.MapState; -import net.minecraft.text.Text; import org.jetbrains.annotations.Nullable; import org.lwjgl.BufferUtils; import org.lwjgl.PointerBuffer; @@ -34,8 +34,8 @@ import java.nio.ByteBuffer; public class SaveMapCommand extends Command { - private static final SimpleCommandExceptionType MAP_NOT_FOUND = new SimpleCommandExceptionType(Text.literal("You must be holding a filled map.")); - private static final SimpleCommandExceptionType OOPS = new SimpleCommandExceptionType(Text.literal("Something went wrong.")); + private static final SimpleCommandExceptionType MAP_NOT_FOUND = new SimpleCommandExceptionType(new LiteralMessage("You must be holding a filled map.")); + private static final SimpleCommandExceptionType OOPS = new SimpleCommandExceptionType(new LiteralMessage("Something went wrong.")); private final PointerBuffer filters; diff --git a/src/main/java/meteordevelopment/meteorclient/commands/commands/SettingCommand.java b/src/main/java/meteordevelopment/meteorclient/commands/commands/SettingCommand.java index ec95761519..837f529c82 100644 --- a/src/main/java/meteordevelopment/meteorclient/commands/commands/SettingCommand.java +++ b/src/main/java/meteordevelopment/meteorclient/commands/commands/SettingCommand.java @@ -55,7 +55,7 @@ public void build(LiteralArgumentBuilder builder) { builder.then( argument("module", ModuleArgumentType.create()) .then( - argument("setting", SettingArgumentType.create()) + argument("setting", SettingArgumentType.createModule(ModuleArgumentType::get)) .executes(context -> { // Get setting value Setting setting = SettingArgumentType.get(context); @@ -65,7 +65,7 @@ public void build(LiteralArgumentBuilder builder) { return SINGLE_SUCCESS; }) .then( - argument("value", SettingValueArgumentType.create()) + argument("value", SettingValueArgumentType.create(SettingArgumentType::get)) .executes(context -> { // Set setting value Setting setting = SettingArgumentType.get(context); diff --git a/src/main/java/meteordevelopment/meteorclient/commands/commands/SpectateCommand.java b/src/main/java/meteordevelopment/meteorclient/commands/commands/SpectateCommand.java index a3e21115a0..8d41a5ddf6 100644 --- a/src/main/java/meteordevelopment/meteorclient/commands/commands/SpectateCommand.java +++ b/src/main/java/meteordevelopment/meteorclient/commands/commands/SpectateCommand.java @@ -16,8 +16,6 @@ public class SpectateCommand extends Command { - private final StaticListener shiftListener = new StaticListener(); - public SpectateCommand() { super("spectate", "Allows you to spectate nearby players"); } @@ -32,19 +30,16 @@ public void build(LiteralArgumentBuilder builder) { builder.then(argument("player", PlayerArgumentType.create()).executes(context -> { mc.setCameraEntity(PlayerArgumentType.get(context)); mc.player.sendMessage(Text.literal("Sneak to un-spectate."), true); - MeteorClient.EVENT_BUS.subscribe(shiftListener); + MeteorClient.EVENT_BUS.subscribe(this); return SINGLE_SUCCESS; })); } - - private static class StaticListener { - @EventHandler - private void onKey(KeyEvent event) { - if (mc.options.sneakKey.matchesKey(event.key, 0) || mc.options.sneakKey.matchesMouse(event.key)) { - mc.setCameraEntity(mc.player); - event.cancel(); - MeteorClient.EVENT_BUS.unsubscribe(this); - } + @EventHandler + private void onKey(KeyEvent event) { + if (mc.options.sneakKey.matchesKey(event.key, 0) || mc.options.sneakKey.matchesMouse(event.key)) { + mc.setCameraEntity(mc.player); + event.cancel(); + MeteorClient.EVENT_BUS.unsubscribe(this); } } } diff --git a/src/main/java/meteordevelopment/meteorclient/commands/commands/SwarmCommand.java b/src/main/java/meteordevelopment/meteorclient/commands/commands/SwarmCommand.java index 1a174501b5..87853af8b0 100644 --- a/src/main/java/meteordevelopment/meteorclient/commands/commands/SwarmCommand.java +++ b/src/main/java/meteordevelopment/meteorclient/commands/commands/SwarmCommand.java @@ -5,6 +5,7 @@ package meteordevelopment.meteorclient.commands.commands; +import com.mojang.brigadier.LiteralMessage; import com.mojang.brigadier.arguments.BoolArgumentType; import com.mojang.brigadier.arguments.IntegerArgumentType; import com.mojang.brigadier.arguments.StringArgumentType; @@ -39,8 +40,8 @@ import java.util.Random; public class SwarmCommand extends Command { - - private final static SimpleCommandExceptionType SWARM_NOT_ACTIVE = new SimpleCommandExceptionType(Text.literal("The swarm module must be active to use this command.")); + private static final SimpleCommandExceptionType SWARM_NOT_ACTIVE = new SimpleCommandExceptionType(new LiteralMessage("The swarm module must be active to use this command.")); + private static final SimpleCommandExceptionType NO_PENDING_CONNECTION_EXCEPTION = new SimpleCommandExceptionType(new LiteralMessage("No pending connections.")); private @Nullable ObjectIntPair pendingConnection; public SwarmCommand() { @@ -82,8 +83,7 @@ public void build(LiteralArgumentBuilder builder) { ) .then(literal("confirm").executes(ctx -> { if (pendingConnection == null) { - error("No pending swarm connections."); - return SINGLE_SUCCESS; + throw NO_PENDING_CONNECTION_EXCEPTION.create(); } Swarm swarm = Modules.get().get(Swarm.class); @@ -96,7 +96,7 @@ public void build(LiteralArgumentBuilder builder) { pendingConnection = null; try { - info("Connected to (highlight)%s.", swarm.worker.getConnection()); + info("Connected to (highlight)%s(default).", swarm.worker.getConnection()); } catch (NullPointerException e) { error("Error connecting to swarm host."); swarm.close(); diff --git a/src/main/java/meteordevelopment/meteorclient/commands/commands/TransmogrifyCommand.java b/src/main/java/meteordevelopment/meteorclient/commands/commands/TransmogrifyCommand.java new file mode 100644 index 0000000000..8d98e262a2 --- /dev/null +++ b/src/main/java/meteordevelopment/meteorclient/commands/commands/TransmogrifyCommand.java @@ -0,0 +1,97 @@ +/* + * This file is part of the Meteor Client distribution (https://github.com/MeteorDevelopment/meteor-client). + * Copyright (c) Meteor Development. + */ + +package meteordevelopment.meteorclient.commands.commands; + +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import meteordevelopment.meteorclient.commands.Command; +import meteordevelopment.meteorclient.utils.commands.CreativeCommandHelper; +import meteordevelopment.meteorclient.utils.network.MeteorExecutor; +import net.minecraft.client.MinecraftClient; +import net.minecraft.command.CommandSource; +import net.minecraft.command.argument.IdentifierArgumentType; +import net.minecraft.command.argument.RegistryKeyArgumentType; +import net.minecraft.component.DataComponentTypes; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.registry.Registries; +import net.minecraft.registry.RegistryKey; +import net.minecraft.registry.RegistryKeys; +import net.minecraft.resource.Resource; +import net.minecraft.text.Style; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import net.minecraft.util.Identifier; + +import javax.annotation.Nullable; +import java.util.Optional; + +public class TransmogrifyCommand extends Command { + public TransmogrifyCommand() { + super("transmogrify", "Camouflages your held item.", "transmog"); + } + + @Override + public void build(LiteralArgumentBuilder builder) { + builder.then(literal("item").then(argument("item", RegistryKeyArgumentType.registryKey(RegistryKeys.ITEM)).executes(context -> { + ItemStack stack = mc.player.getMainHandStack(); + CreativeCommandHelper.assertValid(stack); + + Identifier itemId = context.getArgument("item", RegistryKey.class).getValue(); + Item item = Registries.ITEM.get(itemId); + + transmogrify(stack, itemId, item.getName()); + return SINGLE_SUCCESS; + }))); + + builder.then(literal("model").then(argument("model", IdentifierArgumentType.identifier()).executes(context -> { + ItemStack stack = mc.player.getMainHandStack(); + CreativeCommandHelper.assertValid(stack); + + Identifier modelId = context.getArgument("model", Identifier.class); + + MeteorExecutor.execute(() -> { + // check if resource exists + Identifier resourceId = modelId.withPath(path -> "items/" + path + ".json"); + Optional modelResource = mc.getResourceManager().getResource(resourceId); + + MinecraftClient.getInstance().execute(() -> { // fun fact: ChatUtils#error is not thread safe + if (modelResource.isEmpty()) { + error("Given model identifier does not exist."); + } else { + // check if held item changed during resource check + if (mc.player.getMainHandStack().getItem() != stack.getItem()) { + error("Dont switch held items while transmogrifying >:("); + } else { + try { + transmogrify(stack, modelId, null); + } catch (CommandSyntaxException e) { + error(e.getMessage()); + } + } + } + }); + }); + + return SINGLE_SUCCESS; + }))); + } + + private void transmogrify(ItemStack stack, Identifier modelId, @Nullable Text name) throws CommandSyntaxException { + // check whether the old and new item models match + Identifier oldModel = stack.get(DataComponentTypes.ITEM_MODEL); + if (oldModel != null && oldModel.equals(modelId)) { + info(Text.literal("Nothing changed...").setStyle(Style.EMPTY.withColor(Formatting.GRAY).withItalic(true))); + return; + } + + stack.set(DataComponentTypes.ITEM_MODEL, modelId); + if (name != null) stack.set(DataComponentTypes.ITEM_NAME, name); + + CreativeCommandHelper.setStack(stack); + info(Text.literal("Whoosh!").setStyle(Style.EMPTY.withColor(Formatting.GRAY).withItalic(true))); + } +} diff --git a/src/main/java/meteordevelopment/meteorclient/commands/commands/WaspCommand.java b/src/main/java/meteordevelopment/meteorclient/commands/commands/WaspCommand.java index 1b3fb81e6a..4e37c33fd4 100644 --- a/src/main/java/meteordevelopment/meteorclient/commands/commands/WaspCommand.java +++ b/src/main/java/meteordevelopment/meteorclient/commands/commands/WaspCommand.java @@ -5,6 +5,7 @@ package meteordevelopment.meteorclient.commands.commands; +import com.mojang.brigadier.LiteralMessage; import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; import meteordevelopment.meteorclient.commands.Command; @@ -13,10 +14,9 @@ import meteordevelopment.meteorclient.systems.modules.movement.AutoWasp; import net.minecraft.command.CommandSource; import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.text.Text; public class WaspCommand extends Command { - private static final SimpleCommandExceptionType CANT_WASP_SELF = new SimpleCommandExceptionType(Text.literal("You cannot target yourself!")); + private static final SimpleCommandExceptionType CANT_WASP_SELF = new SimpleCommandExceptionType(new LiteralMessage("You cannot target yourself!")); public WaspCommand() { super("wasp", "Sets the auto wasp target."); diff --git a/src/main/java/meteordevelopment/meteorclient/commands/commands/WaypointCommand.java b/src/main/java/meteordevelopment/meteorclient/commands/commands/WaypointCommand.java index 554046ad1f..54082c09c2 100644 --- a/src/main/java/meteordevelopment/meteorclient/commands/commands/WaypointCommand.java +++ b/src/main/java/meteordevelopment/meteorclient/commands/commands/WaypointCommand.java @@ -9,13 +9,12 @@ import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.context.CommandContext; import meteordevelopment.meteorclient.commands.Command; +import meteordevelopment.meteorclient.commands.arguments.BlockPosArgumentType; import meteordevelopment.meteorclient.commands.arguments.WaypointArgumentType; import meteordevelopment.meteorclient.systems.waypoints.Waypoint; import meteordevelopment.meteorclient.systems.waypoints.Waypoints; import meteordevelopment.meteorclient.utils.player.PlayerUtils; import net.minecraft.command.CommandSource; -import net.minecraft.command.argument.PosArgument; -import net.minecraft.command.argument.Vec3ArgumentType; import net.minecraft.util.Formatting; import net.minecraft.util.math.BlockPos; @@ -47,7 +46,7 @@ public void build(LiteralArgumentBuilder builder) { }))); builder.then(literal("add") - .then(argument("pos", Vec3ArgumentType.vec3()) + .then(argument("pos", BlockPosArgumentType.blockPos()) .then(argument("waypoint", StringArgumentType.greedyString()).executes(context -> addWaypoint(context, true))) ) @@ -84,7 +83,7 @@ private String waypointFullPos(Waypoint waypoint) { private int addWaypoint(CommandContext context, boolean withCoords) { if (mc.player == null) return -1; - BlockPos pos = withCoords ? context.getArgument("pos", PosArgument.class).toAbsoluteBlockPos(mc.player.getCommandSource(mc.getServer().getOverworld())) : mc.player.getBlockPos().up(2); + BlockPos pos = withCoords ? BlockPosArgumentType.getBlockPos(context, "pos") : mc.player.getBlockPos().up(2); Waypoint waypoint = new Waypoint.Builder() .name(StringArgumentType.getString(context, "waypoint")) .pos(pos) diff --git a/src/main/java/meteordevelopment/meteorclient/utils/commands/ArgumentFunction.java b/src/main/java/meteordevelopment/meteorclient/utils/commands/ArgumentFunction.java new file mode 100644 index 0000000000..743da872e7 --- /dev/null +++ b/src/main/java/meteordevelopment/meteorclient/utils/commands/ArgumentFunction.java @@ -0,0 +1,29 @@ +/* + * This file is part of the Meteor Client distribution (https://github.com/MeteorDevelopment/meteor-client). + * Copyright (c) Meteor Development. + */ + +package meteordevelopment.meteorclient.utils.commands; + +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; + +import java.util.function.Function; + +/** + * A {@link Function} like functional interface that maps a {@link CommandContext} to a command argument. + */ +@FunctionalInterface +public interface ArgumentFunction { + T apply(CommandContext context) throws CommandSyntaxException; + + @SuppressWarnings("unchecked") + default T uncheckedApply(CommandContext context) throws CommandSyntaxException { + ArgumentFunction castedArgument = (ArgumentFunction) this; + return castedArgument.apply(context); + } + + default ArgumentFunction andThen(Function after) { + return context -> after.apply(this.apply(context)); + } +} diff --git a/src/main/java/meteordevelopment/meteorclient/utils/misc/ComponentMapReader.java b/src/main/java/meteordevelopment/meteorclient/utils/commands/ComponentMapReader.java similarity index 84% rename from src/main/java/meteordevelopment/meteorclient/utils/misc/ComponentMapReader.java rename to src/main/java/meteordevelopment/meteorclient/utils/commands/ComponentMapReader.java index 950ffff52c..a6afe8a149 100644 --- a/src/main/java/meteordevelopment/meteorclient/utils/misc/ComponentMapReader.java +++ b/src/main/java/meteordevelopment/meteorclient/utils/commands/ComponentMapReader.java @@ -3,7 +3,7 @@ * Copyright (c) Meteor Development. */ -package meteordevelopment.meteorclient.utils.misc; +package meteordevelopment.meteorclient.utils.commands; import com.mojang.brigadier.StringReader; import com.mojang.brigadier.exceptions.CommandSyntaxException; @@ -90,20 +90,34 @@ public ComponentMap read() throws CommandSyntaxException { suggestor = this::suggestComponentType; Set> set = new ReferenceArraySet<>(); - while(reader.canRead() && reader.peek() != ']') { + while (reader.canRead() && reader.peek() != ']') { reader.skipWhitespace(); + + boolean removed = false; + if (reader.peek() == '!') { + removed = true; + suggestor = this::suggestComponentTypeNameOnly; + reader.skip(); + reader.skipWhitespace(); + } + ComponentType 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(); + if (removed) { + builder.add(dataComponentType, null); + } else { + 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; @@ -140,15 +154,25 @@ public static ComponentType readComponentType(StringReader reader) throws Com } private CompletableFuture suggestComponentType(SuggestionsBuilder builder) { + builder.suggest("!"); + suggestComponentType(builder, "="); + return builder.buildFuture(); + } + + private CompletableFuture suggestComponentTypeNameOnly(SuggestionsBuilder builder) { + suggestComponentType(builder, ""); + return builder.buildFuture(); + } + + private static void suggestComponentType(SuggestionsBuilder builder, String suffix) { String string = builder.getRemaining().toLowerCase(Locale.ROOT); CommandSource.forEachMatching(Registries.DATA_COMPONENT_TYPE.getEntrySet(), string, entry -> entry.getKey().getValue(), entry -> { ComponentType dataComponentType = entry.getValue(); if (dataComponentType.getCodec() != null) { Identifier identifier = entry.getKey().getValue(); - builder.suggest(identifier.toString() + "="); + builder.suggest(identifier.toString() + suffix); } }); - return builder.buildFuture(); } private void readComponentValue(StringReader reader, ComponentMap.Builder builder, ComponentType type) throws CommandSyntaxException { diff --git a/src/main/java/meteordevelopment/meteorclient/utils/commands/ComponentMapWriter.java b/src/main/java/meteordevelopment/meteorclient/utils/commands/ComponentMapWriter.java new file mode 100644 index 0000000000..da98af0763 --- /dev/null +++ b/src/main/java/meteordevelopment/meteorclient/utils/commands/ComponentMapWriter.java @@ -0,0 +1,242 @@ +/* + * This file is part of the Meteor Client distribution (https://github.com/MeteorDevelopment/meteor-client). + * Copyright (c) Meteor Development. + */ + +package meteordevelopment.meteorclient.utils.commands; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.DataResult; +import com.mojang.serialization.DynamicOps; +import net.minecraft.command.CommandRegistryAccess; +import net.minecraft.component.Component; +import net.minecraft.component.ComponentChanges; +import net.minecraft.component.ComponentMap; +import net.minecraft.component.ComponentType; +import net.minecraft.nbt.NbtElement; +import net.minecraft.nbt.NbtOps; +import net.minecraft.nbt.visitor.NbtTextFormatter; +import net.minecraft.nbt.visitor.StringNbtWriter; +import net.minecraft.registry.Registries; +import net.minecraft.text.MutableText; +import net.minecraft.text.Text; +import net.minecraft.text.Texts; +import net.minecraft.util.Formatting; +import net.minecraft.util.Identifier; +import org.jetbrains.annotations.Nullable; + +import java.util.Iterator; +import java.util.Map; +import java.util.Optional; + +import static meteordevelopment.meteorclient.MeteorClient.mc; + +/** + * @author Crosby + */ +public class ComponentMapWriter { + private final DynamicOps nbtOps; + + public ComponentMapWriter(CommandRegistryAccess commandRegistryAccess) { + this.nbtOps = mc.getNetworkHandler().getRegistryManager().getOps(NbtOps.INSTANCE); // todo figure out why codecs dislike CommandRegistryAccess + } + + public DataResult write(ComponentMap componentMap) { + StringWriter writer = new StringWriter(); + write(componentMap, writer); + return writer.build(); + } + + public DataResult write(ComponentChanges componentChanges) { + StringWriter writer = new StringWriter(); + write(componentChanges, writer); + return writer.build(); + } + + public DataResult write(ComponentType componentType, T object) { + StringWriter writer = new StringWriter(); + writeComponentValue(writer, componentType, object); + return writer.build(); + } + + public DataResult writePrettyPrinted(ComponentMap componentMap) { + ComponentTextFormatter writer = new ComponentTextFormatter(); + write(componentMap, writer); + return writer.build(); + } + + public DataResult writePrettyPrinted(ComponentChanges componentChanges) { + ComponentTextFormatter writer = new ComponentTextFormatter(); + write(componentChanges, writer); + return writer.build(); + } + + public DataResult writePrettyPrinted(ComponentType componentType, T object) { + ComponentTextFormatter writer = new ComponentTextFormatter(); + writeComponentValue(writer, componentType, object); + return writer.build(); + } + + public void write(ComponentMap componentMap, Writer writer) { + writer.acceptStart(); + for (Iterator> it = componentMap.iterator(); it.hasNext();) { + Component component = it.next(); + Identifier identifier = Registries.DATA_COMPONENT_TYPE.getId(component.type()); + writer.acceptIdentifier(identifier); + writer.acceptValueSeparator(); + writeComponentValue(writer, component); + if (it.hasNext()) writer.acceptComponentSeparator(); + } + writer.acceptEnd(); + } + + private void writeComponentValue(Writer writer, Component component) { + component.encode(this.nbtOps).ifSuccess(writer::acceptComponentValue); + } + + public void write(ComponentChanges componentChanges, Writer writer) { + writer.acceptStart(); + for (Iterator, Optional>> it = componentChanges.entrySet().iterator(); it.hasNext();) { + Map.Entry, Optional> entry = it.next(); + Identifier identifier = Registries.DATA_COMPONENT_TYPE.getId(entry.getKey()); + if (entry.getValue().isPresent()) { + writer.acceptIdentifier(identifier); + writer.acceptValueSeparator(); + writeComponentValue(writer, entry.getKey(), entry.getValue().get()); + } else { + writer.acceptRemoved(); + writer.acceptIdentifier(identifier); + } + if (it.hasNext()) writer.acceptComponentSeparator(); + } + writer.acceptEnd(); + } + + private void writeComponentValue(Writer writer, ComponentType type, Object value) { + Codec codec = type.getCodec(); + if (codec != null) { + codec.encodeStart(this.nbtOps, (T) value) + .ifSuccess(writer::acceptComponentValue) + .ifError(error -> error.resultOrPartial().ifPresentOrElse( + writer::acceptComponentValue, + () -> writer.onError(error) + )); + } + } + + public interface Writer { + void acceptStart(); + void acceptIdentifier(Identifier identifier); + void acceptValueSeparator(); + void acceptComponentValue(NbtElement element); + void acceptRemoved(); + void acceptComponentSeparator(); + void acceptEnd(); + void onError(DataResult.Error error); + } + + public static class StringWriter implements Writer { + private final StringBuilder builder = new StringBuilder(); + private @Nullable DataResult.Error error = null; + + @Override + public void acceptStart() { + this.builder.append('['); + } + + @Override + public void acceptRemoved() { + this.builder.append('!'); + } + + @Override + public void acceptIdentifier(Identifier identifier) { + this.builder.append(identifier); + } + + @Override + public void acceptValueSeparator() { + this.builder.append('='); + } + + @Override + public void acceptComponentValue(NbtElement element) { + this.builder.append(new StringNbtWriter().apply(element)); + } + + @Override + public void acceptComponentSeparator() { + this.builder.append(','); + } + + @Override + public void acceptEnd() { + this.builder.append(']'); + } + + @Override + public void onError(DataResult.Error error) { + this.error = error; + } + + public DataResult build() { + return this.error == null + ? DataResult.success(this.builder.toString()) + : DataResult.error(this.error.messageSupplier(), this.builder.toString()); + } + } + + public static class ComponentTextFormatter implements Writer { + private static final Text VALUE_SEPARATOR = Text.literal(": "); + private static final Text REMOVED = Text.literal("!").formatted(Formatting.RED); + private static final Formatting NAME_COLOR = Formatting.AQUA; + private final MutableText result = Text.empty(); + private @Nullable DataResult.Error error = null; + + @Override + public void acceptStart() { + this.result.append("["); + } + + @Override + public void acceptRemoved() { + this.result.append(REMOVED); + } + + @Override + public void acceptIdentifier(Identifier identifier) { + this.result.append(Text.literal(identifier.toString()).formatted(NAME_COLOR)); + } + + @Override + public void acceptValueSeparator() { + this.result.append(VALUE_SEPARATOR); + } + + @Override + public void acceptComponentValue(NbtElement element) { + this.result.append(new NbtTextFormatter("").apply(element)); + } + + @Override + public void acceptComponentSeparator() { + this.result.append(Texts.DEFAULT_SEPARATOR_TEXT); + } + + @Override + public void acceptEnd() { + this.result.append("]"); + } + + @Override + public void onError(DataResult.Error error) { + this.error = error; + } + + public DataResult build() { + return this.error == null + ? DataResult.success(this.result) + : DataResult.error(this.error.messageSupplier(), this.result); + } + } +} diff --git a/src/main/java/meteordevelopment/meteorclient/utils/commands/CreativeCommandHelper.java b/src/main/java/meteordevelopment/meteorclient/utils/commands/CreativeCommandHelper.java new file mode 100644 index 0000000000..a33ac7b000 --- /dev/null +++ b/src/main/java/meteordevelopment/meteorclient/utils/commands/CreativeCommandHelper.java @@ -0,0 +1,40 @@ +/* + * This file is part of the Meteor Client distribution (https://github.com/MeteorDevelopment/meteor-client). + * Copyright (c) Meteor Development. + */ + +package meteordevelopment.meteorclient.utils.commands; + +import com.mojang.brigadier.LiteralMessage; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; +import net.minecraft.item.ItemStack; + +import static meteordevelopment.meteorclient.MeteorClient.mc; + +public class CreativeCommandHelper { + private static final SimpleCommandExceptionType NOT_IN_CREATIVE_EXCEPTION = new SimpleCommandExceptionType(new LiteralMessage("You must be in creative mode to run this command.")); + private static final SimpleCommandExceptionType NOT_AN_ITEM_EXCEPTION = new SimpleCommandExceptionType(new LiteralMessage("Not an item.")); + + private CreativeCommandHelper() {} + + public static void assertValid(ItemStack stack) throws CommandSyntaxException { + if (stack.isEmpty()) { + throw NOT_AN_ITEM_EXCEPTION.create(); + } + } + + public static void setStack(ItemStack stack) throws CommandSyntaxException { + setStack(stack, mc.player.getInventory().selectedSlot); + } + + public static void setStack(ItemStack stack, int slot) throws CommandSyntaxException { + if (!mc.player.getAbilities().creativeMode) { + throw NOT_IN_CREATIVE_EXCEPTION.create(); + } + + mc.player.getInventory().setStack(slot, stack); + mc.interactionManager.clickCreativeStack(stack, 36 + slot); + mc.player.playerScreenHandler.sendContentUpdates(); + } +} diff --git a/src/main/java/meteordevelopment/meteorclient/utils/commands/PathReader.java b/src/main/java/meteordevelopment/meteorclient/utils/commands/PathReader.java new file mode 100644 index 0000000000..541fe1fbbd --- /dev/null +++ b/src/main/java/meteordevelopment/meteorclient/utils/commands/PathReader.java @@ -0,0 +1,52 @@ +/* + * This file is part of the Meteor Client distribution (https://github.com/MeteorDevelopment/meteor-client). + * Copyright (c) Meteor Development. + */ + +package meteordevelopment.meteorclient.utils.commands; + +import com.mojang.brigadier.LiteralMessage; +import com.mojang.brigadier.StringReader; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.exceptions.DynamicCommandExceptionType; +import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; + +import java.nio.file.InvalidPathException; +import java.nio.file.Path; + +public class PathReader { + private static final SimpleCommandExceptionType EMPTY_PATH_EXCEPTION = new SimpleCommandExceptionType(new LiteralMessage("Path cannot be empty.")); + private static final DynamicCommandExceptionType INVALID_PATH_EXCEPTION = new DynamicCommandExceptionType(path -> new LiteralMessage("Path " + path + " is not valid.")); + + public static Path readPath(StringReader reader) throws CommandSyntaxException { + if (!reader.canRead()) { + throw EMPTY_PATH_EXCEPTION.create(); + } + + String pathString = readPathString(reader); + + try { + return Path.of(pathString); + } catch (InvalidPathException e) { + throw INVALID_PATH_EXCEPTION.create(pathString); + } + } + + public static boolean isAllowedInUnquotedPath(char c) { + return StringReader.isAllowedInUnquotedString(c) || c == '\\' || c == '/'; + } + + private static String readPathString(StringReader reader) throws CommandSyntaxException { + char next = reader.peek(); + if (StringReader.isQuotedStringStart(next)) { + reader.skip(); + return reader.readStringUntil(next); + } + + int start = reader.getCursor(); + while (reader.canRead() && isAllowedInUnquotedPath(reader.peek())) { + reader.skip(); + } + return reader.getString().substring(start, reader.getCursor()); + } +} diff --git a/src/main/resources/assets/meteor-client/items/multitool.json b/src/main/resources/assets/meteor-client/items/multitool.json new file mode 100644 index 0000000000..451b842b1b --- /dev/null +++ b/src/main/resources/assets/meteor-client/items/multitool.json @@ -0,0 +1,6 @@ +{ + "model": { + "type": "minecraft:model", + "model": "meteor-client:item/multitool" + } +} diff --git a/src/main/resources/assets/meteor-client/models/item/multitool.json b/src/main/resources/assets/meteor-client/models/item/multitool.json new file mode 100644 index 0000000000..41c11adb24 --- /dev/null +++ b/src/main/resources/assets/meteor-client/models/item/multitool.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:item/handheld", + "textures": { + "layer0": "meteor-client:item/multitool" + } +} diff --git a/src/main/resources/assets/meteor-client/textures/item/multitool.png b/src/main/resources/assets/meteor-client/textures/item/multitool.png new file mode 100644 index 0000000000..94c02eda1e Binary files /dev/null and b/src/main/resources/assets/meteor-client/textures/item/multitool.png differ