From 03388149b11c01fb49b84da183effde9d50ca3f8 Mon Sep 17 00:00:00 2001 From: Jonathan Coates Date: Sun, 26 Jan 2025 11:13:52 +0000 Subject: [PATCH] Fix command computers being exposed as peripherals - Check whether the computer is a command computer before registering the capability. - Add tests to check what is/isn't a peripheral. See also #2020, where we forgot to register a peripheral on NeoForge 1.21.1. Fixes #2070. --- .../blocks/AbstractComputerBlockEntity.java | 7 +- .../computercraft/gametest/Component_Test.kt | 56 +++++++ .../computercraft/gametest/Computer_Test.kt | 11 -- .../gametest/api/TestExtensions.kt | 16 +- .../computercraft/gametest/core/TestHooks.kt | 1 + .../computer_test.computer_peripheral.snbt | 138 ------------------ .../computercraft/lua/rom/apis/paintutils.lua | 22 ++- .../shared/ForgeCommonHooks.java | 2 +- 8 files changed, 93 insertions(+), 160 deletions(-) create mode 100644 projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Component_Test.kt delete mode 100644 projects/common/src/testMod/resources/data/cctest/structures/computer_test.computer_peripheral.snbt 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 472180b33..73dabe123 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 @@ -29,6 +29,7 @@ import net.minecraft.world.LockCode; import net.minecraft.world.MenuProvider; import net.minecraft.world.Nameable; import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.block.GameMasterBlock; import net.minecraft.world.level.block.entity.BaseContainerBlockEntity; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntityType; @@ -310,6 +311,10 @@ public abstract class AbstractComputerBlockEntity extends BlockEntity implements return label; } + public final boolean isAdminOnly() { + return getBlockState().getBlock() instanceof GameMasterBlock; + } + public final void setComputerID(int id) { if (getLevel().isClientSide || computerID == id) return; @@ -420,6 +425,6 @@ public abstract class AbstractComputerBlockEntity extends BlockEntity implements @Override public boolean onlyOpCanSetNbt() { - return getFamily() == ComputerFamily.COMMAND; + return isAdminOnly(); } } diff --git a/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Component_Test.kt b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Component_Test.kt new file mode 100644 index 000000000..5ec3017d7 --- /dev/null +++ b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Component_Test.kt @@ -0,0 +1,56 @@ +// SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers +// +// SPDX-License-Identifier: MPL-2.0 + +package dan200.computercraft.gametest + +import dan200.computercraft.gametest.api.assertNoPeripheral +import dan200.computercraft.gametest.api.assertPeripheral +import dan200.computercraft.gametest.api.immediate +import dan200.computercraft.shared.ModRegistry +import dan200.computercraft.shared.platform.ComponentAccess +import net.minecraft.core.BlockPos +import net.minecraft.core.Direction +import net.minecraft.gametest.framework.GameTest +import net.minecraft.gametest.framework.GameTestHelper +import java.util.* + +/** + * Checks that we expose [ComponentAccess] for various blocks/block entities + */ +class Component_Test { + @GameTest(template = "default") + fun Peripheral(context: GameTestHelper) = context.immediate { + val pos = BlockPos(2, 2, 2) + // We fetch peripherals from the NORTH, as that is the default direction for modems. This is a bit of a hack, + // but avoids having to override the block state. + val side = Direction.NORTH + + for ((block, type) in mapOf( + // Computers + ModRegistry.Blocks.COMPUTER_NORMAL to Optional.of("computer"), + ModRegistry.Blocks.COMPUTER_ADVANCED to Optional.of("computer"), + ModRegistry.Blocks.COMPUTER_COMMAND to Optional.empty(), + // Turtles + ModRegistry.Blocks.TURTLE_NORMAL to Optional.of("turtle"), + ModRegistry.Blocks.TURTLE_ADVANCED to Optional.of("turtle"), + // Peripherals + ModRegistry.Blocks.SPEAKER to Optional.of("speaker"), + ModRegistry.Blocks.DISK_DRIVE to Optional.of("drive"), + ModRegistry.Blocks.PRINTER to Optional.of("printer"), + ModRegistry.Blocks.MONITOR_NORMAL to Optional.of("monitor"), + ModRegistry.Blocks.MONITOR_ADVANCED to Optional.of("monitor"), + ModRegistry.Blocks.WIRELESS_MODEM_NORMAL to Optional.of("modem"), + ModRegistry.Blocks.WIRELESS_MODEM_ADVANCED to Optional.of("modem"), + ModRegistry.Blocks.WIRED_MODEM_FULL to Optional.of("modem"), + ModRegistry.Blocks.REDSTONE_RELAY to Optional.of("redstone_relay"), + )) { + context.setBlock(pos, block.get()) + if (type.isPresent) { + context.assertPeripheral(pos, side, type.get()) + } else { + context.assertNoPeripheral(pos, side) + } + } + } +} diff --git a/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Computer_Test.kt b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Computer_Test.kt index 7a21696de..49dd54604 100644 --- a/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Computer_Test.kt +++ b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/Computer_Test.kt @@ -95,17 +95,6 @@ class Computer_Test { } } - /** - * Check computers and turtles expose peripherals. - */ - @GameTest - fun Computer_peripheral(context: GameTestHelper) = context.sequence { - thenExecute { - context.assertPeripheral(BlockPos(3, 2, 2), type = "computer") - context.assertPeripheral(BlockPos(1, 2, 2), type = "turtle") - } - } - /** * Check chest peripherals are reattached with a new size. */ 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 4191ecb03..de0c2e0f5 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 @@ -127,6 +127,14 @@ fun GameTestHelper.sequence(run: GameTestSequence.() -> Unit) { sequence.thenSucceed() } +/** + * Run a function immediately, and then succeed. + */ +fun GameTestHelper.immediate(run: () -> Unit) { + run() + succeed() +} + /** * A custom instance of [GameTestAssertPosException] which allows for longer error messages. */ @@ -232,15 +240,17 @@ private fun GameTestHelper.getPeripheralAt(pos: BlockPos, direction: Direction): fun GameTestHelper.assertPeripheral(pos: BlockPos, direction: Direction = Direction.UP, type: String) { val peripheral = getPeripheralAt(pos, direction) + val block = getBlockState(pos).block.name.string when { - peripheral == null -> fail("No peripheral at position", pos) - peripheral.type != type -> fail("Peripheral is of type ${peripheral.type}, expected $type", pos) + peripheral == null -> fail("No peripheral for '$block'", pos) + peripheral.type != type -> fail("Peripheral for '$block' is of type ${peripheral.type}, expected $type", pos) } } fun GameTestHelper.assertNoPeripheral(pos: BlockPos, direction: Direction = Direction.UP) { val peripheral = getPeripheralAt(pos, direction) - if (peripheral != null) fail("Expected no peripheral, got a ${peripheral.type}", pos) + val block = getBlockState(pos).block.name + if (peripheral != null) fail("Expected no peripheral for '$block', got a ${peripheral.type}", pos) } fun GameTestHelper.assertExactlyItems(vararg expected: ItemStack, message: String? = null) { diff --git a/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/core/TestHooks.kt b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/core/TestHooks.kt index da5bf7f18..b1510719e 100644 --- a/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/core/TestHooks.kt +++ b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/core/TestHooks.kt @@ -88,6 +88,7 @@ object TestHooks { fun areComputersIdle(server: MinecraftServer) = ComputerThreadReflection.isFullyIdle(ServerContext.get(server)) private val testClasses = listOf( + Component_Test::class.java, Computer_Test::class.java, CraftOs_Test::class.java, Disk_Drive_Test::class.java, diff --git a/projects/common/src/testMod/resources/data/cctest/structures/computer_test.computer_peripheral.snbt b/projects/common/src/testMod/resources/data/cctest/structures/computer_test.computer_peripheral.snbt deleted file mode 100644 index 9a22ee331..000000000 --- a/projects/common/src/testMod/resources/data/cctest/structures/computer_test.computer_peripheral.snbt +++ /dev/null @@ -1,138 +0,0 @@ -{ - DataVersion: 2975, - 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:turtle_advanced{facing:south,waterlogged:false}", nbt: {Fuel: 0, Items: [], On: 0b, Owner: {LowerId: -6876936588741668278L, Name: "Dev", UpperId: 4039158846114182220L}, Slot: 0, id: "computercraft:turtle_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: "minecraft:air"}, - {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:computer_advanced{facing:north,state:off}", nbt: {On: 0b, id: "computercraft:computer_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:turtle_advanced{facing:south,waterlogged:false}", - "computercraft:computer_advanced{facing:north,state:off}" - ] -} diff --git a/projects/core/src/main/resources/data/computercraft/lua/rom/apis/paintutils.lua b/projects/core/src/main/resources/data/computercraft/lua/rom/apis/paintutils.lua index 567043223..013a51fed 100644 --- a/projects/core/src/main/resources/data/computercraft/lua/rom/apis/paintutils.lua +++ b/projects/core/src/main/resources/data/computercraft/lua/rom/apis/paintutils.lua @@ -47,12 +47,22 @@ local function sortCoords(startX, startY, endX, endY) return minX, maxX, minY, maxY end ---- Parses an image from a multi-line string --- --- @tparam string image The string containing the raw-image data. --- @treturn table The parsed image data, suitable for use with --- [`paintutils.drawImage`]. --- @since 1.80pr1 +--[=[- Parses an image from a multi-line string + +@tparam string image The string containing the raw-image data. +@treturn table The parsed image data, suitable for use with [`paintutils.drawImage`]. +@usage Parse an image from a string, and draw it. + + local image = paintutils.parseImage([[ + e e + + e e + eeee + ]]) + paintutils.drawImage(image, term.getCursorPos()) + +@since 1.80pr1 +]=] function parseImage(image) expect(1, image, "string") local tImage = {} diff --git a/projects/forge/src/main/java/dan200/computercraft/shared/ForgeCommonHooks.java b/projects/forge/src/main/java/dan200/computercraft/shared/ForgeCommonHooks.java index 5c74b32b2..9f6f5f264 100644 --- a/projects/forge/src/main/java/dan200/computercraft/shared/ForgeCommonHooks.java +++ b/projects/forge/src/main/java/dan200/computercraft/shared/ForgeCommonHooks.java @@ -124,7 +124,7 @@ public class ForgeCommonHooks { @SubscribeEvent public static void onCapability(AttachCapabilitiesEvent event) { var blockEntity = event.getObject(); - if (blockEntity instanceof ComputerBlockEntity computer) { + if (blockEntity instanceof ComputerBlockEntity computer && !computer.isAdminOnly()) { CapabilityProvider.attach(event, PERIPHERAL, CAPABILITY_PERIPHERAL, computer::peripheral); } else if (blockEntity instanceof TurtleBlockEntity turtle) { CapabilityProvider.attach(event, INVENTORY, ITEM_HANDLER, () -> new InvWrapper(turtle));