From 9a914e75c4a4e122c16aac5d010059d28b475b5e Mon Sep 17 00:00:00 2001 From: Jonathan Coates Date: Sun, 12 Jan 2025 22:01:43 +0000 Subject: [PATCH 1/7] Rethink PlatformHelper.useOn useOn is now only responsible for firing the actual mod loader events, and just returns the result of firing that event. The actual calling of Block.use/Item.useOn now live in TurtlePlaceCommand. This isn't especially useful for 1.20.1, but is more relevant on 1.21.1 when we look at #2011, as the shared code is much larger. --- .../shared/platform/PlatformHelper.java | 48 ++++-- .../shared/pocket/core/PocketHolder.java | 4 +- .../turtle/core/TurtlePlaceCommand.java | 21 ++- .../shared/turtle/upgrades/TurtleTool.java | 9 +- .../computercraft/TestPlatformHelper.java | 3 +- .../computercraft/gametest/Turtle_Test.kt | 16 ++ ...est.place_into_composter_non_adjacent.snbt | 138 ++++++++++++++++++ .../shared/platform/PlatformHelperImpl.java | 19 +-- .../shared/platform/PlatformHelperImpl.java | 20 +-- 9 files changed, 229 insertions(+), 49 deletions(-) create mode 100644 projects/common/src/testMod/resources/data/cctest/structures/turtle_test.place_into_composter_non_adjacent.snbt diff --git a/projects/common/src/main/java/dan200/computercraft/shared/platform/PlatformHelper.java b/projects/common/src/main/java/dan200/computercraft/shared/platform/PlatformHelper.java index 883a47b39..a0553c6bb 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/platform/PlatformHelper.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/platform/PlatformHelper.java @@ -39,6 +39,7 @@ import net.minecraft.world.item.CreativeModeTab; import net.minecraft.world.item.DyeColor; import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.context.UseOnContext; import net.minecraft.world.item.crafting.Recipe; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.Block; @@ -53,7 +54,6 @@ import java.util.List; import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.Function; -import java.util.function.Predicate; /** * This extends {@linkplain dan200.computercraft.impl.PlatformHelper the API's loader abstraction layer}, adding @@ -375,20 +375,40 @@ public interface PlatformHelper extends dan200.computercraft.impl.PlatformHelper boolean interactWithEntity(ServerPlayer player, Entity entity, Vec3 hitPos); /** - * Place an item against a block. - *

- * Implementations should largely mirror {@link ServerPlayerGameMode#useItemOn(ServerPlayer, Level, ItemStack, InteractionHand, BlockHitResult)} - * (including any loader-specific modifications), except the call to {@link BlockState#use(Level, Player, InteractionHand, BlockHitResult)} - * should only be evaluated when {@code canUseBlock} evaluates to true. - * - * @param player The player which is placing this item. - * @param stack The item to place. - * @param hit The collision with the block we're placing against. - * @param canUseBlock Test whether the block should be interacted with first. - * @return Whether any interaction occurred. - * @see ServerPlayerGameMode#useItemOn(ServerPlayer, Level, ItemStack, InteractionHand, BlockHitResult) + * The result of attempting to use an item on a block. */ - InteractionResult useOn(ServerPlayer player, ItemStack stack, BlockHitResult hit, Predicate canUseBlock); + sealed interface UseOnResult { + /** + * This interaction was intercepted by an event, and handled. + * + * @param result The result of using an item on a block. + */ + record Handled(InteractionResult result) implements UseOnResult { + } + + /** + * This result was not handled, and should be handled by the caller. + * + * @param block Whether the block may be used ({@link BlockState#use(Level, Player, InteractionHand, BlockHitResult)}). + * @param item Whether the item may be used on the block ({@link ItemStack#useOn(UseOnContext)}). + * @see ServerPlayerGameMode#useItemOn(ServerPlayer, Level, ItemStack, InteractionHand, BlockHitResult) + */ + record Continue(boolean block, boolean item) implements UseOnResult { + } + } + + /** + * Run mod-loader specific code before placing an item against a block. + *

+ * This should dispatch any mod-loader specific events that are fired when clicking a block. It does necessarily + * handle the actual clicking of the block — see {@link UseOnResult.Handled} and {@link UseOnResult.Continue}. + * + * @param player The player which is placing this item. + * @param stack The item to place. + * @param hit The collision with the block we're placing against. + * @return Whether any interaction occurred. + */ + UseOnResult useOn(ServerPlayer player, ItemStack stack, BlockHitResult hit); /** * Whether {@link net.minecraft.network.chat.ClickEvent.Action#RUN_COMMAND} can be used to run client commands. diff --git a/projects/common/src/main/java/dan200/computercraft/shared/pocket/core/PocketHolder.java b/projects/common/src/main/java/dan200/computercraft/shared/pocket/core/PocketHolder.java index 16dc81a5d..d9b8c9196 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/pocket/core/PocketHolder.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/pocket/core/PocketHolder.java @@ -16,7 +16,7 @@ import net.minecraft.world.phys.Vec3; /** * An object that holds a pocket computer item. */ -public sealed interface PocketHolder permits PocketHolder.EntityHolder { +public sealed interface PocketHolder { /** * The level this holder is in. * @@ -54,7 +54,7 @@ public sealed interface PocketHolder permits PocketHolder.EntityHolder { /** * An {@link Entity} holding a pocket computer. */ - sealed interface EntityHolder extends PocketHolder permits PocketHolder.PlayerHolder, PocketHolder.ItemEntityHolder { + sealed interface EntityHolder extends PocketHolder { /** * Get the entity holding this pocket computer. * 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 cad9fc63e..82bbbe8c7 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 @@ -202,11 +202,22 @@ public class TurtlePlaceCommand implements TurtleCommand { * @return If this item was deployed. */ private static InteractionResult doDeployOnBlock(ItemStack stack, TurtlePlayer turtlePlayer, BlockHitResult hit, boolean adjacent) { - var result = PlatformHelper.get().useOn( - turtlePlayer.player(), stack, hit, - adjacent ? x -> x.is(ComputerCraftTags.Blocks.TURTLE_CAN_USE) : x -> false - ); - if (result != InteractionResult.PASS) return result; + var result = PlatformHelper.get().useOn(turtlePlayer.player(), stack, hit); + if (result instanceof PlatformHelper.UseOnResult.Handled handled) { + if (handled.result() != InteractionResult.PASS) return handled.result(); + } else { + var canUse = (PlatformHelper.UseOnResult.Continue) result; + + var player = turtlePlayer.player(); + var block = player.level().getBlockState(hit.getBlockPos()); + if (adjacent && canUse.block() && block.is(ComputerCraftTags.Blocks.TURTLE_CAN_USE)) { + var useResult = block.use(player.level(), player, InteractionHand.MAIN_HAND, hit); + if (useResult.consumesAction()) return useResult; + } + + var useOnResult = stack.useOn(new UseOnContext(player, InteractionHand.MAIN_HAND, hit)); + if (useOnResult != InteractionResult.PASS) return useOnResult; + } var level = turtlePlayer.player().level(); 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 6341b1f70..82c48bb83 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 @@ -29,6 +29,7 @@ import net.minecraft.world.entity.decoration.ArmorStand; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.context.UseOnContext; import net.minecraft.world.item.enchantment.EnchantmentHelper; import net.minecraft.world.level.BlockGetter; import net.minecraft.world.level.Level; @@ -343,8 +344,12 @@ public class TurtleTool extends AbstractTurtleUpgrade { } var hit = TurtlePlaceCommand.getHitResult(position, direction.getOpposite()); - var result = PlatformHelper.get().useOn(turtlePlayer.player(), stack, hit, x -> false); - return result.consumesAction(); + var result = PlatformHelper.get().useOn(turtlePlayer.player(), stack, hit); + if (result instanceof PlatformHelper.UseOnResult.Handled handled) { + return handled.result().consumesAction(); + } else { + return ((PlatformHelper.UseOnResult.Continue) result).item() && item.useOn(new UseOnContext(turtlePlayer.player(), InteractionHand.MAIN_HAND, hit)).consumesAction(); + } } private static boolean isTriviallyBreakable(BlockGetter reader, BlockPos pos, BlockState state) { diff --git a/projects/common/src/test/java/dan200/computercraft/TestPlatformHelper.java b/projects/common/src/test/java/dan200/computercraft/TestPlatformHelper.java index 815c716db..879e996d0 100644 --- a/projects/common/src/test/java/dan200/computercraft/TestPlatformHelper.java +++ b/projects/common/src/test/java/dan200/computercraft/TestPlatformHelper.java @@ -59,7 +59,6 @@ import java.util.List; import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.Function; -import java.util.function.Predicate; @AutoService({ PlatformHelper.class, dan200.computercraft.impl.PlatformHelper.class, ComputerCraftAPIService.class }) public class TestPlatformHelper extends AbstractComputerCraftAPI implements PlatformHelper { @@ -223,7 +222,7 @@ public class TestPlatformHelper extends AbstractComputerCraftAPI implements Plat } @Override - public InteractionResult useOn(ServerPlayer player, ItemStack stack, BlockHitResult hit, Predicate canUseBlock) { + public UseOnResult useOn(ServerPlayer player, ItemStack stack, BlockHitResult hit) { throw new UnsupportedOperationException("Cannot interact with the world inside tests"); } 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 c6e68c791..1b1f58ade 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 @@ -142,6 +142,22 @@ class Turtle_Test { } } + /** + * Checks that turtles cannot place items into non-adjacent blocks. + * + * See [ComputerCraftTags.Blocks.TURTLE_CAN_USE]. + */ + @GameTest + fun Place_into_composter_non_adjacent(helper: GameTestHelper) = helper.sequence { + thenOnComputer { + turtle.place(ObjectArguments()).await() + .assertArrayEquals(false, "Cannot place item here", message = "Failed to place item") + } + thenExecute { + helper.assertBlockIs(BlockPos(2, 2, 3)) { it.block == Blocks.COMPOSTER && it.getValue(ComposterBlock.LEVEL) == 0 } + } + } + /** * Checks that turtles can place bottles into beehives. * diff --git a/projects/common/src/testMod/resources/data/cctest/structures/turtle_test.place_into_composter_non_adjacent.snbt b/projects/common/src/testMod/resources/data/cctest/structures/turtle_test.place_into_composter_non_adjacent.snbt new file mode 100644 index 000000000..cb3dc3225 --- /dev/null +++ b/projects/common/src/testMod/resources/data/cctest/structures/turtle_test.place_into_composter_non_adjacent.snbt @@ -0,0 +1,138 @@ +{ + DataVersion: 3465, + size: [5, 5, 5], + 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: [0, 0, 3], state: "minecraft:polished_andesite"}, + {pos: [0, 0, 4], 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: [1, 0, 3], state: "minecraft:polished_andesite"}, + {pos: [1, 0, 4], 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: [2, 0, 3], state: "minecraft:polished_andesite"}, + {pos: [2, 0, 4], state: "minecraft:polished_andesite"}, + {pos: [3, 0, 0], state: "minecraft:polished_andesite"}, + {pos: [3, 0, 1], state: "minecraft:polished_andesite"}, + {pos: [3, 0, 2], state: "minecraft:polished_andesite"}, + {pos: [3, 0, 3], state: "minecraft:polished_andesite"}, + {pos: [3, 0, 4], state: "minecraft:polished_andesite"}, + {pos: [4, 0, 0], state: "minecraft:polished_andesite"}, + {pos: [4, 0, 1], state: "minecraft:polished_andesite"}, + {pos: [4, 0, 2], state: "minecraft:polished_andesite"}, + {pos: [4, 0, 3], state: "minecraft:polished_andesite"}, + {pos: [4, 0, 4], 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: [0, 1, 3], state: "minecraft:air"}, + {pos: [0, 1, 4], 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: [1, 1, 3], state: "minecraft:air"}, + {pos: [1, 1, 4], state: "minecraft:air"}, + {pos: [2, 1, 0], state: "minecraft:air"}, + {pos: [2, 1, 1], state: "computercraft:turtle_normal{facing:south,waterlogged:false}", nbt: {ComputerId: 1, Fuel: 0, Items: [{Count: 1b, Slot: 0b, id: "minecraft:pumpkin_pie"}], Label: "turtle_test.place_into_composter_non_adjacent", On: 1b, Owner: {LowerId: -7298459922670553123L, Name: "Player572", UpperId: -8225029765375707172L}, Slot: 0, id: "computercraft:turtle_normal"}}, + {pos: [2, 1, 2], state: "minecraft:air"}, + {pos: [2, 1, 3], state: "minecraft:composter{level:0}"}, + {pos: [2, 1, 4], state: "minecraft:air"}, + {pos: [3, 1, 0], state: "minecraft:air"}, + {pos: [3, 1, 1], state: "minecraft:air"}, + {pos: [3, 1, 2], state: "minecraft:air"}, + {pos: [3, 1, 3], state: "minecraft:air"}, + {pos: [3, 1, 4], state: "minecraft:air"}, + {pos: [4, 1, 0], state: "minecraft:air"}, + {pos: [4, 1, 1], state: "minecraft:air"}, + {pos: [4, 1, 2], state: "minecraft:air"}, + {pos: [4, 1, 3], state: "minecraft:air"}, + {pos: [4, 1, 4], 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: [0, 2, 3], state: "minecraft:air"}, + {pos: [0, 2, 4], 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: [1, 2, 3], state: "minecraft:air"}, + {pos: [1, 2, 4], state: "minecraft:air"}, + {pos: [2, 2, 0], state: "minecraft:air"}, + {pos: [2, 2, 1], state: "minecraft:air"}, + {pos: [2, 2, 2], state: "minecraft:air"}, + {pos: [2, 2, 3], state: "minecraft:air"}, + {pos: [2, 2, 4], state: "minecraft:air"}, + {pos: [3, 2, 0], state: "minecraft:air"}, + {pos: [3, 2, 1], state: "minecraft:air"}, + {pos: [3, 2, 2], state: "minecraft:air"}, + {pos: [3, 2, 3], state: "minecraft:air"}, + {pos: [3, 2, 4], state: "minecraft:air"}, + {pos: [4, 2, 0], state: "minecraft:air"}, + {pos: [4, 2, 1], state: "minecraft:air"}, + {pos: [4, 2, 2], state: "minecraft:air"}, + {pos: [4, 2, 3], state: "minecraft:air"}, + {pos: [4, 2, 4], state: "minecraft:air"}, + {pos: [0, 3, 0], state: "minecraft:air"}, + {pos: [0, 3, 1], state: "minecraft:air"}, + {pos: [0, 3, 2], state: "minecraft:air"}, + {pos: [0, 3, 3], state: "minecraft:air"}, + {pos: [0, 3, 4], state: "minecraft:air"}, + {pos: [1, 3, 0], state: "minecraft:air"}, + {pos: [1, 3, 1], state: "minecraft:air"}, + {pos: [1, 3, 2], state: "minecraft:air"}, + {pos: [1, 3, 3], state: "minecraft:air"}, + {pos: [1, 3, 4], state: "minecraft:air"}, + {pos: [2, 3, 0], state: "minecraft:air"}, + {pos: [2, 3, 1], state: "minecraft:air"}, + {pos: [2, 3, 2], state: "minecraft:air"}, + {pos: [2, 3, 3], state: "minecraft:air"}, + {pos: [2, 3, 4], state: "minecraft:air"}, + {pos: [3, 3, 0], state: "minecraft:air"}, + {pos: [3, 3, 1], state: "minecraft:air"}, + {pos: [3, 3, 2], state: "minecraft:air"}, + {pos: [3, 3, 3], state: "minecraft:air"}, + {pos: [3, 3, 4], state: "minecraft:air"}, + {pos: [4, 3, 0], state: "minecraft:air"}, + {pos: [4, 3, 1], state: "minecraft:air"}, + {pos: [4, 3, 2], state: "minecraft:air"}, + {pos: [4, 3, 3], state: "minecraft:air"}, + {pos: [4, 3, 4], state: "minecraft:air"}, + {pos: [0, 4, 0], state: "minecraft:air"}, + {pos: [0, 4, 1], state: "minecraft:air"}, + {pos: [0, 4, 2], state: "minecraft:air"}, + {pos: [0, 4, 3], state: "minecraft:air"}, + {pos: [0, 4, 4], state: "minecraft:air"}, + {pos: [1, 4, 0], state: "minecraft:air"}, + {pos: [1, 4, 1], state: "minecraft:air"}, + {pos: [1, 4, 2], state: "minecraft:air"}, + {pos: [1, 4, 3], state: "minecraft:air"}, + {pos: [1, 4, 4], state: "minecraft:air"}, + {pos: [2, 4, 0], state: "minecraft:air"}, + {pos: [2, 4, 1], state: "minecraft:air"}, + {pos: [2, 4, 2], state: "minecraft:air"}, + {pos: [2, 4, 3], state: "minecraft:air"}, + {pos: [2, 4, 4], state: "minecraft:air"}, + {pos: [3, 4, 0], state: "minecraft:air"}, + {pos: [3, 4, 1], state: "minecraft:air"}, + {pos: [3, 4, 2], state: "minecraft:air"}, + {pos: [3, 4, 3], state: "minecraft:air"}, + {pos: [3, 4, 4], state: "minecraft:air"}, + {pos: [4, 4, 0], state: "minecraft:air"}, + {pos: [4, 4, 1], state: "minecraft:air"}, + {pos: [4, 4, 2], state: "minecraft:air"}, + {pos: [4, 4, 3], state: "minecraft:air"}, + {pos: [4, 4, 4], state: "minecraft:air"} + ], + entities: [], + palette: [ + "minecraft:polished_andesite", + "minecraft:air", + "minecraft:composter{level:0}", + "computercraft:turtle_normal{facing:south,waterlogged:false}" + ] +} diff --git a/projects/fabric/src/main/java/dan200/computercraft/shared/platform/PlatformHelperImpl.java b/projects/fabric/src/main/java/dan200/computercraft/shared/platform/PlatformHelperImpl.java index 3ed6f52c0..72bccefb7 100644 --- a/projects/fabric/src/main/java/dan200/computercraft/shared/platform/PlatformHelperImpl.java +++ b/projects/fabric/src/main/java/dan200/computercraft/shared/platform/PlatformHelperImpl.java @@ -67,7 +67,6 @@ import net.minecraft.world.inventory.AbstractContainerMenu; import net.minecraft.world.inventory.CraftingContainer; import net.minecraft.world.inventory.MenuType; import net.minecraft.world.item.*; -import net.minecraft.world.item.context.UseOnContext; import net.minecraft.world.item.crafting.Ingredient; import net.minecraft.world.item.crafting.Recipe; import net.minecraft.world.level.Level; @@ -84,7 +83,10 @@ import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Objects; -import java.util.function.*; +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; @AutoService(dan200.computercraft.impl.PlatformHelper.class) public class PlatformHelperImpl implements PlatformHelper { @@ -309,17 +311,10 @@ public class PlatformHelperImpl implements PlatformHelper { } @Override - public InteractionResult useOn(ServerPlayer player, ItemStack stack, BlockHitResult hit, Predicate canUseBlock) { + public UseOnResult useOn(ServerPlayer player, ItemStack stack, BlockHitResult hit) { var result = UseBlockCallback.EVENT.invoker().interact(player, player.level(), InteractionHand.MAIN_HAND, hit); - if (result != InteractionResult.PASS) return result; - - var block = player.level().getBlockState(hit.getBlockPos()); - if (!block.isAir() && canUseBlock.test(block)) { - var useResult = block.use(player.level(), player, InteractionHand.MAIN_HAND, hit); - if (useResult.consumesAction()) return useResult; - } - - return stack.useOn(new UseOnContext(player, InteractionHand.MAIN_HAND, hit)); + if (result != InteractionResult.PASS) return new UseOnResult.Handled(result); + return new UseOnResult.Continue(true, true); } private record RegistryWrapperImpl( diff --git a/projects/forge/src/main/java/dan200/computercraft/shared/platform/PlatformHelperImpl.java b/projects/forge/src/main/java/dan200/computercraft/shared/platform/PlatformHelperImpl.java index 698c5ce4f..7e84f4ab3 100644 --- a/projects/forge/src/main/java/dan200/computercraft/shared/platform/PlatformHelperImpl.java +++ b/projects/forge/src/main/java/dan200/computercraft/shared/platform/PlatformHelperImpl.java @@ -78,7 +78,10 @@ import net.minecraftforge.registries.RegistryObject; import javax.annotation.Nullable; import java.util.*; -import java.util.function.*; +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; @AutoService(dan200.computercraft.impl.PlatformHelper.class) public class PlatformHelperImpl implements PlatformHelper { @@ -324,25 +327,18 @@ public class PlatformHelperImpl implements PlatformHelper { } @Override - public InteractionResult useOn(ServerPlayer player, ItemStack stack, BlockHitResult hit, Predicate canUseBlock) { - var level = player.level(); + public UseOnResult useOn(ServerPlayer player, ItemStack stack, BlockHitResult hit) { var pos = hit.getBlockPos(); var event = ForgeHooks.onRightClickBlock(player, InteractionHand.MAIN_HAND, pos, hit); - if (event.isCanceled()) return event.getCancellationResult(); + if (event.isCanceled()) return new UseOnResult.Handled(event.getCancellationResult()); var context = new UseOnContext(player, InteractionHand.MAIN_HAND, hit); if (event.getUseItem() != Event.Result.DENY) { var result = stack.onItemUseFirst(context); - if (result != InteractionResult.PASS) return result; + if (result != InteractionResult.PASS) return new UseOnResult.Handled(event.getCancellationResult()); } - var block = level.getBlockState(hit.getBlockPos()); - if (event.getUseBlock() != Event.Result.DENY && !block.isAir() && canUseBlock.test(block)) { - var useResult = block.use(level, player, InteractionHand.MAIN_HAND, hit); - if (useResult.consumesAction()) return useResult; - } - - return event.getUseItem() == Event.Result.DENY ? InteractionResult.PASS : stack.useOn(context); + return new UseOnResult.Continue(event.getUseBlock() != Event.Result.DENY, event.getUseItem() != Event.Result.DENY); } @Override From ef0af67e96d492ce19f71bc52f123842f992b02a Mon Sep 17 00:00:00 2001 From: Jonathan Coates Date: Mon, 13 Jan 2025 00:00:10 +0000 Subject: [PATCH 2/7] Share some dependency exclusion code - Disable Gradle module metadata for all Minecraft projects - Run dependency exclusion code for all projects We need the former for MDG on 1.21, so might as well do some other cleanup while we're here. --- .../kotlin/cc-tweaked.publishing.gradle.kts | 1 + .../cc/tweaked/gradle/CCTweakedExtension.kt | 9 +-- .../cc/tweaked/gradle/CCTweakedPlugin.kt | 10 ++++ .../cc/tweaked/gradle/MavenDependencySpec.kt | 57 +++++-------------- .../tweaked/gradle/MinecraftConfigurations.kt | 8 +++ projects/common/build.gradle.kts | 2 - projects/fabric/build.gradle.kts | 11 ---- projects/forge/build.gradle.kts | 13 ----- 8 files changed, 35 insertions(+), 76 deletions(-) diff --git a/buildSrc/src/main/kotlin/cc-tweaked.publishing.gradle.kts b/buildSrc/src/main/kotlin/cc-tweaked.publishing.gradle.kts index 51a8dd943..a3792d67d 100644 --- a/buildSrc/src/main/kotlin/cc-tweaked.publishing.gradle.kts +++ b/buildSrc/src/main/kotlin/cc-tweaked.publishing.gradle.kts @@ -12,6 +12,7 @@ publishing { register("maven") { artifactId = base.archivesName.get() from(components["java"]) + suppressAllPomMetadataWarnings() pom { name = "CC: Tweaked" diff --git a/buildSrc/src/main/kotlin/cc/tweaked/gradle/CCTweakedExtension.kt b/buildSrc/src/main/kotlin/cc/tweaked/gradle/CCTweakedExtension.kt index f97bc8084..2f6d323b5 100644 --- a/buildSrc/src/main/kotlin/cc/tweaked/gradle/CCTweakedExtension.kt +++ b/buildSrc/src/main/kotlin/cc/tweaked/gradle/CCTweakedExtension.kt @@ -67,7 +67,7 @@ abstract class CCTweakedExtension( /** * Dependencies excluded from published artifacts. */ - private val excludedDeps: ListProperty = project.objects.listProperty(Dependency::class.java) + internal val excludedDeps: ListProperty = project.objects.listProperty(Dependency::class.java) /** All source sets referenced by this project. */ val sourceSets = sourceDirectories.map { x -> x.map { it.sourceSet } } @@ -249,13 +249,6 @@ abstract class CCTweakedExtension( excludedDeps.add(dep) } - /** - * Configure a [MavenDependencySpec]. - */ - fun configureExcludes(spec: MavenDependencySpec) { - for (dep in excludedDeps.get()) spec.exclude(dep) - } - private fun gitProvider(default: T, command: List, process: (String) -> T): Provider { val baseResult = project.providers.exec { commandLine = listOf("git", "-C", project.rootDir.absolutePath) + command diff --git a/buildSrc/src/main/kotlin/cc/tweaked/gradle/CCTweakedPlugin.kt b/buildSrc/src/main/kotlin/cc/tweaked/gradle/CCTweakedPlugin.kt index 5a5e27eb6..60053537e 100644 --- a/buildSrc/src/main/kotlin/cc/tweaked/gradle/CCTweakedPlugin.kt +++ b/buildSrc/src/main/kotlin/cc/tweaked/gradle/CCTweakedPlugin.kt @@ -8,6 +8,9 @@ import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.api.plugins.JavaPlugin import org.gradle.api.plugins.JavaPluginExtension +import org.gradle.api.publish.PublishingExtension +import org.gradle.api.publish.maven.MavenPublication +import org.gradle.api.publish.plugins.PublishingPlugin import org.gradle.jvm.toolchain.JavaLanguageVersion import org.gradle.plugins.ide.idea.model.IdeaModel import org.jetbrains.gradle.ext.IdeaExtPlugin @@ -26,6 +29,13 @@ class CCTweakedPlugin : Plugin { cct.sourceDirectories.add(SourceSetReference.internal(sourceSets.getByName("main"))) } + project.plugins.withType(PublishingPlugin::class.java) { + val publishing = project.extensions.getByType(PublishingExtension::class.java) + publishing.publications.withType(MavenPublication::class.java) { + excludeMavenDependencies(project, this, cct.excludedDeps) + } + } + project.plugins.withType(IdeaExtPlugin::class.java) { extendIdea(project) } } diff --git a/buildSrc/src/main/kotlin/cc/tweaked/gradle/MavenDependencySpec.kt b/buildSrc/src/main/kotlin/cc/tweaked/gradle/MavenDependencySpec.kt index e54ad74c3..9a9c59846 100644 --- a/buildSrc/src/main/kotlin/cc/tweaked/gradle/MavenDependencySpec.kt +++ b/buildSrc/src/main/kotlin/cc/tweaked/gradle/MavenDependencySpec.kt @@ -4,70 +4,43 @@ package cc.tweaked.gradle +import org.gradle.api.Project import org.gradle.api.artifacts.Dependency -import org.gradle.api.artifacts.MinimalExternalModuleDependency import org.gradle.api.artifacts.ProjectDependency import org.gradle.api.plugins.BasePluginExtension +import org.gradle.api.provider.Provider import org.gradle.api.publish.maven.MavenPublication -import org.gradle.api.specs.Spec /** * A dependency in a POM file. */ -data class MavenDependency(val groupId: String?, val artifactId: String?, val version: String?, val scope: String?) - -/** - * A spec specifying which dependencies to include/exclude. - */ -class MavenDependencySpec { - private val excludeSpecs = mutableListOf>() - - fun exclude(spec: Spec) { - excludeSpecs.add(spec) - } - - fun exclude(dep: Dependency) { - exclude { - // We have to cheat a little for project dependencies, as the project name doesn't match the artifact group. - val name = when (dep) { - is ProjectDependency -> dep.dependencyProject.extensions.getByType(BasePluginExtension::class.java).archivesName.get() - else -> dep.name - } - (dep.group.isNullOrEmpty() || dep.group == it.groupId) && - (name.isNullOrEmpty() || name == it.artifactId) && - (dep.version.isNullOrEmpty() || dep.version == it.version) - } - } - - fun exclude(dep: MinimalExternalModuleDependency) { - exclude { - dep.module.group == it.groupId && dep.module.name == it.artifactId - } - } - - fun isIncluded(dep: MavenDependency) = !excludeSpecs.any { it.isSatisfiedBy(dep) } +private data class MavenDependency(val groupId: String?, val artifactId: String?) { + constructor(project: Project, dep: Dependency) : this( + dep.group, + when (dep) { + is ProjectDependency -> project.project(dep.path).extensions.getByType(BasePluginExtension::class.java).archivesName.get() + else -> dep.name + }, + ) } /** - * Configure dependencies present in this publication's POM file. + * Remove dependencies in a POM file based on a list of dependencies * * While this approach is very ugly, it's the easiest way to handle it! */ -fun MavenPublication.mavenDependencies(action: MavenDependencySpec.() -> Unit) { - val spec = MavenDependencySpec() - action(spec) +internal fun excludeMavenDependencies(project: Project, publication: MavenPublication, excluded: Provider>) { + val excludedSpecs = excluded.map { xs -> xs.map { MavenDependency(project, it) } } - pom.withXml { + publication.pom.withXml { val dependencies = XmlUtil.findChild(asNode(), "dependencies") ?: return@withXml dependencies.children().map { it as groovy.util.Node }.forEach { val dep = MavenDependency( groupId = XmlUtil.findChild(it, "groupId")?.text(), artifactId = XmlUtil.findChild(it, "artifactId")?.text(), - version = XmlUtil.findChild(it, "version")?.text(), - scope = XmlUtil.findChild(it, "scope")?.text(), ) - if (!spec.isIncluded(dep)) it.parent().remove(it) + if (excludedSpecs.get().contains(dep)) it.parent().remove(it) } } } diff --git a/buildSrc/src/main/kotlin/cc/tweaked/gradle/MinecraftConfigurations.kt b/buildSrc/src/main/kotlin/cc/tweaked/gradle/MinecraftConfigurations.kt index 6dbe9fbd3..9289ad2f8 100644 --- a/buildSrc/src/main/kotlin/cc/tweaked/gradle/MinecraftConfigurations.kt +++ b/buildSrc/src/main/kotlin/cc/tweaked/gradle/MinecraftConfigurations.kt @@ -11,10 +11,12 @@ import org.gradle.api.artifacts.ModuleDependency import org.gradle.api.artifacts.dsl.DependencyHandler import org.gradle.api.plugins.BasePlugin import org.gradle.api.plugins.JavaPluginExtension +import org.gradle.api.publish.tasks.GenerateModuleMetadata import org.gradle.api.tasks.SourceSet import org.gradle.api.tasks.bundling.Jar import org.gradle.api.tasks.javadoc.Javadoc import org.gradle.kotlin.dsl.get +import org.gradle.kotlin.dsl.withType /** * This sets up a separate client-only source set, and extends that and the main/common source set with additional @@ -95,6 +97,12 @@ class MinecraftConfigurations private constructor(private val project: Project) sourceDirectories.add(SourceSetReference.internal(client)) } + // We can't create accurate module metadata for our additional capabilities, + // so disable module metadata. + project.tasks.withType(GenerateModuleMetadata::class.java).configureEach { + isEnabled = false + } + // Register a task to check there are no conflicts with the core project. val checkDependencyConsistency = project.tasks.register("checkDependencyConsistency", DependencyCheck::class.java) { diff --git a/projects/common/build.gradle.kts b/projects/common/build.gradle.kts index 3fa723ae3..faec01442 100644 --- a/projects/common/build.gradle.kts +++ b/projects/common/build.gradle.kts @@ -126,5 +126,3 @@ val runData by tasks.registering(MergeTrees::class) { val runExampleData by tasks.registering(MergeTrees::class) { configureForDatagen(sourceSets.examples.get(), "src/examples/generatedResources") } - -tasks.withType(GenerateModuleMetadata::class).configureEach { isEnabled = false } diff --git a/projects/fabric/build.gradle.kts b/projects/fabric/build.gradle.kts index d4aa1229f..905fb8eb0 100644 --- a/projects/fabric/build.gradle.kts +++ b/projects/fabric/build.gradle.kts @@ -288,17 +288,6 @@ modPublishing { output = tasks.remapJar } -tasks.withType(GenerateModuleMetadata::class).configureEach { isEnabled = false } -publishing { - publications { - named("maven", MavenPublication::class) { - mavenDependencies { - cct.configureExcludes(this) - } - } - } -} - modrinth { required.project("fabric-api") } diff --git a/projects/forge/build.gradle.kts b/projects/forge/build.gradle.kts index b3a147feb..01433f386 100644 --- a/projects/forge/build.gradle.kts +++ b/projects/forge/build.gradle.kts @@ -230,19 +230,6 @@ tasks.register("checkClient") { dependsOn(runGametestClient) } -// Upload tasks - modPublishing { output = tasks.reobfJar } - -publishing { - publications { - named("maven", MavenPublication::class) { - mavenDependencies { - cct.configureExcludes(this) - exclude(libs.jei.forge.get()) - } - } - } -} From 8204944b5fdf2ec29ff7dcb112dc454b7710d981 Mon Sep 17 00:00:00 2001 From: Jonathan Coates Date: Mon, 13 Jan 2025 21:54:20 +0000 Subject: [PATCH 3/7] More Gradle cleanup Mostly configuration cache support. And an aborted attempt at updating spotless, which just resulted in a bunch of ktlint issues :/. --- .editorconfig | 18 +++++-- .gitignore | 1 + .../cc-tweaked.java-convention.gradle.kts | 1 - .../cc/tweaked/gradle/CCTweakedExtension.kt | 49 ++++--------------- .../cc/tweaked/gradle/CCTweakedPlugin.kt | 10 ---- .../cc/tweaked/gradle/DependencyCheck.kt | 20 +++++--- .../cc/tweaked/gradle/MavenDependencySpec.kt | 46 ----------------- .../tweaked/gradle/MinecraftConfigurations.kt | 12 +---- gradle/libs.versions.toml | 6 ++- projects/common/build.gradle.kts | 5 ++ .../computercraft/gametest/Speaker_Test.kt | 3 +- projects/core/build.gradle.kts | 5 +- .../apis/http/request/HttpRequestHandler.java | 4 -- projects/fabric/build.gradle.kts | 22 ++++++--- projects/forge/build.gradle.kts | 26 +++++++--- projects/web/build.gradle.kts | 4 +- 16 files changed, 87 insertions(+), 145 deletions(-) delete mode 100644 buildSrc/src/main/kotlin/cc/tweaked/gradle/MavenDependencySpec.kt diff --git a/.editorconfig b/.editorconfig index 92cfba65e..11d6673ca 100644 --- a/.editorconfig +++ b/.editorconfig @@ -18,11 +18,6 @@ ij_any_if_brace_force = if_multiline ij_any_for_brace_force = if_multiline ij_any_spaces_within_array_initializer_braces = true -ij_kotlin_allow_trailing_comma = true -ij_kotlin_allow_trailing_comma_on_call_site = true -ij_kotlin_method_parameters_wrap = off -ij_kotlin_call_parameters_wrap = off - [*.md] trim_trailing_whitespace = false @@ -31,3 +26,16 @@ indent_size = 2 [*.yml] indent_size = 2 + +[{*.kt,*.kts}] +ij_kotlin_code_style_defaults = KOTLIN_OFFICIAL +ij_kotlin_continuation_indent_size = 4 +ij_kotlin_spaces_around_equality_operators = true + +ij_kotlin_allow_trailing_comma = true +ij_kotlin_allow_trailing_comma_on_call_site = true + +# Prefer to handle these manually +ij_kotlin_method_parameters_wrap = off +ij_kotlin_call_parameters_wrap = off +ij_kotlin_extends_list_wrap = off diff --git a/.gitignore b/.gitignore index 675c66cf6..7170eed48 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,7 @@ *.iml .idea .gradle +.kotlin *.DS_Store /.classpath diff --git a/buildSrc/src/main/kotlin/cc-tweaked.java-convention.gradle.kts b/buildSrc/src/main/kotlin/cc-tweaked.java-convention.gradle.kts index 3f8c27943..bdfa23a21 100644 --- a/buildSrc/src/main/kotlin/cc-tweaked.java-convention.gradle.kts +++ b/buildSrc/src/main/kotlin/cc-tweaked.java-convention.gradle.kts @@ -220,6 +220,5 @@ idea.module { // Force Gradle to write to inherit the output directory from the parent, instead of writing to out/xxx/classes. // This is required for Loom, and we patch Forge's run configurations to work there. - // TODO: Submit a patch to Forge to support ProjectRootManager. inheritOutputDirs = true } diff --git a/buildSrc/src/main/kotlin/cc/tweaked/gradle/CCTweakedExtension.kt b/buildSrc/src/main/kotlin/cc/tweaked/gradle/CCTweakedExtension.kt index 2f6d323b5..7f73f04fa 100644 --- a/buildSrc/src/main/kotlin/cc/tweaked/gradle/CCTweakedExtension.kt +++ b/buildSrc/src/main/kotlin/cc/tweaked/gradle/CCTweakedExtension.kt @@ -11,13 +11,10 @@ import org.gradle.api.NamedDomainObjectProvider import org.gradle.api.Project import org.gradle.api.Task import org.gradle.api.artifacts.Dependency -import org.gradle.api.attributes.TestSuiteType -import org.gradle.api.file.FileSystemOperations import org.gradle.api.plugins.JavaPluginExtension import org.gradle.api.provider.ListProperty import org.gradle.api.provider.Provider import org.gradle.api.provider.SetProperty -import org.gradle.api.reporting.ReportingExtension import org.gradle.api.tasks.SourceSet import org.gradle.api.tasks.bundling.Jar import org.gradle.api.tasks.compile.JavaCompile @@ -25,7 +22,6 @@ import org.gradle.api.tasks.javadoc.Javadoc import org.gradle.language.base.plugins.LifecycleBasePlugin import org.gradle.language.jvm.tasks.ProcessResources import org.gradle.process.JavaForkOptions -import org.gradle.testing.jacoco.plugins.JacocoCoverageReport import org.gradle.testing.jacoco.plugins.JacocoPluginExtension import org.gradle.testing.jacoco.plugins.JacocoTaskExtension import org.gradle.testing.jacoco.tasks.JacocoReport @@ -36,10 +32,7 @@ import java.io.IOException import java.net.URI import java.util.regex.Pattern -abstract class CCTweakedExtension( - private val project: Project, - private val fs: FileSystemOperations, -) { +abstract class CCTweakedExtension(private val project: Project) { /** Get the current git branch. */ val gitBranch: Provider = gitProvider("", listOf("rev-parse", "--abbrev-ref", "HEAD")) { it.trim() } @@ -64,17 +57,11 @@ abstract class CCTweakedExtension( */ val sourceDirectories: SetProperty = project.objects.setProperty(SourceSetReference::class.java) - /** - * Dependencies excluded from published artifacts. - */ - internal val excludedDeps: ListProperty = project.objects.listProperty(Dependency::class.java) - /** All source sets referenced by this project. */ val sourceSets = sourceDirectories.map { x -> x.map { it.sourceSet } } init { sourceDirectories.finalizeValueOnRead() - excludedDeps.finalizeValueOnRead() project.afterEvaluate { sourceDirectories.disallowChanges() } } @@ -169,23 +156,18 @@ abstract class CCTweakedExtension( } fun jacoco(task: NamedDomainObjectProvider) where T : Task, T : JavaForkOptions { - val classDump = project.layout.buildDirectory.dir("jacocoClassDump/${task.name}") val reportTaskName = "jacoco${task.name.capitalise()}Report" val jacoco = project.extensions.getByType(JacocoPluginExtension::class.java) task.configure { finalizedBy(reportTaskName) - - doFirst("Clean class dump directory") { fs.delete { delete(classDump) } } - jacoco.applyTo(this) - extensions.configure(JacocoTaskExtension::class.java) { - includes = listOf("dan200.computercraft.*") - classDumpDir = classDump.get().asFile - // Older versions of modlauncher don't include a protection domain (and thus no code - // source). Jacoco skips such classes by default, so we need to explicitly include them. - isIncludeNoLocationClasses = true + extensions.configure(JacocoTaskExtension::class.java) { + excludes = listOf( + "dan200.computercraft.mixin.*", // Exclude mixins, as they're not executed at runtime. + "dan200.computercraft.shared.Capabilities$*", // Exclude capability tokens, as Forge rewrites them. + ) } } @@ -194,15 +176,11 @@ abstract class CCTweakedExtension( description = "Generates code coverage report for the ${task.name} task." executionData(task.get()) - classDirectories.from(classDump) - // Don't want to use sourceSets(...) here as we have a custom class directory. - for (ref in sourceSets.get()) sourceDirectories.from(ref.allSource.sourceDirectories) - } - - project.extensions.configure(ReportingExtension::class.java) { - reports.register("${task.name}CodeCoverageReport", JacocoCoverageReport::class.java) { - testType.set(TestSuiteType.INTEGRATION_TEST) + // Don't want to use sourceSets(...) here as we don't use all class directories. + for (ref in this@CCTweakedExtension.sourceDirectories.get()) { + sourceDirectories.from(ref.sourceSet.allSource.sourceDirectories) + if (ref.classes) classDirectories.from(ref.sourceSet.output) } } } @@ -242,13 +220,6 @@ abstract class CCTweakedExtension( ).resolve().single() } - /** - * Exclude a dependency from being published in Maven. - */ - fun exclude(dep: Dependency) { - excludedDeps.add(dep) - } - private fun gitProvider(default: T, command: List, process: (String) -> T): Provider { val baseResult = project.providers.exec { commandLine = listOf("git", "-C", project.rootDir.absolutePath) + command diff --git a/buildSrc/src/main/kotlin/cc/tweaked/gradle/CCTweakedPlugin.kt b/buildSrc/src/main/kotlin/cc/tweaked/gradle/CCTweakedPlugin.kt index 60053537e..5a5e27eb6 100644 --- a/buildSrc/src/main/kotlin/cc/tweaked/gradle/CCTweakedPlugin.kt +++ b/buildSrc/src/main/kotlin/cc/tweaked/gradle/CCTweakedPlugin.kt @@ -8,9 +8,6 @@ import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.api.plugins.JavaPlugin import org.gradle.api.plugins.JavaPluginExtension -import org.gradle.api.publish.PublishingExtension -import org.gradle.api.publish.maven.MavenPublication -import org.gradle.api.publish.plugins.PublishingPlugin import org.gradle.jvm.toolchain.JavaLanguageVersion import org.gradle.plugins.ide.idea.model.IdeaModel import org.jetbrains.gradle.ext.IdeaExtPlugin @@ -29,13 +26,6 @@ class CCTweakedPlugin : Plugin { cct.sourceDirectories.add(SourceSetReference.internal(sourceSets.getByName("main"))) } - project.plugins.withType(PublishingPlugin::class.java) { - val publishing = project.extensions.getByType(PublishingExtension::class.java) - publishing.publications.withType(MavenPublication::class.java) { - excludeMavenDependencies(project, this, cct.excludedDeps) - } - } - project.plugins.withType(IdeaExtPlugin::class.java) { extendIdea(project) } } diff --git a/buildSrc/src/main/kotlin/cc/tweaked/gradle/DependencyCheck.kt b/buildSrc/src/main/kotlin/cc/tweaked/gradle/DependencyCheck.kt index 78987ff72..9f7f41960 100644 --- a/buildSrc/src/main/kotlin/cc/tweaked/gradle/DependencyCheck.kt +++ b/buildSrc/src/main/kotlin/cc/tweaked/gradle/DependencyCheck.kt @@ -22,19 +22,19 @@ import org.gradle.language.base.plugins.LifecycleBasePlugin abstract class DependencyCheck : DefaultTask() { @get:Input - abstract val configuration: ListProperty + protected abstract val dependencies: ListProperty /** * A mapping of module coordinates (`group:module`) to versions, overriding the requested version. */ @get:Input - abstract val overrides: MapProperty + protected abstract val overrides: MapProperty init { description = "Check :core's dependencies are consistent with Minecraft's." group = LifecycleBasePlugin.VERIFICATION_GROUP - configuration.finalizeValueOnRead() + dependencies.finalizeValueOnRead() overrides.finalizeValueOnRead() } @@ -45,13 +45,19 @@ abstract class DependencyCheck : DefaultTask() { overrides.putAll(project.provider { mutableMapOf(module.get().module.toString() to version) }) } + /** + * Add a configuration to check. + */ + fun configuration(configuration: Provider) { + // We can't store the Configuration in the cache, so store the resolved dependencies instead. + dependencies.addAll(configuration.map { it.incoming.resolutionResult.allDependencies }) + } + @TaskAction fun run() { var ok = true - for (configuration in configuration.get()) { - configuration.incoming.resolutionResult.allDependencies { - if (!check(this@allDependencies)) ok = false - } + for (configuration in dependencies.get()) { + if (!check(configuration)) ok = false } if (!ok) { diff --git a/buildSrc/src/main/kotlin/cc/tweaked/gradle/MavenDependencySpec.kt b/buildSrc/src/main/kotlin/cc/tweaked/gradle/MavenDependencySpec.kt deleted file mode 100644 index 9a9c59846..000000000 --- a/buildSrc/src/main/kotlin/cc/tweaked/gradle/MavenDependencySpec.kt +++ /dev/null @@ -1,46 +0,0 @@ -// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers -// -// SPDX-License-Identifier: MPL-2.0 - -package cc.tweaked.gradle - -import org.gradle.api.Project -import org.gradle.api.artifacts.Dependency -import org.gradle.api.artifacts.ProjectDependency -import org.gradle.api.plugins.BasePluginExtension -import org.gradle.api.provider.Provider -import org.gradle.api.publish.maven.MavenPublication - -/** - * A dependency in a POM file. - */ -private data class MavenDependency(val groupId: String?, val artifactId: String?) { - constructor(project: Project, dep: Dependency) : this( - dep.group, - when (dep) { - is ProjectDependency -> project.project(dep.path).extensions.getByType(BasePluginExtension::class.java).archivesName.get() - else -> dep.name - }, - ) -} - -/** - * Remove dependencies in a POM file based on a list of dependencies - * - * While this approach is very ugly, it's the easiest way to handle it! - */ -internal fun excludeMavenDependencies(project: Project, publication: MavenPublication, excluded: Provider>) { - val excludedSpecs = excluded.map { xs -> xs.map { MavenDependency(project, it) } } - - publication.pom.withXml { - val dependencies = XmlUtil.findChild(asNode(), "dependencies") ?: return@withXml - dependencies.children().map { it as groovy.util.Node }.forEach { - val dep = MavenDependency( - groupId = XmlUtil.findChild(it, "groupId")?.text(), - artifactId = XmlUtil.findChild(it, "artifactId")?.text(), - ) - - if (excludedSpecs.get().contains(dep)) it.parent().remove(it) - } - } -} diff --git a/buildSrc/src/main/kotlin/cc/tweaked/gradle/MinecraftConfigurations.kt b/buildSrc/src/main/kotlin/cc/tweaked/gradle/MinecraftConfigurations.kt index 9289ad2f8..a4caa5dec 100644 --- a/buildSrc/src/main/kotlin/cc/tweaked/gradle/MinecraftConfigurations.kt +++ b/buildSrc/src/main/kotlin/cc/tweaked/gradle/MinecraftConfigurations.kt @@ -11,12 +11,10 @@ import org.gradle.api.artifacts.ModuleDependency import org.gradle.api.artifacts.dsl.DependencyHandler import org.gradle.api.plugins.BasePlugin import org.gradle.api.plugins.JavaPluginExtension -import org.gradle.api.publish.tasks.GenerateModuleMetadata import org.gradle.api.tasks.SourceSet import org.gradle.api.tasks.bundling.Jar import org.gradle.api.tasks.javadoc.Javadoc import org.gradle.kotlin.dsl.get -import org.gradle.kotlin.dsl.withType /** * This sets up a separate client-only source set, and extends that and the main/common source set with additional @@ -97,18 +95,12 @@ class MinecraftConfigurations private constructor(private val project: Project) sourceDirectories.add(SourceSetReference.internal(client)) } - // We can't create accurate module metadata for our additional capabilities, - // so disable module metadata. - project.tasks.withType(GenerateModuleMetadata::class.java).configureEach { - isEnabled = false - } - // Register a task to check there are no conflicts with the core project. val checkDependencyConsistency = project.tasks.register("checkDependencyConsistency", DependencyCheck::class.java) { // We need to check both the main and client classpath *configurations*, as the actual configuration - configuration.add(configurations.named(main.runtimeClasspathConfigurationName)) - configuration.add(configurations.named(client.runtimeClasspathConfigurationName)) + configuration(configurations.named(main.runtimeClasspathConfigurationName)) + configuration(configurations.named(client.runtimeClasspathConfigurationName)) } project.tasks.named("check") { dependsOn(checkDependencyConsistency) } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index fb09368a7..40b2a7b5b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -52,7 +52,8 @@ create-fabric = "0.5.1-f-build.1467+mc1.20.1" # Testing hamcrest = "2.2" jqwik = "1.8.2" -junit = "5.10.1" +junit = "5.11.4" +junitPlatform = "1.11.4" jmh = "1.37" # Build tools @@ -130,6 +131,7 @@ jqwik-engine = { module = "net.jqwik:jqwik-engine", version.ref = "jqwik" } junit-jupiter-api = { module = "org.junit.jupiter:junit-jupiter-api", version.ref = "junit" } junit-jupiter-engine = { module = "org.junit.jupiter:junit-jupiter-engine", version.ref = "junit" } junit-jupiter-params = { module = "org.junit.jupiter:junit-jupiter-params", version.ref = "junit" } +junit-platform-launcher = { module = "org.junit.platform:junit-platform-launcher", version.ref = "junitPlatform" } slf4j-simple = { module = "org.slf4j:slf4j-simple", version.ref = "slf4j" } jmh = { module = "org.openjdk.jmh:jmh-core", version.ref = "jmh" } jmh-processor = { module = "org.openjdk.jmh:jmh-generator-annprocess", version.ref = "jmh" } @@ -188,7 +190,7 @@ externalMods-fabric-runtime = ["jei-fabric", "modmenu"] # Testing test = ["junit-jupiter-api", "junit-jupiter-params", "hamcrest", "jqwik-api"] -testRuntime = ["junit-jupiter-engine", "jqwik-engine"] +testRuntime = ["junit-jupiter-engine", "junit-platform-launcher", "jqwik-engine"] # Build tools teavm-api = ["teavm-jso", "teavm-jso-apis", "teavm-platform", "teavm-classlib", "teavm-metaprogramming-api"] diff --git a/projects/common/build.gradle.kts b/projects/common/build.gradle.kts index faec01442..20e74493d 100644 --- a/projects/common/build.gradle.kts +++ b/projects/common/build.gradle.kts @@ -126,3 +126,8 @@ val runData by tasks.registering(MergeTrees::class) { val runExampleData by tasks.registering(MergeTrees::class) { configureForDatagen(sourceSets.examples.get(), "src/examples/generatedResources") } + +// We can't create accurate module metadata for our additional capabilities, so disable it. +project.tasks.withType(GenerateModuleMetadata::class.java).configureEach { + isEnabled = false +} diff --git a/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Speaker_Test.kt b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Speaker_Test.kt index 8a2d9572f..0f88ce296 100644 --- a/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Speaker_Test.kt +++ b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Speaker_Test.kt @@ -23,7 +23,8 @@ class Speaker_Test { callPeripheral("right", "playSound", SoundEvents.NOTE_BLOCK_HARP.key().location().toString()) .assertArrayEquals(true) - tryMultipleTimes(2) { // We could technically call this a tick later, so try twice + tryMultipleTimes(2) { + // We could technically call this a tick later, so try twice callPeripheral("right", "playSound", SoundEvents.NOTE_BLOCK_HARP.key().location().toString()) .assertArrayEquals(false) } diff --git a/projects/core/build.gradle.kts b/projects/core/build.gradle.kts index 033b0af2f..4655b9e50 100644 --- a/projects/core/build.gradle.kts +++ b/projects/core/build.gradle.kts @@ -40,9 +40,8 @@ dependencies { } tasks.processResources { - filesMatching("data/computercraft/lua/rom/help/credits.md") { - expand(mapOf("gitContributors" to cct.gitContributors.map { it.joinToString("\n") }.get())) - } + var props = mapOf("gitContributors" to cct.gitContributors.get().joinToString("\n")) + filesMatching("data/computercraft/lua/rom/help/credits.md") { expand(props) } } tasks.test { diff --git a/projects/core/src/main/java/dan200/computercraft/core/apis/http/request/HttpRequestHandler.java b/projects/core/src/main/java/dan200/computercraft/core/apis/http/request/HttpRequestHandler.java index 005440e59..8ef3a099c 100644 --- a/projects/core/src/main/java/dan200/computercraft/core/apis/http/request/HttpRequestHandler.java +++ b/projects/core/src/main/java/dan200/computercraft/core/apis/http/request/HttpRequestHandler.java @@ -14,8 +14,6 @@ import io.netty.buffer.CompositeByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.handler.codec.http.*; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import javax.annotation.Nullable; import java.io.Closeable; @@ -30,8 +28,6 @@ import java.util.Objects; import static dan200.computercraft.core.apis.http.request.HttpRequest.getHeaderSize; public final class HttpRequestHandler extends SimpleChannelInboundHandler implements Closeable { - private static final Logger LOG = LoggerFactory.getLogger(HttpRequestHandler.class); - /** * Same as {@link io.netty.handler.codec.MessageAggregator}. */ diff --git a/projects/fabric/build.gradle.kts b/projects/fabric/build.gradle.kts index 905fb8eb0..54ec3877d 100644 --- a/projects/fabric/build.gradle.kts +++ b/projects/fabric/build.gradle.kts @@ -61,6 +61,16 @@ configurations { include { extendsFrom(includeRuntimeOnly.get(), includeImplementation.get()) } runtimeOnly { extendsFrom(includeRuntimeOnly.get()) } implementation { extendsFrom(includeImplementation.get()) } + + // Declare a configuration for projects which are on the compile and runtime classpath, but not treated as + // dependencies. This is used for our local projects. + val localImplementation by registering { + isCanBeResolved = false + isCanBeConsumed = false + isVisible = false + } + compileClasspath { extendsFrom(localImplementation.get()) } + runtimeClasspath { extendsFrom(localImplementation.get()) } } dependencies { @@ -90,9 +100,9 @@ dependencies { "includeImplementation"(libs.nightConfig.toml) // Pull in our other projects. See comments in MinecraftConfigurations on this nastiness. - api(commonClasses(project(":fabric-api"))) { cct.exclude(this) } - clientApi(clientClasses(project(":fabric-api"))) { cct.exclude(this) } - implementation(project(":core")) { cct.exclude(this) } + "localImplementation"(project(":core")) + "localImplementation"(commonClasses(project(":fabric-api"))) + clientImplementation(clientClasses(project(":fabric-api"))) annotationProcessorEverywhere(libs.autoService) @@ -210,11 +220,9 @@ loom { } tasks.processResources { - inputs.property("version", modVersion) + var props = mapOf("version" to modVersion) - filesMatching("fabric.mod.json") { - expand(mapOf("version" to modVersion)) - } + filesMatching("fabric.mod.json") { expand(props) } } tasks.jar { diff --git a/projects/forge/build.gradle.kts b/projects/forge/build.gradle.kts index 01433f386..bfd8ee0fa 100644 --- a/projects/forge/build.gradle.kts +++ b/projects/forge/build.gradle.kts @@ -136,6 +136,16 @@ configurations { for (testConfig in listOf("testClientAdditionalRuntimeClasspath", "gametestAdditionalRuntimeClasspath")) { named(testConfig) { extendsFrom(testAdditionalRuntimeClasspath.get()) } } + + // Declare a configuration for projects which are on the compile and runtime classpath, but not treated as + // dependencies. This is used for our local projects. + val localImplementation by registering { + isCanBeResolved = false + isCanBeConsumed = false + isVisible = false + } + compileClasspath { extendsFrom(localImplementation.get()) } + runtimeClasspath { extendsFrom(localImplementation.get()) } } dependencies { @@ -148,9 +158,9 @@ dependencies { modCompileOnly(variantOf(libs.create.forge) { classifier("slim") }) // Depend on our other projects. - api(commonClasses(project(":forge-api"))) { cct.exclude(this) } - clientApi(clientClasses(project(":forge-api"))) { cct.exclude(this) } - implementation(project(":core")) { cct.exclude(this) } + "localImplementation"(project(":core")) + "localImplementation"(commonClasses(project(":forge-api"))) + clientImplementation(clientClasses(project(":forge-api"))) jarJar(libs.cobalt) jarJar(libs.jzlib) @@ -180,12 +190,12 @@ dependencies { // Compile tasks tasks.processResources { - inputs.property("modVersion", modVersion) - inputs.property("forgeVersion", libs.versions.forge.get()) + var props = mapOf( + "forgeVersion" to libs.versions.forge.get(), + "file" to mapOf("jarVersion" to modVersion), + ) - filesMatching("META-INF/mods.toml") { - expand(mapOf("forgeVersion" to libs.versions.forge.get(), "file" to mapOf("jarVersion" to modVersion))) - } + filesMatching("META-INF/mods.toml") { expand(props) } } tasks.jar { diff --git a/projects/web/build.gradle.kts b/projects/web/build.gradle.kts index afcb2319f..1f2330ac2 100644 --- a/projects/web/build.gradle.kts +++ b/projects/web/build.gradle.kts @@ -95,14 +95,14 @@ val illuaminateDocs by tasks.registering(cc.tweaked.gradle.IlluaminateExecToDir: // Sources inputs.files(rootProject.fileTree("doc")).withPropertyName("docs") inputs.files(project(":core").fileTree("src/main/resources/data/computercraft/lua")).withPropertyName("lua rom") - inputs.files(project(":common").tasks.named("luaJavadoc")) + inputs.dir(project(":common").tasks.named("luaJavadoc").map { it.destinationDir!! }).withPropertyName("luaJavadoc") // Assets inputs.files(rollup) // Output directory. Also defined in illuaminate.sexp. output = layout.buildDirectory.dir("illuaminate") - args = listOf("doc-gen") + args("doc-gen") workingDir = rootProject.projectDir } From a2b9490d5c9787e87e5eb8bca9452c9615b9609c Mon Sep 17 00:00:00 2001 From: Jonathan Coates Date: Tue, 14 Jan 2025 09:38:22 +0000 Subject: [PATCH 4/7] Some printer gametests These are a little ugly, but do catch some issues we've seen in the past. Fixes #1682 (for now), and on its birthday too! --- .../printer/PrinterBlockEntity.java | 3 +- .../computercraft/gametest/Printer_Test.kt | 142 +++++++++++++++--- .../computercraft/gametest/Turtle_Test.kt | 63 ++++---- ...tes_state.snbt => printer_test.empty.snbt} | 0 .../printer_test.no_print_when_full.snbt | 137 +++++++++++++++++ 5 files changed, 287 insertions(+), 58 deletions(-) rename projects/common/src/testMod/resources/data/cctest/structures/{printer_test.contents_updates_state.snbt => printer_test.empty.snbt} (100%) create mode 100644 projects/common/src/testMod/resources/data/cctest/structures/printer_test.no_print_when_full.snbt diff --git a/projects/common/src/main/java/dan200/computercraft/shared/peripheral/printer/PrinterBlockEntity.java b/projects/common/src/main/java/dan200/computercraft/shared/peripheral/printer/PrinterBlockEntity.java index f6037802a..a593e6836 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/peripheral/printer/PrinterBlockEntity.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/peripheral/printer/PrinterBlockEntity.java @@ -166,8 +166,7 @@ public final class PrinterBlockEntity extends AbstractContainerBlockEntity imple } private boolean canInputPage() { - var inkStack = inventory.get(0); - return !inkStack.isEmpty() && isInk(inkStack) && getPaperLevel() > 0; + return getInkLevel() > 0 && getPaperLevel() > 0; } private boolean inputPage() { diff --git a/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Printer_Test.kt b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Printer_Test.kt index fd8577c3e..b86931900 100644 --- a/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Printer_Test.kt +++ b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Printer_Test.kt @@ -4,12 +4,13 @@ package dan200.computercraft.gametest -import dan200.computercraft.gametest.api.assertBlockHas -import dan200.computercraft.gametest.api.assertExactlyItems -import dan200.computercraft.gametest.api.getBlockEntity -import dan200.computercraft.gametest.api.sequence +import dan200.computercraft.api.lua.Coerced +import dan200.computercraft.api.lua.LuaException +import dan200.computercraft.gametest.api.* import dan200.computercraft.shared.ModRegistry +import dan200.computercraft.shared.media.items.PrintoutItem import dan200.computercraft.shared.peripheral.printer.PrinterBlock +import dan200.computercraft.shared.peripheral.printer.PrinterPeripheral import net.minecraft.core.BlockPos import net.minecraft.gametest.framework.GameTest import net.minecraft.gametest.framework.GameTestHelper @@ -17,10 +18,12 @@ import net.minecraft.network.chat.Component import net.minecraft.world.item.ItemStack import net.minecraft.world.item.Items import net.minecraft.world.level.block.RedStoneWireBlock +import org.junit.jupiter.api.Assertions.* +import java.util.* class Printer_Test { /** - * Check comparators can read the contents of the disk drive + * Check comparators can read the contents of the printer */ @GameTest fun Comparator(helper: GameTestHelper) = helper.sequence { @@ -29,19 +32,19 @@ class Printer_Test { // Adding items should provide power thenExecute { - val drive = helper.getBlockEntity(printerPos, ModRegistry.BlockEntities.PRINTER.get()) - drive.setItem(0, ItemStack(Items.BLACK_DYE)) - drive.setItem(1, ItemStack(Items.PAPER)) - drive.setChanged() + val printer = helper.getBlockEntity(printerPos, ModRegistry.BlockEntities.PRINTER.get()) + printer.setItem(0, ItemStack(Items.BLACK_DYE)) + printer.setItem(1, ItemStack(Items.PAPER)) + printer.setChanged() } thenIdle(2) thenExecute { helper.assertBlockHas(dustPos, RedStoneWireBlock.POWER, 1) } // And removing them should reset power. thenExecute { - val drive = helper.getBlockEntity(printerPos, ModRegistry.BlockEntities.PRINTER.get()) - drive.clearContent() - drive.setChanged() + val printer = helper.getBlockEntity(printerPos, ModRegistry.BlockEntities.PRINTER.get()) + printer.clearContent() + printer.setChanged() } thenIdle(2) thenExecute { helper.assertBlockHas(dustPos, RedStoneWireBlock.POWER, 0) } @@ -50,35 +53,125 @@ class Printer_Test { /** * Changing the inventory contents updates the block state */ - @GameTest + @GameTest(template = "printer_test.empty") fun Contents_updates_state(helper: GameTestHelper) = helper.sequence { val pos = BlockPos(2, 2, 2) thenExecute { - val drive = helper.getBlockEntity(pos, ModRegistry.BlockEntities.PRINTER.get()) + val printer = helper.getBlockEntity(pos, ModRegistry.BlockEntities.PRINTER.get()) - drive.setItem(1, ItemStack(Items.PAPER)) - drive.setChanged() + printer.setItem(1, ItemStack(Items.PAPER)) + printer.setChanged() helper.assertBlockHas(pos, PrinterBlock.TOP, true, message = "One item in the top row") helper.assertBlockHas(pos, PrinterBlock.BOTTOM, false, message = "One item in the top row") - drive.setItem(7, ItemStack(Items.PAPER)) - drive.setChanged() + printer.setItem(7, ItemStack(Items.PAPER)) + printer.setChanged() helper.assertBlockHas(pos, PrinterBlock.TOP, true, message = "One item in each row") helper.assertBlockHas(pos, PrinterBlock.BOTTOM, true, message = "One item in each row") - drive.setItem(1, ItemStack.EMPTY) - drive.setChanged() + printer.setItem(1, ItemStack.EMPTY) + printer.setChanged() helper.assertBlockHas(pos, PrinterBlock.TOP, false, message = "One item in the bottom") helper.assertBlockHas(pos, PrinterBlock.BOTTOM, true, message = "One item in the bottom row") - drive.setItem(7, ItemStack.EMPTY) - drive.setChanged() + printer.setItem(7, ItemStack.EMPTY) + printer.setChanged() helper.assertBlockHas(pos, PrinterBlock.TOP, false, message = "Empty") helper.assertBlockHas(pos, PrinterBlock.BOTTOM, false, message = "Empty") } } + /** + * Printing a page + */ + @GameTest(template = "printer_test.empty") + fun Print_page(helper: GameTestHelper) = helper.sequence { + val pos = BlockPos(2, 2, 2) + + thenExecute { + val printer = helper.getBlockEntity(pos, ModRegistry.BlockEntities.PRINTER.get()) + val peripheral = printer.peripheral() as PrinterPeripheral + + // Try to print with no pages + assertFalse(peripheral.newPage(), "newPage fails with no items") + + // Try to print with just ink + printer.setItem(0, ItemStack(Items.BLUE_DYE)) + printer.setChanged() + assertFalse(peripheral.newPage(), "newPage fails with no paper") + + printer.clearContent() + + // Try to print with just paper + printer.setItem(1, ItemStack(Items.PAPER)) + printer.setChanged() + assertFalse(peripheral.newPage(), "newPage fails with no ink") + + printer.clearContent() + + // Try to print with both items + printer.setItem(0, ItemStack(Items.BLUE_DYE)) + printer.setItem(1, ItemStack(Items.PAPER)) + printer.setChanged() + assertTrue(peripheral.newPage(), "newPage succeeds") + + // newPage() should consume both items and update the block state + helper.assertContainerEmpty(pos) + helper.assertBlockHas(pos, PrinterBlock.TOP, false, message = "Empty") + helper.assertBlockHas(pos, PrinterBlock.BOTTOM, false, message = "Empty") + + assertFalse(peripheral.newPage(), "Cannot start a page when already printing") + + peripheral.setPageTitle(Optional.of("New Page")) + peripheral.write(Coerced("Hello, world!")) + peripheral.setCursorPos(5, 2) + peripheral.write(Coerced("Second line")) + + // Try to finish the page + assertTrue(peripheral.endPage(), "endPage prints item") + + // endPage() should + helper.assertBlockHas(pos, PrinterBlock.TOP, false, message = "Empty") + helper.assertBlockHas(pos, PrinterBlock.BOTTOM, true, message = "Has pages") + + // And check the inventory matches + val lines = createPageOf(' ') + lines[0] = "Hello, world! " + lines[1] = " Second line " + helper.assertContainerExactly( + pos, + listOf( + // Ink + ItemStack.EMPTY, + // Paper + ItemStack.EMPTY, ItemStack.EMPTY, ItemStack.EMPTY, ItemStack.EMPTY, ItemStack.EMPTY, ItemStack.EMPTY, + // Pages + PrintoutItem.createSingleFromTitleAndText("New Page", lines, createPageOf('b')), + ItemStack.EMPTY, ItemStack.EMPTY, ItemStack.EMPTY, ItemStack.EMPTY, ItemStack.EMPTY, + ), + ) + + val error = assertThrows(LuaException::class.java) { peripheral.endPage() } + assertEquals("Page not started", error.message) + } + } + + /** + * Can't print when full. + */ + @GameTest + fun No_print_when_full(helper: GameTestHelper) = helper.sequence { + val pos = BlockPos(2, 2, 2) + + thenExecute { + val printer = helper.getBlockEntity(pos, ModRegistry.BlockEntities.PRINTER.get()) + val peripheral = printer.peripheral() as PrinterPeripheral + assertTrue(peripheral.newPage()) + assertFalse(peripheral.endPage(), "Cannot print when full") + } + } + /** * When the block is broken, we drop the contents and an optionally named stack. */ @@ -94,4 +187,9 @@ class Printer_Test { ) } } + + private fun createPageOf(c: Char): Array { + val line = c.toString().repeat(PrintoutItem.LINE_MAX_LENGTH) + return Array(PrintoutItem.LINES_PER_PAGE) { line } + } } 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 1b1f58ade..69b113f0b 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 @@ -29,7 +29,6 @@ import dan200.computercraft.shared.util.WaterloggableHelpers import dan200.computercraft.test.core.assertArrayEquals import dan200.computercraft.test.core.computer.LuaTaskContext import dan200.computercraft.test.core.computer.getApi -import dan200.computercraft.test.shared.ItemStackMatcher.isStack import net.minecraft.core.BlockPos import net.minecraft.gametest.framework.GameTest import net.minecraft.gametest.framework.GameTestHelper @@ -46,7 +45,8 @@ import net.minecraft.world.level.block.FenceBlock import net.minecraft.world.level.block.entity.BlockEntityType import net.minecraft.world.level.block.state.properties.BlockStateProperties import org.hamcrest.MatcherAssert.assertThat -import org.hamcrest.Matchers.* +import org.hamcrest.Matchers.array +import org.hamcrest.Matchers.instanceOf import org.junit.jupiter.api.Assertions.* import java.util.* import java.util.concurrent.CopyOnWriteArrayList @@ -138,7 +138,7 @@ class Turtle_Test { ) } thenExecute { - helper.assertBlockIs(BlockPos(2, 2, 2)) { it.block == Blocks.COMPOSTER && it.getValue(ComposterBlock.LEVEL) == 2 } + helper.assertBlockHas(BlockPos(2, 2, 2), ComposterBlock.LEVEL, 2) } } @@ -154,7 +154,7 @@ class Turtle_Test { .assertArrayEquals(false, "Cannot place item here", message = "Failed to place item") } thenExecute { - helper.assertBlockIs(BlockPos(2, 2, 3)) { it.block == Blocks.COMPOSTER && it.getValue(ComposterBlock.LEVEL) == 0 } + helper.assertBlockHas(BlockPos(2, 2, 3), ComposterBlock.LEVEL, 0) } } @@ -175,7 +175,7 @@ class Turtle_Test { ) } thenExecute { - helper.assertBlockIs(BlockPos(2, 2, 2)) { it.block == Blocks.BEEHIVE && it.getValue(BeehiveBlock.HONEY_LEVEL) == 0 } + helper.assertBlockHas(BlockPos(2, 2, 2), BeehiveBlock.HONEY_LEVEL, 0) } } @@ -765,15 +765,13 @@ class Turtle_Test { callPeripheral("left", "craft", 1).assertArrayEquals(true) } thenExecute { - val turtle = helper.getBlockEntity(BlockPos(2, 2, 2), ModRegistry.BlockEntities.TURTLE_NORMAL.get()) - assertThat( - "Inventory is as expected.", - turtle.contents, - contains( - isStack(Items.DIAMOND, 1), isStack(Items.DIAMOND, 1), isStack(Items.DIAMOND, 1), isStack(Items.DIAMOND_PICKAXE, 1), - isStack(ItemStack.EMPTY), isStack(Items.STICK, 1), isStack(ItemStack.EMPTY), isStack(ItemStack.EMPTY), - isStack(ItemStack.EMPTY), isStack(Items.STICK, 1), isStack(ItemStack.EMPTY), isStack(ItemStack.EMPTY), - isStack(ItemStack.EMPTY), isStack(ItemStack.EMPTY), isStack(ItemStack.EMPTY), isStack(ItemStack.EMPTY), + helper.assertContainerExactly( + BlockPos(2, 2, 2), + listOf( + ItemStack(Items.DIAMOND), ItemStack(Items.DIAMOND), ItemStack(Items.DIAMOND), ItemStack(Items.DIAMOND_PICKAXE), + ItemStack.EMPTY, ItemStack(Items.STICK), ItemStack.EMPTY, ItemStack.EMPTY, + ItemStack.EMPTY, ItemStack(Items.STICK), ItemStack.EMPTY, ItemStack.EMPTY, + ItemStack.EMPTY, ItemStack.EMPTY, ItemStack.EMPTY, ItemStack.EMPTY, ), ) } @@ -786,23 +784,20 @@ class Turtle_Test { */ @GameTest fun Craft_remainder(helper: GameTestHelper) = helper.sequence { - thenOnComputer { - callPeripheral("left", "craft", 1).assertArrayEquals(true) - } thenExecute { val turtle = helper.getBlockEntity(BlockPos(2, 2, 2), ModRegistry.BlockEntities.TURTLE_NORMAL.get()) + assertTrue(TurtleCraftCommand(1).execute(turtle.access).isSuccess, "Crafting succeeded") val turtleStack = ItemStack(ModRegistry.Items.TURTLE_NORMAL.get()) turtleStack.orCreateTag - assertThat( - "Inventory is as expected.", - turtle.contents, - contains( - isStack(turtleStack), isStack(Items.WET_SPONGE, 1), isStack(ItemStack.EMPTY), isStack(ItemStack.EMPTY), - isStack(ItemStack.EMPTY), isStack(ItemStack.EMPTY), isStack(ItemStack.EMPTY), isStack(ItemStack.EMPTY), - isStack(ItemStack.EMPTY), isStack(ItemStack.EMPTY), isStack(ItemStack.EMPTY), isStack(ItemStack.EMPTY), - isStack(ItemStack.EMPTY), isStack(ItemStack.EMPTY), isStack(ItemStack.EMPTY), isStack(ItemStack.EMPTY), + helper.assertContainerExactly( + BlockPos(2, 2, 2), + listOf( + turtleStack, ItemStack(Items.WET_SPONGE), ItemStack.EMPTY, ItemStack.EMPTY, + ItemStack.EMPTY, ItemStack.EMPTY, ItemStack.EMPTY, ItemStack.EMPTY, + ItemStack.EMPTY, ItemStack.EMPTY, ItemStack.EMPTY, ItemStack.EMPTY, + ItemStack.EMPTY, ItemStack.EMPTY, ItemStack.EMPTY, ItemStack.EMPTY, ), ) } @@ -817,7 +812,8 @@ class Turtle_Test { fun Craft_offset(helper: GameTestHelper) = helper.sequence { for (offset in listOf(0, 1, 4, 5)) { thenExecute { - val turtle = helper.getBlockEntity(BlockPos(2, 2, 2), ModRegistry.BlockEntities.TURTLE_NORMAL.get()) + val turtlePos = BlockPos(2, 2, 2) + val turtle = helper.getBlockEntity(turtlePos, ModRegistry.BlockEntities.TURTLE_NORMAL.get()) // Set up turtle inventory turtle.clearContent() @@ -831,14 +827,13 @@ class Turtle_Test { assertTrue(TurtleCraftCommand(1).execute(turtle.access).isSuccess, "Crafting succeeded") // And check item was crafted - assertThat( - "Inventory is as expected.", - turtle.contents, - contains( - isStack(Items.STONE_PICKAXE, 1), isStack(ItemStack.EMPTY), isStack(ItemStack.EMPTY), isStack(ItemStack.EMPTY), - isStack(ItemStack.EMPTY), isStack(ItemStack.EMPTY), isStack(ItemStack.EMPTY), isStack(ItemStack.EMPTY), - isStack(ItemStack.EMPTY), isStack(ItemStack.EMPTY), isStack(ItemStack.EMPTY), isStack(ItemStack.EMPTY), - isStack(ItemStack.EMPTY), isStack(ItemStack.EMPTY), isStack(ItemStack.EMPTY), isStack(ItemStack.EMPTY), + helper.assertContainerExactly( + turtlePos, + listOf( + ItemStack(Items.STONE_PICKAXE), ItemStack.EMPTY, ItemStack.EMPTY, ItemStack.EMPTY, + ItemStack.EMPTY, ItemStack.EMPTY, ItemStack.EMPTY, ItemStack.EMPTY, + ItemStack.EMPTY, ItemStack.EMPTY, ItemStack.EMPTY, ItemStack.EMPTY, + ItemStack.EMPTY, ItemStack.EMPTY, ItemStack.EMPTY, ItemStack.EMPTY, ), ) } diff --git a/projects/common/src/testMod/resources/data/cctest/structures/printer_test.contents_updates_state.snbt b/projects/common/src/testMod/resources/data/cctest/structures/printer_test.empty.snbt similarity index 100% rename from projects/common/src/testMod/resources/data/cctest/structures/printer_test.contents_updates_state.snbt rename to projects/common/src/testMod/resources/data/cctest/structures/printer_test.empty.snbt diff --git a/projects/common/src/testMod/resources/data/cctest/structures/printer_test.no_print_when_full.snbt b/projects/common/src/testMod/resources/data/cctest/structures/printer_test.no_print_when_full.snbt new file mode 100644 index 000000000..7a742b2fc --- /dev/null +++ b/projects/common/src/testMod/resources/data/cctest/structures/printer_test.no_print_when_full.snbt @@ -0,0 +1,137 @@ +{ + DataVersion: 3465, + size: [5, 5, 5], + 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: [0, 0, 3], state: "minecraft:polished_andesite"}, + {pos: [0, 0, 4], 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: [1, 0, 3], state: "minecraft:polished_andesite"}, + {pos: [1, 0, 4], 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: [2, 0, 3], state: "minecraft:polished_andesite"}, + {pos: [2, 0, 4], state: "minecraft:polished_andesite"}, + {pos: [3, 0, 0], state: "minecraft:polished_andesite"}, + {pos: [3, 0, 1], state: "minecraft:polished_andesite"}, + {pos: [3, 0, 2], state: "minecraft:polished_andesite"}, + {pos: [3, 0, 3], state: "minecraft:polished_andesite"}, + {pos: [3, 0, 4], state: "minecraft:polished_andesite"}, + {pos: [4, 0, 0], state: "minecraft:polished_andesite"}, + {pos: [4, 0, 1], state: "minecraft:polished_andesite"}, + {pos: [4, 0, 2], state: "minecraft:polished_andesite"}, + {pos: [4, 0, 3], state: "minecraft:polished_andesite"}, + {pos: [4, 0, 4], 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: [0, 1, 3], state: "minecraft:air"}, + {pos: [0, 1, 4], 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: [1, 1, 3], state: "minecraft:air"}, + {pos: [1, 1, 4], state: "minecraft:air"}, + {pos: [2, 1, 0], state: "minecraft:air"}, + {pos: [2, 1, 1], state: "minecraft:air"}, + {pos: [2, 1, 2], state: "computercraft:printer{bottom:true,facing:north,top:true}", nbt: {Items: [{Count: 1b, Slot: 0b, id: "minecraft:black_dye"}, {Count: 1b, Slot: 1b, id: "minecraft:paper"}, {Count: 1b, Slot: 7b, id: "computercraft:printed_page", tag: {Color0: "fffffffffffffffffffffffff", Color1: "fffffffffffffffffffffffff", Color10: "fffffffffffffffffffffffff", Color11: "fffffffffffffffffffffffff", Color12: "fffffffffffffffffffffffff", Color13: "fffffffffffffffffffffffff", Color14: "fffffffffffffffffffffffff", Color15: "fffffffffffffffffffffffff", Color16: "fffffffffffffffffffffffff", Color17: "fffffffffffffffffffffffff", Color18: "fffffffffffffffffffffffff", Color19: "fffffffffffffffffffffffff", Color2: "fffffffffffffffffffffffff", Color20: "fffffffffffffffffffffffff", Color3: "fffffffffffffffffffffffff", Color4: "fffffffffffffffffffffffff", Color5: "fffffffffffffffffffffffff", Color6: "fffffffffffffffffffffffff", Color7: "fffffffffffffffffffffffff", Color8: "fffffffffffffffffffffffff", Color9: "fffffffffffffffffffffffff", Pages: 1, Text0: " ", Text1: " ", Text10: " ", Text11: " ", Text12: " ", Text13: " ", Text14: " ", Text15: " ", Text16: " ", Text17: " ", Text18: " ", Text19: " ", Text2: " ", Text20: " ", Text3: " ", Text4: " ", Text5: " ", Text6: " ", Text7: " ", Text8: " ", Text9: " ", Title: ""}}, {Count: 1b, Slot: 8b, id: "computercraft:printed_page", tag: {Color0: "fffffffffffffffffffffffff", Color1: "fffffffffffffffffffffffff", Color10: "fffffffffffffffffffffffff", Color11: "fffffffffffffffffffffffff", Color12: "fffffffffffffffffffffffff", Color13: "fffffffffffffffffffffffff", Color14: "fffffffffffffffffffffffff", Color15: "fffffffffffffffffffffffff", Color16: "fffffffffffffffffffffffff", Color17: "fffffffffffffffffffffffff", Color18: "fffffffffffffffffffffffff", Color19: "fffffffffffffffffffffffff", Color2: "fffffffffffffffffffffffff", Color20: "fffffffffffffffffffffffff", Color3: "fffffffffffffffffffffffff", Color4: "fffffffffffffffffffffffff", Color5: "fffffffffffffffffffffffff", Color6: "fffffffffffffffffffffffff", Color7: "fffffffffffffffffffffffff", Color8: "fffffffffffffffffffffffff", Color9: "fffffffffffffffffffffffff", Pages: 1, Text0: " ", Text1: " ", Text10: " ", Text11: " ", Text12: " ", Text13: " ", Text14: " ", Text15: " ", Text16: " ", Text17: " ", Text18: " ", Text19: " ", Text2: " ", Text20: " ", Text3: " ", Text4: " ", Text5: " ", Text6: " ", Text7: " ", Text8: " ", Text9: " ", Title: ""}}, {Count: 1b, Slot: 9b, id: "computercraft:printed_page", tag: {Color0: "fffffffffffffffffffffffff", Color1: "fffffffffffffffffffffffff", Color10: "fffffffffffffffffffffffff", Color11: "fffffffffffffffffffffffff", Color12: "fffffffffffffffffffffffff", Color13: "fffffffffffffffffffffffff", Color14: "fffffffffffffffffffffffff", Color15: "fffffffffffffffffffffffff", Color16: "fffffffffffffffffffffffff", Color17: "fffffffffffffffffffffffff", Color18: "fffffffffffffffffffffffff", Color19: "fffffffffffffffffffffffff", Color2: "fffffffffffffffffffffffff", Color20: "fffffffffffffffffffffffff", Color3: "fffffffffffffffffffffffff", Color4: "fffffffffffffffffffffffff", Color5: "fffffffffffffffffffffffff", Color6: "fffffffffffffffffffffffff", Color7: "fffffffffffffffffffffffff", Color8: "fffffffffffffffffffffffff", Color9: "fffffffffffffffffffffffff", Pages: 1, Text0: " ", Text1: " ", Text10: " ", Text11: " ", Text12: " ", Text13: " ", Text14: " ", Text15: " ", Text16: " ", Text17: " ", Text18: " ", Text19: " ", Text2: " ", Text20: " ", Text3: " ", Text4: " ", Text5: " ", Text6: " ", Text7: " ", Text8: " ", Text9: " ", Title: ""}}, {Count: 1b, Slot: 10b, id: "computercraft:printed_page", tag: {Color0: "fffffffffffffffffffffffff", Color1: "fffffffffffffffffffffffff", Color10: "fffffffffffffffffffffffff", Color11: "fffffffffffffffffffffffff", Color12: "fffffffffffffffffffffffff", Color13: "fffffffffffffffffffffffff", Color14: "fffffffffffffffffffffffff", Color15: "fffffffffffffffffffffffff", Color16: "fffffffffffffffffffffffff", Color17: "fffffffffffffffffffffffff", Color18: "fffffffffffffffffffffffff", Color19: "fffffffffffffffffffffffff", Color2: "fffffffffffffffffffffffff", Color20: "fffffffffffffffffffffffff", Color3: "fffffffffffffffffffffffff", Color4: "fffffffffffffffffffffffff", Color5: "fffffffffffffffffffffffff", Color6: "fffffffffffffffffffffffff", Color7: "fffffffffffffffffffffffff", Color8: "fffffffffffffffffffffffff", Color9: "fffffffffffffffffffffffff", Pages: 1, Text0: " ", Text1: " ", Text10: " ", Text11: " ", Text12: " ", Text13: " ", Text14: " ", Text15: " ", Text16: " ", Text17: " ", Text18: " ", Text19: " ", Text2: " ", Text20: " ", Text3: " ", Text4: " ", Text5: " ", Text6: " ", Text7: " ", Text8: " ", Text9: " ", Title: ""}}, {Count: 1b, Slot: 11b, id: "computercraft:printed_page", tag: {Color0: "fffffffffffffffffffffffff", Color1: "fffffffffffffffffffffffff", Color10: "fffffffffffffffffffffffff", Color11: "fffffffffffffffffffffffff", Color12: "fffffffffffffffffffffffff", Color13: "fffffffffffffffffffffffff", Color14: "fffffffffffffffffffffffff", Color15: "fffffffffffffffffffffffff", Color16: "fffffffffffffffffffffffff", Color17: "fffffffffffffffffffffffff", Color18: "fffffffffffffffffffffffff", Color19: "fffffffffffffffffffffffff", Color2: "fffffffffffffffffffffffff", Color20: "fffffffffffffffffffffffff", Color3: "fffffffffffffffffffffffff", Color4: "fffffffffffffffffffffffff", Color5: "fffffffffffffffffffffffff", Color6: "fffffffffffffffffffffffff", Color7: "fffffffffffffffffffffffff", Color8: "fffffffffffffffffffffffff", Color9: "fffffffffffffffffffffffff", Pages: 1, Text0: " ", Text1: " ", Text10: " ", Text11: " ", Text12: " ", Text13: " ", Text14: " ", Text15: " ", Text16: " ", Text17: " ", Text18: " ", Text19: " ", Text2: " ", Text20: " ", Text3: " ", Text4: " ", Text5: " ", Text6: " ", Text7: " ", Text8: " ", Text9: " ", Title: ""}}, {Count: 1b, Slot: 12b, id: "computercraft:printed_page", tag: {Color0: "fffffffffffffffffffffffff", Color1: "fffffffffffffffffffffffff", Color10: "fffffffffffffffffffffffff", Color11: "fffffffffffffffffffffffff", Color12: "fffffffffffffffffffffffff", Color13: "fffffffffffffffffffffffff", Color14: "fffffffffffffffffffffffff", Color15: "fffffffffffffffffffffffff", Color16: "fffffffffffffffffffffffff", Color17: "fffffffffffffffffffffffff", Color18: "fffffffffffffffffffffffff", Color19: "fffffffffffffffffffffffff", Color2: "fffffffffffffffffffffffff", Color20: "fffffffffffffffffffffffff", Color3: "fffffffffffffffffffffffff", Color4: "fffffffffffffffffffffffff", Color5: "fffffffffffffffffffffffff", Color6: "fffffffffffffffffffffffff", Color7: "fffffffffffffffffffffffff", Color8: "fffffffffffffffffffffffff", Color9: "fffffffffffffffffffffffff", Pages: 1, Text0: " ", Text1: " ", Text10: " ", Text11: " ", Text12: " ", Text13: " ", Text14: " ", Text15: " ", Text16: " ", Text17: " ", Text18: " ", Text19: " ", Text2: " ", Text20: " ", Text3: " ", Text4: " ", Text5: " ", Text6: " ", Text7: " ", Text8: " ", Text9: " ", Title: ""}}], PageTitle: "", Printing: 0b, id: "computercraft:printer", term_bgColour: 15, term_cursorBlink: 0b, term_cursorX: 0, term_cursorY: 0, term_palette: [I; 1118481, 13388876, 5744206, 8349260, 3368652, 11691749, 5020082, 10066329, 5000268, 15905484, 8375321, 14605932, 10072818, 15040472, 15905331, 15790320], term_textBgColour_0: "fffffffffffffffffffffffff", term_textBgColour_1: "fffffffffffffffffffffffff", term_textBgColour_10: "fffffffffffffffffffffffff", term_textBgColour_11: "fffffffffffffffffffffffff", term_textBgColour_12: "fffffffffffffffffffffffff", term_textBgColour_13: "fffffffffffffffffffffffff", term_textBgColour_14: "fffffffffffffffffffffffff", term_textBgColour_15: "fffffffffffffffffffffffff", term_textBgColour_16: "fffffffffffffffffffffffff", term_textBgColour_17: "fffffffffffffffffffffffff", term_textBgColour_18: "fffffffffffffffffffffffff", term_textBgColour_19: "fffffffffffffffffffffffff", term_textBgColour_2: "fffffffffffffffffffffffff", term_textBgColour_20: "fffffffffffffffffffffffff", term_textBgColour_3: "fffffffffffffffffffffffff", term_textBgColour_4: "fffffffffffffffffffffffff", term_textBgColour_5: "fffffffffffffffffffffffff", term_textBgColour_6: "fffffffffffffffffffffffff", term_textBgColour_7: "fffffffffffffffffffffffff", term_textBgColour_8: "fffffffffffffffffffffffff", term_textBgColour_9: "fffffffffffffffffffffffff", term_textColour: 15, term_textColour_0: "fffffffffffffffffffffffff", term_textColour_1: "fffffffffffffffffffffffff", term_textColour_10: "fffffffffffffffffffffffff", term_textColour_11: "fffffffffffffffffffffffff", term_textColour_12: "fffffffffffffffffffffffff", term_textColour_13: "fffffffffffffffffffffffff", term_textColour_14: "fffffffffffffffffffffffff", term_textColour_15: "fffffffffffffffffffffffff", term_textColour_16: "fffffffffffffffffffffffff", term_textColour_17: "fffffffffffffffffffffffff", term_textColour_18: "fffffffffffffffffffffffff", term_textColour_19: "fffffffffffffffffffffffff", term_textColour_2: "fffffffffffffffffffffffff", term_textColour_20: "fffffffffffffffffffffffff", term_textColour_3: "fffffffffffffffffffffffff", term_textColour_4: "fffffffffffffffffffffffff", term_textColour_5: "fffffffffffffffffffffffff", term_textColour_6: "fffffffffffffffffffffffff", term_textColour_7: "fffffffffffffffffffffffff", term_textColour_8: "fffffffffffffffffffffffff", term_textColour_9: "fffffffffffffffffffffffff", term_text_0: " ", term_text_1: " ", term_text_10: " ", term_text_11: " ", term_text_12: " ", term_text_13: " ", term_text_14: " ", term_text_15: " ", term_text_16: " ", term_text_17: " ", term_text_18: " ", term_text_19: " ", term_text_2: " ", term_text_20: " ", term_text_3: " ", term_text_4: " ", term_text_5: " ", term_text_6: " ", term_text_7: " ", term_text_8: " ", term_text_9: " "}}, + {pos: [2, 1, 3], state: "minecraft:air"}, + {pos: [2, 1, 4], state: "minecraft:air"}, + {pos: [3, 1, 0], state: "minecraft:air"}, + {pos: [3, 1, 1], state: "minecraft:air"}, + {pos: [3, 1, 2], state: "minecraft:air"}, + {pos: [3, 1, 3], state: "minecraft:air"}, + {pos: [3, 1, 4], state: "minecraft:air"}, + {pos: [4, 1, 0], state: "minecraft:air"}, + {pos: [4, 1, 1], state: "minecraft:air"}, + {pos: [4, 1, 2], state: "minecraft:air"}, + {pos: [4, 1, 3], state: "minecraft:air"}, + {pos: [4, 1, 4], 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: [0, 2, 3], state: "minecraft:air"}, + {pos: [0, 2, 4], 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: [1, 2, 3], state: "minecraft:air"}, + {pos: [1, 2, 4], state: "minecraft:air"}, + {pos: [2, 2, 0], state: "minecraft:air"}, + {pos: [2, 2, 1], state: "minecraft:air"}, + {pos: [2, 2, 2], state: "minecraft:air"}, + {pos: [2, 2, 3], state: "minecraft:air"}, + {pos: [2, 2, 4], state: "minecraft:air"}, + {pos: [3, 2, 0], state: "minecraft:air"}, + {pos: [3, 2, 1], state: "minecraft:air"}, + {pos: [3, 2, 2], state: "minecraft:air"}, + {pos: [3, 2, 3], state: "minecraft:air"}, + {pos: [3, 2, 4], state: "minecraft:air"}, + {pos: [4, 2, 0], state: "minecraft:air"}, + {pos: [4, 2, 1], state: "minecraft:air"}, + {pos: [4, 2, 2], state: "minecraft:air"}, + {pos: [4, 2, 3], state: "minecraft:air"}, + {pos: [4, 2, 4], state: "minecraft:air"}, + {pos: [0, 3, 0], state: "minecraft:air"}, + {pos: [0, 3, 1], state: "minecraft:air"}, + {pos: [0, 3, 2], state: "minecraft:air"}, + {pos: [0, 3, 3], state: "minecraft:air"}, + {pos: [0, 3, 4], state: "minecraft:air"}, + {pos: [1, 3, 0], state: "minecraft:air"}, + {pos: [1, 3, 1], state: "minecraft:air"}, + {pos: [1, 3, 2], state: "minecraft:air"}, + {pos: [1, 3, 3], state: "minecraft:air"}, + {pos: [1, 3, 4], state: "minecraft:air"}, + {pos: [2, 3, 0], state: "minecraft:air"}, + {pos: [2, 3, 1], state: "minecraft:air"}, + {pos: [2, 3, 2], state: "minecraft:air"}, + {pos: [2, 3, 3], state: "minecraft:air"}, + {pos: [2, 3, 4], state: "minecraft:air"}, + {pos: [3, 3, 0], state: "minecraft:air"}, + {pos: [3, 3, 1], state: "minecraft:air"}, + {pos: [3, 3, 2], state: "minecraft:air"}, + {pos: [3, 3, 3], state: "minecraft:air"}, + {pos: [3, 3, 4], state: "minecraft:air"}, + {pos: [4, 3, 0], state: "minecraft:air"}, + {pos: [4, 3, 1], state: "minecraft:air"}, + {pos: [4, 3, 2], state: "minecraft:air"}, + {pos: [4, 3, 3], state: "minecraft:air"}, + {pos: [4, 3, 4], state: "minecraft:air"}, + {pos: [0, 4, 0], state: "minecraft:air"}, + {pos: [0, 4, 1], state: "minecraft:air"}, + {pos: [0, 4, 2], state: "minecraft:air"}, + {pos: [0, 4, 3], state: "minecraft:air"}, + {pos: [0, 4, 4], state: "minecraft:air"}, + {pos: [1, 4, 0], state: "minecraft:air"}, + {pos: [1, 4, 1], state: "minecraft:air"}, + {pos: [1, 4, 2], state: "minecraft:air"}, + {pos: [1, 4, 3], state: "minecraft:air"}, + {pos: [1, 4, 4], state: "minecraft:air"}, + {pos: [2, 4, 0], state: "minecraft:air"}, + {pos: [2, 4, 1], state: "minecraft:air"}, + {pos: [2, 4, 2], state: "minecraft:air"}, + {pos: [2, 4, 3], state: "minecraft:air"}, + {pos: [2, 4, 4], state: "minecraft:air"}, + {pos: [3, 4, 0], state: "minecraft:air"}, + {pos: [3, 4, 1], state: "minecraft:air"}, + {pos: [3, 4, 2], state: "minecraft:air"}, + {pos: [3, 4, 3], state: "minecraft:air"}, + {pos: [3, 4, 4], state: "minecraft:air"}, + {pos: [4, 4, 0], state: "minecraft:air"}, + {pos: [4, 4, 1], state: "minecraft:air"}, + {pos: [4, 4, 2], state: "minecraft:air"}, + {pos: [4, 4, 3], state: "minecraft:air"}, + {pos: [4, 4, 4], state: "minecraft:air"} + ], + entities: [], + palette: [ + "minecraft:polished_andesite", + "minecraft:air", + "computercraft:printer{bottom:true,facing:north,top:true}" + ] +} From ed631b05e7af0f698b04cb9d876b3f9200f91877 Mon Sep 17 00:00:00 2001 From: Jonathan Coates Date: Tue, 14 Jan 2025 10:22:01 +0000 Subject: [PATCH 5/7] Fix TurtleTool using the wrong stack When "placing" the item (e.g. hoeing soil), we were using the tool item, rather than the passed stack. This was introduced in 9a914e75c4a4e122c16aac5d010059d28b475b5e, so never made it into a release. --- .../computercraft/shared/turtle/upgrades/TurtleTool.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 82c48bb83..de72b7e09 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 @@ -333,7 +333,7 @@ public class TurtleTool extends AbstractTurtleUpgrade { * @return Whether the tool was successfully used. * @see PlatformHelper#hasToolUsage(ItemStack) */ - private boolean useTool(ServerLevel level, ITurtleAccess turtle, TurtlePlayer turtlePlayer, ItemStack stack, Direction direction) { + private static 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. @@ -348,7 +348,7 @@ public class TurtleTool extends AbstractTurtleUpgrade { if (result instanceof PlatformHelper.UseOnResult.Handled handled) { return handled.result().consumesAction(); } else { - return ((PlatformHelper.UseOnResult.Continue) result).item() && item.useOn(new UseOnContext(turtlePlayer.player(), InteractionHand.MAIN_HAND, hit)).consumesAction(); + return ((PlatformHelper.UseOnResult.Continue) result).item() && stack.useOn(new UseOnContext(turtlePlayer.player(), InteractionHand.MAIN_HAND, hit)).consumesAction(); } } From 2ca58500604e28fa9bcb43e599477a1d0d422fbc Mon Sep 17 00:00:00 2001 From: Jonathan Coates Date: Tue, 14 Jan 2025 09:49:48 +0000 Subject: [PATCH 6/7] Bump CC:T to 1.114.3 --- gradle.properties | 2 +- .../data/computercraft/lua/rom/help/changelog.md | 9 +++++++++ .../data/computercraft/lua/rom/help/whatsnew.md | 10 +++++++--- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/gradle.properties b/gradle.properties index 6285bdff7..9f56b970c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -10,7 +10,7 @@ kotlin.jvm.target.validation.mode=error # Mod properties isUnstable=false -modVersion=1.114.2 +modVersion=1.114.3 # Minecraft properties: We want to configure this here so we can read it in settings.gradle mcVersion=1.20.1 diff --git a/projects/core/src/main/resources/data/computercraft/lua/rom/help/changelog.md b/projects/core/src/main/resources/data/computercraft/lua/rom/help/changelog.md index bdffb0836..892c96170 100644 --- a/projects/core/src/main/resources/data/computercraft/lua/rom/help/changelog.md +++ b/projects/core/src/main/resources/data/computercraft/lua/rom/help/changelog.md @@ -1,3 +1,12 @@ +# New features in CC: Tweaked 1.114.3 + +* `wget` now prints the error that occurred, rather than a generic "Failed" (tizu69). +* Update several translations. + +Several bug fixes: +* Fix `fs.isDriveRoot` returning true for non-existent files. +* Fix possible memory leak when sending terminal contents. + # New features in CC: Tweaked 1.114.2 One bug fix: diff --git a/projects/core/src/main/resources/data/computercraft/lua/rom/help/whatsnew.md b/projects/core/src/main/resources/data/computercraft/lua/rom/help/whatsnew.md index 203143367..a8fc8a0e6 100644 --- a/projects/core/src/main/resources/data/computercraft/lua/rom/help/whatsnew.md +++ b/projects/core/src/main/resources/data/computercraft/lua/rom/help/whatsnew.md @@ -1,6 +1,10 @@ -New features in CC: Tweaked 1.114.2 +New features in CC: Tweaked 1.114.3 -One bug fix: -* Fix OpenGL errors when rendering empty monitors. +* `wget` now prints the error that occurred, rather than a generic "Failed" (tizu69). +* Update several translations. + +Several bug fixes: +* Fix `fs.isDriveRoot` returning true for non-existent files. +* Fix possible memory leak when sending terminal contents. Type "help changelog" to see the full version history. From 62c9e5b08f859de912b81ed3b22ea2373b8572db Mon Sep 17 00:00:00 2001 From: Jonathan Coates Date: Tue, 14 Jan 2025 18:14:51 +0000 Subject: [PATCH 7/7] Add back missing translations Lost in d3a3ab3c21b1b4c0f65b88e694743f063627727f, due to changes in ea670cc358ba3c96ac66a8133e8b48c5f7540428. --- .../main/resources/assets/computercraft/lang/pt_br.json | 9 +++++++++ .../main/resources/assets/computercraft/lang/ru_ru.json | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/projects/common/src/main/resources/assets/computercraft/lang/pt_br.json b/projects/common/src/main/resources/assets/computercraft/lang/pt_br.json index 56ab8174c..25df945d2 100644 --- a/projects/common/src/main/resources/assets/computercraft/lang/pt_br.json +++ b/projects/common/src/main/resources/assets/computercraft/lang/pt_br.json @@ -82,26 +82,35 @@ "gui.computercraft.config.disabled_generic_methods.tooltip": "Uma lista de métodos genéricos ou fontes de métodos a serem desativados. Métodos genéricos são aqueles adicionados a um bloco ou entidade de bloco quando não há um provedor de periféricos explícito. Isso inclui métodos de inventário (ou seja, inventory.getItemDetail, inventory.pushItems) e, se estiver usando Forge, os métodos fluid_storage e energy_storage. \nOs métodos nesta lista podem ser um grupo inteiro de métodos (computercraft:inventory) ou um único método (computercraft:inventory#pushItems).\n", "gui.computercraft.config.execution": "Execução", "gui.computercraft.config.execution.computer_threads": "Threads por computador", + "gui.computercraft.config.execution.computer_threads.tooltip": "Defina o número de threads que os computadores podem utilizar. Um número maior significa que mais computadores podem ser executados ao mesmo tempo, mas pode causar lag. Observe que alguns mods podem não funcionar com uma contagem de threads superior a 1. Use com cautela.", "gui.computercraft.config.execution.max_main_computer_time": "Limite de tempo do computador por tick do servidor", + "gui.computercraft.config.execution.max_main_computer_time.tooltip": "O tempo máximo ideal que um computador pode executar em um tick, em milissegundos. \nObserve que é bastante provável que ultrapassemos esse limite, pois não há como saber exatamente quanto tempo uma operação levará - isso visa ser o limite superior do tempo médio.", "gui.computercraft.config.execution.max_main_global_time": "Limite de tempo global por tick do servidor", + "gui.computercraft.config.execution.max_main_global_time.tooltip": "O tempo máximo que pode ser gasto executando tarefas em um único tick, em milissegundos. \nObserve que é bastante provável que ultrapassemos esse limite, pois não há como saber exatamente quanto tempo uma operação levará - isso visa ser o limite superior do tempo médio.", "gui.computercraft.config.execution.tooltip": "Controla o comportamento de execução dos computadores. Isso é principalmente destinado ao ajuste fino dos servidores e, geralmente, não deve ser alterado.", "gui.computercraft.config.floppy_space_limit": "Limite de espaço dos Disquetes (bytes)", "gui.computercraft.config.floppy_space_limit.tooltip": "O limite de espaço em disco para disquete em bytes.", "gui.computercraft.config.http": "HTTP", "gui.computercraft.config.http.bandwidth": "Largura de banda", "gui.computercraft.config.http.bandwidth.global_download": "Limite de download global", + "gui.computercraft.config.http.bandwidth.global_download.tooltip": "O número de bytes que podem ser baixados em um segundo. Isso é compartilhado entre todos os computadores. (bytes/s).", "gui.computercraft.config.http.bandwidth.global_upload": "Limite de upload global", + "gui.computercraft.config.http.bandwidth.global_upload.tooltip": "O número de bytes que podem ser enviados em um segundo. Isso é compartilhado entre todos os computadores. (bytes/s).", "gui.computercraft.config.http.bandwidth.tooltip": "Limita a banda usada pelos computadores.", "gui.computercraft.config.http.enabled": "Habilitar a biblioteca de HTTP", "gui.computercraft.config.http.enabled.tooltip": "Ative a API \"http\" nos Computadores. Desativar isso também desativa os programas \"pastebin\" e \"wget\", dos quais muitos usuários dependem. É recomendado deixar isso ativado e usar a opção de configuração \"rules\" para impor um controle mais detalhado.", "gui.computercraft.config.http.max_requests": "Limite de conexões paralelas", + "gui.computercraft.config.http.max_requests.tooltip": "O número de solicitações Http que um computador pode fazer ao mesmo tempo. Solicitações adicionais serão enfileiradas e enviadas quando as solicitações em execução forem concluídas. Defina como 0 para ilimitado.", "gui.computercraft.config.http.max_websockets": "Limite de conexões websocket", + "gui.computercraft.config.http.max_websockets.tooltip": "O número de websockets que um computador pode ter abertos ao mesmo tempo.", "gui.computercraft.config.http.proxy": "Proxy", "gui.computercraft.config.http.proxy.host": "Nome de host", "gui.computercraft.config.http.proxy.host.tooltip": "O nome do host ou endereço IP do servidor proxy.", "gui.computercraft.config.http.proxy.port": "Porta", + "gui.computercraft.config.http.proxy.port.tooltip": "A porta do servidor proxy.", "gui.computercraft.config.http.proxy.tooltip": "Tunéis de solicitações HTTP e websocket através de um servidor proxy. Afeta apenas as regras HTTP com \"use_proxy\" definido como verdadeiro (desligado por padrão). Se a autenticação for necessária para o proxy, crie um arquivo \"computercraft-proxy.pw\" no mesmo diretório que \"computercraft-server.toml\", contendo o nome de usuário e a senha separados por dois pontos, por exemplo, \"meuusuario:minhasenha\". Para proxies SOCKS4, apenas o nome de usuário é necessário.", "gui.computercraft.config.http.proxy.type": "Tipo de proxy", + "gui.computercraft.config.http.proxy.type.tooltip": "O tipo de proxy para usar.", "gui.computercraft.config.http.rules": "Regras Permitir/Negar", "gui.computercraft.config.http.rules.tooltip": "Uma lista de regras que controlam o comportamento da API \"http\" para domínios ou IPs específicos. Cada regra corresponde a um nome de host e uma porta opcional, e então define várias propriedades para a solicitação. As regras são avaliadas em ordem, o que significa que regras anteriores sobrescrevem as posteriores.\n\nPropriedades válidas:\n- \"host\" (obrigatório): O domínio ou endereço IP que esta regra corresponde. Isso pode ser um nome de domínio (\"pastebin.com\"), um curinga (\"*.pastebin.com\") ou notação CIDR (\"127.0.0.0/8\").\n- \"port\" (opcional): Corresponder apenas a solicitações para uma porta específica, como 80 ou 443.\n\n- \"action\" (opcional): Se permitir ou negar esta solicitação.\n- \"max_download\" (opcional): O tamanho máximo (em bytes) que um computador pode baixar nesta solicitação.\n- \"max_upload\" (opcional): O tamanho máximo (em bytes) que um computador pode enviar em uma solicitação.\n- \"max_websocket_message\" (opcional): O tamanho máximo (em bytes) que um computador pode enviar ou receber em um pacote websocket.\n- \"use_proxy\" (opcional): Habilitar o uso do proxy HTTP/SOCKS se estiver configurado.", "gui.computercraft.config.http.tooltip": "Controla a API HTTP", diff --git a/projects/common/src/main/resources/assets/computercraft/lang/ru_ru.json b/projects/common/src/main/resources/assets/computercraft/lang/ru_ru.json index f20146b7c..c1491e199 100644 --- a/projects/common/src/main/resources/assets/computercraft/lang/ru_ru.json +++ b/projects/common/src/main/resources/assets/computercraft/lang/ru_ru.json @@ -74,25 +74,34 @@ "gui.computercraft.config.default_computer_settings.tooltip": "Разделенный запятыми список системных настроек по умолчанию на новых компьютерах.\nНапример: \"shell.autocomplete=false,lua.autocomplete=false,edit.autocomplete=false\"\nотключит всё автодополнение.", "gui.computercraft.config.execution": "Выполнение", "gui.computercraft.config.execution.computer_threads": "Потоки компьютера", + "gui.computercraft.config.execution.computer_threads.tooltip": "Устанавливает количество потоков, на которых работают компьютеры. Большее число\nозначает, что больше компьютеров сможет работать одновременно, но может привести к лагу.\nОбратите внимание, что некоторые моды могут не работать с более чем одним потоком. Используйте с осторожностью.", + "gui.computercraft.config.execution.max_main_computer_time.tooltip": "Идеальный максимум времени, которое отведено компьютеру на выполнение задач, в миллисекундах.\nМы вполне возможно выйдем за этот лимит, так как невозможно предсказать сколько\nвремени будет затрачено на выполнение задач, это лишь верхний лимит среднего значения времени.", "gui.computercraft.config.execution.max_main_global_time": "Глобальный лимит времени на тик сервера", + "gui.computercraft.config.execution.max_main_global_time.tooltip": "Максимум времени, которое может быть потрачено на выполнение задач за один тик, в \nмиллисекундах. \nМы вполне возможно выйдем за этот лимит, так как невозможно предсказать сколько\nвремени будет затрачено на выполнение задач, это лишь верхний лимит среднего значения времени.", "gui.computercraft.config.execution.tooltip": "Контролирует поведение выполнения задач компьютеров. Эта настройка преднезначается для \nтонкой настройки серверов, и в основном не должна быть изменена.", "gui.computercraft.config.floppy_space_limit": "Лимит места на дискетах (байты)", "gui.computercraft.config.floppy_space_limit.tooltip": "Лимит места для хранения информации на дискетах, в байтах.", "gui.computercraft.config.http": "HTTP", "gui.computercraft.config.http.bandwidth": "Пропускная способность", "gui.computercraft.config.http.bandwidth.global_download": "Глобальный лимит на скачивание", + "gui.computercraft.config.http.bandwidth.global_download.tooltip": "Количество байтов, которое можно скачать за секунду. Все компьютеры делят эту пропускную способность. (байты в секунду)", "gui.computercraft.config.http.bandwidth.global_upload": "Глобальный лимит загрузки", + "gui.computercraft.config.http.bandwidth.global_upload.tooltip": "Количество байтов, которое можно загрузить за секунду. Все компьютеры делят эту пропускную способность. (байты в секунду)", "gui.computercraft.config.http.bandwidth.tooltip": "Ограничивает пропускную способность, используемую компьютерами.", "gui.computercraft.config.http.enabled": "Включить HTTP API", "gui.computercraft.config.http.enabled.tooltip": "Включить API \"http\" на Компьютерах. Это также отключает программы \"pastebin\" и \"wget\", \nкоторые нужны многим пользователям. Рекомендуется оставить это включенным и использовать \nконфиг \"rules\" для более тонкой настройки.", "gui.computercraft.config.http.max_requests": "Максимум одновременных запросов", + "gui.computercraft.config.http.max_requests.tooltip": "Количество http-запросов, которые компьютер может сделать одновременно. Дополнительные запросы \nбудут поставлены в очередь, и отправлены когда существующие запросы будут выполнены. Установите на 0 для \nнеограниченных запросов.", "gui.computercraft.config.http.max_websockets": "Максимум одновременных веб-сокетов", + "gui.computercraft.config.http.max_websockets.tooltip": "Количество одновременно открытых веб-сокетов, которые может иметь компьютер. Установите на 0 для неограниченных веб-сокетов.", "gui.computercraft.config.http.proxy": "Proxy", "gui.computercraft.config.http.proxy.host": "Имя хоста", "gui.computercraft.config.http.proxy.host.tooltip": "Имя хоста или IP-адрес прокси-сервера.", "gui.computercraft.config.http.proxy.port": "Порт", + "gui.computercraft.config.http.proxy.port.tooltip": "Порт прокси-сервера.", "gui.computercraft.config.http.proxy.tooltip": "Туннелирует HTTP-запросы и запросы websocket через прокси-сервер. Влияет только на HTTP\nправила с параметром \"use_proxy\" в значении true (отключено по умолчанию).\nЕсли для прокси-сервера требуется аутентификация, создайте \"computercraft-proxy.pw\"\nфайл в том же каталоге, что и \"computercraft-server.toml\", содержащий имя\nпользователя и пароль, разделенные двоеточием, например \"myuser:mypassword\". Для\nпрокси-серверов SOCKS4 требуется только имя пользователя.", "gui.computercraft.config.http.proxy.type": "Тип прокси-сервера", + "gui.computercraft.config.http.proxy.type.tooltip": "Тип используемого прокси-сервера.", "gui.computercraft.config.http.rules": "Разрешающие/запрещающие правила", "gui.computercraft.config.http.rules.tooltip": "Список правил, которые контролируют поведение «http» API для определенных доменов или\nIP-адресов. Каждое правило представляет собой элемент с «узлом» для сопоставления и набором\nсвойств. Правила оцениваются по порядку, то есть более ранние правила перевешивают\nболее поздние.\nХост может быть доменным именем (\"pastebin.com\"), wildcard-сертификатом (\"*.pastebin.com\") или\nнотацией CIDR (\"127.0.0.0/8\").\nЕсли правил нет, домен блокируется.", "gui.computercraft.config.http.websocket_enabled": "Включить веб-сокеты",