diff --git a/common/src/main/java/com/pathmind/nodes/NodeCatalog.java b/common/src/main/java/com/pathmind/nodes/NodeCatalog.java index c5997502..9d38b489 100644 --- a/common/src/main/java/com/pathmind/nodes/NodeCatalog.java +++ b/common/src/main/java/com/pathmind/nodes/NodeCatalog.java @@ -440,7 +440,11 @@ public final class NodeCatalog { NodeType.OPEN_INVENTORY, NodeType.CLOSE_GUI); + // This flag is what makes a node draw its parameter strip, and the mode selector lives + // inside that strip (NodeRenderer.renderInlineParameterContent). A node with modes but + // without this tag has no way to reach them in the editor. tag(NodeFlag.RENDER_INLINE_PARAMETERS, + NodeType.CLICK_SLOT, NodeType.UI_UTILS, NodeType.SENSOR_FABRIC_EVENT, NodeType.SENSOR_ATTRIBUTE_DETECTION, @@ -903,6 +907,12 @@ public final class NodeCatalog { modeParameters(NodeMode.FARM_WAYPOINT, of("Waypoint", ParameterType.STRING, "farm"), of("Range", ParameterType.INTEGER, "10")); + // Click Slot's three modes take the same Slot number; they differ only in the click the + // node sends. Keeping the id and label identical to the old type parameter is what lets + // presets saved before the modes existed restore their slot unchanged. + modeParameters(NodeMode.CLICK_SLOT_LEFT, of("click_slot_index", "Slot", ParameterType.INTEGER, "0")); + modeParameters(NodeMode.CLICK_SLOT_SHIFT, of("click_slot_index", "Slot", ParameterType.INTEGER, "0")); + modeParameters(NodeMode.CLICK_SLOT_SWAP_OFFHAND, of("click_slot_index", "Slot", ParameterType.INTEGER, "0")); modeParameters(NodeMode.WAIT_SECONDS, of("Duration", ParameterType.DOUBLE, "")); modeParameters(NodeMode.WAIT_TICKS, of("Duration", ParameterType.DOUBLE, "")); modeParameters(NodeMode.WAIT_MINUTES, of("Duration", ParameterType.DOUBLE, "")); @@ -948,7 +958,6 @@ public final class NodeCatalog { of("drop_slot_index", "Slot", ParameterType.INTEGER, "0"), of("Count", ParameterType.INTEGER, "0"), of("EntireStack", ParameterType.BOOLEAN, "true")); - typeParameters(NodeType.CLICK_SLOT, of("click_slot_index", "Slot", ParameterType.INTEGER, "0")); typeParameters(NodeType.CLICK_SCREEN, of("X", ParameterType.INTEGER, "0"), of("Y", ParameterType.INTEGER, "0")); diff --git a/common/src/main/java/com/pathmind/nodes/NodeInventoryCommandExecutor.java b/common/src/main/java/com/pathmind/nodes/NodeInventoryCommandExecutor.java index fa3d5f0f..68212789 100644 --- a/common/src/main/java/com/pathmind/nodes/NodeInventoryCommandExecutor.java +++ b/common/src/main/java/com/pathmind/nodes/NodeInventoryCommandExecutor.java @@ -31,6 +31,8 @@ import net.minecraft.world.item.Items; final class NodeInventoryCommandExecutor { + private static final int OFFHAND_SWAP_BUTTON = 40; + private final Node owner; private final NodeType type; private final NodeRuntimeState runtimeState; @@ -197,11 +199,12 @@ void executeClickSlotCommand(CompletableFuture future) { return; } + NodeMode mode = owner.getMode(); interactionManager.handleInventoryMouseClick( handler.containerId, resolution.handlerSlotIndex, - 0, - ClickType.PICKUP, + clickSlotButton(mode), + clickSlotClickType(mode), client.player ); @@ -210,6 +213,24 @@ void executeClickSlotCommand(CompletableFuture future) { future.complete(null); } + // The three Click Slot modes send exactly what the vanilla screen sends for the same input: + // a plain left click is PICKUP, shift-click is QUICK_MOVE, and F is SWAP on button 40, which + // is the offhand's button index in AbstractContainerScreen. A node saved before the modes + // existed loads with the default CLICK_SLOT_LEFT, and a null mode is the same plain click. + static ClickType clickSlotClickType(NodeMode mode) { + if (mode == NodeMode.CLICK_SLOT_SHIFT) { + return ClickType.QUICK_MOVE; + } + if (mode == NodeMode.CLICK_SLOT_SWAP_OFFHAND) { + return ClickType.SWAP; + } + return ClickType.PICKUP; + } + + static int clickSlotButton(NodeMode mode) { + return mode == NodeMode.CLICK_SLOT_SWAP_OFFHAND ? OFFHAND_SWAP_BUTTON : 0; + } + void executeClickScreenCommand(CompletableFuture future) { net.minecraft.client.Minecraft client = net.minecraft.client.Minecraft.getInstance(); if (client == null || client.getWindow() == null) { diff --git a/common/src/main/java/com/pathmind/nodes/NodeMode.java b/common/src/main/java/com/pathmind/nodes/NodeMode.java index 870219d6..560af44a 100644 --- a/common/src/main/java/com/pathmind/nodes/NodeMode.java +++ b/common/src/main/java/com/pathmind/nodes/NodeMode.java @@ -8,6 +8,11 @@ * Each mode corresponds to a specific behavior within a generalized node type. */ public enum NodeMode { + // CLICK_SLOT modes + CLICK_SLOT_LEFT("Left Click", "Pick up or place the stack, as a plain left click does"), + CLICK_SLOT_SHIFT("Shift Click", "Move the stack to the other container, as shift-clicking does"), + CLICK_SLOT_SWAP_OFFHAND("F", "Swap the stack with the offhand, as pressing F does"), + // GOTO modes GOTO_XYZ("Go to XYZ", "Go to specific X, Y, Z coordinates"), GOTO_XZ("Go to XZ", "Go to X, Z coordinates (Y defaults to surface)"), @@ -176,6 +181,9 @@ public static NodeMode[] getModesForNodeType(NodeType nodeType) { case SENSOR_LOOK_DIRECTION -> new NodeMode[]{ SENSOR_LOOK_YAW, SENSOR_LOOK_PITCH, SENSOR_LOOK_ROTATION }; + case CLICK_SLOT -> new NodeMode[]{ + CLICK_SLOT_LEFT, CLICK_SLOT_SHIFT, CLICK_SLOT_SWAP_OFFHAND + }; default -> new NodeMode[0]; }; } @@ -200,6 +208,7 @@ public static NodeMode getDefaultModeForNodeType(NodeType nodeType) { case WAIT, PARAM_DURATION -> WAIT_SECONDS; case SENSOR_POSITION_OF -> SENSOR_POSITION_XYZ; case SENSOR_LOOK_DIRECTION -> SENSOR_LOOK_ROTATION; + case CLICK_SLOT -> CLICK_SLOT_LEFT; default -> null; }; } diff --git a/common/src/main/resources/assets/pathmind/lang/en_us.json b/common/src/main/resources/assets/pathmind/lang/en_us.json index a12e7843..828d375d 100644 --- a/common/src/main/resources/assets/pathmind/lang/en_us.json +++ b/common/src/main/resources/assets/pathmind/lang/en_us.json @@ -283,6 +283,12 @@ "pathmind.node.mode.goal_xz.desc": "Set goal to X, Z coordinates (Y defaults to surface)", "pathmind.node.mode.goal_y": "Set Goal Y", "pathmind.node.mode.goal_y.desc": "Set goal to specific Y level", + "pathmind.node.mode.click_slot_left": "Left Click", + "pathmind.node.mode.click_slot_left.desc": "Pick up or place the stack, as a plain left click does", + "pathmind.node.mode.click_slot_shift": "Shift Click", + "pathmind.node.mode.click_slot_shift.desc": "Move the stack to the other container, as shift-clicking does", + "pathmind.node.mode.click_slot_swap_offhand": "F", + "pathmind.node.mode.click_slot_swap_offhand.desc": "Swap the stack with the offhand, as pressing F does", "pathmind.node.mode.goto_block": "Go to Block", "pathmind.node.mode.goto_block.desc": "Go to nearest block of specified type", "pathmind.node.mode.goto_xyz": "Go to XYZ", diff --git a/common/src/test/java/com/pathmind/nodes/NodeClickSlotTest.java b/common/src/test/java/com/pathmind/nodes/NodeClickSlotTest.java new file mode 100644 index 00000000..ad0eada5 --- /dev/null +++ b/common/src/test/java/com/pathmind/nodes/NodeClickSlotTest.java @@ -0,0 +1,50 @@ +package com.pathmind.nodes; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import net.minecraft.world.inventory.ClickType; +import org.junit.jupiter.api.Test; + +class NodeClickSlotTest { + + @Test + void eachModeSendsTheClickVanillaSendsForTheSameInput() { + assertEquals(ClickType.PICKUP, + NodeInventoryCommandExecutor.clickSlotClickType(NodeMode.CLICK_SLOT_LEFT)); + assertEquals(ClickType.QUICK_MOVE, + NodeInventoryCommandExecutor.clickSlotClickType(NodeMode.CLICK_SLOT_SHIFT)); + assertEquals(ClickType.SWAP, + NodeInventoryCommandExecutor.clickSlotClickType(NodeMode.CLICK_SLOT_SWAP_OFFHAND)); + + // Only the offhand swap carries a button; SWAP with any other button hits a hotbar slot. + assertEquals(0, NodeInventoryCommandExecutor.clickSlotButton(NodeMode.CLICK_SLOT_LEFT)); + assertEquals(0, NodeInventoryCommandExecutor.clickSlotButton(NodeMode.CLICK_SLOT_SHIFT)); + assertEquals(40, NodeInventoryCommandExecutor.clickSlotButton(NodeMode.CLICK_SLOT_SWAP_OFFHAND)); + } + + @Test + void aNodeSavedBeforeTheModesExistedStillSendsAPlainLeftClick() { + assertEquals(ClickType.PICKUP, NodeInventoryCommandExecutor.clickSlotClickType(null)); + assertEquals(0, NodeInventoryCommandExecutor.clickSlotButton(null)); + } + + @Test + void clickSlotOffersItsThreeModesWithLeftClickAsTheDefault() { + assertEquals(NodeMode.CLICK_SLOT_LEFT, NodeMode.getDefaultModeForNodeType(NodeType.CLICK_SLOT)); + assertEquals(3, NodeMode.getModesForNodeType(NodeType.CLICK_SLOT).length); + } + + @Test + void everyModeKeepsTheSlotParameterSoOldPresetsRestoreIt() { + // initializeParameters reads mode parameters instead of type parameters once a node has + // modes, so a mode missing the Slot definition would silently drop the saved value. + for (NodeMode mode : NodeMode.getModesForNodeType(NodeType.CLICK_SLOT)) { + java.util.List parameters = new java.util.ArrayList<>(); + NodeCatalog.initializeParameters(parameters, NodeType.CLICK_SLOT, mode); + assertEquals(1, parameters.size(), mode.name()); + assertEquals("Slot", parameters.get(0).getName(), mode.name()); + assertEquals("click_slot_index", parameters.get(0).getId(), mode.name()); + assertEquals("0", parameters.get(0).getStringValue(), mode.name()); + } + } +}