From 0ad399a528e15526490e2a705ba6bb19ca48b77e Mon Sep 17 00:00:00 2001 From: Jonathan Coates Date: Fri, 21 Jul 2023 08:58:02 +0100 Subject: [PATCH] Rewrite turtle.dig tool actions - Move the tool action before the "is block present" check, fixes #1527. This is where it was before, but we flipped it around in the tool rewrite. - Don't reuse as much turtle.place logic for tool actions. This fixes some instances where tools could till/level dirt through solid blocks. --- .../turtle/core/TurtlePlaceCommand.java | 26 ++++++++---- .../shared/turtle/upgrades/TurtleTool.java | 41 +++++++++++++++---- .../computercraft/gametest/Turtle_Test.kt | 24 ++++++++++- .../turtle_test.hoe_dirt_below.snbt | 40 ++++++++++++++++++ .../turtle_test.hoe_dirt_distant.snbt | 40 ++++++++++++++++++ 5 files changed, 154 insertions(+), 17 deletions(-) create mode 100644 projects/common/src/testMod/resources/data/cctest/structures/turtle_test.hoe_dirt_below.snbt create mode 100644 projects/common/src/testMod/resources/data/cctest/structures/turtle_test.hoe_dirt_distant.snbt diff --git a/projects/common/src/main/java/dan200/computercraft/shared/turtle/core/TurtlePlaceCommand.java b/projects/common/src/main/java/dan200/computercraft/shared/turtle/core/TurtlePlaceCommand.java index d1b9156b3..47f8a7b3f 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/turtle/core/TurtlePlaceCommand.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/turtle/core/TurtlePlaceCommand.java @@ -74,7 +74,7 @@ public TurtleCommandResult execute(ITurtleAccess turtle) { } } - public static boolean deploy( + private static boolean deploy( ItemStack stack, ITurtleAccess turtle, TurtlePlayer turtlePlayer, Direction direction, @Nullable Object[] extraArguments, @Nullable ErrorMessage outErrorMessage ) { @@ -140,6 +140,22 @@ private static boolean canDeployOnBlock( return true; } + /** + * Calculate where a turtle would interact with a block. + * + * @param position The position of the block. + * @param side The side the turtle is clicking on. + * @return The hit result. + */ + public static BlockHitResult getHitResult(BlockPos position, Direction side) { + var hitX = 0.5 + side.getStepX() * 0.5; + var hitY = 0.5 + side.getStepY() * 0.5; + var hitZ = 0.5 + side.getStepZ() * 0.5; + if (Math.abs(hitY - 0.5) < 0.01) hitY = 0.45; + + return new BlockHitResult(new Vec3(position.getX() + hitX, position.getY() + hitY, position.getZ() + hitZ), side, position, false); + } + private static boolean deployOnBlock( ItemStack stack, ITurtleAccess turtle, TurtlePlayer turtlePlayer, BlockPos position, Direction side, @Nullable Object[] extraArguments, boolean adjacent, @Nullable ErrorMessage outErrorMessage @@ -149,14 +165,8 @@ private static boolean deployOnBlock( var playerPosition = position.relative(side); turtlePlayer.setPosition(turtle, playerPosition, playerDir); - // Calculate where the turtle would hit the block - var hitX = 0.5f + side.getStepX() * 0.5f; - var hitY = 0.5f + side.getStepY() * 0.5f; - var hitZ = 0.5f + side.getStepZ() * 0.5f; - if (Math.abs(hitY - 0.5f) < 0.01f) hitY = 0.45f; - // Check if there's something suitable to place onto - var hit = new BlockHitResult(new Vec3(position.getX() + hitX, position.getY() + hitY, position.getZ() + hitZ), side, position, false); + var hit = getHitResult(position, side); var context = new UseOnContext(turtlePlayer.player(), InteractionHand.MAIN_HAND, hit); if (!canDeployOnBlock(new BlockPlaceContext(context), turtle, turtlePlayer, position, side, adjacent, outErrorMessage)) { return false; diff --git a/projects/common/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleTool.java b/projects/common/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleTool.java index 5ca3fd87f..b34ec8180 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleTool.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleTool.java @@ -294,19 +294,20 @@ private boolean attack(ServerPlayer player, Direction direction, Entity entity) private TurtleCommandResult dig(ITurtleAccess turtle, TurtleSide side, Direction direction) { var level = (ServerLevel) turtle.getLevel(); - var blockPosition = turtle.getPosition().relative(direction); - if (level.isEmptyBlock(blockPosition) || WorldUtil.isLiquidBlock(level, blockPosition)) { - return TurtleCommandResult.failure("Nothing to dig here"); - } - return withEquippedItem(turtle, side, direction, turtlePlayer -> { var stack = turtlePlayer.player().getItemInHand(InteractionHand.MAIN_HAND); - // Right-click the block when using a shovel/hoe. - if (PlatformHelper.get().hasToolUsage(item) && TurtlePlaceCommand.deploy(stack, turtle, turtlePlayer, direction, null, null)) { + // Right-click the block when using a shovel/hoe. Important that we do this before checking the block is + // present, as we allow doing these actions from slightly further away. + if (PlatformHelper.get().hasToolUsage(stack) && useTool(level, turtle, turtlePlayer, stack, direction)) { return TurtleCommandResult.success(); } + var blockPosition = turtle.getPosition().relative(direction); + if (level.isEmptyBlock(blockPosition) || WorldUtil.isLiquidBlock(level, blockPosition)) { + return TurtleCommandResult.failure("Nothing to dig here"); + } + // Check if we can break the block var breakable = checkBlockBreakable(level, blockPosition, turtlePlayer); if (!breakable.isSuccess()) return breakable; @@ -320,6 +321,32 @@ private TurtleCommandResult dig(ITurtleAccess turtle, TurtleSide side, Direction }); } + /** + * Attempt to use a tool against a block instead. + * + * @param level The current level. + * @param turtle The current turtle. + * @param turtlePlayer The turtle player, already positioned and with a stack equipped. + * @param stack The current tool's stack. + * @param direction The direction this action occurs in. + * @return Whether the tool was successfully used. + * @see PlatformHelper#hasToolUsage(ItemStack) + */ + private boolean useTool(ServerLevel level, ITurtleAccess turtle, TurtlePlayer turtlePlayer, ItemStack stack, Direction direction) { + var position = turtle.getPosition().relative(direction); + // Allow digging one extra block below the turtle, as you can't till dirt/flatten grass if there's a block + // above. + if (direction == Direction.DOWN && level.isEmptyBlock(position)) position = position.relative(direction); + + if (!level.isInWorldBounds(position) || level.isEmptyBlock(position) || turtlePlayer.isBlockProtected(level, position)) { + return false; + } + + var hit = TurtlePlaceCommand.getHitResult(position, direction.getOpposite()); + var result = PlatformHelper.get().useOn(turtlePlayer.player(), stack, hit, x -> false); + return result.consumesAction(); + } + private static boolean isTriviallyBreakable(BlockGetter reader, BlockPos pos, BlockState state) { return state.is(ComputerCraftTags.Blocks.TURTLE_ALWAYS_BREAKABLE) // Allow breaking any "instabreak" block. diff --git a/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Turtle_Test.kt b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Turtle_Test.kt index 94f226dc3..4d2d67e48 100644 --- a/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Turtle_Test.kt +++ b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Turtle_Test.kt @@ -145,11 +145,31 @@ fun Gather_lava(helper: GameTestHelper) = helper.sequence { */ @GameTest fun Hoe_dirt(helper: GameTestHelper) = helper.sequence { + thenOnComputer { turtle.dig(Optional.empty()).await().assertArrayEquals(true, message = "Dug with hoe") } + thenExecute { helper.assertBlockPresent(Blocks.FARMLAND, BlockPos(1, 2, 1)) } + } + + /** + * Checks turtles can hoe dirt with a block gap below them. + * + * @see [#1527](https://github.com/cc-tweaked/CC-Tweaked/issues/1527) + */ + @GameTest + fun Hoe_dirt_below(helper: GameTestHelper) = helper.sequence { + thenOnComputer { turtle.digDown(Optional.empty()).await().assertArrayEquals(true, message = "Dug with hoe") } + thenExecute { helper.assertBlockPresent(Blocks.FARMLAND, BlockPos(1, 1, 1)) } + } + + /** + * Checks turtles cannot hoe dirt with a block gap in front of them. + */ + @GameTest + fun Hoe_dirt_distant(helper: GameTestHelper) = helper.sequence { thenOnComputer { turtle.dig(Optional.empty()).await() - .assertArrayEquals(true, message = "Dug with hoe") + .assertArrayEquals(false, "Nothing to dig here", message = "Dug with hoe") } - thenExecute { helper.assertBlockPresent(Blocks.FARMLAND, BlockPos(1, 2, 1)) } + thenExecute { helper.assertBlockPresent(Blocks.DIRT, BlockPos(1, 2, 2)) } } /** diff --git a/projects/common/src/testMod/resources/data/cctest/structures/turtle_test.hoe_dirt_below.snbt b/projects/common/src/testMod/resources/data/cctest/structures/turtle_test.hoe_dirt_below.snbt new file mode 100644 index 000000000..f5ed47357 --- /dev/null +++ b/projects/common/src/testMod/resources/data/cctest/structures/turtle_test.hoe_dirt_below.snbt @@ -0,0 +1,40 @@ +{ + DataVersion: 2730, + size: [3, 3, 3], + data: [ + {pos: [0, 0, 0], state: "minecraft:polished_andesite"}, + {pos: [0, 0, 1], state: "minecraft:polished_andesite"}, + {pos: [0, 0, 2], state: "minecraft:polished_andesite"}, + {pos: [1, 0, 0], state: "minecraft:polished_andesite"}, + {pos: [1, 0, 1], state: "minecraft:dirt"}, + {pos: [1, 0, 2], state: "minecraft:polished_andesite"}, + {pos: [2, 0, 0], state: "minecraft:polished_andesite"}, + {pos: [2, 0, 1], state: "minecraft:polished_andesite"}, + {pos: [2, 0, 2], state: "minecraft:polished_andesite"}, + {pos: [0, 1, 0], state: "minecraft:air"}, + {pos: [0, 1, 1], state: "minecraft:air"}, + {pos: [0, 1, 2], state: "minecraft:air"}, + {pos: [1, 1, 0], state: "minecraft:air"}, + {pos: [1, 1, 1], state: "minecraft:air"}, + {pos: [1, 1, 2], state: "minecraft:air"}, + {pos: [2, 1, 0], state: "minecraft:air"}, + {pos: [2, 1, 1], state: "minecraft:air"}, + {pos: [2, 1, 2], state: "minecraft:air"}, + {pos: [0, 2, 0], state: "minecraft:air"}, + {pos: [0, 2, 1], state: "minecraft:air"}, + {pos: [0, 2, 2], state: "minecraft:air"}, + {pos: [1, 2, 0], state: "minecraft:air"}, + {pos: [1, 2, 1], state: "computercraft:turtle_normal{facing:south,waterlogged:false}", nbt: {ComputerId: 1, Fuel: 0, Items: [], Label: "turtle_test.hoe_dirt_below", LeftUpgrade: "minecraft:diamond_hoe", On: 1b, Owner: {LowerId: -6876936588741668278L, Name: "Dev", UpperId: 4039158846114182220L}, Slot: 0, id: "computercraft:turtle_normal"}}, + {pos: [1, 2, 2], state: "minecraft:air"}, + {pos: [2, 2, 0], state: "minecraft:air"}, + {pos: [2, 2, 1], state: "minecraft:air"}, + {pos: [2, 2, 2], state: "minecraft:air"} + ], + entities: [], + palette: [ + "minecraft:polished_andesite", + "minecraft:dirt", + "computercraft:turtle_normal{facing:south,waterlogged:false}", + "minecraft:air" + ] +} diff --git a/projects/common/src/testMod/resources/data/cctest/structures/turtle_test.hoe_dirt_distant.snbt b/projects/common/src/testMod/resources/data/cctest/structures/turtle_test.hoe_dirt_distant.snbt new file mode 100644 index 000000000..79deeda88 --- /dev/null +++ b/projects/common/src/testMod/resources/data/cctest/structures/turtle_test.hoe_dirt_distant.snbt @@ -0,0 +1,40 @@ +{ + DataVersion: 2730, + size: [3, 3, 3], + data: [ + {pos: [0, 0, 0], state: "minecraft:polished_andesite"}, + {pos: [0, 0, 1], state: "minecraft:polished_andesite"}, + {pos: [0, 0, 2], state: "minecraft:polished_andesite"}, + {pos: [1, 0, 0], state: "minecraft:polished_andesite"}, + {pos: [1, 0, 1], state: "minecraft:polished_andesite"}, + {pos: [1, 0, 2], state: "minecraft:polished_andesite"}, + {pos: [2, 0, 0], state: "minecraft:polished_andesite"}, + {pos: [2, 0, 1], state: "minecraft:polished_andesite"}, + {pos: [2, 0, 2], state: "minecraft:polished_andesite"}, + {pos: [0, 1, 0], state: "minecraft:air"}, + {pos: [0, 1, 1], state: "minecraft:air"}, + {pos: [0, 1, 2], state: "minecraft:air"}, + {pos: [1, 1, 0], state: "computercraft:turtle_normal{facing:south,waterlogged:false}", nbt: {ComputerId: 1, Fuel: 0, Items: [], Label: "turtle_test.hoe_dirt_distant", LeftUpgrade: "minecraft:diamond_hoe", On: 1b, Owner: {LowerId: -6876936588741668278L, Name: "Dev", UpperId: 4039158846114182220L}, Slot: 0, id: "computercraft:turtle_normal"}}, + {pos: [1, 1, 1], state: "minecraft:air"}, + {pos: [1, 1, 2], state: "minecraft:dirt"}, + {pos: [2, 1, 0], state: "minecraft:air"}, + {pos: [2, 1, 1], state: "minecraft:air"}, + {pos: [2, 1, 2], state: "minecraft:air"}, + {pos: [0, 2, 0], state: "minecraft:air"}, + {pos: [0, 2, 1], state: "minecraft:air"}, + {pos: [0, 2, 2], state: "minecraft:air"}, + {pos: [1, 2, 0], state: "minecraft:air"}, + {pos: [1, 2, 1], state: "minecraft:air"}, + {pos: [1, 2, 2], state: "minecraft:air"}, + {pos: [2, 2, 0], state: "minecraft:air"}, + {pos: [2, 2, 1], state: "minecraft:air"}, + {pos: [2, 2, 2], state: "minecraft:air"} + ], + entities: [], + palette: [ + "minecraft:polished_andesite", + "minecraft:dirt", + "computercraft:turtle_normal{facing:south,waterlogged:false}", + "minecraft:air" + ] +}