From 8360e8234ddea00d5c444d231d642ea2ca1d0e4c Mon Sep 17 00:00:00 2001 From: Jonathan Coates Date: Thu, 10 Nov 2022 15:48:26 +0000 Subject: [PATCH] Post multi-loader cleanup This commit got away from me, okay? No, I'm not proud of it either. - Remove our overrides of handleUpdate tag: we now try to detect whether we're on the client or server inside BlockEntity.load. Alas, this is needed for Fabric. - Remove BlockGeneric/TileGeneric entirely: we've slowly whittled this down over the years, and nowadays we can get away with putting most of its functionality into subclasses. This allows us to do some nice things with overriding HorizontalBlock (or our new HorizontalContainerBlock class), rather than reimplementing functionality in each class. Though it would be nice if Java had some sort of trait system :D: - Simplify a lot of our container class so it's just defined in terms of a NonNullList. This also includes a total rewrite of the disk drive which I'm not ... thrilled about. It ended up being easier to copy the code from the mc-next branch :D:. - Try to test some of the gnarly bits of this. Still a /lot/ more to be done with testing this. Closes #658 --- .../tweaked/gradle/IdeaRunConfigurations.kt | 1 + projects/common/build.gradle.kts | 1 + .../client/gui/TurtleScreen.java | 3 - .../client/render/PrintoutRenderer.java | 2 +- .../monitor/MonitorBlockEntityRenderer.java | 1 + .../impl/network/wired/WiredNode.java | 1 + .../common/AbstractContainerBlockEntity.java | 35 ++ .../shared/common/GenericBlock.java | 85 ---- .../shared/common/GenericTile.java | 72 ---- .../common/HorizontalContainerBlock.java | 108 +++++ .../shared/computer/apis/CommandAPI.java | 2 +- .../blocks/AbstractComputerBlock.java | 50 ++- .../blocks/AbstractComputerBlockEntity.java | 57 ++- .../shared/computer/blocks/ComputerBlock.java | 14 - .../inventory/AbstractComputerMenu.java | 4 +- .../ComputerMenuWithoutInventory.java | 2 +- .../shared/container/BasicContainer.java | 77 ++++ .../BasicWorldlyContainer.java} | 7 +- .../InventoryDelegate.java | 8 +- .../{util => container}/InvisibleSlot.java | 2 +- .../SingleContainerData.java} | 7 +- .../{util => container}/ValidatingSlot.java | 2 +- .../peripheral/diskdrive/DiskDriveBlock.java | 64 +-- .../diskdrive/DiskDriveBlockEntity.java | 401 +++++------------- .../diskdrive/DiskDrivePeripheral.java | 36 +- .../peripheral/diskdrive/MediaStack.java | 51 +++ .../peripheral/modem/ModemPeripheral.java | 2 +- .../peripheral/modem/wired/CableBlock.java | 41 +- .../modem/wired/CableBlockEntity.java | 37 +- .../modem/wired/WiredModemFullBlock.java | 48 ++- .../wired/WiredModemFullBlockEntity.java | 44 +- .../modem/wired/WiredModemPeripheral.java | 2 + .../modem/wireless/WirelessModemBlock.java | 33 +- .../wireless/WirelessModemBlockEntity.java | 23 +- .../peripheral/monitor/MonitorBlock.java | 74 +++- .../monitor/MonitorBlockEntity.java | 57 +-- .../peripheral/monitor/ServerMonitor.java | 4 +- .../peripheral/printer/PrinterBlock.java | 55 +-- .../printer/PrinterBlockEntity.java | 174 +------- .../peripheral/printer/PrinterMenu.java | 6 +- .../peripheral/speaker/SpeakerBlock.java | 36 +- .../speaker/SpeakerBlockEntity.java | 4 +- .../shared/turtle/blocks/TurtleBlock.java | 33 +- .../turtle/blocks/TurtleBlockEntity.java | 129 +----- .../shared/turtle/core/TurtleBrain.java | 19 +- .../shared/turtle/inventory/TurtleMenu.java | 4 +- .../upgrades/TurtleInventoryCrafting.java | 2 +- .../shared/util/BlockEntityHelpers.java | 69 +++ .../shared/util/DefaultInventory.java | 31 -- .../computercraft/shared/util/RecipeUtil.java | 8 +- .../computercraft/shared/util/WorldUtil.java | 1 + .../test/shared/ItemStackMatcher.java | 2 +- .../mixin/gametest/GameTestSequenceMixin.java | 2 +- .../computercraft/gametest/Disk_Drive_Test.kt | 76 +++- .../computercraft/gametest/Monitor_Test.kt | 19 +- .../computercraft/gametest/Printer_Test.kt | 91 ++++ .../computercraft/gametest/Turtle_Test.kt | 6 +- .../gametest/api/TestExtensions.kt | 13 + .../disk_drive_test.comparator.snbt | 139 ++++++ ...isk_drive_test.contents_updates_state.snbt | 137 ++++++ .../disk_drive_test.drops_contents.snbt | 137 ++++++ .../monitor_test.contract_on_destroy.snbt | 139 ++++++ .../structures/printer_test.comparator.snbt | 139 ++++++ .../printer_test.contents_updates_state.snbt | 137 ++++++ .../printer_test.drops_contents.snbt | 137 ++++++ projects/forge/build.gradle.kts | 3 +- .../platform/ClientPlatformHelperImpl.java | 2 +- .../shared/util/SidedCapabilityProvider.java | 15 +- 68 files changed, 2070 insertions(+), 1153 deletions(-) create mode 100644 projects/common/src/main/java/dan200/computercraft/shared/common/AbstractContainerBlockEntity.java delete mode 100644 projects/common/src/main/java/dan200/computercraft/shared/common/GenericBlock.java delete mode 100644 projects/common/src/main/java/dan200/computercraft/shared/common/GenericTile.java create mode 100644 projects/common/src/main/java/dan200/computercraft/shared/common/HorizontalContainerBlock.java create mode 100644 projects/common/src/main/java/dan200/computercraft/shared/container/BasicContainer.java rename projects/common/src/main/java/dan200/computercraft/shared/{util/DefaultSidedInventory.java => container/BasicWorldlyContainer.java} (71%) rename projects/common/src/main/java/dan200/computercraft/shared/{util => container}/InventoryDelegate.java (91%) rename projects/common/src/main/java/dan200/computercraft/shared/{util => container}/InvisibleSlot.java (94%) rename projects/common/src/main/java/dan200/computercraft/shared/{util/SingleIntArray.java => container/SingleContainerData.java} (72%) rename projects/common/src/main/java/dan200/computercraft/shared/{util => container}/ValidatingSlot.java (94%) create mode 100644 projects/common/src/main/java/dan200/computercraft/shared/peripheral/diskdrive/MediaStack.java create mode 100644 projects/common/src/main/java/dan200/computercraft/shared/util/BlockEntityHelpers.java delete mode 100644 projects/common/src/main/java/dan200/computercraft/shared/util/DefaultInventory.java create mode 100644 projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Printer_Test.kt create mode 100644 projects/common/src/testMod/resources/data/cctest/structures/disk_drive_test.comparator.snbt create mode 100644 projects/common/src/testMod/resources/data/cctest/structures/disk_drive_test.contents_updates_state.snbt create mode 100644 projects/common/src/testMod/resources/data/cctest/structures/disk_drive_test.drops_contents.snbt create mode 100644 projects/common/src/testMod/resources/data/cctest/structures/monitor_test.contract_on_destroy.snbt create mode 100644 projects/common/src/testMod/resources/data/cctest/structures/printer_test.comparator.snbt create mode 100644 projects/common/src/testMod/resources/data/cctest/structures/printer_test.contents_updates_state.snbt create mode 100644 projects/common/src/testMod/resources/data/cctest/structures/printer_test.drops_contents.snbt diff --git a/buildSrc/src/main/kotlin/cc/tweaked/gradle/IdeaRunConfigurations.kt b/buildSrc/src/main/kotlin/cc/tweaked/gradle/IdeaRunConfigurations.kt index 19f7e3c25..1ed7d57b8 100644 --- a/buildSrc/src/main/kotlin/cc/tweaked/gradle/IdeaRunConfigurations.kt +++ b/buildSrc/src/main/kotlin/cc/tweaked/gradle/IdeaRunConfigurations.kt @@ -117,6 +117,7 @@ fun appendUnique(x: String) { if (newClasses.any { it.startsWith("cctest%%") }) { appendUnique(forgeModEntry("cctest", "core", "testFixtures")) + appendUnique(forgeModEntry("cctest", "common", "testFixtures")) appendUnique(forgeModEntry("cctest", "common", "testMod")) } diff --git a/projects/common/build.gradle.kts b/projects/common/build.gradle.kts index c28387d3f..90f1a3f59 100644 --- a/projects/common/build.gradle.kts +++ b/projects/common/build.gradle.kts @@ -32,5 +32,6 @@ dependencies { testRuntimeOnly(libs.bundles.testRuntime) testModImplementation(testFixtures(project(":core"))) + testModImplementation(testFixtures(project(":common"))) testModImplementation(libs.bundles.kotlin) } diff --git a/projects/common/src/client/java/dan200/computercraft/client/gui/TurtleScreen.java b/projects/common/src/client/java/dan200/computercraft/client/gui/TurtleScreen.java index 020ced781..3e4d1af6a 100644 --- a/projects/common/src/client/java/dan200/computercraft/client/gui/TurtleScreen.java +++ b/projects/common/src/client/java/dan200/computercraft/client/gui/TurtleScreen.java @@ -27,11 +27,8 @@ public class TurtleScreen extends AbstractComputerScreen { private static final int TEX_WIDTH = 254; private static final int TEX_HEIGHT = 217; - private final ComputerFamily family; - public TurtleScreen(TurtleMenu container, Inventory player, Component title) { super(container, player, title, BORDER); - family = container.getFamily(); imageWidth = TEX_WIDTH + AbstractComputerMenu.SIDEBAR_WIDTH; imageHeight = TEX_HEIGHT; diff --git a/projects/common/src/client/java/dan200/computercraft/client/render/PrintoutRenderer.java b/projects/common/src/client/java/dan200/computercraft/client/render/PrintoutRenderer.java index db15bd55c..7a3b65614 100644 --- a/projects/common/src/client/java/dan200/computercraft/client/render/PrintoutRenderer.java +++ b/projects/common/src/client/java/dan200/computercraft/client/render/PrintoutRenderer.java @@ -107,7 +107,7 @@ public static void drawBorder(PoseStack transform, MultiBufferSource bufferSourc double thisWidth = Math.min(right - borderX, X_SIZE); drawTexture(matrix, buffer, borderX, y - 8, z - 0.02f, 0, COVER_Y, (float) thisWidth, COVER_SIZE, light); drawTexture(matrix, buffer, borderX, y + Y_SIZE - 4, z - 0.02f, 0, COVER_Y + COVER_SIZE, (float) thisWidth, COVER_SIZE, light); - borderX += thisWidth; + borderX = (float) (borderX + thisWidth); } } diff --git a/projects/common/src/client/java/dan200/computercraft/client/render/monitor/MonitorBlockEntityRenderer.java b/projects/common/src/client/java/dan200/computercraft/client/render/monitor/MonitorBlockEntityRenderer.java index f5f93aa77..8892a20cc 100644 --- a/projects/common/src/client/java/dan200/computercraft/client/render/monitor/MonitorBlockEntityRenderer.java +++ b/projects/common/src/client/java/dan200/computercraft/client/render/monitor/MonitorBlockEntityRenderer.java @@ -229,6 +229,7 @@ private static void renderTerminal( RenderSystem.setInverseViewRotationMatrix(oldInverseRotation); } + case BEST -> throw new IllegalStateException("Impossible: Should never use BEST renderer"); } } diff --git a/projects/common/src/main/java/dan200/computercraft/impl/network/wired/WiredNode.java b/projects/common/src/main/java/dan200/computercraft/impl/network/wired/WiredNode.java index e1c119ace..b40793526 100644 --- a/projects/common/src/main/java/dan200/computercraft/impl/network/wired/WiredNode.java +++ b/projects/common/src/main/java/dan200/computercraft/impl/network/wired/WiredNode.java @@ -108,6 +108,7 @@ public String toString() { return "WiredNode{@" + element.getPosition() + " (" + element.getClass().getSimpleName() + ")}"; } + @SuppressWarnings("LockNotBeforeTry") private void acquireReadLock() { var currentNetwork = network; while (true) { diff --git a/projects/common/src/main/java/dan200/computercraft/shared/common/AbstractContainerBlockEntity.java b/projects/common/src/main/java/dan200/computercraft/shared/common/AbstractContainerBlockEntity.java new file mode 100644 index 000000000..25aa83376 --- /dev/null +++ b/projects/common/src/main/java/dan200/computercraft/shared/common/AbstractContainerBlockEntity.java @@ -0,0 +1,35 @@ +/* + * This file is part of ComputerCraft - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. + * Send enquiries to dratcliffe@gmail.com + */ +package dan200.computercraft.shared.common; + +import dan200.computercraft.shared.container.BasicContainer; +import dan200.computercraft.shared.util.BlockEntityHelpers; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.block.entity.BaseContainerBlockEntity; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.BlockEntityType; +import net.minecraft.world.level.block.state.BlockState; + +/** + * A {@link BlockEntity} which exposes an inventory. + */ +public abstract class AbstractContainerBlockEntity extends BaseContainerBlockEntity implements BasicContainer { + protected AbstractContainerBlockEntity(BlockEntityType type, BlockPos pos, BlockState state) { + super(type, pos, state); + } + + @Override + protected final Component getDefaultName() { + return Component.translatable(getBlockState().getBlock().getDescriptionId()); + } + + @Override + public boolean stillValid(Player player) { + return BlockEntityHelpers.isUsable(this, player, BlockEntityHelpers.DEFAULT_INTERACT_RANGE); + } +} diff --git a/projects/common/src/main/java/dan200/computercraft/shared/common/GenericBlock.java b/projects/common/src/main/java/dan200/computercraft/shared/common/GenericBlock.java deleted file mode 100644 index dc4eddee6..000000000 --- a/projects/common/src/main/java/dan200/computercraft/shared/common/GenericBlock.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * This file is part of ComputerCraft - http://www.computercraft.info - * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. - * Send enquiries to dratcliffe@gmail.com - */ -package dan200.computercraft.shared.common; - -import dan200.computercraft.annotations.ForgeOverride; -import dan200.computercraft.shared.platform.RegistryEntry; -import net.minecraft.core.BlockPos; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.util.RandomSource; -import net.minecraft.world.InteractionHand; -import net.minecraft.world.InteractionResult; -import net.minecraft.world.entity.player.Player; -import net.minecraft.world.level.Level; -import net.minecraft.world.level.LevelReader; -import net.minecraft.world.level.block.BaseEntityBlock; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.RenderShape; -import net.minecraft.world.level.block.entity.BlockEntity; -import net.minecraft.world.level.block.entity.BlockEntityType; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.phys.BlockHitResult; - -import javax.annotation.Nullable; - -public abstract class GenericBlock extends BaseEntityBlock { - private final RegistryEntry> type; - - public GenericBlock(Properties settings, RegistryEntry> type) { - super(settings); - this.type = type; - } - - @Override - @Deprecated - public final void onRemove(BlockState block, Level world, BlockPos pos, BlockState replace, boolean bool) { - if (block.getBlock() == replace.getBlock()) return; - - var tile = world.getBlockEntity(pos); - super.onRemove(block, world, pos, replace, bool); - world.removeBlockEntity(pos); - if (tile instanceof GenericTile generic) generic.destroy(); - } - - @Override - @Deprecated - public final InteractionResult use(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) { - var tile = world.getBlockEntity(pos); - return tile instanceof GenericTile generic ? generic.onActivate(player, hand, hit) : InteractionResult.PASS; - } - - @Override - @Deprecated - public final void neighborChanged(BlockState state, Level world, BlockPos pos, Block neighbourBlock, BlockPos neighbourPos, boolean isMoving) { - var tile = world.getBlockEntity(pos); - if (tile instanceof GenericTile generic) generic.onNeighbourChange(neighbourPos); - } - - @ForgeOverride - public final void onNeighborChange(BlockState state, LevelReader world, BlockPos pos, BlockPos neighbour) { - var tile = world.getBlockEntity(pos); - if (tile instanceof GenericTile generic) generic.onNeighbourTileEntityChange(neighbour); - } - - @Override - @Deprecated - public void tick(BlockState state, ServerLevel world, BlockPos pos, RandomSource rand) { - var te = world.getBlockEntity(pos); - if (te instanceof GenericTile generic) generic.blockTick(); - } - - @Nullable - @Override - public BlockEntity newBlockEntity(BlockPos pos, BlockState state) { - return type.get().create(pos, state); - } - - @Override - @Deprecated - public RenderShape getRenderShape(BlockState state) { - return RenderShape.MODEL; - } -} diff --git a/projects/common/src/main/java/dan200/computercraft/shared/common/GenericTile.java b/projects/common/src/main/java/dan200/computercraft/shared/common/GenericTile.java deleted file mode 100644 index 37b00b0a8..000000000 --- a/projects/common/src/main/java/dan200/computercraft/shared/common/GenericTile.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * This file is part of ComputerCraft - http://www.computercraft.info - * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. - * Send enquiries to dratcliffe@gmail.com - */ -package dan200.computercraft.shared.common; - -import dan200.computercraft.annotations.ForgeOverride; -import net.minecraft.core.BlockPos; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.network.Connection; -import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket; -import net.minecraft.world.InteractionHand; -import net.minecraft.world.InteractionResult; -import net.minecraft.world.entity.player.Player; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.entity.BlockEntity; -import net.minecraft.world.level.block.entity.BlockEntityType; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.phys.BlockHitResult; - -public abstract class GenericTile extends BlockEntity { - public GenericTile(BlockEntityType type, BlockPos pos, BlockState state) { - super(type, pos, state); - } - - public void destroy() { - } - - public final void updateBlock() { - setChanged(); - var pos = getBlockPos(); - var state = getBlockState(); - getLevel().sendBlockUpdated(pos, state, state, Block.UPDATE_ALL); - } - - public InteractionResult onActivate(Player player, InteractionHand hand, BlockHitResult hit) { - return InteractionResult.PASS; - } - - public void onNeighbourChange(BlockPos neighbour) { - } - - public void onNeighbourTileEntityChange(BlockPos neighbour) { - } - - protected void blockTick() { - } - - protected double getInteractRange(Player player) { - return 8.0; - } - - public boolean isUsable(Player player) { - if (player == null || !player.isAlive() || getLevel().getBlockEntity(getBlockPos()) != this) return false; - - var range = getInteractRange(player); - var pos = getBlockPos(); - return player.getCommandSenderWorld() == getLevel() && - player.distanceToSqr(pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5) <= range * range; - } - - @ForgeOverride // FIXME: Implement this: I'd forgotten about this - public final void onDataPacket(Connection net, ClientboundBlockEntityDataPacket packet) { - var tag = packet.getTag(); - if (tag != null) handleUpdateTag(tag); - } - - @ForgeOverride - public void handleUpdateTag(CompoundTag tag) { - } -} diff --git a/projects/common/src/main/java/dan200/computercraft/shared/common/HorizontalContainerBlock.java b/projects/common/src/main/java/dan200/computercraft/shared/common/HorizontalContainerBlock.java new file mode 100644 index 000000000..c358ef354 --- /dev/null +++ b/projects/common/src/main/java/dan200/computercraft/shared/common/HorizontalContainerBlock.java @@ -0,0 +1,108 @@ +/* + * This file is part of ComputerCraft - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. + * Send enquiries to dratcliffe@gmail.com + */ +package dan200.computercraft.shared.common; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.Containers; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.context.BlockPlaceContext; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.BaseEntityBlock; +import net.minecraft.world.level.block.Mirror; +import net.minecraft.world.level.block.RenderShape; +import net.minecraft.world.level.block.Rotation; +import net.minecraft.world.level.block.entity.BaseContainerBlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.properties.BlockStateProperties; +import net.minecraft.world.level.block.state.properties.DirectionProperty; +import net.minecraft.world.phys.BlockHitResult; + +import javax.annotation.Nullable; + +/** + * A block which has a container and can be placed in a horizontal direction. + * + * @see AbstractContainerBlockEntity The container class which should be used on the block entity. + */ +public abstract class HorizontalContainerBlock extends BaseEntityBlock { + public static final DirectionProperty FACING = BlockStateProperties.HORIZONTAL_FACING; + + public HorizontalContainerBlock(Properties properties) { + super(properties); + } + + @Override + public final BlockState getStateForPlacement(BlockPlaceContext placement) { + return defaultBlockState().setValue(FACING, placement.getHorizontalDirection().getOpposite()); + } + + @Override + @Deprecated + public final BlockState mirror(BlockState state, Mirror mirrorIn) { + return state.rotate(mirrorIn.getRotation(state.getValue(FACING))); + } + + @Override + @Deprecated + public final BlockState rotate(BlockState state, Rotation rot) { + return state.setValue(FACING, rot.rotate(state.getValue(FACING))); + } + + @Override + @Deprecated + public InteractionResult use(BlockState state, Level level, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) { + if (level.isClientSide) return InteractionResult.SUCCESS; + + if (level.getBlockEntity(pos) instanceof BaseContainerBlockEntity container) { + player.openMenu(container); + } + + return InteractionResult.CONSUME; + } + + @Override + @Deprecated + public final void onRemove(BlockState state, Level level, BlockPos pos, BlockState newState, boolean isMoving) { + if (state.is(newState.getBlock())) return; + + if (level.getBlockEntity(pos) instanceof BaseContainerBlockEntity container) { + Containers.dropContents(level, pos, container); + level.updateNeighbourForOutputSignal(pos, this); + } + + super.onRemove(state, level, pos, newState, isMoving); + } + + @Override + public final void setPlacedBy(Level world, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) { + if (stack.hasCustomHoverName() && world.getBlockEntity(pos) instanceof BaseContainerBlockEntity container) { + container.setCustomName(stack.getHoverName()); + } + } + + @Override + @Deprecated + public final boolean hasAnalogOutputSignal(BlockState pState) { + return true; + } + + @Override + @Deprecated + public final int getAnalogOutputSignal(BlockState pBlockState, Level pLevel, BlockPos pPos) { + return AbstractContainerMenu.getRedstoneSignalFromBlockEntity(pLevel.getBlockEntity(pPos)); + } + + @Override + @Deprecated + public RenderShape getRenderShape(BlockState state) { + return RenderShape.MODEL; + } +} diff --git a/projects/common/src/main/java/dan200/computercraft/shared/computer/apis/CommandAPI.java b/projects/common/src/main/java/dan200/computercraft/shared/computer/apis/CommandAPI.java index e05f5feb6..7888d8d8c 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/computer/apis/CommandAPI.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/computer/apis/CommandAPI.java @@ -147,7 +147,7 @@ public final List list(IArguments args) throws LuaException { for (CommandNode child : node.getChildren()) { if (child instanceof LiteralCommandNode) result.add(child.getName()); } - return result; + return Collections.unmodifiableList(result); } /** diff --git a/projects/common/src/main/java/dan200/computercraft/shared/computer/blocks/AbstractComputerBlock.java b/projects/common/src/main/java/dan200/computercraft/shared/computer/blocks/AbstractComputerBlock.java index 10fa50cda..ed8aaf512 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/computer/blocks/AbstractComputerBlock.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/computer/blocks/AbstractComputerBlock.java @@ -5,34 +5,42 @@ */ package dan200.computercraft.shared.computer.blocks; +import dan200.computercraft.annotations.ForgeOverride; import dan200.computercraft.api.ComputerCraftAPI; -import dan200.computercraft.shared.common.GenericBlock; import dan200.computercraft.shared.common.IBundledRedstoneBlock; import dan200.computercraft.shared.computer.core.ComputerFamily; import dan200.computercraft.shared.computer.items.IComputerItem; import dan200.computercraft.shared.platform.RegistryEntry; +import dan200.computercraft.shared.util.BlockEntityHelpers; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.level.ServerLevel; import net.minecraft.stats.Stats; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.MenuProvider; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.BlockGetter; import net.minecraft.world.level.Level; -import net.minecraft.world.level.block.BaseEntityBlock; +import net.minecraft.world.level.LevelReader; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.EntityBlock; +import net.minecraft.world.level.block.HorizontalDirectionalBlock; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntityTicker; import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.storage.loot.LootContext; import net.minecraft.world.level.storage.loot.parameters.LootContextParams; +import net.minecraft.world.phys.BlockHitResult; import net.minecraft.world.phys.Vec3; import javax.annotation.Nullable; -public abstract class AbstractComputerBlock extends GenericBlock implements IBundledRedstoneBlock { +public abstract class AbstractComputerBlock extends HorizontalDirectionalBlock implements IBundledRedstoneBlock, EntityBlock { private static final ResourceLocation DROP = new ResourceLocation(ComputerCraftAPI.MOD_ID, "computer"); private final ComputerFamily family; @@ -40,7 +48,7 @@ public abstract class AbstractComputerBlock serverTicker = (level, pos, state, computer) -> computer.serverTick(); protected AbstractComputerBlock(Properties settings, ComputerFamily family, RegistryEntry> type) { - super(settings, type); + super(settings); this.family = family; this.type = type; } @@ -160,9 +168,41 @@ public void setPlacedBy(Level world, BlockPos pos, BlockState state, @Nullable L } } + @Override + @Deprecated + public final InteractionResult use(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) { + return world.getBlockEntity(pos) instanceof AbstractComputerBlockEntity computer ? computer.use(player, hand) : InteractionResult.PASS; + } + + @Override + @Deprecated + public final void neighborChanged(BlockState state, Level world, BlockPos pos, Block neighbourBlock, BlockPos neighbourPos, boolean isMoving) { + var be = world.getBlockEntity(pos); + if (be instanceof AbstractComputerBlockEntity computer) computer.neighborChanged(neighbourPos); + } + + @ForgeOverride + public final void onNeighborChange(BlockState state, LevelReader world, BlockPos pos, BlockPos neighbour) { + var be = world.getBlockEntity(pos); + if (be instanceof AbstractComputerBlockEntity computer) computer.neighborChanged(neighbour); + } + + @Nullable + @Override + @Deprecated + public MenuProvider getMenuProvider(BlockState state, Level level, BlockPos pos) { + return level.getBlockEntity(pos) instanceof AbstractComputerBlockEntity computer ? computer : null; + } + @Override @Nullable public BlockEntityTicker getTicker(Level level, BlockState state, BlockEntityType type) { - return level.isClientSide ? null : BaseEntityBlock.createTickerHelper(type, this.type.get(), serverTicker); + return level.isClientSide ? null : BlockEntityHelpers.createTickerHelper(type, this.type.get(), serverTicker); + } + + @Nullable + @Override + public BlockEntity newBlockEntity(BlockPos pos, BlockState state) { + return type.get().create(pos, state); } } diff --git a/projects/common/src/main/java/dan200/computercraft/shared/computer/blocks/AbstractComputerBlockEntity.java b/projects/common/src/main/java/dan200/computercraft/shared/computer/blocks/AbstractComputerBlockEntity.java index 03b1b6448..79ff383b2 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/computer/blocks/AbstractComputerBlockEntity.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/computer/blocks/AbstractComputerBlockEntity.java @@ -10,7 +10,6 @@ import dan200.computercraft.api.peripheral.IPeripheral; import dan200.computercraft.core.computer.ComputerSide; import dan200.computercraft.impl.BundledRedstone; -import dan200.computercraft.shared.common.GenericTile; import dan200.computercraft.shared.computer.core.ComputerFamily; import dan200.computercraft.shared.computer.core.ComputerState; import dan200.computercraft.shared.computer.core.ServerComputer; @@ -18,6 +17,7 @@ import dan200.computercraft.shared.network.container.ComputerContainerData; import dan200.computercraft.shared.platform.ComponentAccess; import dan200.computercraft.shared.platform.PlatformHelper; +import dan200.computercraft.shared.util.BlockEntityHelpers; import dan200.computercraft.shared.util.DirectionUtil; import dan200.computercraft.shared.util.IDAssigner; import dan200.computercraft.shared.util.RedstoneUtil; @@ -32,14 +32,14 @@ import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.Items; import net.minecraft.world.level.block.entity.BaseContainerBlockEntity; +import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.phys.BlockHitResult; import javax.annotation.Nullable; import java.util.Objects; -public abstract class AbstractComputerBlockEntity extends GenericTile implements IComputerBlockEntity, Nameable, MenuProvider { +public abstract class AbstractComputerBlockEntity extends BlockEntity implements IComputerBlockEntity, Nameable, MenuProvider { private static final String NBT_ID = "ComputerId"; private static final String NBT_LABEL = "Label"; private static final String NBT_ON = "On"; @@ -58,7 +58,7 @@ public abstract class AbstractComputerBlockEntity extends GenericTile implements private final ComputerFamily family; - public AbstractComputerBlockEntity(BlockEntityType type, BlockPos pos, BlockState state, ComputerFamily family) { + public AbstractComputerBlockEntity(BlockEntityType type, BlockPos pos, BlockState state, ComputerFamily family) { super(type, pos, state); this.family = family; } @@ -71,31 +71,26 @@ protected void unload() { instanceID = -1; } - @Override - public void destroy() { - unload(); - for (var dir : DirectionUtil.FACINGS) { - RedstoneUtil.propagateRedstoneOutput(getLevel(), getBlockPos(), dir); - } - } - @Override public void setRemoved() { - unload(); super.setRemoved(); + unload(); } protected boolean canNameWithTag(Player player) { return false; } - @Override - public boolean isUsable(Player player) { - return super.isUsable(player) && BaseContainerBlockEntity.canUnlock(player, lockCode, getDisplayName()); + protected double getInteractRange() { + return BlockEntityHelpers.DEFAULT_INTERACT_RANGE; } - @Override - public InteractionResult onActivate(Player player, InteractionHand hand, BlockHitResult hit) { + public boolean isUsable(Player player) { + return BaseContainerBlockEntity.canUnlock(player, lockCode, getDisplayName()) + && BlockEntityHelpers.isUsable(this, player, getInteractRange()); + } + + public InteractionResult use(Player player, InteractionHand hand) { var currentItem = player.getItemInHand(hand); if (!currentItem.isEmpty() && currentItem.getItem() == Items.NAME_TAG && canNameWithTag(player) && currentItem.hasCustomHoverName()) { // Label to rename computer @@ -120,13 +115,7 @@ public InteractionResult onActivate(Player player, InteractionHand hand, BlockHi return InteractionResult.PASS; } - @Override - public void onNeighbourChange(BlockPos neighbour) { - updateInputAt(neighbour); - } - - @Override - public void onNeighbourTileEntityChange(BlockPos neighbour) { + public void neighborChanged(BlockPos neighbour) { updateInputAt(neighbour); } @@ -179,9 +168,16 @@ public void saveAdditional(CompoundTag nbt) { } @Override - public void load(CompoundTag nbt) { + public final void load(CompoundTag nbt) { super.load(nbt); + if (level != null && level.isClientSide) { + loadClient(nbt); + } else { + loadServer(nbt); + } + } + protected void loadServer(CompoundTag nbt) { // Load ID, label and power state computerID = nbt.contains(NBT_ID) ? nbt.getInt(NBT_ID) : -1; label = nbt.contains(NBT_LABEL) ? nbt.getString(NBT_LABEL) : null; @@ -268,7 +264,7 @@ private void updateInputAt(BlockPos neighbour) { * Update the block's state and propagate redstone output. */ public void updateOutput() { - updateBlock(); + BlockEntityHelpers.updateBlock(this); for (var dir : DirectionUtil.FACINGS) { RedstoneUtil.propagateRedstoneOutput(getLevel(), getBlockPos(), dir); } @@ -319,7 +315,7 @@ public final ServerComputer createServerComputer() { if (computer == null) { if (computerID < 0) { computerID = ComputerCraftAPI.createUniqueNumberedSaveDir(level, IDAssigner.COMPUTER); - updateBlock(); + BlockEntityHelpers.updateBlock(this); } computer = createComputer(computerID); @@ -353,8 +349,7 @@ public CompoundTag getUpdateTag() { return nbt; } - @Override - public void handleUpdateTag(CompoundTag nbt) { + protected void loadClient(CompoundTag nbt) { label = nbt.contains(NBT_LABEL) ? nbt.getString(NBT_LABEL) : null; computerID = nbt.contains(NBT_ID) ? nbt.getInt(NBT_ID) : -1; } @@ -368,7 +363,7 @@ protected void transferStateFrom(AbstractComputerBlockEntity copy) { on = copy.on; startOn = copy.startOn; lockCode = copy.lockCode; - updateBlock(); + BlockEntityHelpers.updateBlock(this); } copy.instanceID = -1; } diff --git a/projects/common/src/main/java/dan200/computercraft/shared/computer/blocks/ComputerBlock.java b/projects/common/src/main/java/dan200/computercraft/shared/computer/blocks/ComputerBlock.java index 9e263e27f..8d4d20530 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/computer/blocks/ComputerBlock.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/computer/blocks/ComputerBlock.java @@ -13,8 +13,6 @@ import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.context.BlockPlaceContext; import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.Mirror; -import net.minecraft.world.level.block.Rotation; import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.StateDefinition; @@ -47,18 +45,6 @@ public BlockState getStateForPlacement(BlockPlaceContext placement) { return defaultBlockState().setValue(FACING, placement.getHorizontalDirection().getOpposite()); } - @Override - @Deprecated - public BlockState mirror(BlockState state, Mirror mirrorIn) { - return state.rotate(mirrorIn.getRotation(state.getValue(FACING))); - } - - @Override - @Deprecated - public BlockState rotate(BlockState state, Rotation rot) { - return state.setValue(FACING, rot.rotate(state.getValue(FACING))); - } - @Override protected ItemStack getItem(AbstractComputerBlockEntity tile) { return tile instanceof ComputerBlockEntity ? ComputerItemFactory.create((ComputerBlockEntity) tile) : ItemStack.EMPTY; diff --git a/projects/common/src/main/java/dan200/computercraft/shared/computer/inventory/AbstractComputerMenu.java b/projects/common/src/main/java/dan200/computercraft/shared/computer/inventory/AbstractComputerMenu.java index 8814d10b0..6eac253c2 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/computer/inventory/AbstractComputerMenu.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/computer/inventory/AbstractComputerMenu.java @@ -14,7 +14,7 @@ import dan200.computercraft.shared.computer.terminal.NetworkedTerminal; import dan200.computercraft.shared.computer.terminal.TerminalState; import dan200.computercraft.shared.network.container.ComputerContainerData; -import dan200.computercraft.shared.util.SingleIntArray; +import dan200.computercraft.shared.container.SingleContainerData; import net.minecraft.world.entity.player.Player; import net.minecraft.world.inventory.AbstractContainerMenu; import net.minecraft.world.inventory.ContainerData; @@ -46,7 +46,7 @@ public AbstractComputerMenu( super(type, id); this.canUse = canUse; this.family = family; - data = computer == null ? new SimpleContainerData(1) : (SingleIntArray) () -> computer.isOn() ? 1 : 0; + data = computer == null ? new SimpleContainerData(1) : (SingleContainerData) () -> computer.isOn() ? 1 : 0; addDataSlots(data); this.computer = computer; diff --git a/projects/common/src/main/java/dan200/computercraft/shared/computer/inventory/ComputerMenuWithoutInventory.java b/projects/common/src/main/java/dan200/computercraft/shared/computer/inventory/ComputerMenuWithoutInventory.java index 0ece91d3d..abadc20fa 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/computer/inventory/ComputerMenuWithoutInventory.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/computer/inventory/ComputerMenuWithoutInventory.java @@ -8,7 +8,7 @@ import dan200.computercraft.shared.computer.core.ComputerFamily; import dan200.computercraft.shared.computer.core.ServerComputer; import dan200.computercraft.shared.network.container.ComputerContainerData; -import dan200.computercraft.shared.util.InvisibleSlot; +import dan200.computercraft.shared.container.InvisibleSlot; import net.minecraft.world.entity.player.Inventory; import net.minecraft.world.entity.player.Player; import net.minecraft.world.inventory.MenuType; diff --git a/projects/common/src/main/java/dan200/computercraft/shared/container/BasicContainer.java b/projects/common/src/main/java/dan200/computercraft/shared/container/BasicContainer.java new file mode 100644 index 000000000..609e09eb4 --- /dev/null +++ b/projects/common/src/main/java/dan200/computercraft/shared/container/BasicContainer.java @@ -0,0 +1,77 @@ +/* + * This file is part of ComputerCraft - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. + * Send enquiries to dratcliffe@gmail.com + */ +package dan200.computercraft.shared.container; + +import net.minecraft.core.NonNullList; +import net.minecraft.world.Container; +import net.minecraft.world.ContainerHelper; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; + +/** + * A basic implementation of {@link Container} which operates on a {@linkplain #getContents() stack of items}. + */ +public interface BasicContainer extends Container { + NonNullList getContents(); + + @Override + default int getMaxStackSize() { + return 64; + } + + @Override + default void startOpen(Player player) { + } + + @Override + default void stopOpen(Player player) { + } + + @Override + default boolean canPlaceItem(int slot, ItemStack stack) { + return true; + } + + @Override + default int getContainerSize() { + return getContents().size(); + } + + @Override + default boolean isEmpty() { + for (var stack : getContents()) { + if (!stack.isEmpty()) return false; + } + + return true; + } + + @Override + default ItemStack getItem(int slot) { + var contents = getContents(); + return slot >= 0 && slot < contents.size() ? contents.get(slot) : ItemStack.EMPTY; + } + + @Override + default ItemStack removeItemNoUpdate(int slot) { + return ContainerHelper.takeItem(getContents(), slot); + } + + @Override + default ItemStack removeItem(int slot, int count) { + return ContainerHelper.removeItem(getContents(), slot, count); + } + + @Override + default void setItem(int slot, ItemStack itemStack) { + getContents().set(slot, itemStack); + } + + @Override + default void clearContent() { + getContents().clear(); + } +} diff --git a/projects/common/src/main/java/dan200/computercraft/shared/util/DefaultSidedInventory.java b/projects/common/src/main/java/dan200/computercraft/shared/container/BasicWorldlyContainer.java similarity index 71% rename from projects/common/src/main/java/dan200/computercraft/shared/util/DefaultSidedInventory.java rename to projects/common/src/main/java/dan200/computercraft/shared/container/BasicWorldlyContainer.java index 5f655d1ce..14c1adf57 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/util/DefaultSidedInventory.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/container/BasicWorldlyContainer.java @@ -3,7 +3,7 @@ * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. * Send enquiries to dratcliffe@gmail.com */ -package dan200.computercraft.shared.util; +package dan200.computercraft.shared.container; import net.minecraft.core.Direction; import net.minecraft.world.WorldlyContainer; @@ -11,7 +11,10 @@ import javax.annotation.Nullable; -public interface DefaultSidedInventory extends DefaultInventory, WorldlyContainer { +/** + * A basic implementation of {@link WorldlyContainer} which operates on a {@linkplain #getContents() stack of items}. + */ +public interface BasicWorldlyContainer extends BasicContainer, WorldlyContainer { @Override default boolean canPlaceItemThroughFace(int slot, ItemStack stack, @Nullable Direction side) { return canPlaceItem(slot, stack); diff --git a/projects/common/src/main/java/dan200/computercraft/shared/util/InventoryDelegate.java b/projects/common/src/main/java/dan200/computercraft/shared/container/InventoryDelegate.java similarity index 91% rename from projects/common/src/main/java/dan200/computercraft/shared/util/InventoryDelegate.java rename to projects/common/src/main/java/dan200/computercraft/shared/container/InventoryDelegate.java index 2fdd0f391..b2bff261e 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/util/InventoryDelegate.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/container/InventoryDelegate.java @@ -3,7 +3,7 @@ * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. * Send enquiries to dratcliffe@gmail.com */ -package dan200.computercraft.shared.util; +package dan200.computercraft.shared.container; import net.minecraft.world.Container; import net.minecraft.world.entity.player.Player; @@ -12,6 +12,7 @@ import net.minecraft.world.level.block.entity.BlockEntity; import java.util.Set; +import java.util.function.Predicate; /** * Provides a delegate over inventories. @@ -97,4 +98,9 @@ default int countItem(Item stack) { default boolean hasAnyOf(Set set) { return getInventory().hasAnyOf(set); } + + @Override + default boolean hasAnyMatching(Predicate predicate) { + return getInventory().hasAnyMatching(predicate); + } } diff --git a/projects/common/src/main/java/dan200/computercraft/shared/util/InvisibleSlot.java b/projects/common/src/main/java/dan200/computercraft/shared/container/InvisibleSlot.java similarity index 94% rename from projects/common/src/main/java/dan200/computercraft/shared/util/InvisibleSlot.java rename to projects/common/src/main/java/dan200/computercraft/shared/container/InvisibleSlot.java index 2312c658d..365fb8ce8 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/util/InvisibleSlot.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/container/InvisibleSlot.java @@ -3,7 +3,7 @@ * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. * Send enquiries to dratcliffe@gmail.com */ -package dan200.computercraft.shared.util; +package dan200.computercraft.shared.container; import net.minecraft.world.Container; import net.minecraft.world.entity.player.Player; diff --git a/projects/common/src/main/java/dan200/computercraft/shared/util/SingleIntArray.java b/projects/common/src/main/java/dan200/computercraft/shared/container/SingleContainerData.java similarity index 72% rename from projects/common/src/main/java/dan200/computercraft/shared/util/SingleIntArray.java rename to projects/common/src/main/java/dan200/computercraft/shared/container/SingleContainerData.java index b2a1a94a1..e5d623120 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/util/SingleIntArray.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/container/SingleContainerData.java @@ -3,12 +3,15 @@ * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. * Send enquiries to dratcliffe@gmail.com */ -package dan200.computercraft.shared.util; +package dan200.computercraft.shared.container; import net.minecraft.world.inventory.ContainerData; +/** + * A basic {@link ContainerData} implementation which provides a single value. + */ @FunctionalInterface -public interface SingleIntArray extends ContainerData { +public interface SingleContainerData extends ContainerData { int get(); @Override diff --git a/projects/common/src/main/java/dan200/computercraft/shared/util/ValidatingSlot.java b/projects/common/src/main/java/dan200/computercraft/shared/container/ValidatingSlot.java similarity index 94% rename from projects/common/src/main/java/dan200/computercraft/shared/util/ValidatingSlot.java rename to projects/common/src/main/java/dan200/computercraft/shared/container/ValidatingSlot.java index 0d0d19258..dec3dc102 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/util/ValidatingSlot.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/container/ValidatingSlot.java @@ -3,7 +3,7 @@ * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. * Send enquiries to dratcliffe@gmail.com */ -package dan200.computercraft.shared.util; +package dan200.computercraft.shared.container; import net.minecraft.world.Container; import net.minecraft.world.inventory.Slot; diff --git a/projects/common/src/main/java/dan200/computercraft/shared/peripheral/diskdrive/DiskDriveBlock.java b/projects/common/src/main/java/dan200/computercraft/shared/peripheral/diskdrive/DiskDriveBlock.java index d3d583b07..23d68fd20 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/peripheral/diskdrive/DiskDriveBlock.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/peripheral/diskdrive/DiskDriveBlock.java @@ -5,40 +5,34 @@ */ package dan200.computercraft.shared.peripheral.diskdrive; +import dan200.computercraft.impl.MediaProviders; import dan200.computercraft.shared.ModRegistry; -import dan200.computercraft.shared.common.GenericBlock; +import dan200.computercraft.shared.common.HorizontalContainerBlock; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; -import net.minecraft.stats.Stats; -import net.minecraft.world.Nameable; -import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; import net.minecraft.world.entity.player.Player; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.context.BlockPlaceContext; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.BaseEntityBlock; import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.Mirror; -import net.minecraft.world.level.block.Rotation; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntityTicker; import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.StateDefinition; -import net.minecraft.world.level.block.state.properties.BlockStateProperties; -import net.minecraft.world.level.block.state.properties.DirectionProperty; import net.minecraft.world.level.block.state.properties.EnumProperty; +import net.minecraft.world.phys.BlockHitResult; import javax.annotation.Nullable; -public class DiskDriveBlock extends GenericBlock { - static final DirectionProperty FACING = BlockStateProperties.HORIZONTAL_FACING; +public class DiskDriveBlock extends HorizontalContainerBlock { public static final EnumProperty STATE = EnumProperty.create("state", DiskDriveState.class); private static final BlockEntityTicker serverTicker = (level, pos, state, drive) -> drive.serverTick(); public DiskDriveBlock(Properties settings) { - super(settings, ModRegistry.BlockEntities.DISK_DRIVE); + super(settings); registerDefaultState(getStateDefinition().any() .setValue(FACING, Direction.NORTH) .setValue(STATE, DiskDriveState.EMPTY)); @@ -52,41 +46,25 @@ protected void createBlockStateDefinition(StateDefinition.Builder computers = new HashMap<>(); + private final @GuardedBy("this") Map computers = new HashMap<>(); - private ItemStack diskStack = ItemStack.EMPTY; - private @Nullable IPeripheral peripheral; - private @Nullable IMount diskMount = null; + private final NonNullList inventory = NonNullList.withSize(1, ItemStack.EMPTY); - private boolean recordQueued = false; + private MediaStack media = MediaStack.EMPTY; private boolean recordPlaying = false; - private boolean restartRecord = false; - private boolean ejectQueued; + // In order to avoid main-thread calls in the peripheral, we set flags to mark which operation should be performed, + // then read them when ticking. + private final AtomicReference recordQueued = new AtomicReference<>(null); + private final AtomicBoolean ejectQueued = new AtomicBoolean(false); public DiskDriveBlockEntity(BlockEntityType type, BlockPos pos, BlockState state) { super(type, pos, state); } + public IPeripheral peripheral() { + return peripheral; + } + @Override - public void destroy() { - ejectContents(true); + public void clearRemoved() { + updateItem(); + } + + @Override + public void setRemoved() { if (recordPlaying) stopRecord(); } - @Override - public boolean isUsable(Player player) { - return super.isUsable(player) && BaseContainerBlockEntity.canUnlock(player, lockCode, getDisplayName()); - } - - @Override - public InteractionResult onActivate(Player player, InteractionHand hand, BlockHitResult hit) { - if (player.isCrouching()) { - // Try to put a disk into the drive - var disk = player.getItemInHand(hand); - if (disk.isEmpty()) return InteractionResult.PASS; - if (!getLevel().isClientSide && getItem(0).isEmpty() && MediaProviders.get(disk) != null) { - setDiskStack(disk); - player.setItemInHand(hand, ItemStack.EMPTY); - } - return InteractionResult.SUCCESS; - } else { - // Open the GUI - if (!getLevel().isClientSide && isUsable(player)) player.openMenu(this); - return InteractionResult.SUCCESS; - } - } - public Direction getDirection() { return getBlockState().getValue(DiskDriveBlock.FACING); } @@ -103,52 +78,32 @@ public Direction getDirection() { @Override public void load(CompoundTag nbt) { super.load(nbt); - customName = nbt.contains(NBT_NAME) ? Component.Serializer.fromJson(nbt.getString(NBT_NAME)) : null; - if (nbt.contains(NBT_ITEM)) { - var item = nbt.getCompound(NBT_ITEM); - diskStack = ItemStack.of(item); - diskMount = null; - } - - lockCode = LockCode.fromTag(nbt); + setDiskStack(nbt.contains(NBT_ITEM) ? ItemStack.of(nbt.getCompound(NBT_ITEM)) : ItemStack.EMPTY); } @Override - public void saveAdditional(CompoundTag nbt) { - if (customName != null) nbt.putString(NBT_NAME, Component.Serializer.toJson(customName)); + public void saveAdditional(CompoundTag tag) { + super.saveAdditional(tag); - if (!diskStack.isEmpty()) { - var item = new CompoundTag(); - diskStack.save(item); - nbt.put(NBT_ITEM, item); - } - - lockCode.addToTag(nbt); - - super.saveAdditional(nbt); + var stack = getDiskStack(); + if (!stack.isEmpty()) tag.put(NBT_ITEM, stack.save(new CompoundTag())); } void serverTick() { - // Ejection - if (ejectQueued) { - ejectContents(false); - ejectQueued = false; - } + if (ejectQueued.getAndSet(false)) ejectContents(); - // Music - synchronized (this) { - if (recordPlaying != recordQueued || restartRecord) { - restartRecord = false; - if (recordQueued) { - var contents = getDiskMedia(); - var record = contents != null ? contents.getAudio(diskStack) : null; + var recordQueued = this.recordQueued.getAndSet(null); + if (recordQueued != null) { + switch (recordQueued) { + case PLAY -> { + var record = media.getAudio(); if (record != null) { recordPlaying = true; - playRecord(); - } else { - recordQueued = false; + var title = media.getAudioTitle(); + sendMessage(new PlayRecordClientMessage(getBlockPos(), record, title)); } - } else { + } + case STOP -> { stopRecord(); recordPlaying = false; } @@ -156,115 +111,61 @@ var record = contents != null ? contents.getAudio(diskStack) : null; } } - // IInventory implementation - @Override - public int getContainerSize() { - return 1; + public NonNullList getContents() { + return inventory; } @Override - public boolean isEmpty() { - return diskStack.isEmpty(); + public void setChanged() { + if (level != null && !level.isClientSide) updateItem(); + super.setChanged(); } - @Override - public ItemStack getItem(int slot) { - return diskStack; - } + private void updateItem() { + var newDisk = getDiskStack(); + if (ItemStack.isSame(newDisk, media.stack)) return; - @Override - public ItemStack removeItemNoUpdate(int slot) { - var result = diskStack; - diskStack = ItemStack.EMPTY; - diskMount = null; + var media = new MediaStack(newDisk.copy()); - return result; - } - - @Override - public ItemStack removeItem(int slot, int count) { - if (diskStack.isEmpty()) return ItemStack.EMPTY; - - if (diskStack.getCount() <= count) { - var disk = diskStack; - setItem(slot, ItemStack.EMPTY); - return disk; - } - - var part = diskStack.split(count); - setItem(slot, diskStack.isEmpty() ? ItemStack.EMPTY : diskStack); - return part; - } - - @Override - public void setItem(int slot, ItemStack stack) { - if (getLevel().isClientSide) { - diskStack = stack; - diskMount = null; - setChanged(); - return; + if (newDisk.isEmpty()) { + updateBlockState(DiskDriveState.EMPTY); + } else { + updateBlockState(media.media != null ? DiskDriveState.FULL : DiskDriveState.INVALID); } synchronized (this) { - if (ItemStack.isSameItemSameTags(stack, diskStack)) { - diskStack = stack; - return; - } - // Unmount old disk - if (!diskStack.isEmpty()) { - // TODO: Is this iteration thread safe? - var computers = this.computers.keySet(); - for (var computer : computers) unmountDisk(computer); + if (!this.media.stack.isEmpty()) { + for (var computer : computers.entrySet()) unmountDisk(computer.getKey(), computer.getValue()); } // Stop music if (recordPlaying) { stopRecord(); recordPlaying = false; - recordQueued = false; } - // Swap disk over - diskStack = stack; - diskMount = null; - setChanged(); + this.media = media; // Mount new disk - if (!diskStack.isEmpty()) { - var computers = this.computers.keySet(); - for (var computer : computers) mountDisk(computer); + if (!this.media.stack.isEmpty()) { + for (var computer : computers.entrySet()) mountDisk(computer.getKey(), computer.getValue(), this.media); } } } - @Override - public void setChanged() { - if (!level.isClientSide) updateBlockState(); - super.setChanged(); - } - - @Override - public boolean stillValid(Player player) { - return isUsable(player); - } - - @Override - public void clearContent() { - setItem(0, ItemStack.EMPTY); - } - ItemStack getDiskStack() { return getItem(0); } - void setDiskStack(ItemStack stack) { - setItem(0, stack); + MediaStack getMedia() { + return media; } - private @Nullable IMedia getDiskMedia() { - return MediaProviders.get(getDiskStack()); + void setDiskStack(ItemStack stack) { + setItem(0, stack); + setChanged(); } @Nullable @@ -277,95 +178,62 @@ String getDiskMountPath(IComputerAccess computer) { void mount(IComputerAccess computer) { synchronized (this) { - computers.put(computer, new MountInfo()); - mountDisk(computer); + var info = new MountInfo(); + computers.put(computer, info); + mountDisk(computer, info, media); } } void unmount(IComputerAccess computer) { synchronized (this) { - unmountDisk(computer); - computers.remove(computer); + unmountDisk(computer, computers.remove(computer)); } } void playDiskAudio() { - synchronized (this) { - var media = getDiskMedia(); - if (media != null && media.getAudioTitle(diskStack) != null) { - recordQueued = true; - restartRecord = recordPlaying; - } - } + recordQueued.set(RecordCommand.PLAY); } void stopDiskAudio() { - synchronized (this) { - recordQueued = false; - restartRecord = false; - } + recordQueued.set(RecordCommand.STOP); } void ejectDisk() { - synchronized (this) { - ejectQueued = true; - } + ejectQueued.set(true); } - // private methods - - private synchronized void mountDisk(IComputerAccess computer) { - if (!diskStack.isEmpty()) { - var info = assertNonNull(computers.get(computer)); - var contents = getDiskMedia(); - if (contents != null) { - if (diskMount == null) { - diskMount = contents.createDataMount(diskStack, getLevel()); + private void mountDisk(IComputerAccess computer, MountInfo info, MediaStack disk) { + var mount = disk.getMount((ServerLevel) getLevel()); + if (mount != null) { + if (mount instanceof IWritableMount writable) { + // Try mounting at the lowest numbered "disk" name we can + var n = 1; + while (info.mountPath == null) { + info.mountPath = computer.mountWritable(n == 1 ? "disk" : "disk" + n, writable); + n++; } - if (diskMount != null) { - if (diskMount instanceof IWritableMount) { - // Try mounting at the lowest numbered "disk" name we can - var n = 1; - while (info.mountPath == null) { - info.mountPath = computer.mountWritable(n == 1 ? "disk" : "disk" + n, (IWritableMount) diskMount); - n++; - } - } else { - // Try mounting at the lowest numbered "disk" name we can - var n = 1; - while (info.mountPath == null) { - info.mountPath = computer.mount(n == 1 ? "disk" : "disk" + n, diskMount); - n++; - } - } - } else { - info.mountPath = null; + } else { + // Try mounting at the lowest numbered "disk" name we can + var n = 1; + while (info.mountPath == null) { + info.mountPath = computer.mount(n == 1 ? "disk" : "disk" + n, mount); + n++; } } - computer.queueEvent("disk", computer.getAttachmentName()); - } - } - - private synchronized void unmountDisk(IComputerAccess computer) { - if (!diskStack.isEmpty()) { - var info = Objects.requireNonNull(computers.get(computer), "No mount info"); - if (info.mountPath != null) { - computer.unmount(info.mountPath); - info.mountPath = null; - } - computer.queueEvent("disk_eject", computer.getAttachmentName()); - } - } - - private void updateBlockState() { - if (remove || level == null) return; - - if (!diskStack.isEmpty()) { - var contents = getDiskMedia(); - updateBlockState(contents != null ? DiskDriveState.FULL : DiskDriveState.INVALID); } else { - updateBlockState(DiskDriveState.EMPTY); + info.mountPath = null; } + + computer.queueEvent("disk", computer.getAttachmentName()); + } + + private static void unmountDisk(IComputerAccess computer, MountInfo info) { + if (info.mountPath != null) { + computer.unmount(info.mountPath); + info.mountPath = null; + } + + computer.queueEvent("disk_eject", computer.getAttachmentName()); } private void updateBlockState(DiskDriveState state) { @@ -375,81 +243,32 @@ private void updateBlockState(DiskDriveState state) { getLevel().setBlockAndUpdate(getBlockPos(), blockState.setValue(DiskDriveBlock.STATE, state)); } - private synchronized void ejectContents(boolean destroyed) { - if (getLevel().isClientSide || diskStack.isEmpty()) return; + private void ejectContents() { + if (getLevel().isClientSide) return; - // Remove the disks from the inventory - var disks = diskStack; + var stack = getDiskStack(); + if (stack.isEmpty()) return; setDiskStack(ItemStack.EMPTY); - // Spawn the item in the world - var xOff = 0; - var zOff = 0; - if (!destroyed) { - var dir = getDirection(); - xOff = dir.getStepX(); - zOff = dir.getStepZ(); - } - - var pos = getBlockPos(); - var x = pos.getX() + 0.5 + xOff * 0.5; - var y = pos.getY() + 0.75; - var z = pos.getZ() + 0.5 + zOff * 0.5; - var entityitem = new ItemEntity(getLevel(), x, y, z, disks); - entityitem.setDeltaMovement(xOff * 0.15, 0, zOff * 0.15); - - getLevel().addFreshEntity(entityitem); - if (!destroyed) getLevel().globalLevelEvent(1000, getBlockPos(), 0); - } - - // Private methods - - private void playRecord() { - var contents = getDiskMedia(); - var record = contents != null ? contents.getAudio(diskStack) : null; - if (record != null) { - playRecord(new PlayRecordClientMessage(getBlockPos(), record, assertNonNull(contents).getAudioTitle(diskStack))); - } else { - stopRecord(); - } + WorldUtil.dropItemStack(stack, getLevel(), getBlockPos(), getDirection()); + getLevel().levelEvent(LevelEvent.SOUND_DISPENSER_DISPENSE, getBlockPos(), 0); } private void stopRecord() { - playRecord(new PlayRecordClientMessage(getBlockPos())); + sendMessage(new PlayRecordClientMessage(getBlockPos())); } - private void playRecord(PlayRecordClientMessage message) { + private void sendMessage(PlayRecordClientMessage message) { PlatformHelper.get().sendToAllAround(message, (ServerLevel) getLevel(), Vec3.atCenterOf(getBlockPos()), 64); } @Override - public boolean hasCustomName() { - return customName != null; - } - - @Nullable - @Override - public Component getCustomName() { - return customName; - } - - @Override - public Component getName() { - return customName != null ? customName : Component.translatable(getBlockState().getBlock().getDescriptionId()); - } - - @Override - public Component getDisplayName() { - return Nameable.super.getDisplayName(); - } - - @Override - public AbstractContainerMenu createMenu(int id, Inventory inventory, Player player) { + protected AbstractContainerMenu createMenu(int id, Inventory inventory) { return new DiskDriveMenu(id, inventory, this); } - public IPeripheral peripheral() { - if (peripheral != null) return peripheral; - return peripheral = new DiskDrivePeripheral(this); + private enum RecordCommand { + PLAY, + STOP, } } diff --git a/projects/common/src/main/java/dan200/computercraft/shared/peripheral/diskdrive/DiskDrivePeripheral.java b/projects/common/src/main/java/dan200/computercraft/shared/peripheral/diskdrive/DiskDrivePeripheral.java index 213556d54..d698d8132 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/peripheral/diskdrive/DiskDrivePeripheral.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/peripheral/diskdrive/DiskDrivePeripheral.java @@ -10,7 +10,6 @@ import dan200.computercraft.api.peripheral.IComputerAccess; import dan200.computercraft.api.peripheral.IPeripheral; import dan200.computercraft.core.util.StringUtil; -import dan200.computercraft.impl.MediaProviders; import dan200.computercraft.shared.media.items.DiskItem; import javax.annotation.Nullable; @@ -53,20 +52,20 @@ public String getType() { */ @LuaFunction public final boolean isDiskPresent() { - return !diskDrive.getDiskStack().isEmpty(); + return !diskDrive.getMedia().stack.isEmpty(); } /** * Returns the label of the disk in the drive if available. * * @return The label of the disk, or {@code nil} if either no disk is inserted or the disk doesn't have a label. - * @cc.treturn string The label of the disk, or {@code nil} if either no disk is inserted or the disk doesn't have a label. + * @cc.treturn string|nil The label of the disk, or {@code nil} if either no disk is inserted or the disk doesn't have a label. */ + @Nullable @LuaFunction - public final @Nullable Object[] getDiskLabel() { - var stack = diskDrive.getDiskStack(); - var media = MediaProviders.get(stack); - return media == null ? null : new Object[]{ media.getLabel(stack) }; + public final Object[] getDiskLabel() { + var media = diskDrive.getMedia(); + return media.media == null ? null : new Object[]{ media.media.getLabel(media.stack) }; } /** @@ -82,11 +81,11 @@ public final boolean isDiskPresent() { */ @LuaFunction(mainThread = true) public final void setDiskLabel(Optional label) throws LuaException { - var stack = diskDrive.getDiskStack(); - var media = MediaProviders.get(stack); - if (media == null) return; + var media = diskDrive.getMedia(); + if (media.media == null) return; - if (!media.setLabel(stack, label.map(StringUtil::normaliseLabel).orElse(null))) { + var stack = media.stack.copy(); + if (!media.media.setLabel(stack, label.map(StringUtil::normaliseLabel).orElse(null))) { throw new LuaException("Disk label cannot be changed"); } diskDrive.setDiskStack(stack); @@ -122,9 +121,7 @@ public final String getMountPath(IComputerAccess computer) { */ @LuaFunction public final boolean hasAudio() { - var stack = diskDrive.getDiskStack(); - var media = MediaProviders.get(stack); - return media != null && media.getAudio(stack) != null; + return diskDrive.getMedia().getAudio() != null; } /** @@ -136,9 +133,7 @@ public final boolean hasAudio() { @LuaFunction @Nullable public final Object getAudioTitle() { - var stack = diskDrive.getDiskStack(); - var media = MediaProviders.get(stack); - return media != null ? media.getAudioTitle(stack) : false; + return diskDrive.getMedia().getAudioTitle(); } /** @@ -171,12 +166,13 @@ public final void ejectDisk() { * Returns the ID of the disk inserted in the drive. * * @return The ID of the disk in the drive, or {@code nil} if no disk with an ID is inserted. - * @cc.treturn number The The ID of the disk in the drive, or {@code nil} if no disk with an ID is inserted. + * @cc.treturn number|nil The ID of the disk in the drive, or {@code nil} if no disk with an ID is inserted. * @cc.since 1.4 */ + @Nullable @LuaFunction - public final @Nullable Object[] getDiskID() { - var disk = diskDrive.getDiskStack(); + public final Object[] getDiskID() { + var disk = diskDrive.getMedia().stack; return disk.getItem() instanceof DiskItem ? new Object[]{ DiskItem.getDiskID(disk) } : null; } diff --git a/projects/common/src/main/java/dan200/computercraft/shared/peripheral/diskdrive/MediaStack.java b/projects/common/src/main/java/dan200/computercraft/shared/peripheral/diskdrive/MediaStack.java new file mode 100644 index 000000000..089e90618 --- /dev/null +++ b/projects/common/src/main/java/dan200/computercraft/shared/peripheral/diskdrive/MediaStack.java @@ -0,0 +1,51 @@ +/* + * This file is part of ComputerCraft - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. + * Send enquiries to dratcliffe@gmail.com + */ +package dan200.computercraft.shared.peripheral.diskdrive; + +import dan200.computercraft.api.filesystem.IMount; +import dan200.computercraft.api.media.IMedia; +import dan200.computercraft.impl.MediaProviders; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.sounds.SoundEvent; +import net.minecraft.world.item.ItemStack; + +import javax.annotation.Nullable; + +/** + * An immutable snapshot of the current disk. This allows us to read the stack in a thread-safe manner. + */ +class MediaStack { + static final MediaStack EMPTY = new MediaStack(ItemStack.EMPTY); + + final ItemStack stack; + final @Nullable IMedia media; + + @Nullable + private IMount mount; + + MediaStack(ItemStack stack) { + this.stack = stack; + media = MediaProviders.get(stack); + } + + @Nullable + SoundEvent getAudio() { + return media != null ? media.getAudio(stack) : null; + } + + @Nullable + String getAudioTitle() { + return media != null ? media.getAudioTitle(stack) : null; + } + + @Nullable + public IMount getMount(ServerLevel level) { + if (media == null) return null; + + if (mount == null) mount = media.createDataMount(stack, level); + return mount; + } +} diff --git a/projects/common/src/main/java/dan200/computercraft/shared/peripheral/modem/ModemPeripheral.java b/projects/common/src/main/java/dan200/computercraft/shared/peripheral/modem/ModemPeripheral.java index 97a907c1b..c4f589a40 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/peripheral/modem/ModemPeripheral.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/peripheral/modem/ModemPeripheral.java @@ -109,7 +109,7 @@ private synchronized void setNetwork(@Nullable IPacketNetwork network) { if (this.network != null) this.network.addReceiver(this); } - public void destroy() { + public void removed() { setNetwork(null); } diff --git a/projects/common/src/main/java/dan200/computercraft/shared/peripheral/modem/wired/CableBlock.java b/projects/common/src/main/java/dan200/computercraft/shared/peripheral/modem/wired/CableBlock.java index eaf96e838..636f298e9 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/peripheral/modem/wired/CableBlock.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/peripheral/modem/wired/CableBlock.java @@ -8,24 +8,30 @@ import com.google.common.collect.ImmutableMap; import dan200.computercraft.annotations.ForgeOverride; import dan200.computercraft.shared.ModRegistry; -import dan200.computercraft.shared.common.GenericBlock; import dan200.computercraft.shared.platform.PlatformHelper; import dan200.computercraft.shared.util.WaterloggableHelpers; import dan200.computercraft.shared.util.WorldUtil; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.util.RandomSource; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.context.BlockPlaceContext; import net.minecraft.world.level.*; import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.EntityBlock; import net.minecraft.world.level.block.SimpleWaterloggedBlock; +import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.StateDefinition; import net.minecraft.world.level.block.state.properties.BooleanProperty; import net.minecraft.world.level.block.state.properties.EnumProperty; import net.minecraft.world.level.material.FluidState; +import net.minecraft.world.phys.BlockHitResult; import net.minecraft.world.phys.HitResult; import net.minecraft.world.phys.shapes.CollisionContext; import net.minecraft.world.phys.shapes.VoxelShape; @@ -36,7 +42,7 @@ import static dan200.computercraft.shared.util.WaterloggableHelpers.WATERLOGGED; import static dan200.computercraft.shared.util.WaterloggableHelpers.getFluidStateForPlacement; -public class CableBlock extends GenericBlock implements SimpleWaterloggedBlock { +public class CableBlock extends Block implements SimpleWaterloggedBlock, EntityBlock { public static final EnumProperty MODEM = EnumProperty.create("modem", CableModemVariant.class); public static final BooleanProperty CABLE = BooleanProperty.create("cable"); @@ -55,7 +61,7 @@ public class CableBlock extends GenericBlock implements SimpleWaterloggedBlock { .build()); public CableBlock(Properties settings) { - super(settings, ModRegistry.BlockEntities.CABLE); + super(settings); registerDefaultState(getStateDefinition().any() .setValue(MODEM, CableModemVariant.None) @@ -218,4 +224,33 @@ public static BlockState correctConnections(Level world, BlockPos pos, BlockStat .setValue(WEST, false).setValue(UP, false).setValue(DOWN, false); } } + + @Override + @Deprecated + public final InteractionResult use(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) { + return world.getBlockEntity(pos) instanceof CableBlockEntity modem ? modem.use(player) : InteractionResult.PASS; + } + + @Override + @Deprecated + public final void neighborChanged(BlockState state, Level world, BlockPos pos, Block neighbourBlock, BlockPos neighbourPos, boolean isMoving) { + if (world.getBlockEntity(pos) instanceof CableBlockEntity modem) modem.neighborChanged(neighbourPos); + } + + @ForgeOverride + public final void onNeighborChange(BlockState state, LevelReader world, BlockPos pos, BlockPos neighbour) { + if (world.getBlockEntity(pos) instanceof CableBlockEntity modem) modem.neighborChanged(neighbour); + } + + @Override + @Deprecated + public void tick(BlockState state, ServerLevel world, BlockPos pos, RandomSource rand) { + if (world.getBlockEntity(pos) instanceof CableBlockEntity modem) modem.blockTick(); + } + + @Nullable + @Override + public BlockEntity newBlockEntity(BlockPos pos, BlockState state) { + return ModRegistry.BlockEntities.CABLE.get().create(pos, state); + } } diff --git a/projects/common/src/main/java/dan200/computercraft/shared/peripheral/modem/wired/CableBlockEntity.java b/projects/common/src/main/java/dan200/computercraft/shared/peripheral/modem/wired/CableBlockEntity.java index da4db4801..7462c97a2 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/peripheral/modem/wired/CableBlockEntity.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/peripheral/modem/wired/CableBlockEntity.java @@ -11,7 +11,6 @@ import dan200.computercraft.api.peripheral.IPeripheral; import dan200.computercraft.shared.ModRegistry; import dan200.computercraft.shared.command.text.ChatHelpers; -import dan200.computercraft.shared.common.GenericTile; import dan200.computercraft.shared.peripheral.modem.ModemState; import dan200.computercraft.shared.platform.ComponentAccess; import dan200.computercraft.shared.platform.PlatformHelper; @@ -22,21 +21,20 @@ import net.minecraft.nbt.CompoundTag; import net.minecraft.network.chat.Component; import net.minecraft.server.level.ServerLevel; -import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionResult; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.phys.BlockHitResult; import net.minecraft.world.phys.Vec3; import javax.annotation.Nullable; import java.util.Collections; -public class CableBlockEntity extends GenericTile { +public class CableBlockEntity extends BlockEntity { private static final String NBT_PERIPHERAL_ENABLED = "PeirpheralAccess"; private class CableElement extends WiredModemElement { @@ -66,8 +64,6 @@ protected void detachPeripheral(String name) { private final WiredModemLocalPeripheral peripheral = new WiredModemLocalPeripheral(this::queueRefreshPeripheral); private @Nullable Runnable modemChanged; - private boolean destroyed = false; - private boolean connectionsFormed = false; private final WiredModemElement cable = new CableElement(); @@ -106,24 +102,16 @@ private void onRemove() { } } - @Override - public void destroy() { - if (!destroyed) { - destroyed = true; - modem.destroy(); - onRemove(); - } - } - @Override public void setRemoved() { super.setRemoved(); + modem.removed(); onRemove(); } @Override public void clearRemoved() { - super.clearRemoved(); // TODO: Replace with onLoad + super.clearRemoved(); TickScheduler.schedule(tickToken); } @@ -147,8 +135,7 @@ private Direction getDirection() { return direction == null ? Direction.NORTH : direction; } - @Override - public void onNeighbourChange(BlockPos neighbour) { + void neighborChanged(BlockPos neighbour) { var dir = getDirection(); if (neighbour.equals(getBlockPos().relative(dir)) && hasModem() && !getBlockState().canSurvive(getLevel(), getBlockPos())) { if (hasCable()) { @@ -167,12 +154,6 @@ public void onNeighbourChange(BlockPos neighbour) { return; } - onNeighbourTileEntityChange(neighbour); - } - - @Override - public void onNeighbourTileEntityChange(BlockPos neighbour) { - super.onNeighbourTileEntityChange(neighbour); if (!level.isClientSide && peripheralAccessAllowed) { var facing = getDirection(); if (getBlockPos().relative(facing).equals(neighbour)) queueRefreshPeripheral(); @@ -192,8 +173,7 @@ private void refreshPeripheral() { } } - @Override - public InteractionResult onActivate(Player player, InteractionHand hand, BlockHitResult hit) { + InteractionResult use(Player player) { if (player.isCrouching() || !player.mayBuild()) return InteractionResult.PASS; if (!canAttachPeripheral()) return InteractionResult.FAIL; @@ -241,8 +221,7 @@ private void updateBlockState() { } } - @Override - public void blockTick() { + void blockTick() { if (getLevel().isClientSide) return; if (invalidPeripheral) refreshPeripheral(); @@ -331,13 +310,11 @@ private void updateConnectedPeripherals() { @Nullable public IWiredElement getWiredElement(@Nullable Direction direction) { - if (destroyed) return null; return direction == null || CableBlock.canConnectIn(getBlockState(), direction) ? cable : null; } @Nullable public IPeripheral getPeripheral(@Nullable Direction direction) { - if (destroyed) return null; return direction == null || getMaybeDirection() == direction ? modem : null; } diff --git a/projects/common/src/main/java/dan200/computercraft/shared/peripheral/modem/wired/WiredModemFullBlock.java b/projects/common/src/main/java/dan200/computercraft/shared/peripheral/modem/wired/WiredModemFullBlock.java index 9306615d0..00960125f 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/peripheral/modem/wired/WiredModemFullBlock.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/peripheral/modem/wired/WiredModemFullBlock.java @@ -5,19 +5,32 @@ */ package dan200.computercraft.shared.peripheral.modem.wired; +import dan200.computercraft.annotations.ForgeOverride; import dan200.computercraft.shared.ModRegistry; -import dan200.computercraft.shared.common.GenericBlock; +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.util.RandomSource; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.LevelReader; import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.EntityBlock; +import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.StateDefinition; import net.minecraft.world.level.block.state.properties.BooleanProperty; +import net.minecraft.world.phys.BlockHitResult; -public class WiredModemFullBlock extends GenericBlock { +import javax.annotation.Nullable; + +public class WiredModemFullBlock extends Block implements EntityBlock { public static final BooleanProperty MODEM_ON = BooleanProperty.create("modem"); public static final BooleanProperty PERIPHERAL_ON = BooleanProperty.create("peripheral"); public WiredModemFullBlock(Properties settings) { - super(settings, ModRegistry.BlockEntities.WIRED_MODEM_FULL); + super(settings); registerDefaultState(getStateDefinition().any() .setValue(MODEM_ON, false) .setValue(PERIPHERAL_ON, false) @@ -28,4 +41,33 @@ public WiredModemFullBlock(Properties settings) { protected void createBlockStateDefinition(StateDefinition.Builder builder) { builder.add(MODEM_ON, PERIPHERAL_ON); } + + @Override + @Deprecated + public final InteractionResult use(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) { + return world.getBlockEntity(pos) instanceof WiredModemFullBlockEntity modem ? modem.use(player) : InteractionResult.PASS; + } + + @Override + @Deprecated + public final void neighborChanged(BlockState state, Level world, BlockPos pos, Block neighbourBlock, BlockPos neighbourPos, boolean isMoving) { + if (world.getBlockEntity(pos) instanceof WiredModemFullBlockEntity modem) modem.neighborChanged(neighbourPos); + } + + @ForgeOverride + public final void onNeighborChange(BlockState state, LevelReader world, BlockPos pos, BlockPos neighbour) { + if (world.getBlockEntity(pos) instanceof WiredModemFullBlockEntity modem) modem.neighborChanged(neighbour); + } + + @Override + @Deprecated + public void tick(BlockState state, ServerLevel world, BlockPos pos, RandomSource rand) { + if (world.getBlockEntity(pos) instanceof WiredModemFullBlockEntity modem) modem.blockTick(); + } + + @Nullable + @Override + public BlockEntity newBlockEntity(BlockPos pos, BlockState state) { + return ModRegistry.BlockEntities.WIRED_MODEM_FULL.get().create(pos, state); + } } diff --git a/projects/common/src/main/java/dan200/computercraft/shared/peripheral/modem/wired/WiredModemFullBlockEntity.java b/projects/common/src/main/java/dan200/computercraft/shared/peripheral/modem/wired/WiredModemFullBlockEntity.java index 890ea578e..326e0f67f 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/peripheral/modem/wired/WiredModemFullBlockEntity.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/peripheral/modem/wired/WiredModemFullBlockEntity.java @@ -10,7 +10,6 @@ import dan200.computercraft.api.network.wired.IWiredNode; import dan200.computercraft.api.peripheral.IPeripheral; import dan200.computercraft.shared.command.text.ChatHelpers; -import dan200.computercraft.shared.common.GenericTile; import dan200.computercraft.shared.peripheral.modem.ModemState; import dan200.computercraft.shared.platform.ComponentAccess; import dan200.computercraft.shared.platform.PlatformHelper; @@ -21,13 +20,12 @@ import net.minecraft.nbt.CompoundTag; import net.minecraft.network.chat.Component; import net.minecraft.server.level.ServerLevel; -import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionResult; import net.minecraft.world.entity.player.Player; import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.phys.BlockHitResult; import net.minecraft.world.phys.Vec3; import javax.annotation.Nullable; @@ -36,7 +34,7 @@ import static dan200.computercraft.shared.peripheral.modem.wired.WiredModemFullBlock.MODEM_ON; import static dan200.computercraft.shared.peripheral.modem.wired.WiredModemFullBlock.PERIPHERAL_ON; -public class WiredModemFullBlockEntity extends GenericTile { +public class WiredModemFullBlockEntity extends BlockEntity { private static final String NBT_PERIPHERAL_ENABLED = "PeripheralAccess"; private static final class FullElement extends WiredModemElement { @@ -78,7 +76,6 @@ public Vec3 getPosition() { private boolean peripheralAccessAllowed = false; private final WiredModemLocalPeripheral[] peripherals = new WiredModemLocalPeripheral[6]; - private boolean destroyed = false; private boolean connectionsFormed = false; private final TickScheduler.Token tickToken = new TickScheduler.Token(this); @@ -98,35 +95,16 @@ public WiredModemFullBlockEntity(BlockEntityType type } } - private void doRemove() { + @Override + public void setRemoved() { + super.setRemoved(); if (level == null || !level.isClientSide) { node.remove(); connectionsFormed = false; } } - @Override - public void destroy() { - if (!destroyed) { - destroyed = true; - doRemove(); - } - super.destroy(); - } - - @Override - public void setRemoved() { - super.setRemoved(); - doRemove(); - } - - @Override - public void onNeighbourChange(BlockPos neighbour) { - onNeighbourTileEntityChange(neighbour); - } - - @Override - public void onNeighbourTileEntityChange(BlockPos neighbour) { + void neighborChanged(BlockPos neighbour) { if (!level.isClientSide && peripheralAccessAllowed) { for (var facing : DirectionUtil.FACINGS) { if (getBlockPos().relative(facing).equals(neighbour)) queueRefreshPeripheral(facing); @@ -147,8 +125,7 @@ private void refreshPeripheral(Direction facing) { } } - @Override - public InteractionResult onActivate(Player player, InteractionHand hand, BlockHitResult hit) { + public InteractionResult use(Player player) { if (player.isCrouching() || !player.mayBuild()) return InteractionResult.PASS; if (getLevel().isClientSide) return InteractionResult.SUCCESS; @@ -204,12 +181,11 @@ private void updateBlockState() { @Override public void clearRemoved() { - super.clearRemoved(); // TODO: Replace with onLoad + super.clearRemoved(); TickScheduler.schedule(tickToken); } - @Override - public void blockTick() { + void blockTick() { if (getLevel().isClientSide) return; if (invalidSides != 0) { @@ -288,7 +264,7 @@ private Map getConnectedPeripherals() { Map peripherals = new HashMap<>(6); for (var peripheral : this.peripherals) peripheral.extendMap(peripherals); - return peripherals; + return Collections.unmodifiableMap(peripherals); } private void updateConnectedPeripherals() { diff --git a/projects/common/src/main/java/dan200/computercraft/shared/peripheral/modem/wired/WiredModemPeripheral.java b/projects/common/src/main/java/dan200/computercraft/shared/peripheral/modem/wired/WiredModemPeripheral.java index 494d3e0f8..8d7a6db08 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/peripheral/modem/wired/WiredModemPeripheral.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/peripheral/modem/wired/WiredModemPeripheral.java @@ -219,6 +219,7 @@ public final MethodResult callRemote(IComputerAccess computer, ILuaContext conte } @Override + @SuppressWarnings("UnsynchronizedOverridesSynchronized") public void attach(IComputerAccess computer) { super.attach(computer); @@ -236,6 +237,7 @@ public void attach(IComputerAccess computer) { } @Override + @SuppressWarnings("UnsynchronizedOverridesSynchronized") public void detach(IComputerAccess computer) { Map wrappers; synchronized (peripheralWrappers) { diff --git a/projects/common/src/main/java/dan200/computercraft/shared/peripheral/modem/wireless/WirelessModemBlock.java b/projects/common/src/main/java/dan200/computercraft/shared/peripheral/modem/wireless/WirelessModemBlock.java index 25bd5abeb..96feac717 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/peripheral/modem/wireless/WirelessModemBlock.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/peripheral/modem/wireless/WirelessModemBlock.java @@ -5,26 +5,23 @@ */ package dan200.computercraft.shared.peripheral.modem.wireless; -import dan200.computercraft.shared.common.GenericBlock; import dan200.computercraft.shared.peripheral.modem.ModemShapes; import dan200.computercraft.shared.platform.RegistryEntry; import dan200.computercraft.shared.util.WaterloggableHelpers; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.util.RandomSource; import net.minecraft.world.item.context.BlockPlaceContext; import net.minecraft.world.level.BlockGetter; import net.minecraft.world.level.LevelAccessor; import net.minecraft.world.level.LevelReader; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.Mirror; -import net.minecraft.world.level.block.Rotation; -import net.minecraft.world.level.block.SimpleWaterloggedBlock; +import net.minecraft.world.level.block.*; +import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.StateDefinition; -import net.minecraft.world.level.block.state.properties.BlockStateProperties; import net.minecraft.world.level.block.state.properties.BooleanProperty; -import net.minecraft.world.level.block.state.properties.DirectionProperty; import net.minecraft.world.level.material.FluidState; import net.minecraft.world.phys.shapes.CollisionContext; import net.minecraft.world.phys.shapes.VoxelShape; @@ -34,12 +31,15 @@ import static dan200.computercraft.shared.util.WaterloggableHelpers.WATERLOGGED; import static dan200.computercraft.shared.util.WaterloggableHelpers.getFluidStateForPlacement; -public class WirelessModemBlock extends GenericBlock implements SimpleWaterloggedBlock { - public static final DirectionProperty FACING = BlockStateProperties.FACING; +public class WirelessModemBlock extends DirectionalBlock implements SimpleWaterloggedBlock, EntityBlock { public static final BooleanProperty ON = BooleanProperty.create("on"); + private final RegistryEntry> type; + public WirelessModemBlock(Properties settings, RegistryEntry> type) { - super(settings, type); + super(settings); + this.type = type; + registerDefaultState(getStateDefinition().any() .setValue(FACING, Direction.NORTH) .setValue(ON, false) @@ -98,4 +98,17 @@ public BlockState mirror(BlockState state, Mirror mirrorIn) { public BlockState rotate(BlockState state, Rotation rot) { return state.setValue(FACING, rot.rotate(state.getValue(FACING))); } + + @Override + @Deprecated + public void tick(BlockState state, ServerLevel level, BlockPos pos, RandomSource rand) { + var te = level.getBlockEntity(pos); + if (te instanceof WirelessModemBlockEntity modem) modem.blockTick(); + } + + @Nullable + @Override + public BlockEntity newBlockEntity(BlockPos blockPos, BlockState blockState) { + return type.get().create(blockPos, blockState); + } } diff --git a/projects/common/src/main/java/dan200/computercraft/shared/peripheral/modem/wireless/WirelessModemBlockEntity.java b/projects/common/src/main/java/dan200/computercraft/shared/peripheral/modem/wireless/WirelessModemBlockEntity.java index 3bad001df..a5ec27ff0 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/peripheral/modem/wireless/WirelessModemBlockEntity.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/peripheral/modem/wireless/WirelessModemBlockEntity.java @@ -6,20 +6,20 @@ package dan200.computercraft.shared.peripheral.modem.wireless; import dan200.computercraft.api.peripheral.IPeripheral; -import dan200.computercraft.shared.common.GenericTile; import dan200.computercraft.shared.peripheral.modem.ModemPeripheral; import dan200.computercraft.shared.peripheral.modem.ModemState; import dan200.computercraft.shared.util.TickScheduler; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.phys.Vec3; import javax.annotation.Nullable; -public class WirelessModemBlockEntity extends GenericTile { +public class WirelessModemBlockEntity extends BlockEntity { private static class Peripheral extends WirelessModemPeripheral { private final WirelessModemBlockEntity entity; @@ -52,7 +52,6 @@ public Object getTarget() { private final boolean advanced; private final ModemPeripheral modem; - private boolean destroyed = false; private @Nullable Runnable modemChanged; private final TickScheduler.Token tickToken = new TickScheduler.Token(this); @@ -63,17 +62,15 @@ public WirelessModemBlockEntity(BlockEntityType STATE = EnumProperty.create("state", MonitorEdgeState.class); + private final RegistryEntry> type; + public MonitorBlock(Properties settings, RegistryEntry> type) { - super(settings, type); + super(settings); + this.type = type; + // TODO: Test underwater - do we need isSolid at all? registerDefaultState(getStateDefinition().any() .setValue(ORIENTATION, Direction.NORTH) @@ -49,18 +58,6 @@ protected void createBlockStateDefinition(StateDefinition.Builder type, Bl } @Override - public void clearRemoved() { // TODO: Switch back to onLood + public void clearRemoved() { super.clearRemoved(); needsValidating = true; // Same, tbh TickScheduler.schedule(tickToken); } - @Override - public void destroy() { + void destroy() { // TODO: Call this before using the block - if (destroyed) return; - destroyed = true; if (!getLevel().isClientSide) contractNeighbours(); } @@ -99,22 +93,6 @@ public void setRemoved() { if (clientMonitor != null && xIndex == 0 && yIndex == 0) clientMonitor.destroy(); } - @Override - public InteractionResult onActivate(Player player, InteractionHand hand, BlockHitResult hit) { - if (!player.isCrouching() && getFront() == hit.getDirection()) { - if (!getLevel().isClientSide) { - monitorTouched( - (float) (hit.getLocation().x - hit.getBlockPos().getX()), - (float) (hit.getLocation().y - hit.getBlockPos().getY()), - (float) (hit.getLocation().z - hit.getBlockPos().getZ()) - ); - } - return InteractionResult.SUCCESS; - } - - return InteractionResult.PASS; - } - @Override public void saveAdditional(CompoundTag tag) { tag.putInt(NBT_X, xIndex); @@ -128,14 +106,18 @@ public void saveAdditional(CompoundTag tag) { public void load(CompoundTag nbt) { super.load(nbt); + var oldXIndex = xIndex; + var oldYIndex = yIndex; + xIndex = nbt.getInt(NBT_X); yIndex = nbt.getInt(NBT_Y); width = nbt.getInt(NBT_WIDTH); height = nbt.getInt(NBT_HEIGHT); + + if (level != null && level.isClientSide) onClientLoad(oldXIndex, oldYIndex); } - @Override - public void blockTick() { + void blockTick() { if (needsValidating) { needsValidating = false; validate(); @@ -223,18 +205,7 @@ public final CompoundTag getUpdateTag() { return nbt; } - @Override - public final void handleUpdateTag(CompoundTag nbt) { - super.handleUpdateTag(nbt); - - var oldXIndex = xIndex; - var oldYIndex = yIndex; - - xIndex = nbt.getInt(NBT_X); - yIndex = nbt.getInt(NBT_Y); - width = nbt.getInt(NBT_WIDTH); - height = nbt.getInt(NBT_HEIGHT); - + private void onClientLoad(int oldXIndex, int oldYIndex) { if (oldXIndex != xIndex || oldYIndex != yIndex) { // If our index has changed then it's possible the origin monitor has changed. Thus // we'll clear our cache. If we're the origin then we'll need to remove the glList as well. @@ -401,7 +372,7 @@ void resize(int width, int height) { monitor.serverMonitor = serverMonitor; monitor.needsUpdate = monitor.needsValidating = false; monitor.updateBlockState(); - monitor.updateBlock(); + BlockEntityHelpers.updateBlock(monitor); } } } @@ -476,7 +447,7 @@ private void validate() { } // endregion - private void monitorTouched(float xPos, float yPos, float zPos) { + void monitorTouched(float xPos, float yPos, float zPos) { if (!advanced) return; var pair = XYPair diff --git a/projects/common/src/main/java/dan200/computercraft/shared/peripheral/monitor/ServerMonitor.java b/projects/common/src/main/java/dan200/computercraft/shared/peripheral/monitor/ServerMonitor.java index 03980c516..eca373bee 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/peripheral/monitor/ServerMonitor.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/peripheral/monitor/ServerMonitor.java @@ -34,11 +34,11 @@ synchronized void rebuild() { var textScale = this.textScale * 0.5; var termWidth = (int) Math.max( - Math.round((origin.getWidth() - 2.0 * (MonitorBlockEntity.RENDER_BORDER + MonitorBlockEntity.RENDER_MARGIN)) / (textScale * 6.0 * MonitorBlockEntity.RENDER_PIXEL_SCALE)), + (double) Math.round((origin.getWidth() - 2.0 * (MonitorBlockEntity.RENDER_BORDER + MonitorBlockEntity.RENDER_MARGIN)) / (textScale * 6.0 * MonitorBlockEntity.RENDER_PIXEL_SCALE)), 1.0 ); var termHeight = (int) Math.max( - Math.round((origin.getHeight() - 2.0 * (MonitorBlockEntity.RENDER_BORDER + MonitorBlockEntity.RENDER_MARGIN)) / (textScale * 9.0 * MonitorBlockEntity.RENDER_PIXEL_SCALE)), + (double) Math.round((origin.getHeight() - 2.0 * (MonitorBlockEntity.RENDER_BORDER + MonitorBlockEntity.RENDER_MARGIN)) / (textScale * 9.0 * MonitorBlockEntity.RENDER_PIXEL_SCALE)), 1.0 ); diff --git a/projects/common/src/main/java/dan200/computercraft/shared/peripheral/printer/PrinterBlock.java b/projects/common/src/main/java/dan200/computercraft/shared/peripheral/printer/PrinterBlock.java index 6f7b72acb..ceb873170 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/peripheral/printer/PrinterBlock.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/peripheral/printer/PrinterBlock.java @@ -6,35 +6,23 @@ package dan200.computercraft.shared.peripheral.printer; import dan200.computercraft.shared.ModRegistry; -import dan200.computercraft.shared.common.GenericBlock; +import dan200.computercraft.shared.common.HorizontalContainerBlock; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; -import net.minecraft.stats.Stats; -import net.minecraft.world.Nameable; -import net.minecraft.world.entity.LivingEntity; -import net.minecraft.world.entity.player.Player; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.context.BlockPlaceContext; -import net.minecraft.world.level.Level; import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.Mirror; -import net.minecraft.world.level.block.Rotation; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.StateDefinition; -import net.minecraft.world.level.block.state.properties.BlockStateProperties; import net.minecraft.world.level.block.state.properties.BooleanProperty; -import net.minecraft.world.level.block.state.properties.DirectionProperty; import javax.annotation.Nullable; -public class PrinterBlock extends GenericBlock { - private static final DirectionProperty FACING = BlockStateProperties.HORIZONTAL_FACING; +public class PrinterBlock extends HorizontalContainerBlock { public static final BooleanProperty TOP = BooleanProperty.create("top"); public static final BooleanProperty BOTTOM = BooleanProperty.create("bottom"); public PrinterBlock(Properties settings) { - super(settings, ModRegistry.BlockEntities.PRINTER); + super(settings); registerDefaultState(getStateDefinition().any() .setValue(FACING, Direction.NORTH) .setValue(TOP, false) @@ -46,42 +34,9 @@ protected void createBlockStateDefinition(StateDefinition.Builder inventory = NonNullList.withSize(SLOTS, ItemStack.EMPTY); - private @Nullable IPeripheral peripheral; private final NetworkedTerminal page = new NetworkedTerminal(PrintoutItem.LINE_MAX_LENGTH, PrintoutItem.LINES_PER_PAGE, true); private String pageTitle = ""; @@ -58,30 +46,14 @@ public PrinterBlockEntity(BlockEntityType type, BlockPos pos super(type, pos, state); } - @Override - public void destroy() { - ejectContents(); - } - - @Override - public boolean isUsable(Player player) { - return super.isUsable(player) && BaseContainerBlockEntity.canUnlock(player, lockCode, getDisplayName()); - } - - @Override - public InteractionResult onActivate(Player player, InteractionHand hand, BlockHitResult hit) { - if (player.isCrouching()) return InteractionResult.PASS; - - if (!getLevel().isClientSide && isUsable(player)) player.openMenu(this); - return InteractionResult.SUCCESS; + public IPeripheral peripheral() { + return peripheral; } @Override public void load(CompoundTag nbt) { super.load(nbt); - customName = nbt.contains(NBT_NAME) ? Component.Serializer.fromJson(nbt.getString(NBT_NAME)) : null; - // Read page synchronized (page) { printing = nbt.getBoolean(NBT_PRINTING); @@ -91,91 +63,35 @@ public void load(CompoundTag nbt) { // Read inventory ContainerHelper.loadAllItems(nbt, inventory); - - lockCode = LockCode.fromTag(nbt); } @Override - public void saveAdditional(CompoundTag nbt) { - if (customName != null) nbt.putString(NBT_NAME, Component.Serializer.toJson(customName)); - + public void saveAdditional(CompoundTag tag) { // Write page synchronized (page) { - nbt.putBoolean(NBT_PRINTING, printing); - nbt.putString(NBT_PAGE_TITLE, pageTitle); - page.writeToNBT(nbt); + tag.putBoolean(NBT_PRINTING, printing); + tag.putString(NBT_PAGE_TITLE, pageTitle); + page.writeToNBT(tag); } // Write inventory - ContainerHelper.saveAllItems(nbt, inventory); + ContainerHelper.saveAllItems(tag, inventory); - lockCode.addToTag(nbt); - - super.saveAdditional(nbt); + super.saveAdditional(tag); } boolean isPrinting() { return printing; } - // IInventory implementation @Override - public int getContainerSize() { - return inventory.size(); + public NonNullList getContents() { + return inventory; } @Override - public boolean isEmpty() { - for (var stack : inventory) { - if (!stack.isEmpty()) return false; - } - return true; - } - - @Override - public ItemStack getItem(int slot) { - return inventory.get(slot); - } - - @Override - public ItemStack removeItemNoUpdate(int slot) { - var result = inventory.get(slot); - inventory.set(slot, ItemStack.EMPTY); - setChanged(); - updateBlockState(); - return result; - } - - @Override - public ItemStack removeItem(int slot, int count) { - var stack = inventory.get(slot); - if (stack.isEmpty()) return ItemStack.EMPTY; - - if (stack.getCount() <= count) { - setItem(slot, ItemStack.EMPTY); - return stack; - } - - var part = stack.split(count); - if (inventory.get(slot).isEmpty()) { - inventory.set(slot, ItemStack.EMPTY); - updateBlockState(); - } - setChanged(); - return part; - } - - @Override - public void setItem(int slot, ItemStack stack) { - inventory.set(slot, stack); - setChanged(); - updateBlockState(); - } - - @Override - public void clearContent() { - for (var i = 0; i < inventory.size(); i++) inventory.set(i, ItemStack.EMPTY); - setChanged(); + public void setChanged() { + super.setChanged(); updateBlockState(); } @@ -190,24 +106,17 @@ public boolean canPlaceItem(int slot, ItemStack stack) { } } - @Override - public boolean stillValid(Player playerEntity) { - return isUsable(playerEntity); - } - - // ISidedInventory implementation - @Override public int[] getSlotsForFace(Direction side) { return switch (side) { - case DOWN -> BOTTOM_SLOTS; // Out tray - case UP -> TOP_SLOTS; // In tray - default -> SIDE_SLOTS; // Ink + case DOWN -> BOTTOM_SLOTS; // Bottom (Out tray) + case UP -> TOP_SLOTS; // Top (In tray) + default -> SIDE_SLOTS; // Sides (Ink) }; } @Nullable - Terminal getCurrentPage() { + NetworkedTerminal getCurrentPage() { synchronized (page) { return printing ? page : null; } @@ -325,19 +234,6 @@ private boolean outputPage() { return false; } - private void ejectContents() { - for (var i = 0; i < 13; i++) { - var stack = inventory.get(i); - if (!stack.isEmpty()) { - // Remove the stack from the inventory - setItem(i, ItemStack.EMPTY); - - // Spawn the item in the world - WorldUtil.dropItemStack(stack, getLevel(), Vec3.atLowerCornerOf(getBlockPos()).add(0.5, 0.75, 0.5)); - } - } - } - private void updateBlockState() { boolean top = false, bottom = false; for (var i = 1; i < 7; i++) { @@ -367,34 +263,8 @@ private void updateBlockState(boolean top, boolean bottom) { getLevel().setBlockAndUpdate(getBlockPos(), state.setValue(PrinterBlock.TOP, top).setValue(PrinterBlock.BOTTOM, bottom)); } - public IPeripheral peripheral() { - if (peripheral == null) peripheral = new PrinterPeripheral(this); - return peripheral; - } - @Override - public boolean hasCustomName() { - return customName != null; - } - - @Nullable - @Override - public Component getCustomName() { - return customName; - } - - @Override - public Component getName() { - return customName != null ? customName : Component.translatable(getBlockState().getBlock().getDescriptionId()); - } - - @Override - public Component getDisplayName() { - return Nameable.super.getDisplayName(); - } - - @Override - public AbstractContainerMenu createMenu(int id, Inventory inventory, Player player) { + protected AbstractContainerMenu createMenu(int id, Inventory inventory) { return new PrinterMenu(id, inventory, this); } } diff --git a/projects/common/src/main/java/dan200/computercraft/shared/peripheral/printer/PrinterMenu.java b/projects/common/src/main/java/dan200/computercraft/shared/peripheral/printer/PrinterMenu.java index 5c86fa622..e11cdf002 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/peripheral/printer/PrinterMenu.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/peripheral/printer/PrinterMenu.java @@ -6,8 +6,8 @@ package dan200.computercraft.shared.peripheral.printer; import dan200.computercraft.shared.ModRegistry; -import dan200.computercraft.shared.util.SingleIntArray; -import dan200.computercraft.shared.util.ValidatingSlot; +import dan200.computercraft.shared.container.SingleContainerData; +import dan200.computercraft.shared.container.ValidatingSlot; import net.minecraft.world.Container; import net.minecraft.world.SimpleContainer; import net.minecraft.world.entity.player.Inventory; @@ -58,7 +58,7 @@ public PrinterMenu(int id, Inventory player) { } public PrinterMenu(int id, Inventory player, PrinterBlockEntity printer) { - this(id, player, printer, (SingleIntArray) () -> printer.isPrinting() ? 1 : 0); + this(id, player, printer, (SingleContainerData) () -> printer.isPrinting() ? 1 : 0); } public boolean isPrinting() { diff --git a/projects/common/src/main/java/dan200/computercraft/shared/peripheral/speaker/SpeakerBlock.java b/projects/common/src/main/java/dan200/computercraft/shared/peripheral/speaker/SpeakerBlock.java index 2841444bf..5482e339f 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/peripheral/speaker/SpeakerBlock.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/peripheral/speaker/SpeakerBlock.java @@ -6,31 +6,27 @@ package dan200.computercraft.shared.peripheral.speaker; import dan200.computercraft.shared.ModRegistry; -import dan200.computercraft.shared.common.GenericBlock; +import dan200.computercraft.shared.util.BlockEntityHelpers; +import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.world.item.context.BlockPlaceContext; import net.minecraft.world.level.Level; -import net.minecraft.world.level.block.BaseEntityBlock; import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.Mirror; -import net.minecraft.world.level.block.Rotation; +import net.minecraft.world.level.block.EntityBlock; +import net.minecraft.world.level.block.HorizontalDirectionalBlock; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntityTicker; import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.StateDefinition; -import net.minecraft.world.level.block.state.properties.BlockStateProperties; -import net.minecraft.world.level.block.state.properties.DirectionProperty; import javax.annotation.Nullable; -public class SpeakerBlock extends GenericBlock { - private static final DirectionProperty FACING = BlockStateProperties.HORIZONTAL_FACING; - +public class SpeakerBlock extends HorizontalDirectionalBlock implements EntityBlock { private static final BlockEntityTicker serverTicker = (level, pos, state, drive) -> drive.serverTick(); public SpeakerBlock(Properties settings) { - super(settings, ModRegistry.BlockEntities.SPEAKER); + super(settings); registerDefaultState(getStateDefinition().any() .setValue(FACING, Direction.NORTH)); } @@ -40,18 +36,6 @@ protected void createBlockStateDefinition(StateDefinition.Builder BlockEntityTicker getTicker(Level level, BlockState state, BlockEntityType type) { - return level.isClientSide ? null : BaseEntityBlock.createTickerHelper(type, ModRegistry.BlockEntities.SPEAKER.get(), serverTicker); + return level.isClientSide ? null : BlockEntityHelpers.createTickerHelper(type, ModRegistry.BlockEntities.SPEAKER.get(), serverTicker); + } + + @Nullable + @Override + public BlockEntity newBlockEntity(BlockPos pos, BlockState state) { + return ModRegistry.BlockEntities.SPEAKER.get().create(pos, state); } } diff --git a/projects/common/src/main/java/dan200/computercraft/shared/peripheral/speaker/SpeakerBlockEntity.java b/projects/common/src/main/java/dan200/computercraft/shared/peripheral/speaker/SpeakerBlockEntity.java index 6578a0cd6..f34ef6d83 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/peripheral/speaker/SpeakerBlockEntity.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/peripheral/speaker/SpeakerBlockEntity.java @@ -7,17 +7,17 @@ import dan200.computercraft.api.peripheral.IPeripheral; import dan200.computercraft.core.util.Nullability; -import dan200.computercraft.shared.common.GenericTile; import dan200.computercraft.shared.network.client.SpeakerStopClientMessage; import dan200.computercraft.shared.platform.PlatformHelper; import net.minecraft.core.BlockPos; +import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.phys.Vec3; import javax.annotation.Nullable; -public class SpeakerBlockEntity extends GenericTile { +public class SpeakerBlockEntity extends BlockEntity { private final SpeakerPeripheral peripheral; public SpeakerBlockEntity(BlockEntityType type, BlockPos pos, BlockState state) { diff --git a/projects/common/src/main/java/dan200/computercraft/shared/turtle/blocks/TurtleBlock.java b/projects/common/src/main/java/dan200/computercraft/shared/turtle/blocks/TurtleBlock.java index 17ab2e175..b2d07ef66 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/turtle/blocks/TurtleBlock.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/turtle/blocks/TurtleBlock.java @@ -15,9 +15,11 @@ import dan200.computercraft.shared.turtle.core.TurtleBrain; import dan200.computercraft.shared.turtle.items.ITurtleItem; import dan200.computercraft.shared.turtle.items.TurtleItemFactory; +import dan200.computercraft.shared.util.BlockEntityHelpers; import dan200.computercraft.shared.util.WaterloggableHelpers; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; +import net.minecraft.world.Containers; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.projectile.AbstractHurtingProjectile; @@ -27,7 +29,9 @@ import net.minecraft.world.level.Explosion; import net.minecraft.world.level.Level; import net.minecraft.world.level.LevelAccessor; -import net.minecraft.world.level.block.*; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.RenderShape; +import net.minecraft.world.level.block.SimpleWaterloggedBlock; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntityTicker; import net.minecraft.world.level.block.entity.BlockEntityType; @@ -69,18 +73,6 @@ protected void createBlockStateDefinition(StateDefinition.Builder BlockEntityTicker getTicker(Level level, BlockState state, BlockEntityType type) { - return level.isClientSide ? BaseEntityBlock.createTickerHelper(type, this.type.get(), clientTicker) : super.getTicker(level, state, type); + return level.isClientSide ? BlockEntityHelpers.createTickerHelper(type, this.type.get(), clientTicker) : super.getTicker(level, state, type); } } diff --git a/projects/common/src/main/java/dan200/computercraft/shared/turtle/blocks/TurtleBlockEntity.java b/projects/common/src/main/java/dan200/computercraft/shared/turtle/blocks/TurtleBlockEntity.java index 4254caf34..190be1f73 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/turtle/blocks/TurtleBlockEntity.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/turtle/blocks/TurtleBlockEntity.java @@ -11,20 +11,16 @@ import dan200.computercraft.api.turtle.ITurtleUpgrade; import dan200.computercraft.api.turtle.TurtleSide; import dan200.computercraft.core.computer.ComputerSide; -import dan200.computercraft.shared.common.GenericTile; import dan200.computercraft.shared.computer.blocks.AbstractComputerBlockEntity; import dan200.computercraft.shared.computer.blocks.ComputerPeripheral; import dan200.computercraft.shared.computer.core.ComputerFamily; import dan200.computercraft.shared.computer.core.ComputerState; import dan200.computercraft.shared.computer.core.ServerComputer; import dan200.computercraft.shared.config.Config; +import dan200.computercraft.shared.container.BasicContainer; import dan200.computercraft.shared.turtle.apis.TurtleAPI; import dan200.computercraft.shared.turtle.core.TurtleBrain; import dan200.computercraft.shared.turtle.inventory.TurtleMenu; -import dan200.computercraft.shared.util.DefaultInventory; -import dan200.computercraft.shared.util.DirectionUtil; -import dan200.computercraft.shared.util.RedstoneUtil; -import dan200.computercraft.shared.util.WorldUtil; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.core.NonNullList; @@ -43,13 +39,12 @@ import net.minecraft.world.item.Items; import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.phys.BlockHitResult; import net.minecraft.world.phys.Vec3; import javax.annotation.Nullable; import java.util.Collections; -public class TurtleBlockEntity extends AbstractComputerBlockEntity implements ITurtleBlockEntity, DefaultInventory { +public class TurtleBlockEntity extends AbstractComputerBlockEntity implements BasicContainer, ITurtleBlockEntity { public static final int INVENTORY_SIZE = 16; public static final int INVENTORY_WIDTH = 4; public static final int INVENTORY_HEIGHT = 4; @@ -68,7 +63,7 @@ enum MoveState { private @Nullable IPeripheral peripheral; private @Nullable Runnable onMoved; - public TurtleBlockEntity(BlockEntityType type, BlockPos pos, BlockState state, ComputerFamily family) { + public TurtleBlockEntity(BlockEntityType type, BlockPos pos, BlockState state, ComputerFamily family) { super(type, pos, state, family); } @@ -88,39 +83,13 @@ protected ServerComputer createComputer(int id) { return computer; } - @Override - public void destroy() { - if (!hasMoved()) { - // Stop computer - super.destroy(); - - // Drop contents - if (!getLevel().isClientSide) { - var size = getContainerSize(); - for (var i = 0; i < size; i++) { - var stack = getItem(i); - if (!stack.isEmpty()) { - WorldUtil.dropItemStack(stack, getLevel(), getBlockPos()); - } - } - } - } else { - // Just turn off any redstone we had on - for (var dir : DirectionUtil.FACINGS) { - RedstoneUtil.propagateRedstoneOutput(getLevel(), getBlockPos(), dir); - } - } - } - @Override protected void unload() { - if (!hasMoved()) { - super.unload(); - } + if (!hasMoved()) super.unload(); } @Override - public InteractionResult onActivate(Player player, InteractionHand hand, BlockHitResult hit) { + public InteractionResult use(Player player, InteractionHand hand) { // Apply dye var currentItem = player.getItemInHand(hand); if (!currentItem.isEmpty()) { @@ -152,7 +121,7 @@ public InteractionResult onActivate(Player player, InteractionHand hand, BlockHi } // Open GUI or whatever - return super.onActivate(player, hand, hit); + return super.use(player, hand); } @Override @@ -161,7 +130,7 @@ protected boolean canNameWithTag(Player player) { } @Override - protected double getInteractRange(Player player) { + protected double getInteractRange() { return 12.0; } @@ -189,13 +158,8 @@ protected void updateBlockState(ComputerState newState) { } @Override - public void onNeighbourChange(BlockPos neighbour) { - if (moveState == MoveState.NOT_MOVED) super.onNeighbourChange(neighbour); - } - - @Override - public void onNeighbourTileEntityChange(BlockPos neighbour) { - if (moveState == MoveState.NOT_MOVED) super.onNeighbourTileEntityChange(neighbour); + public void neighborChanged(BlockPos neighbour) { + if (moveState == MoveState.NOT_MOVED) super.neighborChanged(neighbour); } public void notifyMoveStart() { @@ -208,8 +172,8 @@ public void notifyMoveEnd() { } @Override - public void load(CompoundTag nbt) { - super.load(nbt); + public void loadServer(CompoundTag nbt) { + super.loadServer(nbt); // Read inventory var nbttaglist = nbt.getList("Items", Tag.TAG_COMPOUND); @@ -315,66 +279,8 @@ void setOwningPlayer(GameProfile player) { // IInventory @Override - public int getContainerSize() { - return INVENTORY_SIZE; - } - - @Override - public boolean isEmpty() { - for (var stack : inventory) { - if (!stack.isEmpty()) return false; - } - return true; - } - - @Override - public ItemStack getItem(int slot) { - return slot >= 0 && slot < INVENTORY_SIZE ? inventory.get(slot) : ItemStack.EMPTY; - } - - @Override - public ItemStack removeItemNoUpdate(int slot) { - var result = getItem(slot); - setItem(slot, ItemStack.EMPTY); - return result; - } - - @Override - public ItemStack removeItem(int slot, int count) { - if (count == 0) return ItemStack.EMPTY; - - var stack = getItem(slot); - if (stack.isEmpty()) return ItemStack.EMPTY; - - if (stack.getCount() <= count) { - setItem(slot, ItemStack.EMPTY); - return stack; - } - - var part = stack.split(count); - onInventoryDefinitelyChanged(); - return part; - } - - @Override - public void setItem(int i, ItemStack stack) { - if (i >= 0 && i < INVENTORY_SIZE && !ItemStack.matches(stack, inventory.get(i))) { - inventory.set(i, stack); - onInventoryDefinitelyChanged(); - } - } - - @Override - public void clearContent() { - var changed = false; - for (var i = 0; i < INVENTORY_SIZE; i++) { - if (!inventory.get(i).isEmpty()) { - inventory.set(i, ItemStack.EMPTY); - changed = true; - } - } - - if (changed) onInventoryDefinitelyChanged(); + public NonNullList getContents() { + return inventory; } @Override @@ -395,11 +301,6 @@ public boolean stillValid(Player player) { return isUsable(player); } - private void onInventoryDefinitelyChanged() { - super.setChanged(); - inventoryChanged = true; - } - public void onTileEntityChange() { super.setChanged(); } @@ -414,8 +315,8 @@ public CompoundTag getUpdateTag() { } @Override - public void handleUpdateTag(CompoundTag nbt) { - super.handleUpdateTag(nbt); + public void loadClient(CompoundTag nbt) { + super.loadClient(nbt); brain.readDescription(nbt); } diff --git a/projects/common/src/main/java/dan200/computercraft/shared/turtle/core/TurtleBrain.java b/projects/common/src/main/java/dan200/computercraft/shared/turtle/core/TurtleBrain.java index 2cdab16fd..9b71ca89c 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/turtle/core/TurtleBrain.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/turtle/core/TurtleBrain.java @@ -17,11 +17,12 @@ import dan200.computercraft.shared.computer.core.ComputerFamily; import dan200.computercraft.shared.computer.core.ServerComputer; import dan200.computercraft.shared.config.Config; +import dan200.computercraft.shared.container.InventoryDelegate; import dan200.computercraft.shared.platform.PlatformHelper; import dan200.computercraft.shared.turtle.blocks.TurtleBlockEntity; +import dan200.computercraft.shared.util.BlockEntityHelpers; import dan200.computercraft.shared.util.Holiday; import dan200.computercraft.shared.util.HolidayUtil; -import dan200.computercraft.shared.util.InventoryDelegate; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.core.particles.ParticleTypes; @@ -345,6 +346,8 @@ public float getVisualYaw(float f) { yaw += 360.0f; } } + default -> { + } } return yaw; } @@ -454,7 +457,7 @@ public void playAnimation(TurtleAnimation animation) { animationProgress = 0; lastAnimationProgress = 0; } - owner.updateBlock(); + BlockEntityHelpers.updateBlock(owner); } public @Nullable ResourceLocation getOverlay() { @@ -464,7 +467,7 @@ public void playAnimation(TurtleAnimation animation) { public void setOverlay(ResourceLocation overlay) { if (!Objects.equal(this.overlay, overlay)) { this.overlay = overlay; - owner.updateBlock(); + BlockEntityHelpers.updateBlock(owner); } } @@ -481,7 +484,7 @@ public void setDyeColour(@Nullable DyeColor dyeColour) { } if (colourHex != newColour) { colourHex = newColour; - owner.updateBlock(); + BlockEntityHelpers.updateBlock(owner); } } @@ -490,11 +493,11 @@ public void setColour(int colour) { if (colour >= 0 && colour <= 0xFFFFFF) { if (colourHex != colour) { colourHex = colour; - owner.updateBlock(); + BlockEntityHelpers.updateBlock(owner); } } else if (colourHex != -1) { colourHex = -1; - owner.updateBlock(); + BlockEntityHelpers.updateBlock(owner); } } @@ -525,7 +528,7 @@ public void setUpgrade(TurtleSide side, @Nullable ITurtleUpgrade upgrade) { // This is a separate function to avoid updating the block when reading the NBT. We don't need to do this as // either the block is newly placed (and so won't have changed) or is being updated with /data, which calls // updateBlock for us. - owner.updateBlock(); + BlockEntityHelpers.updateBlock(owner); // Recompute peripherals in case an upgrade being removed has exposed a new peripheral. // TODO: Only update peripherals, or even only two sides? @@ -568,7 +571,7 @@ public CompoundTag getUpgradeNBTData(TurtleSide side) { @Override public void updateUpgradeNBTData(TurtleSide side) { - owner.updateBlock(); + BlockEntityHelpers.updateBlock(owner); } public Vec3 getRenderOffset(float f) { diff --git a/projects/common/src/main/java/dan200/computercraft/shared/turtle/inventory/TurtleMenu.java b/projects/common/src/main/java/dan200/computercraft/shared/turtle/inventory/TurtleMenu.java index 75a9a1476..04ec5a260 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/turtle/inventory/TurtleMenu.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/turtle/inventory/TurtleMenu.java @@ -12,7 +12,7 @@ import dan200.computercraft.shared.network.container.ComputerContainerData; import dan200.computercraft.shared.turtle.blocks.TurtleBlockEntity; import dan200.computercraft.shared.turtle.core.TurtleBrain; -import dan200.computercraft.shared.util.SingleIntArray; +import dan200.computercraft.shared.container.SingleContainerData; import net.minecraft.world.Container; import net.minecraft.world.SimpleContainer; import net.minecraft.world.entity.player.Inventory; @@ -65,7 +65,7 @@ public static TurtleMenu ofBrain(int id, Inventory player, TurtleBrain turtle) { return new TurtleMenu( // Laziness in turtle.getOwner() is important here! id, p -> turtle.getOwner().stillValid(p), turtle.getFamily(), turtle.getOwner().createServerComputer(), null, - player, turtle.getInventory(), (SingleIntArray) turtle::getSelectedSlot + player, turtle.getInventory(), (SingleContainerData) turtle::getSelectedSlot ); } diff --git a/projects/common/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleInventoryCrafting.java b/projects/common/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleInventoryCrafting.java index 653ba2d83..12facc8d7 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleInventoryCrafting.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleInventoryCrafting.java @@ -106,7 +106,7 @@ public List doCrafting(Level world, int maxCount) { } } - return results; + return Collections.unmodifiableList(results); } @Override diff --git a/projects/common/src/main/java/dan200/computercraft/shared/util/BlockEntityHelpers.java b/projects/common/src/main/java/dan200/computercraft/shared/util/BlockEntityHelpers.java new file mode 100644 index 000000000..5a96391ec --- /dev/null +++ b/projects/common/src/main/java/dan200/computercraft/shared/util/BlockEntityHelpers.java @@ -0,0 +1,69 @@ +/* + * This file is part of ComputerCraft - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. + * Send enquiries to dratcliffe@gmail.com + */ +package dan200.computercraft.shared.util; + +import dan200.computercraft.shared.platform.PlatformHelper; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.BlockEntityTicker; +import net.minecraft.world.level.block.entity.BlockEntityType; +import net.minecraft.world.phys.Vec3; + +import javax.annotation.Nullable; + +public final class BlockEntityHelpers { + /** + * The maximum limit a player can be away from a block to still have its UI open. + * + * @see #isUsable(BlockEntity, Player, double) + */ + public static final double DEFAULT_INTERACT_RANGE = 8.0; + + private BlockEntityHelpers() { + } + + @Nullable + @SuppressWarnings("unchecked") + public static BlockEntityTicker createTickerHelper( + BlockEntityType actualType, BlockEntityType expectedType, BlockEntityTicker ticker + ) { + return actualType == expectedType ? (BlockEntityTicker) ticker : null; + } + + /** + * Determine if a block entity is "usable" by a player. + * + * @param blockEntity The current block entity. + * @param player The player who is trying to interact with the block. + * @param range The default distance the player can be away. This typically defaults to {@link #DEFAULT_INTERACT_RANGE}, + * but a custom value may be used. If {@link PlatformHelper#getReachDistance(Player)} is larger, + * that will be used instead. + * @return Whether this block entity is usable. + */ + public static boolean isUsable(BlockEntity blockEntity, Player player, double range) { + var level = blockEntity.getLevel(); + var pos = blockEntity.getBlockPos(); + + range = Math.max(range, PlatformHelper.get().getReachDistance(player)); + + return player.isAlive() && player.getCommandSenderWorld() == level && + !blockEntity.isRemoved() && level.getBlockEntity(pos) == blockEntity && + player.distanceToSqr(Vec3.atCenterOf(pos)) <= range * range; + } + + /** + * Update a block entity, marking it as changed and propagating changes to the client. + * + * @param blockEntity The block entity which has updated. + */ + public static void updateBlock(BlockEntity blockEntity) { + blockEntity.setChanged(); + + var state = blockEntity.getBlockState(); + blockEntity.getLevel().sendBlockUpdated(blockEntity.getBlockPos(), state, state, Block.UPDATE_ALL); + } +} diff --git a/projects/common/src/main/java/dan200/computercraft/shared/util/DefaultInventory.java b/projects/common/src/main/java/dan200/computercraft/shared/util/DefaultInventory.java deleted file mode 100644 index 9ae0f9be5..000000000 --- a/projects/common/src/main/java/dan200/computercraft/shared/util/DefaultInventory.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * This file is part of ComputerCraft - http://www.computercraft.info - * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. - * Send enquiries to dratcliffe@gmail.com - */ -package dan200.computercraft.shared.util; - -import net.minecraft.world.Container; -import net.minecraft.world.entity.player.Player; -import net.minecraft.world.item.ItemStack; - - -public interface DefaultInventory extends Container { - @Override - default int getMaxStackSize() { - return 64; - } - - @Override - default void startOpen(Player player) { - } - - @Override - default void stopOpen(Player player) { - } - - @Override - default boolean canPlaceItem(int slot, ItemStack stack) { - return true; - } -} diff --git a/projects/common/src/main/java/dan200/computercraft/shared/util/RecipeUtil.java b/projects/common/src/main/java/dan200/computercraft/shared/util/RecipeUtil.java index b11807dac..432247c22 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/util/RecipeUtil.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/util/RecipeUtil.java @@ -63,14 +63,16 @@ public static ShapedTemplate getTemplate(JsonObject json) { Set missingKeys = Sets.newHashSet(ingMap.keySet()); missingKeys.remove(' '); - var i = 0; + var ingredientIdx = 0; for (var line : pattern) { - for (var chr : line.toCharArray()) { + for (var i = 0; i < line.length(); i++) { + var chr = line.charAt(i); + var ing = ingMap.get(chr); if (ing == null) { throw new JsonSyntaxException("Pattern references symbol '" + chr + "' but it's not defined in the key"); } - ingredients.set(i++, ing); + ingredients.set(ingredientIdx++, ing); missingKeys.remove(chr); } } diff --git a/projects/common/src/main/java/dan200/computercraft/shared/util/WorldUtil.java b/projects/common/src/main/java/dan200/computercraft/shared/util/WorldUtil.java index 322839adb..9311a910e 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/util/WorldUtil.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/util/WorldUtil.java @@ -29,6 +29,7 @@ import java.util.function.Predicate; public final class WorldUtil { + @SuppressWarnings("UnnecessaryLambda") private static final Predicate CAN_COLLIDE = x -> x != null && x.isAlive() && x.isPickable(); public static boolean isLiquidBlock(Level world, BlockPos pos) { diff --git a/projects/common/src/testFixtures/java/dan200/computercraft/test/shared/ItemStackMatcher.java b/projects/common/src/testFixtures/java/dan200/computercraft/test/shared/ItemStackMatcher.java index d4e11cfa3..64ae2c9d9 100644 --- a/projects/common/src/testFixtures/java/dan200/computercraft/test/shared/ItemStackMatcher.java +++ b/projects/common/src/testFixtures/java/dan200/computercraft/test/shared/ItemStackMatcher.java @@ -24,7 +24,7 @@ protected boolean matchesSafely(ItemStack item) { @Override public void describeTo(Description description) { - description.appendValue(stack); + description.appendValue(stack).appendValue(stack.getTag()); } public static Matcher isStack(ItemStack stack) { diff --git a/projects/common/src/testMod/java/dan200/computercraft/mixin/gametest/GameTestSequenceMixin.java b/projects/common/src/testMod/java/dan200/computercraft/mixin/gametest/GameTestSequenceMixin.java index 3e994244f..fb812c5b1 100644 --- a/projects/common/src/testMod/java/dan200/computercraft/mixin/gametest/GameTestSequenceMixin.java +++ b/projects/common/src/testMod/java/dan200/computercraft/mixin/gametest/GameTestSequenceMixin.java @@ -35,7 +35,7 @@ public void tickAndContinue(long ticks) { // Mimic the original behaviour. } catch (AssertionError e) { parent.fail(e); - } catch (Exception e) { + } catch (Exception | LinkageError | VirtualMachineError e) { // Fail the test, rather than crashing the server. TestHooks.LOG.error("{} threw unexpected exception", parent.getTestName(), e); parent.fail(e); diff --git a/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Disk_Drive_Test.kt b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Disk_Drive_Test.kt index a45cf0772..ce823c37b 100644 --- a/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Disk_Drive_Test.kt +++ b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Disk_Drive_Test.kt @@ -6,15 +6,19 @@ package dan200.computercraft.gametest import dan200.computercraft.core.apis.FSAPI -import dan200.computercraft.gametest.api.GameTestHolder -import dan200.computercraft.gametest.api.sequence -import dan200.computercraft.gametest.api.thenOnComputer +import dan200.computercraft.gametest.api.* +import dan200.computercraft.shared.ModRegistry +import dan200.computercraft.shared.peripheral.diskdrive.DiskDriveBlock +import dan200.computercraft.shared.peripheral.diskdrive.DiskDriveState import dan200.computercraft.test.core.assertArrayEquals import dan200.computercraft.test.core.computer.getApi import net.minecraft.core.BlockPos import net.minecraft.gametest.framework.GameTest import net.minecraft.gametest.framework.GameTestHelper +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.assertEquals @GameTestHolder @@ -52,4 +56,70 @@ fun Adds_removes_mount(helper: GameTestHelper) = helper.sequence { thenIdle(2) thenOnComputer { assertEquals(null, getApi().getDrive("disk")) } } + + /** + * Check comparators can read the contents of the disk drive + */ + @GameTest + fun Comparator(helper: GameTestHelper) = helper.sequence { + val drivePos = BlockPos(2, 2, 2) + val dustPos = BlockPos(2, 2, 4) + + // Adding items should provide power + thenExecute { + val drive = helper.getBlockEntity(drivePos, ModRegistry.BlockEntities.DISK_DRIVE.get()) + drive.setItem(0, ItemStack(ModRegistry.Items.TREASURE_DISK.get())) + drive.setChanged() + } + thenIdle(2) + thenExecute { helper.assertBlockHas(dustPos, RedStoneWireBlock.POWER, 15) } + + // And removing them should reset power. + thenExecute { + val drive = helper.getBlockEntity(drivePos, ModRegistry.BlockEntities.DISK_DRIVE.get()) + drive.setItem(0, ItemStack.EMPTY) + drive.setChanged() + } + thenIdle(2) + thenExecute { helper.assertBlockHas(dustPos, RedStoneWireBlock.POWER, 0) } + } + + /** + * Changing the inventory contents updates the block state + */ + @GameTest + fun Contents_updates_state(helper: GameTestHelper) = helper.sequence { + val pos = BlockPos(2, 2, 2) + + thenExecute { + val drive = helper.getBlockEntity(pos, ModRegistry.BlockEntities.DISK_DRIVE.get()) + + drive.setItem(0, ItemStack(Items.DIRT)) + drive.setChanged() + helper.assertBlockHas(pos, DiskDriveBlock.STATE, DiskDriveState.INVALID) + + drive.setItem(0, ItemStack(ModRegistry.Items.TREASURE_DISK.get())) + drive.setChanged() + helper.assertBlockHas(pos, DiskDriveBlock.STATE, DiskDriveState.FULL) + + drive.setItem(0, ItemStack.EMPTY) + drive.setChanged() + helper.assertBlockHas(pos, DiskDriveBlock.STATE, DiskDriveState.EMPTY) + } + } + + /** + * When the block is broken, we drop the contents and an optionally named stack. + */ + @GameTest + fun Drops_contents(helper: GameTestHelper) = helper.sequence { + thenExecute { + helper.level.destroyBlock(helper.absolutePos(BlockPos(2, 2, 2)), true) + helper.assertExactlyItems( + ItemStack(ModRegistry.Items.DISK_DRIVE.get()).setHoverName(Component.literal("My Disk Drive")), + ItemStack(ModRegistry.Items.TREASURE_DISK.get()), + message = "Breaking a disk drive should drop the contents", + ) + } + } } diff --git a/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Monitor_Test.kt b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Monitor_Test.kt index f24eb3b8b..b5d0c30d8 100644 --- a/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Monitor_Test.kt +++ b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Monitor_Test.kt @@ -5,11 +5,10 @@ */ package dan200.computercraft.gametest -import dan200.computercraft.gametest.api.GameTestHolder -import dan200.computercraft.gametest.api.getBlockEntity -import dan200.computercraft.gametest.api.sequence -import dan200.computercraft.gametest.api.setBlock +import dan200.computercraft.gametest.api.* import dan200.computercraft.shared.ModRegistry +import dan200.computercraft.shared.peripheral.monitor.MonitorBlock +import dan200.computercraft.shared.peripheral.monitor.MonitorEdgeState import net.minecraft.commands.arguments.blocks.BlockInput import net.minecraft.core.BlockPos import net.minecraft.gametest.framework.GameTest @@ -47,4 +46,16 @@ fun Ensures_valid_on_place(context: GameTestHelper) = context.sequence { } } } + + /** + * When a monitor is destroyed, assert its neighbors correctly contract. + */ + @GameTest + fun Contract_on_destroy(helper: GameTestHelper) = helper.sequence { + thenExecute { + helper.setBlock(BlockPos(2, 2, 2), Blocks.AIR.defaultBlockState()) + helper.assertBlockHas(BlockPos(1, 2, 2), MonitorBlock.STATE, MonitorEdgeState.NONE) + helper.assertBlockHas(BlockPos(3, 2, 2), MonitorBlock.STATE, MonitorEdgeState.NONE) + } + } } 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 new file mode 100644 index 000000000..d44d954ed --- /dev/null +++ b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Printer_Test.kt @@ -0,0 +1,91 @@ +package dan200.computercraft.gametest + +import dan200.computercraft.gametest.api.* +import dan200.computercraft.shared.ModRegistry +import dan200.computercraft.shared.peripheral.printer.PrinterBlock +import net.minecraft.core.BlockPos +import net.minecraft.gametest.framework.GameTest +import net.minecraft.gametest.framework.GameTestHelper +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 + +@GameTestHolder +class Printer_Test { + /** + * Check comparators can read the contents of the disk drive + */ + @GameTest + fun Comparator(helper: GameTestHelper) = helper.sequence { + val printerPos = BlockPos(2, 2, 2) + val dustPos = BlockPos(2, 2, 4) + + // 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() + } + 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() + } + thenIdle(2) + thenExecute { helper.assertBlockHas(dustPos, RedStoneWireBlock.POWER, 0) } + } + + /** + * Changing the inventory contents updates the block state + */ + @GameTest + fun Contents_updates_state(helper: GameTestHelper) = helper.sequence { + val pos = BlockPos(2, 2, 2) + + thenExecute { + val drive = helper.getBlockEntity(pos, ModRegistry.BlockEntities.PRINTER.get()) + + drive.setItem(1, ItemStack(Items.PAPER)) + drive.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() + 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() + 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() + helper.assertBlockHas(pos, PrinterBlock.TOP, false, message = "Empty") + helper.assertBlockHas(pos, PrinterBlock.BOTTOM, false, message = "Empty") + } + } + + /** + * When the block is broken, we drop the contents and an optionally named stack. + */ + @GameTest + fun Drops_contents(helper: GameTestHelper) = helper.sequence { + thenExecute { + helper.level.destroyBlock(helper.absolutePos(BlockPos(2, 2, 2)), true) + helper.assertExactlyItems( + ItemStack(ModRegistry.Items.PRINTER.get()).setHoverName(Component.literal("My Printer")), + ItemStack(Items.PAPER), + ItemStack(Items.BLACK_DYE), + message = "Breaking a printer should drop the contents", + ) + } + } +} 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 de19ad006..4cfc60928 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 @@ -36,6 +36,7 @@ import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertNotEquals import java.util.* +import kotlin.time.Duration.Companion.milliseconds @GameTestHolder class Turtle_Test { @@ -402,7 +403,9 @@ fun Peripheral_change(helper: GameTestHelper) = helper.sequence { val testInfo = (helper as GameTestHelperAccessor).testInfo as GameTestInfoAccessor val events = mutableListOf>() + var running = false thenStartComputer("listen") { + running = true while (true) { val event = pullEvent() TestHooks.LOG.info("[{}] Got event {} at tick {}", testInfo, event[0], testInfo.`computercraft$getTick`()) @@ -411,8 +414,9 @@ fun Peripheral_change(helper: GameTestHelper) = helper.sequence { } } } - thenIdle(2) thenOnComputer("turtle") { + while (!running) sleep(10.milliseconds) + turtle.forward().await().assertArrayEquals(true, message = "Moved turtle forward") turtle.back().await().assertArrayEquals(true, message = "Moved turtle forward") TestHooks.LOG.info("[{}] Finished turtle at {}", testInfo, testInfo.`computercraft$getTick`()) diff --git a/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/api/TestExtensions.kt b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/api/TestExtensions.kt index f8ab09745..18bcfbf4e 100644 --- a/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/api/TestExtensions.kt +++ b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/api/TestExtensions.kt @@ -11,6 +11,7 @@ import dan200.computercraft.shared.platform.PlatformHelper import dan200.computercraft.shared.platform.Registries import dan200.computercraft.test.core.computer.LuaTaskContext +import dan200.computercraft.test.shared.ItemStackMatcher.isStack import net.minecraft.commands.arguments.blocks.BlockInput import net.minecraft.core.BlockPos import net.minecraft.core.Direction @@ -24,6 +25,8 @@ import net.minecraft.world.level.block.entity.BlockEntityType import net.minecraft.world.level.block.state.BlockState import net.minecraft.world.level.block.state.properties.Property +import org.hamcrest.Matchers +import org.hamcrest.StringDescription /** * Globally usable structures. @@ -202,6 +205,16 @@ Items do not match (first mismatch at slot $slot). if (peripheral != null) fail("Expected no peripheral, got a ${peripheral.type}", pos) } +fun GameTestHelper.assertExactlyItems(vararg expected: ItemStack, message: String? = null) { + val actual = getEntities(EntityType.ITEM).map { it.item } + val matcher = Matchers.containsInAnyOrder(expected.map { isStack(it) }) + if (!matcher.matches(actual)) { + val description = StringDescription() + matcher.describeMismatch(actual, description) + fail(if (message.isNullOrEmpty()) description.toString() else "$message: $description") + } +} + private fun getName(type: BlockEntityType<*>): ResourceLocation = Registries.BLOCK_ENTITY_TYPES.getKey(type)!! /** diff --git a/projects/common/src/testMod/resources/data/cctest/structures/disk_drive_test.comparator.snbt b/projects/common/src/testMod/resources/data/cctest/structures/disk_drive_test.comparator.snbt new file mode 100644 index 000000000..278871a50 --- /dev/null +++ b/projects/common/src/testMod/resources/data/cctest/structures/disk_drive_test.comparator.snbt @@ -0,0 +1,139 @@ +{ + DataVersion: 3120, + 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:disk_drive{facing:north,state:empty}", nbt: {id: "computercraft:disk_drive"}}, + {pos: [2, 1, 3], state: "minecraft:comparator{facing:north,mode:compare,powered:false}", nbt: {OutputSignal: 0, id: "minecraft:comparator"}}, + {pos: [2, 1, 4], state: "minecraft:redstone_wire{east:none,north:side,power:0,south:side,west:none}"}, + {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:redstone_wire{east:none,north:side,power:0,south:side,west:none}", + "computercraft:disk_drive{facing:north,state:empty}", + "minecraft:comparator{facing:north,mode:compare,powered:false}" + ] +} diff --git a/projects/common/src/testMod/resources/data/cctest/structures/disk_drive_test.contents_updates_state.snbt b/projects/common/src/testMod/resources/data/cctest/structures/disk_drive_test.contents_updates_state.snbt new file mode 100644 index 000000000..2add8c4f5 --- /dev/null +++ b/projects/common/src/testMod/resources/data/cctest/structures/disk_drive_test.contents_updates_state.snbt @@ -0,0 +1,137 @@ +{ + DataVersion: 3120, + 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:disk_drive{facing:north,state:empty}", nbt: {ForgeCaps: {}, id: "computercraft:disk_drive"}}, + {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:disk_drive{facing:north,state:empty}" + ] +} diff --git a/projects/common/src/testMod/resources/data/cctest/structures/disk_drive_test.drops_contents.snbt b/projects/common/src/testMod/resources/data/cctest/structures/disk_drive_test.drops_contents.snbt new file mode 100644 index 000000000..e94dc4311 --- /dev/null +++ b/projects/common/src/testMod/resources/data/cctest/structures/disk_drive_test.drops_contents.snbt @@ -0,0 +1,137 @@ +{ + DataVersion: 3120, + 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:disk_drive{facing:north,state:full}", nbt: {CustomName: '{"text":"My Disk Drive"}', ForgeCaps: {}, Item: {Count: 1b, id: "computercraft:treasure_disk"}, id: "computercraft:disk_drive"}}, + {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:disk_drive{facing:north,state:full}" + ] +} diff --git a/projects/common/src/testMod/resources/data/cctest/structures/monitor_test.contract_on_destroy.snbt b/projects/common/src/testMod/resources/data/cctest/structures/monitor_test.contract_on_destroy.snbt new file mode 100644 index 000000000..ac59155b0 --- /dev/null +++ b/projects/common/src/testMod/resources/data/cctest/structures/monitor_test.contract_on_destroy.snbt @@ -0,0 +1,139 @@ +{ + DataVersion: 3120, + 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: "computercraft:monitor_advanced{facing:north,orientation:north,state:l}", nbt: {ForgeCaps: {}, Height: 1, Width: 3, XIndex: 2, YIndex: 0, id: "computercraft:monitor_advanced"}}, + {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:monitor_advanced{facing:north,orientation:north,state:lr}", nbt: {ForgeCaps: {}, Height: 1, Width: 3, XIndex: 1, YIndex: 0, id: "computercraft:monitor_advanced"}}, + {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: "computercraft:monitor_advanced{facing:north,orientation:north,state:r}", nbt: {ForgeCaps: {}, Height: 1, Width: 3, XIndex: 0, YIndex: 0, id: "computercraft:monitor_advanced"}}, + {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:monitor_advanced{facing:north,orientation:north,state:l}", + "computercraft:monitor_advanced{facing:north,orientation:north,state:lr}", + "computercraft:monitor_advanced{facing:north,orientation:north,state:r}" + ] +} diff --git a/projects/common/src/testMod/resources/data/cctest/structures/printer_test.comparator.snbt b/projects/common/src/testMod/resources/data/cctest/structures/printer_test.comparator.snbt new file mode 100644 index 000000000..4d68c3f1d --- /dev/null +++ b/projects/common/src/testMod/resources/data/cctest/structures/printer_test.comparator.snbt @@ -0,0 +1,139 @@ +{ + DataVersion: 3120, + 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:false,facing:north,top:false}", nbt: {ForgeCaps: {}, Items: [], 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: 0, term_textColour_0: "0000000000000000000000000", term_textColour_1: "0000000000000000000000000", term_textColour_10: "0000000000000000000000000", term_textColour_11: "0000000000000000000000000", term_textColour_12: "0000000000000000000000000", term_textColour_13: "0000000000000000000000000", term_textColour_14: "0000000000000000000000000", term_textColour_15: "0000000000000000000000000", term_textColour_16: "0000000000000000000000000", term_textColour_17: "0000000000000000000000000", term_textColour_18: "0000000000000000000000000", term_textColour_19: "0000000000000000000000000", term_textColour_2: "0000000000000000000000000", term_textColour_20: "0000000000000000000000000", term_textColour_3: "0000000000000000000000000", term_textColour_4: "0000000000000000000000000", term_textColour_5: "0000000000000000000000000", term_textColour_6: "0000000000000000000000000", term_textColour_7: "0000000000000000000000000", term_textColour_8: "0000000000000000000000000", term_textColour_9: "0000000000000000000000000", 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:comparator{facing:north,mode:compare,powered:false}", nbt: {OutputSignal: 0, id: "minecraft:comparator"}}, + {pos: [2, 1, 4], state: "minecraft:redstone_wire{east:none,north:side,power:0,south:side,west:none}"}, + {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:redstone_wire{east:none,north:side,power:0,south:side,west:none}", + "computercraft:printer{bottom:false,facing:north,top:false}", + "minecraft:comparator{facing:north,mode:compare,powered:false}" + ] +} 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.contents_updates_state.snbt new file mode 100644 index 000000000..ca594ce80 --- /dev/null +++ b/projects/common/src/testMod/resources/data/cctest/structures/printer_test.contents_updates_state.snbt @@ -0,0 +1,137 @@ +{ + DataVersion: 3120, + 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:false,facing:north,top:false}", nbt: {ForgeCaps: {}, Items: [], 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: 0, term_textColour_0: "0000000000000000000000000", term_textColour_1: "0000000000000000000000000", term_textColour_10: "0000000000000000000000000", term_textColour_11: "0000000000000000000000000", term_textColour_12: "0000000000000000000000000", term_textColour_13: "0000000000000000000000000", term_textColour_14: "0000000000000000000000000", term_textColour_15: "0000000000000000000000000", term_textColour_16: "0000000000000000000000000", term_textColour_17: "0000000000000000000000000", term_textColour_18: "0000000000000000000000000", term_textColour_19: "0000000000000000000000000", term_textColour_2: "0000000000000000000000000", term_textColour_20: "0000000000000000000000000", term_textColour_3: "0000000000000000000000000", term_textColour_4: "0000000000000000000000000", term_textColour_5: "0000000000000000000000000", term_textColour_6: "0000000000000000000000000", term_textColour_7: "0000000000000000000000000", term_textColour_8: "0000000000000000000000000", term_textColour_9: "0000000000000000000000000", 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:false,facing:north,top:false}" + ] +} diff --git a/projects/common/src/testMod/resources/data/cctest/structures/printer_test.drops_contents.snbt b/projects/common/src/testMod/resources/data/cctest/structures/printer_test.drops_contents.snbt new file mode 100644 index 000000000..2ed581375 --- /dev/null +++ b/projects/common/src/testMod/resources/data/cctest/structures/printer_test.drops_contents.snbt @@ -0,0 +1,137 @@ +{ + DataVersion: 3120, + 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:false,facing:north,top:true}", nbt: {CustomName: '{"text":"My Printer"}', ForgeCaps: {}, Items: [{Count: 1b, Slot: 0b, id: "minecraft:black_dye"}, {Count: 1b, Slot: 1b, id: "minecraft:paper"}], 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: 0, term_textColour_0: "0000000000000000000000000", term_textColour_1: "0000000000000000000000000", term_textColour_10: "0000000000000000000000000", term_textColour_11: "0000000000000000000000000", term_textColour_12: "0000000000000000000000000", term_textColour_13: "0000000000000000000000000", term_textColour_14: "0000000000000000000000000", term_textColour_15: "0000000000000000000000000", term_textColour_16: "0000000000000000000000000", term_textColour_17: "0000000000000000000000000", term_textColour_18: "0000000000000000000000000", term_textColour_19: "0000000000000000000000000", term_textColour_2: "0000000000000000000000000", term_textColour_20: "0000000000000000000000000", term_textColour_3: "0000000000000000000000000", term_textColour_4: "0000000000000000000000000", term_textColour_5: "0000000000000000000000000", term_textColour_6: "0000000000000000000000000", term_textColour_7: "0000000000000000000000000", term_textColour_8: "0000000000000000000000000", term_textColour_9: "0000000000000000000000000", 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:false,facing:north,top:true}" + ] +} diff --git a/projects/forge/build.gradle.kts b/projects/forge/build.gradle.kts index d84c5238a..d87b66c8c 100644 --- a/projects/forge/build.gradle.kts +++ b/projects/forge/build.gradle.kts @@ -99,8 +99,6 @@ minecraft { mods.register("cctest") { source(sourceSets["testMod"]) source(sourceSets["testFixtures"]) - // FIXME: We need this for running in-dev but not from Gradle: - // source(project(":core").sourceSets.testFixtures.get()) } } @@ -159,6 +157,7 @@ dependencies { testRuntimeOnly(libs.bundles.testRuntime) testModImplementation(testFixtures(project(":core"))) + testModImplementation(testFixtures(project(":forge"))) "cctJavadoc"(libs.cctJavadoc) } diff --git a/projects/forge/src/client/java/dan200/computercraft/client/platform/ClientPlatformHelperImpl.java b/projects/forge/src/client/java/dan200/computercraft/client/platform/ClientPlatformHelperImpl.java index c161bbfa5..a3b9dc83c 100644 --- a/projects/forge/src/client/java/dan200/computercraft/client/platform/ClientPlatformHelperImpl.java +++ b/projects/forge/src/client/java/dan200/computercraft/client/platform/ClientPlatformHelperImpl.java @@ -13,7 +13,7 @@ import net.minecraft.client.resources.model.ModelManager; import net.minecraft.resources.ResourceLocation; -@AutoService(ClientPlatformHelper.class) +@AutoService(dan200.computercraft.impl.client.ClientPlatformHelper.class) public class ClientPlatformHelperImpl implements ClientPlatformHelper { @Override public BakedModel getModel(ModelManager manager, ResourceLocation location) { diff --git a/projects/forge/src/main/java/dan200/computercraft/shared/util/SidedCapabilityProvider.java b/projects/forge/src/main/java/dan200/computercraft/shared/util/SidedCapabilityProvider.java index 5c47c97fc..a7fe636c5 100644 --- a/projects/forge/src/main/java/dan200/computercraft/shared/util/SidedCapabilityProvider.java +++ b/projects/forge/src/main/java/dan200/computercraft/shared/util/SidedCapabilityProvider.java @@ -7,6 +7,8 @@ import net.minecraft.core.Direction; import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraftforge.common.capabilities.Capability; import net.minecraftforge.common.capabilities.ICapabilityProvider; import net.minecraftforge.common.util.LazyOptional; @@ -14,6 +16,7 @@ import javax.annotation.Nullable; import java.util.Objects; +import java.util.function.BooleanSupplier; /** * A {@link ICapabilityProvider} which provides a different single capability, with different instances for each @@ -28,15 +31,21 @@ public final class SidedCapabilityProvider implements ICapabilityProvider { private final Capability cap; private final Provider supplier; + private final BooleanSupplier isRemoved; private @Nullable LazyOptional[] instances; - private SidedCapabilityProvider(Capability cap, Provider supplier) { + private SidedCapabilityProvider(Capability cap, Provider supplier, BooleanSupplier isRemoved) { this.cap = Objects.requireNonNull(cap, "Capability cannot be null"); this.supplier = supplier; + this.isRemoved = isRemoved; } public static SidedCapabilityProvider attach(AttachCapabilitiesEvent event, ResourceLocation id, Capability cap, Provider supplier) { - var provider = new SidedCapabilityProvider<>(cap, supplier); + BooleanSupplier isRemoved + = event.getObject() instanceof BlockEntity be ? be::isRemoved + : event.getObject() instanceof Entity entity ? entity::isRemoved + : () -> true; + var provider = new SidedCapabilityProvider<>(cap, supplier, isRemoved); event.addCapability(id, provider); event.addListener(provider::invalidate); return provider; @@ -49,7 +58,7 @@ public void invalidate() { @Override @SuppressWarnings({ "unchecked", "rawtypes" }) public LazyOptional getCapability(Capability cap, @Nullable Direction side) { - if (cap != this.cap) return LazyOptional.empty(); + if (cap != this.cap || isRemoved.getAsBoolean()) return LazyOptional.empty(); var instances = this.instances; if (instances == null) instances = this.instances = new LazyOptional[6];