diff --git a/README.md b/README.md index 8469f1e..92c6a57 100644 --- a/README.md +++ b/README.md @@ -118,9 +118,11 @@ Fake players can be spawned using the `/dummy` command. Dummies won't save or lo * `dummy sneak [true|false]`: makes the dummy hold shift or un-shift (not the same as currently crouching) * `dummy sprint [true|false]`: makes the dummy sprint or un-sprint * `dummy drop [all]`: makes the dummy drop the current mainhand, either one item or the entire stack +* `dummy drop from [all]`: makes the dummy drop from an inventory slot, either one item or the entire stack * `dummy swap`: makes the dummy swap its mainhand and offhand -* `dummy selectslot`: makes the dummy select a different hotbar slot -* `dummy use item`: makes the dummy use its hand item, either mainhand or offhand +* `dummy selectslot `: makes the dummy select a different hotbar slot, from `1` to `9` +* `dummy mainhand `: makes the dummy select a different hotbar slot, from `0` to `8` +* `dummy use [item]`: makes the dummy use its hand item, either mainhand or offhand * `dummy use block []`: makes the dummy use its hand item on a block position * `dummy use entity `: makes the dummy use its hand item on an entity * `dummy attack `: makes the dummy attack an entity with its mainhand diff --git a/src/main/java/io/github/misode/packtest/commands/DummyCommand.java b/src/main/java/io/github/misode/packtest/commands/DummyCommand.java index 6bdaf34..a94eb17 100644 --- a/src/main/java/io/github/misode/packtest/commands/DummyCommand.java +++ b/src/main/java/io/github/misode/packtest/commands/DummyCommand.java @@ -15,6 +15,7 @@ import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.Commands; import net.minecraft.commands.arguments.EntityArgument; +import net.minecraft.commands.arguments.SlotArgument; import net.minecraft.commands.arguments.coordinates.BlockPosArgument; import net.minecraft.commands.arguments.coordinates.Vec3Argument; import net.minecraft.commands.arguments.selector.EntitySelector; @@ -26,7 +27,9 @@ import net.minecraft.server.players.PlayerList; import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionResult; +import net.minecraft.util.Prediction; import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.SlotAccess; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.component.SwingAnimation; import net.minecraft.world.phys.BlockHitResult; @@ -104,13 +107,22 @@ public static void register(CommandDispatcher dispatcher) { .then(literal("drop") .executes(ctx -> dropMainhand(ctx, false)) .then(literal("all") - .executes(ctx -> dropMainhand(ctx, true)))) + .executes(ctx -> dropMainhand(ctx, true))) + .then(literal("from") + .then(argument("slot", SlotArgument.slot()) + .executes(ctx -> dropFromSlot(ctx, false)) + .then(literal("all") + .executes(ctx -> dropFromSlot(ctx, true)))))) .then(literal("swap") .executes(DummyCommand::swap)) .then(literal("selectslot") .then(argument("slot", IntegerArgumentType.integer(1, 9)) - .executes(DummyCommand::selectSlot))) + .executes(ctx -> selectSlot(ctx, 1)))) + .then(literal("mainhand") + .then(argument("slot", IntegerArgumentType.integer(0, 8)) + .executes(ctx -> selectSlot(ctx, 0)))) .then(literal("use") + .executes(DummyCommand::useItem) .then(literal("item") .executes(DummyCommand::useItem)) .then(literal("block") @@ -224,13 +236,28 @@ private static int swap(CommandContext ctx) throws CommandSy return 1; } - private static int selectSlot(CommandContext ctx) throws CommandSyntaxException { + private static int dropFromSlot(CommandContext ctx, boolean stack) throws CommandSyntaxException { + Dummy dummy = getDummy(ctx); + int slot = SlotArgument.getSlot(ctx, "slot"); + SlotAccess access = dummy.getSlot(slot); + if (access == null) { + return 0; + } + ItemStack current = access.get(); + ItemStack removed = current.split(stack ? current.count() : 1); + access.set(current); + dummy.containerMenu.findSlot(dummy.getInventory(), slot).ifPresent(i -> dummy.containerMenu.setRemoteSlot(i, access.get())); + dummy.drop(removed, true, Prediction.PREDICTED); + return removed.count(); + } + + private static int selectSlot(CommandContext ctx, int first) throws CommandSyntaxException { Dummy dummy = getDummy(ctx); int slot = IntegerArgumentType.getInteger(ctx, "slot"); - if (dummy.getInventory().getSelectedSlot() == slot - 1) { + if (dummy.getInventory().getSelectedSlot() == slot - first) { throw ERROR_SLOT_SELECTED.create(dummy.getUsername(), slot); } - dummy.getInventory().setSelectedSlot(slot - 1); + dummy.getInventory().setSelectedSlot(slot - first); return 1; } diff --git a/src/main/java/io/github/misode/packtest/commands/assertions/DataAssertion.java b/src/main/java/io/github/misode/packtest/commands/assertions/DataAssertion.java index a337dc6..05bfeee 100644 --- a/src/main/java/io/github/misode/packtest/commands/assertions/DataAssertion.java +++ b/src/main/java/io/github/misode/packtest/commands/assertions/DataAssertion.java @@ -9,11 +9,15 @@ import net.minecraft.commands.Commands; import net.minecraft.commands.arguments.NbtPathArgument; import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.Tag; import net.minecraft.network.chat.Component; import net.minecraft.server.commands.ArgProvider; import net.minecraft.server.commands.data.DataAccessor; import net.minecraft.server.commands.data.DataCommands; +import java.util.List; +import java.util.Objects; + class DataAssertion implements Assertion { @Override public void attach( @@ -34,9 +38,49 @@ private static int run(CommandContext context, Mode mode, Ar DataAccessor accessor = provider.access(context); CompoundTag data = accessor.getData(); - return AssertResult.of(path.countMatching(data), negated -> Component.literal(!negated - ? String.format("Expected data at %s, found %s", path.asString(), data) - : String.format("Did not expect data at %s", path.asString()))); + return AssertResult.of(path.countMatching(data), negated -> negated + ? Component.literal(String.format("Did not expect data at %s", path.asString())) + : mismatch(path, data)); }); } + + private static Component mismatch(NbtPathArgument.NbtPath path, Tag data) { + String raw = path.asString(); + List found = List.of(data); + String reached = ""; + + for (NbtPathArgument.Node node : path.nodes) { + List next = node.get(found); + if (next.isEmpty()) { + return describe(raw, found, reached, node); + } + found = next; + reached = raw.substring(0, path.nodeToOriginalPosition.getInt(node)); + } + + return describe(raw, found, reached); + } + + private static Component describe(String raw, List found, String at) { + String value = found.size() == 1 ? found.getFirst().toString() : found.toString(); + + return Component.literal(at.isEmpty() + ? String.format("Expected data at %s, found %s", raw, value) + : String.format("Expected data at %s, found %s at %s", raw, value, at)); + } + + private static Component describe(String raw, List found, String reached, NbtPathArgument.Node unmatched) { + if (!(unmatched instanceof NbtPathArgument.MatchObjectNode filter)) { + return describe(raw, found, reached); + } + + List filtered = found.stream() + .map(tag -> tag instanceof CompoundTag compound ? compound.get(filter.name) : null) + .filter(Objects::nonNull) + .toList(); + + return filtered.isEmpty() + ? describe(raw, found, reached) + : describe(raw, filtered, reached.isEmpty() ? filter.name : reached + "." + filter.name); + } } diff --git a/src/main/resources/packtest.accesswidener b/src/main/resources/packtest.accesswidener index def9b83..2416106 100644 --- a/src/main/resources/packtest.accesswidener +++ b/src/main/resources/packtest.accesswidener @@ -1,6 +1,11 @@ accessWidener v2 official accessible class net/minecraft/core/MappedRegistry$TagSet +accessible class net/minecraft/commands/arguments/NbtPathArgument$Node +accessible class net/minecraft/commands/arguments/NbtPathArgument$MatchObjectNode +accessible field net/minecraft/commands/arguments/NbtPathArgument$MatchObjectNode name Ljava/lang/String; +accessible field net/minecraft/commands/arguments/NbtPathArgument$NbtPath nodes [Lnet/minecraft/commands/arguments/NbtPathArgument$Node; +accessible field net/minecraft/commands/arguments/NbtPathArgument$NbtPath nodeToOriginalPosition Lit/unimi/dsi/fastutil/objects/Object2IntMap; accessible field net/minecraft/commands/arguments/selector/EntitySelector playerName Ljava/lang/String; accessible field net/minecraft/gametest/framework/GameTestHelper testInfo Lnet/minecraft/gametest/framework/GameTestInfo; accessible method net/minecraft/server/commands/ExecuteCommand checkCustomPredicate (Lnet/minecraft/commands/CommandSourceStack;Lnet/minecraft/core/Holder;)Z