diff --git a/README.md b/README.md index de6068a..f9add5e 100644 --- a/README.md +++ b/README.md @@ -231,8 +231,6 @@ for custom locales, from English. Existing translations are preserved. | Existing sticks stop working after an item config change | Reissue them with `/dsp give`; recognition uses the currently configured material. | | Frozen blocks need to be released | The player can left-click in Freeze mode, or an administrator can run `/dsp reload`. | -Known limitation: players must sneak to change a candle's `lit` value. - ### Compatibility testing Continuous integration builds the plugin and starts every stable Paper and Folia diff --git a/integration/mineflayer/test.mjs b/integration/mineflayer/test.mjs index 65931c1..4f47946 100644 --- a/integration/mineflayer/test.mjs +++ b/integration/mineflayer/test.mjs @@ -56,18 +56,47 @@ function waitFor(predicate, description, timeout = timeoutMs) { }) } -async function rightClickBlock(target) { +async function rightClickBlock(target, cursorHeight = 0.5, cursorInset = 0, includeOffhand = false) { const swingArm = bot.swingArm bot.swingArm = () => {} try { const westFace = target.position.offset(-1, 0, 0).minus(target.position) - const westFaceCenter = target.position.offset(0, 0.5, 0.5).minus(target.position) + const westFaceCenter = target.position.offset(cursorInset, cursorHeight, 0.5).minus(target.position) await bot.activateBlock(target, westFace, westFaceCenter) + if (includeOffhand) { + bot._client.write('block_place', { + location: target.position, + direction: 4, + hand: 1, + cursorX: cursorInset, + cursorY: cursorHeight, + cursorZ: 0.5, + insideBlock: false, + sequence: 0, + worldBorderHit: false + }) + } } finally { bot.swingArm = swingArm } } +async function leftClickBlock(target) { + const packet = { + location: target.position, + face: 1 + } + bot._client.write('block_dig', { ...packet, status: 0 }) + bot.swingArm('right') + await bot.waitForTicks(1) + bot._client.write('block_dig', { ...packet, status: 1 }) + await bot.waitForTicks(2) +} + +function blockProperty(position, property) { + return bot.blockAt(position)?.getProperties()?.[property] +} + async function exerciseFreezeLifecycle(target, expectedBlockName) { const packetOffset = entityPackets.length await rightClickBlock(target) @@ -140,6 +169,46 @@ try { await bot.equip(debugStick, 'hand') await bot.waitForTicks(20) + const targetPosition = bot.entity.position.floored().offset(1, 0, 0) + bot.chat(`/setblock ${targetPosition.x} ${targetPosition.y} ${targetPosition.z} minecraft:candle[lit=false]`) + const candle = await waitFor(() => { + const block = bot.blockAt(targetPosition) + return block?.name === 'candle' ? block : null + }, 'integration-test candle block') + await bot.lookAt(candle.position.offset(0.5, 0.2, 0.5), true) + + // The first right click initializes CandleData; one left click then advances to LightableData. + await rightClickBlock(candle, 0.2, 0.4375) + await leftClickBlock(bot.blockAt(targetPosition)) + bot.chat(`/setblock ${targetPosition.x} ${targetPosition.y} ${targetPosition.z} minecraft:candle[lit=false]`) + await waitFor( + () => blockProperty(targetPosition, 'lit') === false, + 'candle selection reset' + ) + + bot.setControlState('sneak', true) + await bot.waitForTicks(2) + await rightClickBlock(candle, 0.2, 0.4375) + await waitFor( + () => blockProperty(targetPosition, 'lit') === true, + 'candle lighting while sneaking' + ) + + bot.setControlState('sneak', false) + await bot.waitForTicks(2) + bot.chat(`/setblock ${targetPosition.x} ${targetPosition.y} ${targetPosition.z} minecraft:candle[lit=false]`) + await waitFor( + () => blockProperty(targetPosition, 'lit') === false, + 'unlit candle reset' + ) + await rightClickBlock(bot.blockAt(targetPosition), 0.2, 0.4375, true) + await bot.waitForTicks(10) + assert.equal( + blockProperty(targetPosition, 'lit'), + true, + 'Classic mode must light a candle without sneaking' + ) + bot.chat('/dsp mode freeze') await waitFor( () => messages.some(message => /freeze|凍結/i.test(message)), @@ -147,7 +216,6 @@ try { ) await bot.waitForTicks(10) - const targetPosition = bot.entity.position.floored().offset(1, 0, 0) bot.chat(`/setblock ${targetPosition.x} ${targetPosition.y} ${targetPosition.z} minecraft:stone`) const target = await waitFor(() => { const block = bot.blockAt(targetPosition) @@ -172,6 +240,7 @@ try { version, locale: 'zh_TW', command: true, + candleWithoutSneaking: true, miniMessageItem: debugStick.customName?.toString() ?? debugStick.displayName, virtualEntities: ['item_display', 'block_display'], removal: true, diff --git a/src/main/java/dev/twme/debugstickpro/listeners/LeftClickListener.java b/src/main/java/dev/twme/debugstickpro/listeners/LeftClickListener.java index a0982cb..ccf841e 100644 --- a/src/main/java/dev/twme/debugstickpro/listeners/LeftClickListener.java +++ b/src/main/java/dev/twme/debugstickpro/listeners/LeftClickListener.java @@ -2,6 +2,7 @@ import dev.twme.debugstickpro.playerdata.PlayerDataManager; import dev.twme.debugstickpro.utils.DebugStickItem; +import org.bukkit.block.Block; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; @@ -26,10 +27,14 @@ public void onLeftClickEvent(PlayerInteractEvent event) { return; } + Block targetBlock = event.getClickedBlock(); + if (targetBlock == null) { + targetBlock = player.getTargetBlockExact(5); + } + event.setCancelled(true); - // TODO: 未來改成這個版本 - PlayerDataManager.playerLeftClick(player.getUniqueId()); + PlayerDataManager.playerLeftClick(player.getUniqueId(), targetBlock); } } diff --git a/src/main/java/dev/twme/debugstickpro/listeners/RightClickListener.java b/src/main/java/dev/twme/debugstickpro/listeners/RightClickListener.java index 5dd77bc..1f539f9 100644 --- a/src/main/java/dev/twme/debugstickpro/listeners/RightClickListener.java +++ b/src/main/java/dev/twme/debugstickpro/listeners/RightClickListener.java @@ -17,11 +17,6 @@ public class RightClickListener implements Listener { @EventHandler public void onRightClick(PlayerInteractEvent event) { - - if (event.getHand() != EquipmentSlot.HAND) { - return; - } - Player player = event.getPlayer(); if (!player.hasPermission("debugstickpro.use")) { @@ -36,10 +31,14 @@ public void onRightClick(PlayerInteractEvent event) { return; } + Block targetBlock = event.getClickedBlock(); + if (targetBlock == null) { + targetBlock = player.getTargetBlockExact(5); + } + // In classic mode, don't cancel the event if the target block has no available SubBlockData PlayerData playerData = PlayerDataManager.getOrCreatePlayerData(player.getUniqueId()); if (playerData.getDebugStickMode() == DebugStickMode.CLASSIC) { - Block targetBlock = player.getTargetBlockExact(5); if (targetBlock == null || BlockDataSeparater.separate(targetBlock, player.getUniqueId()).isEmpty()) { return; } @@ -47,9 +46,16 @@ public void onRightClick(PlayerInteractEvent event) { event.setCancelled(true); + // The vanilla client may follow a main-hand PASS with an off-hand interaction. + // Consume that event, but only execute the Debug Stick action for the main hand. + if (event.getHand() != EquipmentSlot.HAND) { + return; + } + PlayerDataManager.playerRightClick( player.getUniqueId(), event.getAction(), + targetBlock, event.getClickedBlock(), event.getBlockFace() ); diff --git a/src/main/java/dev/twme/debugstickpro/mode/classic/ClassicLeftClick.java b/src/main/java/dev/twme/debugstickpro/mode/classic/ClassicLeftClick.java index caca31d..507ff82 100644 --- a/src/main/java/dev/twme/debugstickpro/mode/classic/ClassicLeftClick.java +++ b/src/main/java/dev/twme/debugstickpro/mode/classic/ClassicLeftClick.java @@ -16,12 +16,10 @@ public class ClassicLeftClick { // 更改選擇的 SubBlockData 類型 // change selected SubBlockData type - public static void changeSelectedSubBlockType(UUID playerUUID, PlayerData playerData) { + public static void changeSelectedSubBlockType(UUID playerUUID, PlayerData playerData, Block block) { Player player = Bukkit.getPlayer(playerUUID); - Block block = player.getTargetBlockExact(5); - if (block == null) { return; } diff --git a/src/main/java/dev/twme/debugstickpro/mode/classic/ClassicRightClick.java b/src/main/java/dev/twme/debugstickpro/mode/classic/ClassicRightClick.java index 8afcf25..a9b82d8 100644 --- a/src/main/java/dev/twme/debugstickpro/mode/classic/ClassicRightClick.java +++ b/src/main/java/dev/twme/debugstickpro/mode/classic/ClassicRightClick.java @@ -18,12 +18,10 @@ import java.util.UUID; public class ClassicRightClick { - public static void changeSelectedSubBlockDataValue(UUID playerUUID, PlayerData playerData) { + public static void changeSelectedSubBlockDataValue(UUID playerUUID, PlayerData playerData, Block block) { Player player = Bukkit.getPlayer(playerUUID); - Block block = player.getTargetBlockExact(5); - if (block == null) { return; } diff --git a/src/main/java/dev/twme/debugstickpro/mode/copy/CopyLeftClick.java b/src/main/java/dev/twme/debugstickpro/mode/copy/CopyLeftClick.java index 42a7735..f0e63e1 100644 --- a/src/main/java/dev/twme/debugstickpro/mode/copy/CopyLeftClick.java +++ b/src/main/java/dev/twme/debugstickpro/mode/copy/CopyLeftClick.java @@ -17,9 +17,8 @@ import dev.twme.debugstickpro.utils.AutoCheckCanChangeUtil; public class CopyLeftClick { - public static void onLeftClick(UUID playerUUID, PlayerData playerData) { + public static void onLeftClick(UUID playerUUID, PlayerData playerData, Block block) { Player player = Bukkit.getPlayer(playerUUID); - Block block = player.getTargetBlockExact(5); if (block == null) { return; diff --git a/src/main/java/dev/twme/debugstickpro/mode/copy/CopyRightClick.java b/src/main/java/dev/twme/debugstickpro/mode/copy/CopyRightClick.java index 01b4128..35b8067 100644 --- a/src/main/java/dev/twme/debugstickpro/mode/copy/CopyRightClick.java +++ b/src/main/java/dev/twme/debugstickpro/mode/copy/CopyRightClick.java @@ -19,9 +19,8 @@ import java.util.UUID; public class CopyRightClick { - public static void onRightClick(UUID playerUUID, PlayerData playerData) { + public static void onRightClick(UUID playerUUID, PlayerData playerData, Block block) { Player player = Bukkit.getPlayer(playerUUID); - Block block = player.getTargetBlockExact(5); if (block == null) { return; diff --git a/src/main/java/dev/twme/debugstickpro/playerdata/PlayerDataManager.java b/src/main/java/dev/twme/debugstickpro/playerdata/PlayerDataManager.java index 3e8b6e6..8ffd279 100644 --- a/src/main/java/dev/twme/debugstickpro/playerdata/PlayerDataManager.java +++ b/src/main/java/dev/twme/debugstickpro/playerdata/PlayerDataManager.java @@ -208,17 +208,18 @@ public static boolean modeChangeEventCancelled(UUID playerUUID, DebugStickMode p * player left click * * @param uuid player UUID + * @param targetBlock interaction target, or null when no block was targeted */ - public static void playerLeftClick(UUID uuid) { + public static void playerLeftClick(UUID uuid, Block targetBlock) { PlayerData playerData = getOrCreatePlayerData(uuid); switch (playerData.getDebugStickMode()) { case CLASSIC: - ClassicLeftClick.changeSelectedSubBlockType(uuid, playerData); + ClassicLeftClick.changeSelectedSubBlockType(uuid, playerData, targetBlock); break; case COPY: - CopyLeftClick.onLeftClick(uuid, playerData); + CopyLeftClick.onLeftClick(uuid, playerData, targetBlock); break; case FREEZE: FreezeLeftClick.onLeftClick(uuid); @@ -231,19 +232,20 @@ public static void playerLeftClick(UUID uuid) { * * @param uuid player UUID * @param action click action + * @param targetBlock interaction target, or null when no block was targeted * @param clickedBlock clicked block * @param clickedFace clicked face */ - public static void playerRightClick(UUID uuid, Action action, Block clickedBlock, BlockFace clickedFace) { + public static void playerRightClick(UUID uuid, Action action, Block targetBlock, Block clickedBlock, BlockFace clickedFace) { PlayerData playerData = getOrCreatePlayerData(uuid); switch (playerData.getDebugStickMode()) { case CLASSIC: - ClassicRightClick.changeSelectedSubBlockDataValue(uuid, playerData); + ClassicRightClick.changeSelectedSubBlockDataValue(uuid, playerData, targetBlock); break; case COPY: - CopyRightClick.onRightClick(uuid, playerData); + CopyRightClick.onRightClick(uuid, playerData, targetBlock); break; case FREEZE: FreezeRightClick.onRightClick(uuid, action, clickedBlock, clickedFace);