From d77f5f135f9251d027cc900dc27fd80160b632b9 Mon Sep 17 00:00:00 2001 From: csqrb <56765288+CaptainSqrBeard@users.noreply.github.com> Date: Wed, 3 Jul 2024 13:21:02 +0600 Subject: [PATCH 01/19] Preserve item data when upgrading pocket computers (#1888) --- .../pocket/recipes/PocketComputerUpgradeRecipe.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/projects/common/src/main/java/dan200/computercraft/shared/pocket/recipes/PocketComputerUpgradeRecipe.java b/projects/common/src/main/java/dan200/computercraft/shared/pocket/recipes/PocketComputerUpgradeRecipe.java index bf55800a2..d6e7f8d9e 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/pocket/recipes/PocketComputerUpgradeRecipe.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/pocket/recipes/PocketComputerUpgradeRecipe.java @@ -59,7 +59,6 @@ public final class PocketComputerUpgradeRecipe extends CustomRecipe { if (computer.isEmpty()) return ItemStack.EMPTY; - var itemComputer = (PocketComputerItem) computer.getItem(); if (PocketComputerItem.getUpgrade(computer) != null) return ItemStack.EMPTY; // Check for upgrades around the item @@ -81,10 +80,9 @@ public final class PocketComputerUpgradeRecipe extends CustomRecipe { if (upgrade == null) return ItemStack.EMPTY; // Construct the new stack - var computerID = itemComputer.getComputerID(computer); - var label = itemComputer.getLabel(computer); - var colour = itemComputer.getColour(computer); - return itemComputer.create(computerID, label, colour, upgrade); + var newStack = computer.copyWithCount(1); + PocketComputerItem.setUpgrade(newStack, upgrade); + return newStack; } @Override From f5ed43584dca84f3cb6ce872815e678acd52cf63 Mon Sep 17 00:00:00 2001 From: Jonathan Coates Date: Wed, 24 Jul 2024 19:06:55 +0100 Subject: [PATCH 02/19] Add tests for turtle equipping and crafting --- .../computercraft/gametest/Turtle_Test.kt | 41 +++++- .../cctest/structures/turtle_test.craft.snbt | 137 ++++++++++++++++++ .../structures/turtle_test.equip_tool.snbt | 137 ++++++++++++++++++ 3 files changed, 313 insertions(+), 2 deletions(-) create mode 100644 projects/common/src/testMod/resources/data/cctest/structures/turtle_test.craft.snbt create mode 100644 projects/common/src/testMod/resources/data/cctest/structures/turtle_test.equip_tool.snbt 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 40ed67cf4..c02d84013 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 @@ -27,6 +27,7 @@ import dan200.computercraft.shared.util.WaterloggableHelpers import dan200.computercraft.test.core.assertArrayEquals import dan200.computercraft.test.core.computer.LuaTaskContext import dan200.computercraft.test.core.computer.getApi +import dan200.computercraft.test.shared.ItemStackMatcher.isStack import net.minecraft.core.BlockPos import net.minecraft.gametest.framework.GameTest import net.minecraft.gametest.framework.GameTestHelper @@ -40,8 +41,7 @@ import net.minecraft.world.level.block.FenceBlock import net.minecraft.world.level.block.entity.BlockEntityType import net.minecraft.world.level.block.state.properties.BlockStateProperties import org.hamcrest.MatcherAssert.assertThat -import org.hamcrest.Matchers.array -import org.hamcrest.Matchers.instanceOf +import org.hamcrest.Matchers.* import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertNotEquals import java.util.* @@ -656,6 +656,43 @@ class Turtle_Test { } } + /** + * `turtle.craft` works as expected + */ + @GameTest + fun Craft(helper: GameTestHelper) = helper.sequence { + thenOnComputer { + callPeripheral("left", "craft", 1).assertArrayEquals(true) + } + thenExecute { + val turtle = helper.getBlockEntity(BlockPos(2, 2, 2), ModRegistry.BlockEntities.TURTLE_NORMAL.get()) + assertThat( + "Inventory is as expected.", + turtle.contents, + contains( + isStack(Items.DIAMOND, 1), isStack(Items.DIAMOND, 1), isStack(Items.DIAMOND, 1), isStack(Items.DIAMOND_PICKAXE, 1), + isStack(ItemStack.EMPTY), isStack(Items.STICK, 1), isStack(ItemStack.EMPTY), isStack(ItemStack.EMPTY), + isStack(ItemStack.EMPTY), isStack(Items.STICK, 1), isStack(ItemStack.EMPTY), isStack(ItemStack.EMPTY), + isStack(ItemStack.EMPTY), isStack(ItemStack.EMPTY), isStack(ItemStack.EMPTY), isStack(ItemStack.EMPTY), + ), + ) + } + } + + /** + * `turtle.equipLeft` equips a tool. + */ + @GameTest + fun Equip_tool(helper: GameTestHelper) = helper.sequence { + thenOnComputer { + turtle.equipLeft().await().assertArrayEquals(true) + } + thenExecute { + val turtle = helper.getBlockEntity(BlockPos(2, 2, 2), ModRegistry.BlockEntities.TURTLE_NORMAL.get()) + assertEquals(TurtleUpgrades.instance().get("minecraft:diamond_pickaxe"), turtle.getUpgrade(TurtleSide.LEFT)) + } + } + /** * Render turtles as an item. */ diff --git a/projects/common/src/testMod/resources/data/cctest/structures/turtle_test.craft.snbt b/projects/common/src/testMod/resources/data/cctest/structures/turtle_test.craft.snbt new file mode 100644 index 000000000..1dac5c659 --- /dev/null +++ b/projects/common/src/testMod/resources/data/cctest/structures/turtle_test.craft.snbt @@ -0,0 +1,137 @@ +{ + DataVersion: 3465, + size: [5, 5, 5], + data: [ + {pos: [0, 0, 0], state: "minecraft:polished_andesite"}, + {pos: [0, 0, 1], state: "minecraft:polished_andesite"}, + {pos: [0, 0, 2], state: "minecraft:polished_andesite"}, + {pos: [0, 0, 3], state: "minecraft:polished_andesite"}, + {pos: [0, 0, 4], state: "minecraft:polished_andesite"}, + {pos: [1, 0, 0], state: "minecraft:polished_andesite"}, + {pos: [1, 0, 1], state: "minecraft:polished_andesite"}, + {pos: [1, 0, 2], state: "minecraft:polished_andesite"}, + {pos: [1, 0, 3], state: "minecraft:polished_andesite"}, + {pos: [1, 0, 4], state: "minecraft:polished_andesite"}, + {pos: [2, 0, 0], state: "minecraft:polished_andesite"}, + {pos: [2, 0, 1], state: "minecraft:polished_andesite"}, + {pos: [2, 0, 2], state: "minecraft:polished_andesite"}, + {pos: [2, 0, 3], state: "minecraft:polished_andesite"}, + {pos: [2, 0, 4], state: "minecraft:polished_andesite"}, + {pos: [3, 0, 0], state: "minecraft:polished_andesite"}, + {pos: [3, 0, 1], state: "minecraft:polished_andesite"}, + {pos: [3, 0, 2], state: "minecraft:polished_andesite"}, + {pos: [3, 0, 3], state: "minecraft:polished_andesite"}, + {pos: [3, 0, 4], state: "minecraft:polished_andesite"}, + {pos: [4, 0, 0], state: "minecraft:polished_andesite"}, + {pos: [4, 0, 1], state: "minecraft:polished_andesite"}, + {pos: [4, 0, 2], state: "minecraft:polished_andesite"}, + {pos: [4, 0, 3], state: "minecraft:polished_andesite"}, + {pos: [4, 0, 4], state: "minecraft:polished_andesite"}, + {pos: [0, 1, 0], state: "minecraft:air"}, + {pos: [0, 1, 1], state: "minecraft:air"}, + {pos: [0, 1, 2], state: "minecraft:air"}, + {pos: [0, 1, 3], state: "minecraft:air"}, + {pos: [0, 1, 4], state: "minecraft:air"}, + {pos: [1, 1, 0], state: "minecraft:air"}, + {pos: [1, 1, 1], state: "minecraft:air"}, + {pos: [1, 1, 2], state: "minecraft:air"}, + {pos: [1, 1, 3], state: "minecraft:air"}, + {pos: [1, 1, 4], state: "minecraft:air"}, + {pos: [2, 1, 0], state: "minecraft:air"}, + {pos: [2, 1, 1], state: "minecraft:air"}, + {pos: [2, 1, 2], state: "computercraft:turtle_normal{facing:north,waterlogged:false}", nbt: {ComputerId: 1, Fuel: 0, Items: [{Count: 2b, Slot: 0b, id: "minecraft:diamond"}, {Count: 2b, Slot: 1b, id: "minecraft:diamond"}, {Count: 2b, Slot: 2b, id: "minecraft:diamond"}, {Count: 2b, Slot: 5b, id: "minecraft:stick"}, {Count: 2b, Slot: 9b, id: "minecraft:stick"}], Label: "turtle_test.craft", LeftUpgrade: "minecraft:crafting_table", LeftUpgradeNbt: {}, On: 1b, Slot: 0, id: "computercraft:turtle_normal"}}, + {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:turtle_normal{facing:north,waterlogged:false}" + ] +} diff --git a/projects/common/src/testMod/resources/data/cctest/structures/turtle_test.equip_tool.snbt b/projects/common/src/testMod/resources/data/cctest/structures/turtle_test.equip_tool.snbt new file mode 100644 index 000000000..09c3db282 --- /dev/null +++ b/projects/common/src/testMod/resources/data/cctest/structures/turtle_test.equip_tool.snbt @@ -0,0 +1,137 @@ +{ + DataVersion: 3465, + size: [5, 5, 5], + data: [ + {pos: [0, 0, 0], state: "minecraft:polished_andesite"}, + {pos: [0, 0, 1], state: "minecraft:polished_andesite"}, + {pos: [0, 0, 2], state: "minecraft:polished_andesite"}, + {pos: [0, 0, 3], state: "minecraft:polished_andesite"}, + {pos: [0, 0, 4], state: "minecraft:polished_andesite"}, + {pos: [1, 0, 0], state: "minecraft:polished_andesite"}, + {pos: [1, 0, 1], state: "minecraft:polished_andesite"}, + {pos: [1, 0, 2], state: "minecraft:polished_andesite"}, + {pos: [1, 0, 3], state: "minecraft:polished_andesite"}, + {pos: [1, 0, 4], state: "minecraft:polished_andesite"}, + {pos: [2, 0, 0], state: "minecraft:polished_andesite"}, + {pos: [2, 0, 1], state: "minecraft:polished_andesite"}, + {pos: [2, 0, 2], state: "minecraft:polished_andesite"}, + {pos: [2, 0, 3], state: "minecraft:polished_andesite"}, + {pos: [2, 0, 4], state: "minecraft:polished_andesite"}, + {pos: [3, 0, 0], state: "minecraft:polished_andesite"}, + {pos: [3, 0, 1], state: "minecraft:polished_andesite"}, + {pos: [3, 0, 2], state: "minecraft:polished_andesite"}, + {pos: [3, 0, 3], state: "minecraft:polished_andesite"}, + {pos: [3, 0, 4], state: "minecraft:polished_andesite"}, + {pos: [4, 0, 0], state: "minecraft:polished_andesite"}, + {pos: [4, 0, 1], state: "minecraft:polished_andesite"}, + {pos: [4, 0, 2], state: "minecraft:polished_andesite"}, + {pos: [4, 0, 3], state: "minecraft:polished_andesite"}, + {pos: [4, 0, 4], state: "minecraft:polished_andesite"}, + {pos: [0, 1, 0], state: "minecraft:air"}, + {pos: [0, 1, 1], state: "minecraft:air"}, + {pos: [0, 1, 2], state: "minecraft:air"}, + {pos: [0, 1, 3], state: "minecraft:air"}, + {pos: [0, 1, 4], state: "minecraft:air"}, + {pos: [1, 1, 0], state: "minecraft:air"}, + {pos: [1, 1, 1], state: "minecraft:air"}, + {pos: [1, 1, 2], state: "minecraft:air"}, + {pos: [1, 1, 3], state: "minecraft:air"}, + {pos: [1, 1, 4], state: "minecraft:air"}, + {pos: [2, 1, 0], state: "minecraft:air"}, + {pos: [2, 1, 1], state: "minecraft:air"}, + {pos: [2, 1, 2], state: "computercraft:turtle_normal{facing:north,waterlogged:false}", nbt: {ComputerId: 1, Fuel: 0, Items: [{Count: 1b, Slot: 0b, id: "minecraft:diamond_pickaxe"}], Label: "turtle_test.equip_tool", On: 1b, Slot: 0, id: "computercraft:turtle_normal"}}, + {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:turtle_normal{facing:north,waterlogged:false}" + ] +} From 5926b6c994371a2b962cdf37fb848db3b9db0cad Mon Sep 17 00:00:00 2001 From: Jonathan Coates Date: Wed, 24 Jul 2024 19:27:50 +0100 Subject: [PATCH 03/19] Update Gradle to 8.9 Fix several deprecation warnings, and specify the toolchain required to launch the daemon. --- .reuse/dep5 | 1 + .../kotlin/cc/tweaked/gradle/CCTweakedExtension.kt | 3 +-- .../src/main/kotlin/cc/tweaked/gradle/Extensions.kt | 12 ++++++++++++ gradle/gradle-daemon-jvm.properties | 2 ++ gradle/wrapper/gradle-wrapper.properties | 2 +- 5 files changed, 17 insertions(+), 3 deletions(-) create mode 100644 gradle/gradle-daemon-jvm.properties diff --git a/.reuse/dep5 b/.reuse/dep5 index d21e4874b..4468beedf 100644 --- a/.reuse/dep5 +++ b/.reuse/dep5 @@ -4,6 +4,7 @@ Upstream-Name: CC: Tweaked Upstream-Contact: Jonathan Coates Files: + gradle/gradle-daemon-jvm.properties projects/common/src/main/resources/assets/computercraft/sounds.json projects/common/src/main/resources/assets/computercraft/sounds/empty.ogg projects/common/src/testMod/resources/data/cctest/computercraft/turtle_upgrades/* diff --git a/buildSrc/src/main/kotlin/cc/tweaked/gradle/CCTweakedExtension.kt b/buildSrc/src/main/kotlin/cc/tweaked/gradle/CCTweakedExtension.kt index 3066459e0..09be926cb 100644 --- a/buildSrc/src/main/kotlin/cc/tweaked/gradle/CCTweakedExtension.kt +++ b/buildSrc/src/main/kotlin/cc/tweaked/gradle/CCTweakedExtension.kt @@ -22,7 +22,6 @@ import org.gradle.api.tasks.SourceSet import org.gradle.api.tasks.bundling.Jar import org.gradle.api.tasks.compile.JavaCompile import org.gradle.api.tasks.javadoc.Javadoc -import org.gradle.configurationcache.extensions.capitalized import org.gradle.language.base.plugins.LifecycleBasePlugin import org.gradle.language.jvm.tasks.ProcessResources import org.gradle.process.JavaForkOptions @@ -181,7 +180,7 @@ abstract class CCTweakedExtension( fun jacoco(task: NamedDomainObjectProvider) where T : Task, T : JavaForkOptions { val classDump = project.layout.buildDirectory.dir("jacocoClassDump/${task.name}") - val reportTaskName = "jacoco${task.name.capitalized()}Report" + val reportTaskName = "jacoco${task.name.capitalise()}Report" val jacoco = project.extensions.getByType(JacocoPluginExtension::class.java) task.configure { diff --git a/buildSrc/src/main/kotlin/cc/tweaked/gradle/Extensions.kt b/buildSrc/src/main/kotlin/cc/tweaked/gradle/Extensions.kt index 93870248b..dada28b64 100644 --- a/buildSrc/src/main/kotlin/cc/tweaked/gradle/Extensions.kt +++ b/buildSrc/src/main/kotlin/cc/tweaked/gradle/Extensions.kt @@ -155,3 +155,15 @@ fun getNextVersion(version: String): String { if (dashIndex >= 0) out.append(version, dashIndex, version.length) return out.toString() } + +/** + * Capitalise the first letter of the string. + * + * This is a replacement for the now deprecated [String.capitalize]. + */ +fun String.capitalise(): String { + if (isEmpty()) return this + val first = this[0] + val firstTitle = first.titlecaseChar() + return if (first == firstTitle) this else firstTitle + substring(1) +} diff --git a/gradle/gradle-daemon-jvm.properties b/gradle/gradle-daemon-jvm.properties new file mode 100644 index 000000000..858feb7e3 --- /dev/null +++ b/gradle/gradle-daemon-jvm.properties @@ -0,0 +1,2 @@ +#This file is generated by updateDaemonJvm +toolchainVersion=17 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a4413138c..09523c0e5 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME From 4bfb9ac323c90eda8df6805241c9b71790b822fe Mon Sep 17 00:00:00 2001 From: Jonathan Coates Date: Wed, 24 Jul 2024 19:28:20 +0100 Subject: [PATCH 04/19] Fix/update language checker script - Update location of the generated language file to point to common rather than Fabric. - Remove usage of OrderedDict, as dicts are ordered on recent versions of Python. --- tools/language.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/tools/language.py b/tools/language.py index 277aa25c4..562a0d9fa 100755 --- a/tools/language.py +++ b/tools/language.py @@ -17,12 +17,11 @@ ensure language files are mostly correct. import json import pathlib -from collections import OrderedDict root = pathlib.Path("projects/common/src/main/resources/assets/computercraft/lang") -with open("projects/fabric/src/generated/resources/assets/computercraft/lang/en_us.json", encoding="utf-8") as file: - en_us = json.load(file, object_hook=OrderedDict) +with open("projects/common/src/generated/resources/assets/computercraft/lang/en_us.json", encoding="utf-8") as file: + en_us = json.load(file) for path in root.glob("*.json"): if path.name == "en_us.json": @@ -31,7 +30,7 @@ for path in root.glob("*.json"): with path.open(encoding="utf-8") as file: lang = json.load(file) - out = OrderedDict() + out = {} missing = 0 for k in en_us.keys(): if k not in lang: @@ -46,4 +45,6 @@ for path in root.glob("*.json"): file.write("\n") if missing > 0: - print("{} has {} missing translations.".format(path.name, missing)) + print("{} has {} missing translations. {:.2f}% complete".format(path.name, missing, len(out) / len(en_us) * 100)) + else: + print("{} is complete".format(path.name)) From 63185629b7aa25ec32a000f669685ae234b5fd85 Mon Sep 17 00:00:00 2001 From: Jonathan Coates Date: Wed, 24 Jul 2024 19:30:12 +0100 Subject: [PATCH 05/19] Use "require" in textutils This avoids us having to support requireless environments inside cc.strings. I do kinda wonder if os.loadAPI-loaded files should also have their own shared "require", just so we're not loading 10 copies of cc.expect. --- .../data/computercraft/lua/rom/apis/textutils.lua | 8 ++++++-- .../data/computercraft/lua/rom/modules/main/cc/expect.lua | 4 ++-- .../computercraft/lua/rom/modules/main/cc/strings.lua | 2 +- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/projects/core/src/main/resources/data/computercraft/lua/rom/apis/textutils.lua b/projects/core/src/main/resources/data/computercraft/lua/rom/apis/textutils.lua index c32b619ea..8c52a8a2c 100644 --- a/projects/core/src/main/resources/data/computercraft/lua/rom/apis/textutils.lua +++ b/projects/core/src/main/resources/data/computercraft/lua/rom/apis/textutils.lua @@ -7,9 +7,13 @@ -- @module textutils -- @since 1.2 -local expect = dofile("rom/modules/main/cc/expect.lua") +local pgk_env = setmetatable({}, { __index = _ENV }) +pgk_env.require = dofile("rom/modules/main/cc/require.lua").make(pgk_env, "rom/modules/main") +local require = pgk_env.require + +local expect = require("cc.expect") local expect, field = expect.expect, expect.field -local wrap = dofile("rom/modules/main/cc/strings.lua").wrap +local wrap = require("cc.strings").wrap --- Slowly writes string text at current cursor position, -- character-by-character. diff --git a/projects/core/src/main/resources/data/computercraft/lua/rom/modules/main/cc/expect.lua b/projects/core/src/main/resources/data/computercraft/lua/rom/modules/main/cc/expect.lua index f4cade7f4..376dcf911 100644 --- a/projects/core/src/main/resources/data/computercraft/lua/rom/modules/main/cc/expect.lua +++ b/projects/core/src/main/resources/data/computercraft/lua/rom/modules/main/cc/expect.lua @@ -118,8 +118,8 @@ end --- Expect a number to be within a specific range. -- -- @tparam number num The value to check. --- @tparam number min The minimum value, if nil then `-math.huge` is used. --- @tparam number max The maximum value, if nil then `math.huge` is used. +-- @tparam[opt=-math.huge] number min The minimum value. +-- @tparam[opt=math.huge] number max The maximum value. -- @return The given `value`. -- @throws If the value is outside of the allowed range. -- @since 1.96.0 diff --git a/projects/core/src/main/resources/data/computercraft/lua/rom/modules/main/cc/strings.lua b/projects/core/src/main/resources/data/computercraft/lua/rom/modules/main/cc/strings.lua index e28356d85..054673f36 100644 --- a/projects/core/src/main/resources/data/computercraft/lua/rom/modules/main/cc/strings.lua +++ b/projects/core/src/main/resources/data/computercraft/lua/rom/modules/main/cc/strings.lua @@ -8,7 +8,7 @@ -- @since 1.95.0 -- @see textutils For additional string related utilities. -local expect = (require and require("cc.expect") or dofile("rom/modules/main/cc/expect.lua")).expect +local expect = require("cc.expect").expect --[[- Wraps a block of text, so that each line fits within the given width. From f80373e7a22101c318baa2dd2027fa4dbf2feb45 Mon Sep 17 00:00:00 2001 From: Jonathan Coates Date: Wed, 24 Jul 2024 19:34:34 +0100 Subject: [PATCH 06/19] Add bounds check to cc.strings.wrap Fixes #1905, closes #1906. Co-authored-by: Lupus590 --- .../data/computercraft/lua/rom/modules/main/cc/strings.lua | 5 +++-- .../test/resources/test-rom/spec/modules/cc/strings_spec.lua | 4 +++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/projects/core/src/main/resources/data/computercraft/lua/rom/modules/main/cc/strings.lua b/projects/core/src/main/resources/data/computercraft/lua/rom/modules/main/cc/strings.lua index 054673f36..09fc057e7 100644 --- a/projects/core/src/main/resources/data/computercraft/lua/rom/modules/main/cc/strings.lua +++ b/projects/core/src/main/resources/data/computercraft/lua/rom/modules/main/cc/strings.lua @@ -8,7 +8,8 @@ -- @since 1.95.0 -- @see textutils For additional string related utilities. -local expect = require("cc.expect").expect +local expect = require("cc.expect") +local expect, range = expect.expect, expect.range --[[- Wraps a block of text, so that each line fits within the given width. @@ -32,7 +33,7 @@ local function wrap(text, width) expect(1, text, "string") expect(2, width, "number", "nil") width = width or term.getSize() - + range(width, 1) local lines, lines_n, current_line = {}, 0, "" local function push_line() diff --git a/projects/core/src/test/resources/test-rom/spec/modules/cc/strings_spec.lua b/projects/core/src/test/resources/test-rom/spec/modules/cc/strings_spec.lua index 585f8ef1a..32d26d514 100644 --- a/projects/core/src/test/resources/test-rom/spec/modules/cc/strings_spec.lua +++ b/projects/core/src/test/resources/test-rom/spec/modules/cc/strings_spec.lua @@ -2,7 +2,7 @@ -- -- SPDX-License-Identifier: MPL-2.0 -describe("cc.pretty", function() +describe("cc.strings", function() local str = require("cc.strings") describe("wrap", function() @@ -11,6 +11,8 @@ describe("cc.pretty", function() str.wrap("test string is long", 11) expect.error(str.wrap, nil):eq("bad argument #1 (string expected, got nil)") expect.error(str.wrap, "", false):eq("bad argument #2 (number expected, got boolean)") + + expect.error(str.wrap, "", 0):eq("number outside of range (expected 0 to be within 1 and inf)") end) it("wraps lines", function() From 1d45935a257cee7aee4347cc24153c86cc7cc052 Mon Sep 17 00:00:00 2001 From: Jonathan Coates Date: Wed, 24 Jul 2024 21:36:31 +0100 Subject: [PATCH 07/19] Update links to IRC The EsperNet webchat has been decommissioned, with no plans to replace. KiwiIRC is the recommended replacement, but I'm not comfortable using it as a drop-in replacement[^1], so I've rephrased this section to make it more clear. [^1]: There seems to be ongoing issues with TLS certificates (https://github.com/kiwiirc/kiwiirc/issues/1870), which meant it wasn't usable when I first tried. --- README.md | 9 +++++---- doc/guides/speaker_audio.md | 5 ++--- doc/index.md | 9 +++++++-- doc/mod-page.md | 9 +++++++-- 4 files changed, 21 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index df34d9280..f37016ec7 100644 --- a/README.md +++ b/README.md @@ -26,8 +26,9 @@ developing the mod, [check out the instructions here](CONTRIBUTING.md#developing ## Community If you need help getting started with CC: Tweaked, want to show off your latest project, or just want to chat about -ComputerCraft, do check out our [forum] and [GitHub discussions page][GitHub discussions]! There's also a fairly -populated, albeit quiet [IRC channel][irc], if that's more your cup of tea. +ComputerCraft, do check out our [GitHub discussions page][GitHub discussions]! There's also a fairly populated, +albeit quiet IRC channel on [EsperNet], if that's more your cup of tea. You can join `#computercraft` through your +desktop client, or online using [KiwiIRC]. We also host fairly comprehensive documentation at [tweaked.cc](https://tweaked.cc/ "The CC: Tweaked website"). @@ -86,6 +87,6 @@ the generated documentation [can be browsed online](https://tweaked.cc/javadoc/) [modrinth]: https://modrinth.com/mod/gu7yAYhd "Download CC: Tweaked from Modrinth" [Minecraft Forge]: https://files.minecraftforge.net/ "Download Minecraft Forge." [Fabric]: https://fabricmc.net/use/installer/ "Download Fabric." -[forum]: https://forums.computercraft.cc/ [GitHub Discussions]: https://github.com/cc-tweaked/CC-Tweaked/discussions -[IRC]: https://webchat.esper.net/?channels=computercraft "#computercraft on EsperNet" +[EsperNet]: https://www.esper.net/ +[KiwiIRC]: https://kiwiirc.com/nextclient/#irc://irc.esper.net:+6697/#computercraft "#computercraft on EsperNet" diff --git a/doc/guides/speaker_audio.md b/doc/guides/speaker_audio.md index d29cfc32f..f2bc2bf7f 100644 --- a/doc/guides/speaker_audio.md +++ b/doc/guides/speaker_audio.md @@ -191,7 +191,7 @@ end > [Confused?][!NOTE] > Don't worry if you don't understand this example. It's quite advanced, and does use some ideas that this guide doesn't -> cover. That said, don't be afraid to ask on [GitHub Discussions] or [IRC] either! +> cover. That said, don't be afraid to ask [the community for help][community]. It's worth noting that the examples of audio processing we've mentioned here are about manipulating the _amplitude_ of the wave. If you wanted to modify the _frequency_ (for instance, shifting the pitch), things get rather more complex. @@ -205,5 +205,4 @@ This is, I'm afraid, left as an exercise to the reader. [PCM]: https://en.wikipedia.org/wiki/Pulse-code_modulation "Pulse-code Modulation - Wikipedia" [Ring Buffer]: https://en.wikipedia.org/wiki/Circular_buffer "Circular buffer - Wikipedia" [Sine Wave]: https://en.wikipedia.org/wiki/Sine_wave "Sine wave - Wikipedia" -[GitHub Discussions]: https://github.com/cc-tweaked/CC-Tweaked/discussions -[IRC]: https://webchat.esper.net/?channels=computercraft "#computercraft on EsperNet" +[Community]: /#community diff --git a/doc/index.md b/doc/index.md index cfc570810..86f64f65b 100644 --- a/doc/index.md +++ b/doc/index.md @@ -50,7 +50,11 @@ little daunting getting started. Thankfully, there's several fantastic tutorials Once you're a little more familiar with the mod, the sidebar and links below provide more detailed documentation on the various APIs and peripherals provided by the mod. -If you get stuck, do [ask a question on GitHub][GitHub Discussions] or pop in to the ComputerCraft's [IRC channel][IRC]. +

Community

+If you need help getting started with CC: Tweaked, want to show off your latest project, or just want to chat about +ComputerCraft, do check out our [GitHub discussions page][GitHub discussions]! There's also a fairly populated, +albeit quiet IRC channel on [EsperNet], if that's more your cup of tea. You can join `#computercraft` through your +desktop client, or online using [KiwiIRC]. ## Get Involved CC: Tweaked lives on [GitHub]. If you've got any ideas, feedback or bugs please do [create an issue][bug]. @@ -65,4 +69,5 @@ CC: Tweaked lives on [GitHub]. If you've got any ideas, feedback or bugs please [Fabric]: https://fabricmc.net/use/installer/ "Download Fabric." [lua]: https://www.lua.org/ "Lua's main website" [GitHub Discussions]: https://github.com/cc-tweaked/CC-Tweaked/discussions -[IRC]: https://webchat.esper.net/?channels=computercraft "#computercraft on EsperNet" +[EsperNet]: https://www.esper.net/ +[KiwiIRC]: https://kiwiirc.com/nextclient/#irc://irc.esper.net:+6697/#computercraft "#computercraft on EsperNet" diff --git a/doc/mod-page.md b/doc/mod-page.md index 4002ba246..8962859c9 100644 --- a/doc/mod-page.md +++ b/doc/mod-page.md @@ -50,7 +50,11 @@ little daunting getting started. Thankfully, there's several fantastic tutorials Once you're a little more familiar with the mod, the [wiki](https://tweaked.cc/) provides more detailed documentation on the various APIs and peripherals provided by the mod. -If you get stuck, do [ask a question on GitHub][GitHub Discussions] or pop in to the ComputerCraft's [IRC channel][IRC]. +## Community +If you need help getting started with CC: Tweaked, want to show off your latest project, or just want to chat about +ComputerCraft, do check out our [GitHub discussions page][GitHub discussions]! There's also a fairly populated, +albeit quiet IRC channel on [EsperNet], if that's more your cup of tea. You can join `#computercraft` through your +desktop client, or online using [KiwiIRC]. ## Get Involved CC: Tweaked lives on [GitHub]. If you've got any ideas, feedback or bugs please do [create an issue][bug]. @@ -60,4 +64,5 @@ CC: Tweaked lives on [GitHub]. If you've got any ideas, feedback or bugs please [computercraft]: https://github.com/dan200/ComputerCraft "ComputerCraft on GitHub" [lua]: https://www.lua.org/ "Lua's main website" [GitHub Discussions]: https://github.com/cc-tweaked/CC-Tweaked/discussions -[IRC]: http://webchat.esper.net/?channels=computercraft "#computercraft on EsperNet" +[EsperNet]: https://www.esper.net/ +[KiwiIRC]: https://kiwiirc.com/nextclient/#irc://irc.esper.net:+6697/#computercraft "#computercraft on EsperNet" From 63e40cf3cb33ab0ced3cfe3ba6de82dcbb6159ab Mon Sep 17 00:00:00 2001 From: Jonathan Coates Date: Wed, 24 Jul 2024 22:18:50 +0100 Subject: [PATCH 08/19] Add a cc.strings.split method This is largely copied from metis, with the documentation updated. --- .../lua/rom/modules/main/cc/strings.lua | 56 +++++++++++++++++++ .../test-rom/spec/modules/cc/strings_spec.lua | 29 ++++++++++ 2 files changed, 85 insertions(+) diff --git a/projects/core/src/main/resources/data/computercraft/lua/rom/modules/main/cc/strings.lua b/projects/core/src/main/resources/data/computercraft/lua/rom/modules/main/cc/strings.lua index 09fc057e7..44a9e5ce7 100644 --- a/projects/core/src/main/resources/data/computercraft/lua/rom/modules/main/cc/strings.lua +++ b/projects/core/src/main/resources/data/computercraft/lua/rom/modules/main/cc/strings.lua @@ -110,7 +110,63 @@ local function ensure_width(line, width) return line end +--[[- Split a string into parts, each separated by a deliminator. + +For instance, splitting the string `"a b c"` with the deliminator `" "`, would +return a table with three strings: `"a"`, `"b"`, and `"c"`. + +By default, the deliminator is given as a [Lua pattern][pattern]. Passing `true` +to the `plain` argument will cause the deliminator to be treated as a litteral +string. + +[pattern]: https://www.lua.org/manual/5.3/manual.html#6.4.1 + +@tparam string str The string to split. +@tparam string deliminator The pattern to split this string on. +@tparam[opt=false] boolean plain Treat the deliminator as a plain string, rather than a pattern. +@tparam[opt] number limit The maximum number of elements in the returned list. +@treturn { string... } The list of split strings. + +@usage Split a string into words. + + require "cc.strings".split("This is a sentence.", "%s+") + +@usage Split a string by "-" into at most 3 elements. + + require "cc.strings".split("a-separated-string-of-sorts", "-", true, 3) + +@see table.concat To join strings together. + +@since 1.112.0 +]] +local function split(str, deliminator, plain, limit) + expect(1, str, "string") + expect(2, deliminator, "string") + expect(3, plain, "boolean", "nil") + expect(4, limit, "number", "nil") + + local out, out_n, pos = {}, 0, 1 + while not limit or out_n < limit - 1 do + local start, finish = str:find(deliminator, pos, plain) + if not start then break end + + out_n = out_n + 1 + out[out_n] = str:sub(pos, start - 1) + + -- Require us to advance by at least one character. + if finish < start then error("separator is empty", 2) end + + pos = finish + 1 + end + + if pos == 1 then return { str } end + + out[out_n + 1] = str:sub(pos) + return out +end + return { wrap = wrap, ensure_width = ensure_width, + split = split, } diff --git a/projects/core/src/test/resources/test-rom/spec/modules/cc/strings_spec.lua b/projects/core/src/test/resources/test-rom/spec/modules/cc/strings_spec.lua index 32d26d514..3cde011e2 100644 --- a/projects/core/src/test/resources/test-rom/spec/modules/cc/strings_spec.lua +++ b/projects/core/src/test/resources/test-rom/spec/modules/cc/strings_spec.lua @@ -44,4 +44,33 @@ describe("cc.strings", function() expect(str.ensure_width("test string is long", 15)):eq("test string is ") end) end) + + describe("split", function() + it("splits with empty segments", function() + expect(str.split("", "%-")):same { "" } + expect(str.split("-", "%-")):same { "", "" } + expect(str.split("---", "%-")):same { "", "", "", "" } + expect(str.split("-a", "%-")):same { "", "a" } + expect(str.split("a-", "%-")):same { "a", "" } + end) + + it("cannot split with an empty separator", function() + expect.error(str.split, "abc", ""):eq("separator is empty") + end) + + it("splits on patterns", function() + expect(str.split("a.bcd ef", "%W+")):same { "a", "bcd", "ef" } + end) + + it("splits on literal strings", function() + expect(str.split("a-bcd-ef", "-", true)):same { "a", "bcd", "ef" } + end) + + it("accepts a limit", function() + expect(str.split("foo-bar-baz-qux-quyux", "-", true, 3)):same { "foo", "bar", "baz-qux-quyux" } + expect(str.split("foo-bar-baz", "-", true, 5)):same { "foo", "bar", "baz" } + expect(str.split("foo-bar-baz", "-", true, 1)):same { "foo-bar-baz" } + expect(str.split("foo-bar-baz", "-", true, 1)):same { "foo-bar-baz" } + end) + end) end) From 99c60ac54be7619f8b42482fa8dd959036e087f7 Mon Sep 17 00:00:00 2001 From: Jonathan Coates Date: Thu, 25 Jul 2024 09:28:00 +0100 Subject: [PATCH 09/19] Remove IComputerBlockEntity We only really made use of it in the has_computer_id loot condition, so probably easier to remove it. --- .../blocks/AbstractComputerBlock.java | 2 +- .../blocks/AbstractComputerBlockEntity.java | 7 +----- .../computer/blocks/IComputerBlockEntity.java | 22 ------------------- .../data/HasComputerIdLootCondition.java | 4 ++-- 4 files changed, 4 insertions(+), 31 deletions(-) delete mode 100644 projects/common/src/main/java/dan200/computercraft/shared/computer/blocks/IComputerBlockEntity.java 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 263456005..7647b36e6 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 @@ -146,7 +146,7 @@ public abstract class AbstractComputerBlock= 0; + return tile instanceof AbstractComputerBlockEntity computer && computer.getComputerID() >= 0; } @Override From 7285c32d58aadf5037597471c28cacf1733cdae0 Mon Sep 17 00:00:00 2001 From: Jonathan Coates Date: Thu, 25 Jul 2024 20:32:08 +0100 Subject: [PATCH 10/19] Document that the speaker re-encoded audio samples --- .../peripheral/speaker/SpeakerPeripheral.java | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/projects/common/src/main/java/dan200/computercraft/shared/peripheral/speaker/SpeakerPeripheral.java b/projects/common/src/main/java/dan200/computercraft/shared/peripheral/speaker/SpeakerPeripheral.java index 8a081deb6..96405f1d9 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/peripheral/speaker/SpeakerPeripheral.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/peripheral/speaker/SpeakerPeripheral.java @@ -273,16 +273,18 @@ public abstract class SpeakerPeripheral implements IPeripheral { * Attempt to stream some audio data to the speaker. *

* This accepts a list of audio samples as amplitudes between -128 and 127. These are stored in an internal buffer - * and played back at 48kHz. If this buffer is full, this function will return {@literal false}. You should wait for - * a [`speaker_audio_empty`] event before trying again. + * and played back at 48kHz. If this buffer is full, this function will return {@literal false}. Programs should + * wait for a [`speaker_audio_empty`] event before trying to play audio again. *

- * > [!NOTE] - * > The speaker only buffers a single call to {@link #playAudio} at once. This means if you try to play a small - * > number of samples, you'll have a lot of stutter. You should try to play as many samples in one call as possible - * > (up to 128×1024), as this reduces the chances of audio stuttering or halting, especially when the server or - * > computer is lagging. + * The speaker only buffers a single call to {@link #playAudio} at once. This means if you try to play a small + * number of samples, you'll have a lot of stutter. You should try to play as many samples in one call as possible + * (up to 128×1024), as this reduces the chances of audio stuttering or halting, especially when the server or + * computer is lagging. *

- * [`speaker_audio`] provides a more complete guide to using speakers + * While the speaker accepts 8-bit PCM audio, the audio stream is re-encoded before being played. This means that + * the supplied samples may not be played out exactly. + *

+ * [`speaker_audio`] provides a more complete guide to using speakers. * * @param context The Lua context. * @param audio The audio data to play. From 6c8e64ffcdad17b3ebbe9d19132763166f6c13f6 Mon Sep 17 00:00:00 2001 From: Jonathan Coates Date: Fri, 26 Jul 2024 09:51:09 +0100 Subject: [PATCH 11/19] Add get/setUpgrade to IPocketAccess We have similar methods in ITurtleAccess, so makes sense for them to be here too. --- .../api/pocket/IPocketAccess.java | 24 +++++++++++++++++++ .../shared/pocket/apis/PocketAPI.java | 20 ++++++++-------- .../pocket/core/PocketServerComputer.java | 2 ++ 3 files changed, 36 insertions(+), 10 deletions(-) diff --git a/projects/common-api/src/main/java/dan200/computercraft/api/pocket/IPocketAccess.java b/projects/common-api/src/main/java/dan200/computercraft/api/pocket/IPocketAccess.java index 99ac916b9..cf16a836e 100644 --- a/projects/common-api/src/main/java/dan200/computercraft/api/pocket/IPocketAccess.java +++ b/projects/common-api/src/main/java/dan200/computercraft/api/pocket/IPocketAccess.java @@ -6,10 +6,12 @@ package dan200.computercraft.api.pocket; import dan200.computercraft.api.peripheral.IPeripheral; import dan200.computercraft.api.upgrades.UpgradeBase; +import dan200.computercraft.api.upgrades.UpgradeData; import net.minecraft.nbt.CompoundTag; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.entity.Entity; import net.minecraft.world.item.ItemStack; +import org.jetbrains.annotations.ApiStatus; import javax.annotation.Nullable; import java.util.Map; @@ -17,6 +19,7 @@ import java.util.Map; /** * Wrapper class for pocket computers. */ +@ApiStatus.NonExtendable public interface IPocketAccess { /** * Gets the entity holding this item. @@ -64,6 +67,26 @@ public interface IPocketAccess { */ void setLight(int colour); + /** + * Get the currently equipped upgrade. + * + * @return The currently equipped upgrade. + * @see #getUpgradeNBTData() + * @see #setUpgrade(UpgradeData) + */ + @Nullable + UpgradeData getUpgrade(); + + /** + * Set the upgrade for this pocket computer, also updating the item stack. + *

+ * Note this method is not thread safe - it must be called from the server thread. + * + * @param upgrade The new upgrade to set it to, may be {@code null}. + * @see #getUpgrade() + */ + void setUpgrade(@Nullable UpgradeData upgrade); + /** * Get the upgrade-specific NBT. *

@@ -73,6 +96,7 @@ public interface IPocketAccess { * @see #updateUpgradeNBTData() * @see UpgradeBase#getUpgradeItem(CompoundTag) * @see UpgradeBase#getUpgradeData(ItemStack) + * @see #getUpgrade() */ CompoundTag getUpgradeNBTData(); diff --git a/projects/common/src/main/java/dan200/computercraft/shared/pocket/apis/PocketAPI.java b/projects/common/src/main/java/dan200/computercraft/shared/pocket/apis/PocketAPI.java index a0749450d..67b171c37 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/pocket/apis/PocketAPI.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/pocket/apis/PocketAPI.java @@ -6,10 +6,10 @@ package dan200.computercraft.shared.pocket.apis; import dan200.computercraft.api.lua.ILuaAPI; import dan200.computercraft.api.lua.LuaFunction; +import dan200.computercraft.api.pocket.IPocketAccess; import dan200.computercraft.api.pocket.IPocketUpgrade; import dan200.computercraft.api.upgrades.UpgradeData; import dan200.computercraft.impl.PocketUpgrades; -import dan200.computercraft.shared.pocket.core.PocketServerComputer; import net.minecraft.core.NonNullList; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; @@ -34,10 +34,10 @@ import java.util.Objects; * @cc.module pocket */ public class PocketAPI implements ILuaAPI { - private final PocketServerComputer computer; + private final IPocketAccess pocket; - public PocketAPI(PocketServerComputer computer) { - this.computer = computer; + public PocketAPI(IPocketAccess pocket) { + this.pocket = pocket; } @Override @@ -56,10 +56,10 @@ public class PocketAPI implements ILuaAPI { */ @LuaFunction(mainThread = true) public final Object[] equipBack() { - var entity = computer.getEntity(); + var entity = pocket.getEntity(); if (!(entity instanceof Player player)) return new Object[]{ false, "Cannot find player" }; var inventory = player.getInventory(); - var previousUpgrade = computer.getUpgrade(); + var previousUpgrade = pocket.getUpgrade(); // Attempt to find the upgrade, starting in the main segment, and then looking in the opposite // one. We start from the position the item is currently in and loop round to the start. @@ -73,7 +73,7 @@ public class PocketAPI implements ILuaAPI { if (previousUpgrade != null) storeItem(player, previousUpgrade.getUpgradeItem()); // Set the new upgrade - computer.setUpgrade(newUpgrade); + pocket.setUpgrade(newUpgrade); return new Object[]{ true }; } @@ -87,13 +87,13 @@ public class PocketAPI implements ILuaAPI { */ @LuaFunction(mainThread = true) public final Object[] unequipBack() { - var entity = computer.getEntity(); + var entity = pocket.getEntity(); if (!(entity instanceof Player player)) return new Object[]{ false, "Cannot find player" }; - var previousUpgrade = computer.getUpgrade(); + var previousUpgrade = pocket.getUpgrade(); if (previousUpgrade == null) return new Object[]{ false, "Nothing to unequip" }; - computer.setUpgrade(null); + pocket.setUpgrade(null); storeItem(player, previousUpgrade.getUpgradeItem()); diff --git a/projects/common/src/main/java/dan200/computercraft/shared/pocket/core/PocketServerComputer.java b/projects/common/src/main/java/dan200/computercraft/shared/pocket/core/PocketServerComputer.java index 90c6127e5..7231e1c15 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/pocket/core/PocketServerComputer.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/pocket/core/PocketServerComputer.java @@ -111,6 +111,7 @@ public class PocketServerComputer extends ServerComputer implements IPocketAcces return upgrade == null ? Map.of() : Collections.singletonMap(upgrade.getUpgradeID(), getPeripheral(ComputerSide.BACK)); } + @Override public @Nullable UpgradeData getUpgrade() { return upgrade == null ? null : UpgradeData.of(upgrade, getUpgradeNBTData()); } @@ -122,6 +123,7 @@ public class PocketServerComputer extends ServerComputer implements IPocketAcces * * @param upgrade The new upgrade to set it to, may be {@code null}. */ + @Override public void setUpgrade(@Nullable UpgradeData upgrade) { synchronized (this) { PocketComputerItem.setUpgrade(stack, upgrade); From 70a31855acf3dc2a805b9d38759bab2cf11145c3 Mon Sep 17 00:00:00 2001 From: Jonathan Coates Date: Fri, 26 Jul 2024 10:04:02 +0100 Subject: [PATCH 12/19] Only send pocket computer updates to players in range Previously we sent it to all players in the current level. This updates the check to only send it to players tracking the current chunk. --- .../pocket/core/PocketServerComputer.java | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/projects/common/src/main/java/dan200/computercraft/shared/pocket/core/PocketServerComputer.java b/projects/common/src/main/java/dan200/computercraft/shared/pocket/core/PocketServerComputer.java index 7231e1c15..4e3484de0 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/pocket/core/PocketServerComputer.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/pocket/core/PocketServerComputer.java @@ -28,9 +28,12 @@ import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.item.ItemEntity; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.ChunkPos; import javax.annotation.Nullable; -import java.util.*; +import java.util.Collections; +import java.util.Map; +import java.util.Set; public class PocketServerComputer extends ServerComputer implements IPocketAccess { private @Nullable IPocketUpgrade upgrade; @@ -43,7 +46,7 @@ public class PocketServerComputer extends ServerComputer implements IPocketAcces private int oldLightColour = -1; private @Nullable ComputerState oldComputerState; - private final Set tracking = new HashSet<>(); + private Set tracking = Set.of(); public PocketServerComputer(ServerLevel world, BlockPos position, int computerID, @Nullable String label, ComputerFamily family) { super(world, position, computerID, label, family, Config.pocketTermWidth, Config.pocketTermHeight); @@ -152,8 +155,9 @@ public class PocketServerComputer extends ServerComputer implements IPocketAcces protected void tickServer() { super.tickServer(); - // Find any players which have gone missing and remove them from the tracking list. - tracking.removeIf(player -> !player.isAlive() || player.level() != getLevel()); + // Get the new set of players tracking the current position. + var newTracking = getLevel().getChunkSource().chunkMap.getPlayers(new ChunkPos(getPosition()), false); + var trackingChanged = tracking.size() != newTracking.size() || !tracking.containsAll(newTracking); // And now find any new players, add them to the tracking list, and broadcast state where appropriate. var state = getState(); @@ -162,18 +166,16 @@ public class PocketServerComputer extends ServerComputer implements IPocketAcces oldLightColour = lightColour; // Broadcast the state to all players - tracking.addAll(getLevel().players()); - ServerNetworking.sendToPlayers(new PocketComputerDataMessage(this, false), tracking); - } else { + ServerNetworking.sendToPlayers(new PocketComputerDataMessage(this, false), newTracking); + } else if (trackingChanged) { // Broadcast the state to new players. - List added = new ArrayList<>(); - for (var player : getLevel().players()) { - if (tracking.add(player)) added.add(player); - } + var added = newTracking.stream().filter(x -> !tracking.contains(x)).toList(); if (!added.isEmpty()) { ServerNetworking.sendToPlayers(new PocketComputerDataMessage(this, false), added); } } + + if (trackingChanged) tracking = Set.copyOf(newTracking); } @Override From 38e516d7c791b62d3a9006d620151d55070c8ba4 Mon Sep 17 00:00:00 2001 From: Jonathan Coates Date: Fri, 26 Jul 2024 18:28:00 +0100 Subject: [PATCH 13/19] Update to Reuse 4.0 --- .pre-commit-config.yaml | 2 +- .reuse/dep5 | 100 ------------------------------------ REUSE.toml | 110 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 111 insertions(+), 101 deletions(-) delete mode 100644 .reuse/dep5 create mode 100644 REUSE.toml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1556ce3fa..bb23a776f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -27,7 +27,7 @@ repos: exclude: "^(.*\\.(bat)|LICENSE)$" - repo: https://github.com/fsfe/reuse-tool - rev: v2.1.0 + rev: v4.0.3 hooks: - id: reuse diff --git a/.reuse/dep5 b/.reuse/dep5 deleted file mode 100644 index 4468beedf..000000000 --- a/.reuse/dep5 +++ /dev/null @@ -1,100 +0,0 @@ -Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ -Source: https://github.com/cc-tweaked/cc-tweaked -Upstream-Name: CC: Tweaked -Upstream-Contact: Jonathan Coates - -Files: - gradle/gradle-daemon-jvm.properties - projects/common/src/main/resources/assets/computercraft/sounds.json - projects/common/src/main/resources/assets/computercraft/sounds/empty.ogg - projects/common/src/testMod/resources/data/cctest/computercraft/turtle_upgrades/* - projects/common/src/testMod/resources/data/cctest/structures/* - projects/*/src/generated/* - projects/web/src/htmlTransform/export/index.json - projects/web/src/htmlTransform/export/items/minecraft/* -Comment: Generated/data files are CC0. -Copyright: The CC: Tweaked Developers -License: CC0-1.0 - -Files: - doc/images/* - package.json - package-lock.json - projects/common/src/client/resources/computercraft-client.mixins.json - projects/common/src/main/resources/assets/minecraft/shaders/core/computercraft/monitor_tbo.json - projects/common/src/main/resources/computercraft.mixins.json - projects/common/src/testMod/resources/computercraft-gametest.mixins.json - projects/common/src/testMod/resources/data/computercraft/loot_tables/treasure_disk.json - projects/common/src/testMod/resources/pack.mcmeta - projects/core/src/main/resources/data/computercraft/lua/rom/modules/command/.ignoreme - projects/core/src/main/resources/data/computercraft/lua/rom/modules/main/.ignoreme - projects/core/src/main/resources/data/computercraft/lua/rom/modules/turtle/.ignoreme - projects/core/src/main/resources/data/computercraft/lua/rom/motd.txt - projects/fabric-api/src/main/modJson/fabric.mod.json - projects/fabric/src/client/resources/computercraft-client.fabric.mixins.json - projects/fabric/src/main/resources/computercraft.fabric.mixins.json - projects/fabric/src/main/resources/fabric.mod.json - projects/fabric/src/testMod/resources/computercraft-gametest.fabric.mixins.json - projects/fabric/src/testMod/resources/fabric.mod.json - projects/forge/src/client/resources/computercraft-client.forge.mixins.json - projects/web/src/frontend/mount/.settings - projects/web/src/frontend/mount/example.nfp - projects/web/src/frontend/mount/example.nft - projects/web/src/frontend/mount/expr_template.lua - projects/web/tsconfig.json -Comment: Several assets where it's inconvenient to create a .license file. -Copyright: The CC: Tweaked Developers -License: MPL-2.0 - -Files: - doc/logo.png - doc/logo-darkmode.png - projects/common/src/main/resources/assets/computercraft/models/* - projects/common/src/main/resources/assets/computercraft/textures/* - projects/common/src/main/resources/pack.mcmeta - projects/common/src/main/resources/pack.png - projects/core/src/main/resources/assets/computercraft/textures/gui/term_font.png - projects/core/src/main/resources/data/computercraft/lua/rom/autorun/.ignoreme - projects/core/src/main/resources/data/computercraft/lua/rom/help/* - projects/core/src/main/resources/data/computercraft/lua/rom/programs/fun/advanced/levels/* - projects/web/src/htmlTransform/export/items/computercraft/* -Comment: Bulk-license original assets as CCPL. -Copyright: 2011 Daniel Ratcliffe -License: LicenseRef-CCPL - -Files: - projects/common/src/main/resources/assets/computercraft/lang/cs_cz.json - projects/common/src/main/resources/assets/computercraft/lang/ko_kr.json - projects/common/src/main/resources/assets/computercraft/lang/pl_pl.json - projects/common/src/main/resources/assets/computercraft/lang/pt_br.json - projects/common/src/main/resources/assets/computercraft/lang/ru_ru.json - projects/common/src/main/resources/assets/computercraft/lang/uk_ua.json - projects/common/src/main/resources/assets/computercraft/lang/zh_cn.json -Comment: Community-contributed license files -Copyright: 2017 The CC: Tweaked Developers -License: LicenseRef-CCPL - -Files: - projects/common/src/main/resources/assets/computercraft/lang/* -Comment: Community-contributed license files -Copyright: 2017 The CC: Tweaked Developers -License: MPL-2.0 - -Files: - .github/* -Comment: - GitHub build scripts are CC0. While we could add a header to each file, - it's unclear if it will break actions or issue templates in some way. -Copyright: Jonathan Coates -License: CC0-1.0 - -Files: - gradle/wrapper/* - gradlew - gradlew.bat -Copyright: Gradle Inc -License: Apache-2.0 - -Files: projects/core/src/test/resources/test-rom/data/json-parsing/* -Copyright: 2016 Nicolas Seriot -License: MIT diff --git a/REUSE.toml b/REUSE.toml new file mode 100644 index 000000000..7bacbc129 --- /dev/null +++ b/REUSE.toml @@ -0,0 +1,110 @@ +# SPDX-FileCopyrightText: 2017 The CC: Tweaked Developers +# +# SPDX-License-Identifier: MPL-2.0 + +version = 1 +SPDX-PackageName = "CC: Tweaked" +SPDX-PackageSupplier = "Jonathan Coates " +SPDX-PackageDownloadLocation = "https://github.com/cc-tweaked/cc-tweaked" + +[[annotations]] +# Generated/data files are CC0. +SPDX-FileCopyrightText = "The CC: Tweaked Developers" +SPDX-License-Identifier = "CC0-1.0" +path = [ + "gradle/gradle-daemon-jvm.properties", + "projects/common/src/main/resources/assets/computercraft/sounds.json", + "projects/common/src/main/resources/assets/computercraft/sounds/empty.ogg", + "projects/common/src/testMod/resources/data/cctest/computercraft/turtle_upgrades/**", + "projects/common/src/testMod/resources/data/cctest/structures/**", + "projects/**/src/generated/**", + "projects/web/src/htmlTransform/export/index.json", + "projects/web/src/htmlTransform/export/items/minecraft/**", +] + +[[annotations]] +# Several assets where it's inconvenient to create a .license file. +SPDX-FileCopyrightText = "The CC: Tweaked Developers" +SPDX-License-Identifier = "MPL-2.0" +path = [ + "doc/images/**", + "package.json", + "package-lock.json", + "projects/common/src/client/resources/computercraft-client.mixins.json", + "projects/common/src/main/resources/assets/minecraft/shaders/core/computercraft/monitor_tbo.json", + "projects/common/src/main/resources/computercraft.mixins.json", + "projects/common/src/testMod/resources/computercraft-gametest.mixins.json", + "projects/common/src/testMod/resources/data/computercraft/loot_tables/treasure_disk.json", + "projects/common/src/testMod/resources/pack.mcmeta", + "projects/core/src/main/resources/data/computercraft/lua/rom/modules/command/.ignoreme", + "projects/core/src/main/resources/data/computercraft/lua/rom/modules/main/.ignoreme", + "projects/core/src/main/resources/data/computercraft/lua/rom/modules/turtle/.ignoreme", + "projects/core/src/main/resources/data/computercraft/lua/rom/motd.txt", + "projects/fabric-api/src/main/modJson/fabric.mod.json", + "projects/fabric/src/client/resources/computercraft-client.fabric.mixins.json", + "projects/fabric/src/main/resources/computercraft.fabric.mixins.json", + "projects/fabric/src/main/resources/fabric.mod.json", + "projects/fabric/src/testMod/resources/computercraft-gametest.fabric.mixins.json", + "projects/fabric/src/testMod/resources/fabric.mod.json", + "projects/forge/src/client/resources/computercraft-client.forge.mixins.json", + "projects/web/src/frontend/mount/.settings", + "projects/web/src/frontend/mount/example.nfp", + "projects/web/src/frontend/mount/example.nft", + "projects/web/src/frontend/mount/expr_template.lua", + "projects/web/tsconfig.json", +] + +[[annotations]] +# Bulk-license original assets as CCPL. +SPDX-FileCopyrightText = "2011 Daniel Ratcliffe" +SPDX-License-Identifier = "LicenseRef-CCPL" +path = [ + "doc/logo.png", + "doc/logo-darkmode.png", + "projects/common/src/main/resources/assets/computercraft/models/**", + "projects/common/src/main/resources/assets/computercraft/textures/**", + "projects/common/src/main/resources/pack.mcmeta", + "projects/common/src/main/resources/pack.png", + "projects/core/src/main/resources/assets/computercraft/textures/gui/term_font.png", + "projects/core/src/main/resources/data/computercraft/lua/rom/autorun/.ignoreme", + "projects/core/src/main/resources/data/computercraft/lua/rom/help/**", + "projects/core/src/main/resources/data/computercraft/lua/rom/programs/fun/advanced/levels/**", + "projects/web/src/htmlTransform/export/items/computercraft/**", +] + +[[annotations]] +# Community-contributed license files +SPDX-FileCopyrightText = "2017 The CC: Tweaked Developers" +SPDX-License-Identifier = "LicenseRef-CCPL" +path = [ + "projects/common/src/main/resources/assets/computercraft/lang/cs_cz.json", + "projects/common/src/main/resources/assets/computercraft/lang/ko_kr.json", + "projects/common/src/main/resources/assets/computercraft/lang/pl_pl.json", + "projects/common/src/main/resources/assets/computercraft/lang/pt_br.json", + "projects/common/src/main/resources/assets/computercraft/lang/ru_ru.json", + "projects/common/src/main/resources/assets/computercraft/lang/uk_ua.json", + "projects/common/src/main/resources/assets/computercraft/lang/zh_cn.json", +] + +[[annotations]] +# Community-contributed license files +SPDX-FileCopyrightText = "2017 The CC: Tweaked Developers" +SPDX-License-Identifier = "MPL-2.0" +path = "projects/common/src/main/resources/assets/computercraft/lang/**" + +[[annotations]] +# GitHub build scripts are CC0. While we could add a header to each file, +# it's unclear if it will break actions or issue templates in some way. +SPDX-FileCopyrightText = "Jonathan Coates " +SPDX-License-Identifier = "CC0-1.0" +path = ".github/**" + +[[annotations]] +path = ["gradle/wrapper/**", "gradlew", "gradlew.bat"] +SPDX-FileCopyrightText = "Gradle Inc" +SPDX-License-Identifier = "Apache-2.0" + +[[annotations]] +path = "projects/core/src/test/resources/test-rom/data/json-parsing/**" +SPDX-FileCopyrightText = "2016 Nicolas Seriot" +SPDX-License-Identifier = "MIT" From 4dd073506627ef74860c1155cf2b5f028f79a361 Mon Sep 17 00:00:00 2001 From: Jonathan Coates Date: Fri, 26 Jul 2024 18:28:13 +0100 Subject: [PATCH 14/19] Register modems as attached to their adjacent block MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In c8eadf401190db2b9c3145f768063097b9c345bd we marked our various modems as "brittle", which ensures they do not pop-off computers when the whole structure moves. However, this still requires the modem to be glued — if the modem is outside the superglue range, it will still pop off. We can fix it by registering a special "attached check" for the various modem blocks, which says that the modem should be moved when the adjacent block does. Fixes #1913 --- .../cc-tweaked.java-convention.gradle.kts | 1 + gradle/libs.versions.toml | 12 ++++--- projects/common/build.gradle.kts | 1 + .../shared/integration/CreateIntegration.java | 34 +++++++++++++++++++ .../shared/integration/ExternalModTags.java | 5 +-- projects/fabric/build.gradle.kts | 1 + .../computercraft/shared/ComputerCraft.java | 4 +++ projects/forge/build.gradle.kts | 5 +++ .../dan200/computercraft/ComputerCraft.java | 2 ++ 9 files changed, 59 insertions(+), 6 deletions(-) create mode 100644 projects/common/src/main/java/dan200/computercraft/shared/integration/CreateIntegration.java diff --git a/buildSrc/src/main/kotlin/cc-tweaked.java-convention.gradle.kts b/buildSrc/src/main/kotlin/cc-tweaked.java-convention.gradle.kts index 4eddc8da1..e0bd006ec 100644 --- a/buildSrc/src/main/kotlin/cc-tweaked.java-convention.gradle.kts +++ b/buildSrc/src/main/kotlin/cc-tweaked.java-convention.gradle.kts @@ -54,6 +54,7 @@ repositories { filter { includeGroup("cc.tweaked") // Things we mirror + includeGroup("com.simibubi.create") includeGroup("commoble.morered") includeGroup("dev.architectury") includeGroup("dev.emi") diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3ab4b08f0..1510581d8 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -46,6 +46,8 @@ oculus = "1.2.5" rei = "12.0.626" rubidium = "0.6.1" sodium = "mc1.20-0.4.10" +create-forge = "0.5.1.f-33" +create-fabric = "0.5.1-f-build.1467+mc1.20.1" # Testing hamcrest = "2.2" @@ -100,11 +102,13 @@ nightConfig-toml = { module = "com.electronwill.night-config:toml", version.ref slf4j = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" } # Minecraft mods -fabric-api = { module = "net.fabricmc.fabric-api:fabric-api", version.ref = "fabric-api" } -fabric-loader = { module = "net.fabricmc:fabric-loader", version.ref = "fabric-loader" } -fabric-junit = { module = "net.fabricmc:fabric-loader-junit", version.ref = "fabric-loader" } -fabricPermissions = { module = "me.lucko:fabric-permissions-api", version.ref = "fabricPermissions" } +create-fabric = { module = "com.simibubi.create:create-fabric-1.20.1", version.ref = "create-fabric" } +create-forge = { module = "com.simibubi.create:create-1.20.1", version.ref = "create-forge" } emi = { module = "dev.emi:emi-xplat-mojmap", version.ref = "emi" } +fabric-api = { module = "net.fabricmc.fabric-api:fabric-api", version.ref = "fabric-api" } +fabric-junit = { module = "net.fabricmc:fabric-loader-junit", version.ref = "fabric-loader" } +fabric-loader = { module = "net.fabricmc:fabric-loader", version.ref = "fabric-loader" } +fabricPermissions = { module = "me.lucko:fabric-permissions-api", version.ref = "fabricPermissions" } iris = { module = "maven.modrinth:iris", version.ref = "iris" } jei-api = { module = "mezz.jei:jei-1.20.1-common-api", version.ref = "jei" } jei-fabric = { module = "mezz.jei:jei-1.20.1-fabric", version.ref = "jei" } diff --git a/projects/common/build.gradle.kts b/projects/common/build.gradle.kts index e0b20e3d0..200fb5ccb 100644 --- a/projects/common/build.gradle.kts +++ b/projects/common/build.gradle.kts @@ -43,6 +43,7 @@ dependencies { clientImplementation(clientClasses(project(":common-api"))) compileOnly(libs.bundles.externalMods.common) + compileOnly(variantOf(libs.create.forge) { classifier("slim") }) { isTransitive = false } clientCompileOnly(variantOf(libs.emi) { classifier("api") }) annotationProcessorEverywhere(libs.autoService) diff --git a/projects/common/src/main/java/dan200/computercraft/shared/integration/CreateIntegration.java b/projects/common/src/main/java/dan200/computercraft/shared/integration/CreateIntegration.java new file mode 100644 index 000000000..d7880d950 --- /dev/null +++ b/projects/common/src/main/java/dan200/computercraft/shared/integration/CreateIntegration.java @@ -0,0 +1,34 @@ +// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers +// +// SPDX-License-Identifier: MPL-2.0 + +package dan200.computercraft.shared.integration; + +import com.simibubi.create.content.contraptions.BlockMovementChecks; +import com.simibubi.create.content.contraptions.BlockMovementChecks.CheckResult; +import dan200.computercraft.shared.peripheral.modem.wired.CableBlock; +import dan200.computercraft.shared.peripheral.modem.wireless.WirelessModemBlock; + +/** + * Integration with Create. + */ +public final class CreateIntegration { + public static final String ID = "create"; + + private CreateIntegration() { + } + + public static void setup() { + // Allow modems to be treated as "attached" to their adjacent block. + BlockMovementChecks.registerAttachedCheck((state, world, pos, direction) -> { + var block = state.getBlock(); + if (block instanceof WirelessModemBlock) { + return CheckResult.of(state.getValue(WirelessModemBlock.FACING) == direction); + } else if (block instanceof CableBlock) { + return CheckResult.of(state.getValue(CableBlock.MODEM).getFacing() == direction); + } else { + return CheckResult.PASS; + } + }); + } +} diff --git a/projects/common/src/main/java/dan200/computercraft/shared/integration/ExternalModTags.java b/projects/common/src/main/java/dan200/computercraft/shared/integration/ExternalModTags.java index 29a666217..cf8aaba93 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/integration/ExternalModTags.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/integration/ExternalModTags.java @@ -8,6 +8,7 @@ import net.minecraft.core.registries.Registries; import net.minecraft.resources.ResourceLocation; import net.minecraft.tags.TagKey; import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; /** * Tags defined by external mods. @@ -26,9 +27,9 @@ public final class ExternalModTags { /** * Create's "brittle" tag, used to determine if this block needs to be moved before its neighbours. * - * @see {@code BlockMovementChecks} + * @see com.simibubi.create.content.contraptions.BlockMovementChecks#isBrittle(BlockState) */ - public static final TagKey CREATE_BRITTLE = make("create", "brittle"); + public static final TagKey CREATE_BRITTLE = make(CreateIntegration.ID, "brittle"); private static TagKey make(String mod, String name) { return TagKey.create(Registries.BLOCK, new ResourceLocation(mod, name)); diff --git a/projects/fabric/build.gradle.kts b/projects/fabric/build.gradle.kts index 19f98cfe9..69bc0d041 100644 --- a/projects/fabric/build.gradle.kts +++ b/projects/fabric/build.gradle.kts @@ -69,6 +69,7 @@ dependencies { exclude("net.fabricmc", "fabric-loader") exclude("net.fabricmc.fabric-api") } + modCompileOnly(libs.create.fabric) { isTransitive = false } modClientRuntimeOnly(libs.bundles.externalMods.fabric.runtime) { exclude("net.fabricmc", "fabric-loader") diff --git a/projects/fabric/src/main/java/dan200/computercraft/shared/ComputerCraft.java b/projects/fabric/src/main/java/dan200/computercraft/shared/ComputerCraft.java index 1233608c0..b77d74388 100644 --- a/projects/fabric/src/main/java/dan200/computercraft/shared/ComputerCraft.java +++ b/projects/fabric/src/main/java/dan200/computercraft/shared/ComputerCraft.java @@ -13,6 +13,7 @@ import dan200.computercraft.shared.command.CommandComputerCraft; import dan200.computercraft.shared.config.Config; import dan200.computercraft.shared.config.ConfigSpec; import dan200.computercraft.shared.details.FluidDetails; +import dan200.computercraft.shared.integration.CreateIntegration; import dan200.computercraft.shared.network.NetworkMessages; import dan200.computercraft.shared.network.client.UpgradesLoadedMessage; import dan200.computercraft.shared.network.server.ServerNetworking; @@ -34,6 +35,7 @@ import net.fabricmc.fabric.api.loot.v2.LootTableEvents; import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; import net.fabricmc.fabric.api.resource.IdentifiableResourceReloadListener; import net.fabricmc.fabric.api.resource.ResourceManagerHelper; +import net.fabricmc.loader.api.FabricLoader; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.packs.PackType; @@ -119,6 +121,8 @@ public class ComputerCraft { ComputerCraftAPI.registerGenericSource(new InventoryMethods()); Peripherals.addGenericLookup((world, pos, state, blockEntity, side, invalidate) -> InventoryMethods.extractContainer(world, pos, state, blockEntity, side)); + + if (FabricLoader.getInstance().isModLoaded(CreateIntegration.ID)) CreateIntegration.setup(); } private record ReloadListener(String name, PreparableReloadListener listener) diff --git a/projects/forge/build.gradle.kts b/projects/forge/build.gradle.kts index 5c6108257..a1454b5df 100644 --- a/projects/forge/build.gradle.kts +++ b/projects/forge/build.gradle.kts @@ -121,6 +121,11 @@ dependencies { libs.bundles.externalMods.forge.compile.get().map { compileOnly(fg.deobf(it)) } libs.bundles.externalMods.forge.runtime.get().map { runtimeOnly(fg.deobf(it)) } + // fg.debof only accepts a closure to configure the dependency, so doesn't work with Kotlin. We create and configure + // the dep first, and then pass it off to ForgeGradle. + (create(variantOf(libs.create.forge) { classifier("slim") }.get()) as ExternalModuleDependency) + .apply { isTransitive = false }.let { compileOnly(fg.deobf(it)) } + // Depend on our other projects. api(commonClasses(project(":forge-api"))) { cct.exclude(this) } clientApi(clientClasses(project(":forge-api"))) { cct.exclude(this) } diff --git a/projects/forge/src/main/java/dan200/computercraft/ComputerCraft.java b/projects/forge/src/main/java/dan200/computercraft/ComputerCraft.java index c198a94f2..9b50fe335 100644 --- a/projects/forge/src/main/java/dan200/computercraft/ComputerCraft.java +++ b/projects/forge/src/main/java/dan200/computercraft/ComputerCraft.java @@ -16,6 +16,7 @@ import dan200.computercraft.shared.CommonHooks; import dan200.computercraft.shared.ModRegistry; import dan200.computercraft.shared.config.ConfigSpec; import dan200.computercraft.shared.details.FluidData; +import dan200.computercraft.shared.integration.CreateIntegration; import dan200.computercraft.shared.integration.MoreRedIntegration; import dan200.computercraft.shared.peripheral.generic.methods.EnergyMethods; import dan200.computercraft.shared.peripheral.generic.methods.FluidMethods; @@ -78,6 +79,7 @@ public final class ComputerCraft { ForgeDetailRegistries.FLUID_STACK.addProvider(FluidData::fill); + if (ModList.get().isLoaded(CreateIntegration.ID)) event.enqueueWork(CreateIntegration::setup); if (ModList.get().isLoaded(MoreRedIntegration.MOD_ID)) MoreRedIntegration.setup(); } From ed0b156e05ff64784f2f80346c82ab5142d954dc Mon Sep 17 00:00:00 2001 From: Jonathan Coates Date: Sun, 28 Jul 2024 21:13:07 +0100 Subject: [PATCH 15/19] Attempt at splitting up pocket computer logic Oh, I hate the pocket computer code so much. Minecraft was really not designed to attach this sort of behaviour to computers. This commit is an attempt of cleaning this up[^1]. Firstly, we move the the pocket computer state (upgrades, light) out of PocketServerComputer and into a new PocketBrain class. This now acts as the sole source-of-truth, with all state being synced back to the original item stack on the entity tick. This also adds a new PocketHolder interface, which generalises over the various types that can hold a pocket computer (players and item entities right now, possibly lecterns in the future). [^1]: I'd say simplifying, but this would be a lie. --- .../client/PocketComputerDataMessage.java | 2 +- .../shared/pocket/core/PocketBrain.java | 167 ++++++++++++++++++ .../shared/pocket/core/PocketHolder.java | 102 +++++++++++ .../pocket/core/PocketServerComputer.java | 150 +++------------- .../pocket/items/PocketComputerItem.java | 133 +++++++++----- .../shared/util/InventoryUtil.java | 17 ++ 6 files changed, 399 insertions(+), 172 deletions(-) create mode 100644 projects/common/src/main/java/dan200/computercraft/shared/pocket/core/PocketBrain.java create mode 100644 projects/common/src/main/java/dan200/computercraft/shared/pocket/core/PocketHolder.java diff --git a/projects/common/src/main/java/dan200/computercraft/shared/network/client/PocketComputerDataMessage.java b/projects/common/src/main/java/dan200/computercraft/shared/network/client/PocketComputerDataMessage.java index d9396707d..0f0ac70c3 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/network/client/PocketComputerDataMessage.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/network/client/PocketComputerDataMessage.java @@ -27,7 +27,7 @@ public class PocketComputerDataMessage implements NetworkMessage + * This state is read when the brain is created, and written back to the holding item stack when the holding entity is + * ticked (see {@link #updateItem(ItemStack)}). + */ +public final class PocketBrain implements IPocketAccess { + private final PocketServerComputer computer; + + private PocketHolder holder; + + private boolean dirty = false; + private @Nullable UpgradeData upgrade; + private int colour = -1; + private int lightColour = -1; + + public PocketBrain(PocketHolder holder, int computerID, @Nullable String label, ComputerFamily family, @Nullable UpgradeData upgrade) { + this.computer = new PocketServerComputer(this, holder, computerID, label, family); + this.holder = holder; + this.upgrade = UpgradeData.copyOf(upgrade); + invalidatePeripheral(); + } + + /** + * Get the corresponding pocket computer for this brain. + * + * @return The pocket computer. + */ + public PocketServerComputer computer() { + return computer; + } + + PocketHolder holder() { + return holder; + } + + /** + * Update the position and holder for this computer. + * + * @param newHolder The new holder + */ + public void updateHolder(PocketHolder newHolder) { + computer.setPosition(newHolder.level(), newHolder.blockPos()); + + var oldHolder = this.holder; + if (holder.equals(newHolder)) return; + holder = newHolder; + + // If a new player has picked it up then rebroadcast the terminal to them + var oldPlayer = oldHolder instanceof PocketHolder.PlayerHolder p ? p.entity() : null; + if (newHolder instanceof PocketHolder.PlayerHolder player && player.entity() != oldPlayer) { + ServerNetworking.sendToPlayer(new PocketComputerDataMessage(computer, true), player.entity()); + } + } + + /** + * Write back properties of the pocket brain to the item. + * + * @param stack The pocket computer stack to update. + * @return Whether the item was changed. + */ + public boolean updateItem(ItemStack stack) { + if (!dirty) return false; + this.dirty = false; + + IColouredItem.setColourBasic(stack, colour); + PocketComputerItem.setUpgrade(stack, UpgradeData.copyOf(upgrade)); + return true; + } + + @Override + public @Nullable Entity getEntity() { + return holder instanceof PocketHolder.EntityHolder entity && holder.isValid(computer) ? entity.entity() : null; + } + + @Override + public int getColour() { + return colour; + } + + @Override + public void setColour(int colour) { + if (this.colour == colour) return; + dirty = true; + this.colour = colour; + } + + @Override + public int getLight() { + return lightColour; + } + + @Override + public void setLight(int colour) { + if (colour < 0 || colour > 0xFFFFFF) colour = -1; + lightColour = colour; + } + + @Override + public CompoundTag getUpgradeNBTData() { + var upgrade = this.upgrade; + return upgrade == null ? new CompoundTag() : upgrade.data(); + } + + @Override + public void updateUpgradeNBTData() { + dirty = true; + } + + @Override + public void invalidatePeripheral() { + var peripheral = upgrade == null ? null : upgrade.upgrade().createPeripheral(this); + computer.setPeripheral(ComputerSide.BACK, peripheral); + } + + @Override + @Deprecated(forRemoval = true) + public Map getUpgrades() { + var upgrade = this.upgrade; + return upgrade == null ? Map.of() : Collections.singletonMap(upgrade.upgrade().getUpgradeID(), computer.getPeripheral(ComputerSide.BACK)); + } + + @Override + public @Nullable UpgradeData getUpgrade() { + return upgrade; + } + + /** + * Set the upgrade for this pocket computer, also updating the item stack. + *

+ * Note this method is not thread safe - it must be called from the server thread. + * + * @param upgrade The new upgrade to set it to, may be {@code null}. + */ + @Override + public void setUpgrade(@Nullable UpgradeData upgrade) { + this.upgrade = upgrade; + dirty = true; + invalidatePeripheral(); + } +} diff --git a/projects/common/src/main/java/dan200/computercraft/shared/pocket/core/PocketHolder.java b/projects/common/src/main/java/dan200/computercraft/shared/pocket/core/PocketHolder.java new file mode 100644 index 000000000..5e6187a56 --- /dev/null +++ b/projects/common/src/main/java/dan200/computercraft/shared/pocket/core/PocketHolder.java @@ -0,0 +1,102 @@ +// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers +// +// SPDX-License-Identifier: MPL-2.0 + +package dan200.computercraft.shared.pocket.core; + +import dan200.computercraft.shared.computer.core.ServerComputer; +import dan200.computercraft.shared.pocket.items.PocketComputerItem; +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.item.ItemEntity; + +/** + * An object that holds a pocket computer item. + */ +public sealed interface PocketHolder permits PocketHolder.EntityHolder { + /** + * The level this holder is in. + * + * @return The holder's level. + */ + ServerLevel level(); + + /** + * The block position of this holder. + * + * @return The position of this holder. + */ + BlockPos blockPos(); + + /** + * Determine if this holder is still valid for a particular computer. + * + * @param computer The current computer. + * @return Whether this holder is valid. + */ + boolean isValid(ServerComputer computer); + + /** + * Mark the pocket computer item as having changed. + */ + void setChanged(); + + /** + * An {@link Entity} holding a pocket computer. + */ + sealed interface EntityHolder extends PocketHolder permits PocketHolder.PlayerHolder, PocketHolder.ItemEntityHolder { + /** + * Get the entity holding this pocket computer. + * + * @return The holding entity. + */ + Entity entity(); + + @Override + default ServerLevel level() { + return (ServerLevel) entity().level(); + } + + @Override + default BlockPos blockPos() { + return entity().blockPosition(); + } + } + + /** + * A pocket computer in a player's slot. + * + * @param entity The current player. + * @param slot The slot the pocket computer is in. + */ + record PlayerHolder(ServerPlayer entity, int slot) implements EntityHolder { + @Override + public boolean isValid(ServerComputer computer) { + return entity().isAlive() && PocketComputerItem.isServerComputer(computer, entity().getInventory().getItem(this.slot())); + } + + @Override + public void setChanged() { + entity.getInventory().setChanged(); + } + } + + /** + * A pocket computer in an {@link ItemEntity}. + * + * @param entity The item entity. + */ + record ItemEntityHolder(ItemEntity entity) implements EntityHolder { + @Override + public boolean isValid(ServerComputer computer) { + return entity().isAlive() && PocketComputerItem.isServerComputer(computer, this.entity().getItem()); + } + + @Override + public void setChanged() { + entity.setItem(entity.getItem().copy()); + } + } +} diff --git a/projects/common/src/main/java/dan200/computercraft/shared/pocket/core/PocketServerComputer.java b/projects/common/src/main/java/dan200/computercraft/shared/pocket/core/PocketServerComputer.java index 4e3484de0..87450a0c8 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/pocket/core/PocketServerComputer.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/pocket/core/PocketServerComputer.java @@ -4,12 +4,6 @@ package dan200.computercraft.shared.pocket.core; -import dan200.computercraft.api.peripheral.IPeripheral; -import dan200.computercraft.api.pocket.IPocketAccess; -import dan200.computercraft.api.pocket.IPocketUpgrade; -import dan200.computercraft.api.upgrades.UpgradeData; -import dan200.computercraft.core.computer.ComputerSide; -import dan200.computercraft.shared.common.IColouredItem; import dan200.computercraft.shared.computer.core.ComputerFamily; import dan200.computercraft.shared.computer.core.ComputerState; import dan200.computercraft.shared.computer.core.ServerComputer; @@ -18,29 +12,26 @@ import dan200.computercraft.shared.network.client.PocketComputerDataMessage; import dan200.computercraft.shared.network.client.PocketComputerDeletedClientMessage; import dan200.computercraft.shared.network.server.ServerNetworking; import dan200.computercraft.shared.pocket.items.PocketComputerItem; -import net.minecraft.core.BlockPos; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; -import net.minecraft.world.entity.Entity; -import net.minecraft.world.entity.LivingEntity; -import net.minecraft.world.entity.item.ItemEntity; -import net.minecraft.world.entity.player.Player; -import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.ChunkPos; import javax.annotation.Nullable; -import java.util.Collections; -import java.util.Map; import java.util.Set; -public class PocketServerComputer extends ServerComputer implements IPocketAccess { - private @Nullable IPocketUpgrade upgrade; - private @Nullable Entity entity; - private ItemStack stack = ItemStack.EMPTY; - - private int lightColour = -1; +/** + * A {@link ServerComputer}-subclass for {@linkplain PocketComputerItem pocket computers}. + *

+ * This extends default {@link ServerComputer} behaviour by also syncing pocket computer state to nearby players, and + * syncing the terminal to the current player. + *

+ * The actual pocket computer state (upgrade, light) is maintained in {@link PocketBrain}. The two classes are tightly + * coupled, and maintain a reference to each other. + * + * @see PocketComputerDataMessage + * @see PocketComputerDeletedClientMessage + */ +public final class PocketServerComputer extends ServerComputer { + private final PocketBrain brain; // The state the previous tick, used to determine if the state needs to be sent to the client. private int oldLightColour = -1; @@ -48,107 +39,13 @@ public class PocketServerComputer extends ServerComputer implements IPocketAcces private Set tracking = Set.of(); - public PocketServerComputer(ServerLevel world, BlockPos position, int computerID, @Nullable String label, ComputerFamily family) { - super(world, position, computerID, label, family, Config.pocketTermWidth, Config.pocketTermHeight); + PocketServerComputer(PocketBrain brain, PocketHolder holder, int computerID, @Nullable String label, ComputerFamily family) { + super(holder.level(), holder.blockPos(), computerID, label, family, Config.pocketTermWidth, Config.pocketTermHeight); + this.brain = brain; } - @Nullable - @Override - public Entity getEntity() { - var entity = this.entity; - if (entity == null || stack.isEmpty() || !entity.isAlive()) return null; - - if (entity instanceof Player) { - var inventory = ((Player) entity).getInventory(); - return inventory.items.contains(stack) || inventory.offhand.contains(stack) ? entity : null; - } else if (entity instanceof LivingEntity living) { - return living.getMainHandItem() == stack || living.getOffhandItem() == stack ? entity : null; - } else if (entity instanceof ItemEntity itemEntity) { - return itemEntity.getItem() == stack ? entity : null; - } else { - return null; - } - } - - @Override - public int getColour() { - return IColouredItem.getColourBasic(stack); - } - - @Override - public void setColour(int colour) { - IColouredItem.setColourBasic(stack, colour); - updateUpgradeNBTData(); - } - - @Override - public int getLight() { - return lightColour; - } - - @Override - public void setLight(int colour) { - if (colour < 0 || colour > 0xFFFFFF) colour = -1; - lightColour = colour; - } - - @Override - public CompoundTag getUpgradeNBTData() { - return PocketComputerItem.getUpgradeInfo(stack); - } - - @Override - public void updateUpgradeNBTData() { - if (entity instanceof Player player) player.getInventory().setChanged(); - } - - @Override - public void invalidatePeripheral() { - var peripheral = upgrade == null ? null : upgrade.createPeripheral(this); - setPeripheral(ComputerSide.BACK, peripheral); - } - - @Override - @Deprecated(forRemoval = true) - public Map getUpgrades() { - return upgrade == null ? Map.of() : Collections.singletonMap(upgrade.getUpgradeID(), getPeripheral(ComputerSide.BACK)); - } - - @Override - public @Nullable UpgradeData getUpgrade() { - return upgrade == null ? null : UpgradeData.of(upgrade, getUpgradeNBTData()); - } - - /** - * Set the upgrade for this pocket computer, also updating the item stack. - *

- * Note this method is not thread safe - it must be called from the server thread. - * - * @param upgrade The new upgrade to set it to, may be {@code null}. - */ - @Override - public void setUpgrade(@Nullable UpgradeData upgrade) { - synchronized (this) { - PocketComputerItem.setUpgrade(stack, upgrade); - updateUpgradeNBTData(); - this.upgrade = upgrade == null ? null : upgrade.upgrade(); - invalidatePeripheral(); - } - } - - public synchronized void updateValues(@Nullable Entity entity, ItemStack stack, @Nullable IPocketUpgrade upgrade) { - if (entity != null) setPosition((ServerLevel) entity.level(), entity.blockPosition()); - - // If a new entity has picked it up then rebroadcast the terminal to them - if (entity != this.entity && entity instanceof ServerPlayer) markTerminalChanged(); - - this.entity = entity; - this.stack = stack; - - if (this.upgrade != upgrade) { - this.upgrade = upgrade; - invalidatePeripheral(); - } + public PocketBrain getBrain() { + return brain; } @Override @@ -161,9 +58,10 @@ public class PocketServerComputer extends ServerComputer implements IPocketAcces // And now find any new players, add them to the tracking list, and broadcast state where appropriate. var state = getState(); - if (oldLightColour != lightColour || oldComputerState != state) { + var light = brain.getLight(); + if (oldLightColour != light || oldComputerState != state) { oldComputerState = state; - oldLightColour = lightColour; + oldLightColour = light; // Broadcast the state to all players ServerNetworking.sendToPlayers(new PocketComputerDataMessage(this, false), newTracking); @@ -182,9 +80,9 @@ public class PocketServerComputer extends ServerComputer implements IPocketAcces protected void onTerminalChanged() { super.onTerminalChanged(); - if (entity instanceof ServerPlayer player && entity.isAlive()) { + if (brain.holder() instanceof PocketHolder.PlayerHolder holder && holder.isValid(this)) { // Broadcast the terminal to the current player. - ServerNetworking.sendToPlayer(new PocketComputerDataMessage(this, true), player); + ServerNetworking.sendToPlayer(new PocketComputerDataMessage(this, true), holder.entity()); } } diff --git a/projects/common/src/main/java/dan200/computercraft/shared/pocket/items/PocketComputerItem.java b/projects/common/src/main/java/dan200/computercraft/shared/pocket/items/PocketComputerItem.java index c3be7f404..8ba9d7542 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/pocket/items/PocketComputerItem.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/pocket/items/PocketComputerItem.java @@ -14,21 +14,26 @@ import dan200.computercraft.core.computer.ComputerSide; import dan200.computercraft.impl.PocketUpgrades; import dan200.computercraft.shared.common.IColouredItem; import dan200.computercraft.shared.computer.core.ComputerFamily; +import dan200.computercraft.shared.computer.core.ServerComputer; +import dan200.computercraft.shared.computer.core.ServerComputerRegistry; import dan200.computercraft.shared.computer.core.ServerContext; import dan200.computercraft.shared.computer.items.IComputerItem; import dan200.computercraft.shared.config.Config; import dan200.computercraft.shared.network.container.ComputerContainerData; import dan200.computercraft.shared.pocket.apis.PocketAPI; +import dan200.computercraft.shared.pocket.core.PocketBrain; +import dan200.computercraft.shared.pocket.core.PocketHolder; import dan200.computercraft.shared.pocket.core.PocketServerComputer; import dan200.computercraft.shared.pocket.inventory.PocketComputerMenuProvider; import dan200.computercraft.shared.util.IDAssigner; +import dan200.computercraft.shared.util.InventoryUtil; import dan200.computercraft.shared.util.NBTUtil; import net.minecraft.ChatFormatting; import net.minecraft.nbt.CompoundTag; import net.minecraft.network.chat.Component; import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerLevel; -import net.minecraft.world.Container; +import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionResult; import net.minecraft.world.InteractionResultHolder; @@ -72,12 +77,33 @@ public class PocketComputerItem extends Item implements IComputerItem, IMedia, I return result; } - private boolean tick(ItemStack stack, Entity entity, PocketServerComputer computer) { - var upgrade = getUpgrade(stack); + /** + * Tick a pocket computer. + * + * @param stack The current pocket computer stack. + * @param holder The entity holding the pocket item. + * @param brain The pocket computer brain. + */ + private void tick(ItemStack stack, PocketHolder holder, PocketBrain brain) { + brain.updateHolder(holder); - computer.updateValues(entity, stack, upgrade); + // Update pocket upgrade + var upgrade = brain.getUpgrade(); + if (upgrade != null) upgrade.upgrade().update(brain, brain.computer().getPeripheral(ComputerSide.BACK)); - var changed = false; + if (updateItem(stack, brain)) holder.setChanged(); + } + + /** + * Copy properties from the brain back to the item stack. + * + * @param stack The current pocket computer stack. + * @param brain The current pocket brain. + * @return Whether the item was changed. + */ + private boolean updateItem(ItemStack stack, PocketBrain brain) { + var changed = brain.updateItem(stack); + var computer = brain.computer(); // Sync ID var id = computer.getID(); @@ -99,21 +125,20 @@ public class PocketComputerItem extends Item implements IComputerItem, IMedia, I stack.getOrCreateTag().putBoolean(NBT_ON, on); } - // Update pocket upgrade - if (upgrade != null) upgrade.update(computer, computer.getPeripheral(ComputerSide.BACK)); - return changed; } @Override public void inventoryTick(ItemStack stack, Level world, Entity entity, int slotNum, boolean selected) { - if (world.isClientSide) return; - Container inventory = entity instanceof Player player ? player.getInventory() : null; - var computer = createServerComputer((ServerLevel) world, entity, inventory, stack); - computer.keepAlive(); + // This (in vanilla at least) is only called for players. Don't bother to handle other entities. + if (world.isClientSide || !(entity instanceof ServerPlayer player)) return; - var changed = tick(stack, entity, computer); - if (changed && inventory != null) inventory.setChanged(); + // If we're in the inventory, create a computer and keep it alive. + var holder = new PocketHolder.PlayerHolder(player, slotNum); + var brain = getOrCreateBrain((ServerLevel) world, holder, stack); + brain.computer().keepAlive(); + + tick(stack, holder, brain); } @ForgeOverride @@ -121,8 +146,11 @@ public class PocketComputerItem extends Item implements IComputerItem, IMedia, I var level = entity.level(); if (level.isClientSide || level.getServer() == null) return false; + // If we're an item entity, tick an already existing computer (as to update the position), but do not keep the + // computer alive. var computer = getServerComputer(level.getServer(), stack); - if (computer != null && tick(stack, entity, computer)) entity.setItem(stack.copy()); + if (computer != null) tick(stack, new PocketHolder.ItemEntityHolder(entity), computer.getBrain()); + return false; } @@ -130,14 +158,18 @@ public class PocketComputerItem extends Item implements IComputerItem, IMedia, I public InteractionResultHolder use(Level world, Player player, InteractionHand hand) { var stack = player.getItemInHand(hand); if (!world.isClientSide) { - var computer = createServerComputer((ServerLevel) world, player, player.getInventory(), stack); + var holder = new PocketHolder.PlayerHolder((ServerPlayer) player, InventoryUtil.getHandSlot(player, hand)); + var brain = getOrCreateBrain((ServerLevel) world, holder, stack); + var computer = brain.computer(); computer.turnOn(); var stop = false; var upgrade = getUpgrade(stack); if (upgrade != null) { - computer.updateValues(player, stack, upgrade); - stop = upgrade.onRightClick(world, computer, computer.getPeripheral(ComputerSide.BACK)); + brain.updateHolder(holder); + stop = upgrade.onRightClick(world, brain, computer.getPeripheral(ComputerSide.BACK)); + // Sync back just in case. We don't need to setChanged, as we'll return the item anyway. + updateItem(stack, brain); } if (!stop) { @@ -187,40 +219,51 @@ public class PocketComputerItem extends Item implements IComputerItem, IMedia, I return ComputerCraftAPI.MOD_ID; } - public PocketServerComputer createServerComputer(ServerLevel level, Entity entity, @Nullable Container inventory, ItemStack stack) { - + private PocketBrain getOrCreateBrain(ServerLevel level, PocketHolder holder, ItemStack stack) { var registry = ServerContext.get(level.getServer()).registry(); - var computer = (PocketServerComputer) registry.get(getSessionID(stack), getInstanceID(stack)); - if (computer == null) { - var computerID = getComputerID(stack); - if (computerID < 0) { - computerID = ComputerCraftAPI.createUniqueNumberedSaveDir(level.getServer(), IDAssigner.COMPUTER); - setComputerID(stack, computerID); - } - - computer = new PocketServerComputer(level, entity.blockPosition(), getComputerID(stack), getLabel(stack), getFamily()); - - var tag = stack.getOrCreateTag(); - tag.putInt(NBT_SESSION, registry.getSessionID()); - tag.putUUID(NBT_INSTANCE, computer.register()); - - var upgrade = getUpgrade(stack); - - computer.updateValues(entity, stack, upgrade); - computer.addAPI(new PocketAPI(computer)); - - // Only turn on when initially creating the computer, rather than each tick. - if (isMarkedOn(stack) && entity instanceof Player) computer.turnOn(); - - if (inventory != null) inventory.setChanged(); + { + var computer = getServerComputer(registry, stack); + if (computer != null) return computer.getBrain(); } - return computer; + var computerID = getComputerID(stack); + if (computerID < 0) { + computerID = ComputerCraftAPI.createUniqueNumberedSaveDir(level.getServer(), IDAssigner.COMPUTER); + setComputerID(stack, computerID); + } + + var brain = new PocketBrain(holder, getComputerID(stack), getLabel(stack), getFamily(), getUpgradeWithData(stack)); + var computer = brain.computer(); + + var tag = stack.getOrCreateTag(); + tag.putInt(NBT_SESSION, registry.getSessionID()); + tag.putUUID(NBT_INSTANCE, computer.register()); + + computer.addAPI(new PocketAPI(brain)); + + // Only turn on when initially creating the computer, rather than each tick. + if (isMarkedOn(stack) && holder instanceof PocketHolder.PlayerHolder) computer.turnOn(); + + updateItem(stack, brain); + + holder.setChanged(); + + return brain; + } + + public static boolean isServerComputer(ServerComputer computer, ItemStack stack) { + return stack.getItem() instanceof PocketComputerItem + && getServerComputer(computer.getLevel().getServer(), stack) == computer; + } + + @Nullable + public static PocketServerComputer getServerComputer(ServerComputerRegistry registry, ItemStack stack) { + return (PocketServerComputer) registry.get(getSessionID(stack), getInstanceID(stack)); } @Nullable public static PocketServerComputer getServerComputer(MinecraftServer server, ItemStack stack) { - return (PocketServerComputer) ServerContext.get(server).registry().get(getSessionID(stack), getInstanceID(stack)); + return getServerComputer(ServerContext.get(server).registry(), stack); } // IComputerItem implementation diff --git a/projects/common/src/main/java/dan200/computercraft/shared/util/InventoryUtil.java b/projects/common/src/main/java/dan200/computercraft/shared/util/InventoryUtil.java index 22b361ff7..35ed3adbd 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/util/InventoryUtil.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/util/InventoryUtil.java @@ -8,6 +8,9 @@ import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.Container; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; import net.minecraft.world.phys.EntityHitResult; import net.minecraft.world.phys.Vec3; @@ -18,6 +21,20 @@ public final class InventoryUtil { private InventoryUtil() { } + /** + * Get the inventory slot for a given hand. + * + * @param player The player to get the slot from. + * @param hand The hand to get. + * @return The current slot. + */ + public static int getHandSlot(Player player, InteractionHand hand) { + return switch (hand) { + case MAIN_HAND -> player.getInventory().selected; + case OFF_HAND -> Inventory.SLOT_OFFHAND; + }; + } + public static @Nullable Container getEntityContainer(ServerLevel level, BlockPos pos, Direction side) { var vecStart = new Vec3( pos.getX() + 0.5 + 0.6 * side.getStepX(), From cbe075b001c46cee95e00b8151f59f86203a03ac Mon Sep 17 00:00:00 2001 From: Jonathan Coates Date: Sun, 28 Jul 2024 21:15:25 +0100 Subject: [PATCH 16/19] Expose level+position in IPocketAccess This allows pocket upgrades (modems and speakers) to read the position directly, rather than checking whether the entity is present. --- .../api/pocket/IPocketAccess.java | 16 ++++++++++++++++ .../shared/pocket/core/PocketBrain.java | 17 +++++++++++++++++ .../shared/pocket/core/PocketHolder.java | 13 +++++++++++++ .../shared/pocket/peripherals/PocketModem.java | 2 -- .../peripherals/PocketModemPeripheral.java | 18 ++++-------------- .../peripherals/PocketSpeakerPeripheral.java | 12 +----------- 6 files changed, 51 insertions(+), 27 deletions(-) diff --git a/projects/common-api/src/main/java/dan200/computercraft/api/pocket/IPocketAccess.java b/projects/common-api/src/main/java/dan200/computercraft/api/pocket/IPocketAccess.java index cf16a836e..e0d539a1d 100644 --- a/projects/common-api/src/main/java/dan200/computercraft/api/pocket/IPocketAccess.java +++ b/projects/common-api/src/main/java/dan200/computercraft/api/pocket/IPocketAccess.java @@ -9,8 +9,10 @@ import dan200.computercraft.api.upgrades.UpgradeBase; import dan200.computercraft.api.upgrades.UpgradeData; import net.minecraft.nbt.CompoundTag; import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerLevel; import net.minecraft.world.entity.Entity; import net.minecraft.world.item.ItemStack; +import net.minecraft.world.phys.Vec3; import org.jetbrains.annotations.ApiStatus; import javax.annotation.Nullable; @@ -21,6 +23,20 @@ import java.util.Map; */ @ApiStatus.NonExtendable public interface IPocketAccess { + /** + * Get the level in which the pocket computer exists. + * + * @return The pocket computer's level. + */ + ServerLevel getLevel(); + + /** + * Get the position of the pocket computer. + * + * @return The pocket computer's position. + */ + Vec3 getPosition(); + /** * Gets the entity holding this item. *

diff --git a/projects/common/src/main/java/dan200/computercraft/shared/pocket/core/PocketBrain.java b/projects/common/src/main/java/dan200/computercraft/shared/pocket/core/PocketBrain.java index 5348c1dab..33b5e8231 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/pocket/core/PocketBrain.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/pocket/core/PocketBrain.java @@ -16,8 +16,10 @@ import dan200.computercraft.shared.network.server.ServerNetworking; import dan200.computercraft.shared.pocket.items.PocketComputerItem; import net.minecraft.nbt.CompoundTag; import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerLevel; import net.minecraft.world.entity.Entity; import net.minecraft.world.item.ItemStack; +import net.minecraft.world.phys.Vec3; import javax.annotation.Nullable; import java.util.Collections; @@ -34,6 +36,7 @@ public final class PocketBrain implements IPocketAccess { private final PocketServerComputer computer; private PocketHolder holder; + private Vec3 position; private boolean dirty = false; private @Nullable UpgradeData upgrade; @@ -43,6 +46,7 @@ public final class PocketBrain implements IPocketAccess { public PocketBrain(PocketHolder holder, int computerID, @Nullable String label, ComputerFamily family, @Nullable UpgradeData upgrade) { this.computer = new PocketServerComputer(this, holder, computerID, label, family); this.holder = holder; + this.position = holder.pos(); this.upgrade = UpgradeData.copyOf(upgrade); invalidatePeripheral(); } @@ -66,6 +70,7 @@ public final class PocketBrain implements IPocketAccess { * @param newHolder The new holder */ public void updateHolder(PocketHolder newHolder) { + position = newHolder.pos(); computer.setPosition(newHolder.level(), newHolder.blockPos()); var oldHolder = this.holder; @@ -94,6 +99,18 @@ public final class PocketBrain implements IPocketAccess { return true; } + @Override + public ServerLevel getLevel() { + return computer.getLevel(); + } + + @Override + public Vec3 getPosition() { + // This method can be called from off-thread, and so we must use the cached position rather than rereading + // from the holder. + return position; + } + @Override public @Nullable Entity getEntity() { return holder instanceof PocketHolder.EntityHolder entity && holder.isValid(computer) ? entity.entity() : null; diff --git a/projects/common/src/main/java/dan200/computercraft/shared/pocket/core/PocketHolder.java b/projects/common/src/main/java/dan200/computercraft/shared/pocket/core/PocketHolder.java index 5e6187a56..16dc81a5d 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/pocket/core/PocketHolder.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/pocket/core/PocketHolder.java @@ -11,6 +11,7 @@ import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.item.ItemEntity; +import net.minecraft.world.phys.Vec3; /** * An object that holds a pocket computer item. @@ -23,6 +24,13 @@ public sealed interface PocketHolder permits PocketHolder.EntityHolder { */ ServerLevel level(); + /** + * The position of this holder. + * + * @return The position of this holder. + */ + Vec3 pos(); + /** * The block position of this holder. * @@ -59,6 +67,11 @@ public sealed interface PocketHolder permits PocketHolder.EntityHolder { return (ServerLevel) entity().level(); } + @Override + default Vec3 pos() { + return entity().getEyePosition(); + } + @Override default BlockPos blockPos() { return entity().blockPosition(); diff --git a/projects/common/src/main/java/dan200/computercraft/shared/pocket/peripherals/PocketModem.java b/projects/common/src/main/java/dan200/computercraft/shared/pocket/peripherals/PocketModem.java index 273c5e8a4..6ab3469a0 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/pocket/peripherals/PocketModem.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/pocket/peripherals/PocketModem.java @@ -31,8 +31,6 @@ public class PocketModem extends AbstractPocketUpgrade { public void update(IPocketAccess access, @Nullable IPeripheral peripheral) { if (!(peripheral instanceof PocketModemPeripheral modem)) return; - modem.setLocation(access); - var state = modem.getModemState(); if (state.pollChanged()) access.setLight(state.isOpen() ? 0xBA0000 : -1); } diff --git a/projects/common/src/main/java/dan200/computercraft/shared/pocket/peripherals/PocketModemPeripheral.java b/projects/common/src/main/java/dan200/computercraft/shared/pocket/peripherals/PocketModemPeripheral.java index a55c7fa16..bd1d8b289 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/pocket/peripherals/PocketModemPeripheral.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/pocket/peripherals/PocketModemPeripheral.java @@ -14,31 +14,21 @@ import net.minecraft.world.phys.Vec3; import javax.annotation.Nullable; public class PocketModemPeripheral extends WirelessModemPeripheral { - private @Nullable Level level = null; - private Vec3 position = Vec3.ZERO; + private final IPocketAccess access; public PocketModemPeripheral(boolean advanced, IPocketAccess access) { super(new ModemState(), advanced); - setLocation(access); - } - - void setLocation(IPocketAccess access) { - var entity = access.getEntity(); - if (entity != null) { - level = entity.level(); - position = entity.getEyePosition(1); - } + this.access = access; } @Override public Level getLevel() { - if (level == null) throw new IllegalStateException("Using modem before position has been defined"); - return level; + return access.getLevel(); } @Override public Vec3 getPosition() { - return position; + return access.getPosition(); } @Override diff --git a/projects/common/src/main/java/dan200/computercraft/shared/pocket/peripherals/PocketSpeakerPeripheral.java b/projects/common/src/main/java/dan200/computercraft/shared/pocket/peripherals/PocketSpeakerPeripheral.java index 538790560..58a8df873 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/pocket/peripherals/PocketSpeakerPeripheral.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/pocket/peripherals/PocketSpeakerPeripheral.java @@ -8,15 +8,11 @@ import dan200.computercraft.api.peripheral.IPeripheral; import dan200.computercraft.api.pocket.IPocketAccess; import dan200.computercraft.shared.peripheral.speaker.SpeakerPosition; import dan200.computercraft.shared.peripheral.speaker.UpgradeSpeakerPeripheral; -import net.minecraft.world.level.Level; -import net.minecraft.world.phys.Vec3; import javax.annotation.Nullable; public class PocketSpeakerPeripheral extends UpgradeSpeakerPeripheral { private final IPocketAccess access; - private @Nullable Level level; - private Vec3 position = Vec3.ZERO; public PocketSpeakerPeripheral(IPocketAccess access) { this.access = access; @@ -25,7 +21,7 @@ public class PocketSpeakerPeripheral extends UpgradeSpeakerPeripheral { @Override public SpeakerPosition getPosition() { var entity = access.getEntity(); - return entity == null ? SpeakerPosition.of(level, position) : SpeakerPosition.of(entity); + return entity == null ? SpeakerPosition.of(access.getLevel(), access.getPosition()) : SpeakerPosition.of(entity); } @Override @@ -35,12 +31,6 @@ public class PocketSpeakerPeripheral extends UpgradeSpeakerPeripheral { @Override public void update() { - var entity = access.getEntity(); - if (entity != null) { - level = entity.level(); - position = entity.position(); - } - super.update(); access.setLight(madeSound() ? 0x3320fc : -1); From dc3d8ea198ac400175de65abbfab53bacdd5e9ae Mon Sep 17 00:00:00 2001 From: Jonathan Coates Date: Mon, 29 Jul 2024 19:46:25 +0100 Subject: [PATCH 17/19] Move API factories to the common package We don't actually use this functionality in other projects (e.g. emulators). In fact the method to add new APIs only exists in the mod itself! We still need some mechanism to remove mounts when the computer is shutdown. We add a new ApiLifecycle interface (with startup and shutdown hooks), and use those in the ComputerSystem impl. --- .../api/lua/IComputerSystem.java | 2 ++ .../computercraft/api/lua/ILuaAPIFactory.java | 6 ++-- .../shared/computer/core}/ComputerSystem.java | 11 +++++-- .../shared/computer/core/ServerComputer.java | 8 +++++ .../shared/computer/core/ServerContext.java | 2 -- .../computercraft/core/ComputerContext.java | 32 ++----------------- .../core/apis/ComputerAccess.java | 2 +- .../core/computer/ApiLifecycle.java | 28 ++++++++++++++++ .../core/computer/ApiWrapper.java | 18 ++++------- .../computercraft/core/computer/Computer.java | 4 +++ .../core/computer/ComputerExecutor.java | 11 +++---- 11 files changed, 68 insertions(+), 56 deletions(-) rename projects/{core-api => common-api}/src/main/java/dan200/computercraft/api/lua/IComputerSystem.java (89%) rename projects/{core-api => common-api}/src/main/java/dan200/computercraft/api/lua/ILuaAPIFactory.java (76%) rename projects/{core/src/main/java/dan200/computercraft/core/computer => common/src/main/java/dan200/computercraft/shared/computer/core}/ComputerSystem.java (83%) create mode 100644 projects/core/src/main/java/dan200/computercraft/core/computer/ApiLifecycle.java diff --git a/projects/core-api/src/main/java/dan200/computercraft/api/lua/IComputerSystem.java b/projects/common-api/src/main/java/dan200/computercraft/api/lua/IComputerSystem.java similarity index 89% rename from projects/core-api/src/main/java/dan200/computercraft/api/lua/IComputerSystem.java rename to projects/common-api/src/main/java/dan200/computercraft/api/lua/IComputerSystem.java index 2a0c4ab8e..f851d45cd 100644 --- a/projects/core-api/src/main/java/dan200/computercraft/api/lua/IComputerSystem.java +++ b/projects/common-api/src/main/java/dan200/computercraft/api/lua/IComputerSystem.java @@ -5,6 +5,7 @@ package dan200.computercraft.api.lua; import dan200.computercraft.api.peripheral.IComputerAccess; +import org.jetbrains.annotations.ApiStatus; import javax.annotation.Nullable; @@ -12,6 +13,7 @@ import javax.annotation.Nullable; * An interface passed to {@link ILuaAPIFactory} in order to provide additional information * about a computer. */ +@ApiStatus.NonExtendable public interface IComputerSystem extends IComputerAccess { /** * Get the label for this computer. diff --git a/projects/core-api/src/main/java/dan200/computercraft/api/lua/ILuaAPIFactory.java b/projects/common-api/src/main/java/dan200/computercraft/api/lua/ILuaAPIFactory.java similarity index 76% rename from projects/core-api/src/main/java/dan200/computercraft/api/lua/ILuaAPIFactory.java rename to projects/common-api/src/main/java/dan200/computercraft/api/lua/ILuaAPIFactory.java index 75167739b..cb0f35500 100644 --- a/projects/core-api/src/main/java/dan200/computercraft/api/lua/ILuaAPIFactory.java +++ b/projects/common-api/src/main/java/dan200/computercraft/api/lua/ILuaAPIFactory.java @@ -4,13 +4,15 @@ package dan200.computercraft.api.lua; +import dan200.computercraft.api.ComputerCraftAPI; + import javax.annotation.Nullable; /** - * Construct an {@link ILuaAPI} for a specific computer. + * Construct an {@link ILuaAPI} for a computer. * * @see ILuaAPI - * @see dan200.computercraft.api.ComputerCraftAPI#registerAPIFactory(ILuaAPIFactory) + * @see ComputerCraftAPI#registerAPIFactory(ILuaAPIFactory) */ @FunctionalInterface public interface ILuaAPIFactory { diff --git a/projects/core/src/main/java/dan200/computercraft/core/computer/ComputerSystem.java b/projects/common/src/main/java/dan200/computercraft/shared/computer/core/ComputerSystem.java similarity index 83% rename from projects/core/src/main/java/dan200/computercraft/core/computer/ComputerSystem.java rename to projects/common/src/main/java/dan200/computercraft/shared/computer/core/ComputerSystem.java index 12bb8cc8c..737be3c0d 100644 --- a/projects/core/src/main/java/dan200/computercraft/core/computer/ComputerSystem.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/computer/core/ComputerSystem.java @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: MPL-2.0 -package dan200.computercraft.core.computer; +package dan200.computercraft.shared.computer.core; import dan200.computercraft.api.lua.IComputerSystem; import dan200.computercraft.api.lua.ILuaAPIFactory; @@ -10,6 +10,7 @@ import dan200.computercraft.api.peripheral.IComputerAccess; import dan200.computercraft.api.peripheral.IPeripheral; import dan200.computercraft.core.apis.ComputerAccess; import dan200.computercraft.core.apis.IAPIEnvironment; +import dan200.computercraft.core.computer.ApiLifecycle; import javax.annotation.Nullable; import java.util.Map; @@ -18,9 +19,8 @@ import java.util.Map; * Implementation of {@link IComputerAccess}/{@link IComputerSystem} for usage by externally registered APIs. * * @see ILuaAPIFactory - * @see ApiWrapper */ -public class ComputerSystem extends ComputerAccess implements IComputerSystem { +class ComputerSystem extends ComputerAccess implements IComputerSystem, ApiLifecycle { private final IAPIEnvironment environment; ComputerSystem(IAPIEnvironment environment) { @@ -28,6 +28,11 @@ public class ComputerSystem extends ComputerAccess implements IComputerSystem { this.environment = environment; } + @Override + public void shutdown() { + unmountAll(); + } + @Override public String getAttachmentName() { return "computer"; diff --git a/projects/common/src/main/java/dan200/computercraft/shared/computer/core/ServerComputer.java b/projects/common/src/main/java/dan200/computercraft/shared/computer/core/ServerComputer.java index 60d350c7e..22da0cb5a 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/computer/core/ServerComputer.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/computer/core/ServerComputer.java @@ -13,6 +13,7 @@ import dan200.computercraft.core.computer.Computer; import dan200.computercraft.core.computer.ComputerEnvironment; import dan200.computercraft.core.computer.ComputerSide; import dan200.computercraft.core.metrics.MetricsObserver; +import dan200.computercraft.impl.ApiFactories; import dan200.computercraft.shared.computer.apis.CommandAPI; import dan200.computercraft.shared.computer.menu.ComputerMenu; import dan200.computercraft.shared.computer.terminal.NetworkedTerminal; @@ -63,6 +64,13 @@ public class ServerComputer implements InputHandler, ComputerEnvironment { computer = new Computer(context.computerContext(), this, terminal, computerID); computer.setLabel(label); + // Load in the externally registered APIs. + for (var factory : ApiFactories.getAll()) { + var system = new ComputerSystem(computer.getAPIEnvironment()); + var api = factory.create(system); + if (api != null) computer.addApi(api, system); + } + if (family == ComputerFamily.COMMAND) addAPI(new CommandAPI(this)); } diff --git a/projects/common/src/main/java/dan200/computercraft/shared/computer/core/ServerContext.java b/projects/common/src/main/java/dan200/computercraft/shared/computer/core/ServerContext.java index 64480353b..3131c667a 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/computer/core/ServerContext.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/computer/core/ServerContext.java @@ -17,7 +17,6 @@ import dan200.computercraft.core.lua.ILuaMachine; import dan200.computercraft.core.methods.MethodSupplier; import dan200.computercraft.core.methods.PeripheralMethod; import dan200.computercraft.impl.AbstractComputerCraftAPI; -import dan200.computercraft.impl.ApiFactories; import dan200.computercraft.impl.GenericSources; import dan200.computercraft.shared.CommonHooks; import dan200.computercraft.shared.computer.metrics.GlobalMetrics; @@ -74,7 +73,6 @@ public final class ServerContext { .computerThreads(ConfigSpec.computerThreads.get()) .mainThreadScheduler(mainThread) .luaFactory(luaMachine) - .apiFactories(ApiFactories.getAll()) .genericMethods(GenericSources.getAllMethods()) .build(); idAssigner = new IDAssigner(storageDir.resolve("ids.json")); diff --git a/projects/core/src/main/java/dan200/computercraft/core/ComputerContext.java b/projects/core/src/main/java/dan200/computercraft/core/ComputerContext.java index d251e2a7e..d77b21c95 100644 --- a/projects/core/src/main/java/dan200/computercraft/core/ComputerContext.java +++ b/projects/core/src/main/java/dan200/computercraft/core/ComputerContext.java @@ -4,7 +4,6 @@ package dan200.computercraft.core; -import dan200.computercraft.api.lua.ILuaAPIFactory; import dan200.computercraft.core.asm.GenericMethod; import dan200.computercraft.core.asm.LuaMethodSupplier; import dan200.computercraft.core.asm.PeripheralMethodSupplier; @@ -35,21 +34,19 @@ public final class ComputerContext { private final ComputerScheduler computerScheduler; private final MainThreadScheduler mainThreadScheduler; private final ILuaMachine.Factory luaFactory; - private final List apiFactories; private final MethodSupplier luaMethods; private final MethodSupplier peripheralMethods; - ComputerContext( + private ComputerContext( GlobalEnvironment globalEnvironment, ComputerScheduler computerScheduler, MainThreadScheduler mainThreadScheduler, ILuaMachine.Factory luaFactory, - List apiFactories, MethodSupplier luaMethods, + MethodSupplier luaMethods, MethodSupplier peripheralMethods ) { this.globalEnvironment = globalEnvironment; this.computerScheduler = computerScheduler; this.mainThreadScheduler = mainThreadScheduler; this.luaFactory = luaFactory; - this.apiFactories = apiFactories; this.luaMethods = luaMethods; this.peripheralMethods = peripheralMethods; } @@ -91,15 +88,6 @@ public final class ComputerContext { return luaFactory; } - /** - * Additional APIs to inject into each computer. - * - * @return All available API factories. - */ - public List apiFactories() { - return apiFactories; - } - /** * Get the {@link MethodSupplier} used to find methods on Lua values. * @@ -166,7 +154,6 @@ public final class ComputerContext { private @Nullable ComputerScheduler computerScheduler = null; private @Nullable MainThreadScheduler mainThreadScheduler; private @Nullable ILuaMachine.Factory luaFactory; - private @Nullable List apiFactories; private @Nullable List genericMethods; Builder(GlobalEnvironment environment) { @@ -227,20 +214,6 @@ public final class ComputerContext { return this; } - /** - * Set the additional {@linkplain ILuaAPIFactory APIs} to add to each computer. - * - * @param apis A list of API factories. - * @return {@code this}, for chaining - * @see ComputerContext#apiFactories() - */ - public Builder apiFactories(Collection apis) { - Objects.requireNonNull(apis); - if (apiFactories != null) throw new IllegalStateException("Main-thread scheduler already specified"); - apiFactories = List.copyOf(apis); - return this; - } - /** * Set the set of {@link GenericMethod}s used by the {@linkplain MethodSupplier method suppliers}. * @@ -267,7 +240,6 @@ public final class ComputerContext { computerScheduler == null ? new ComputerThread(1) : computerScheduler, mainThreadScheduler == null ? new NoWorkMainThreadScheduler() : mainThreadScheduler, luaFactory == null ? CobaltLuaMachine::new : luaFactory, - apiFactories == null ? List.of() : apiFactories, LuaMethodSupplier.create(genericMethods == null ? List.of() : genericMethods), PeripheralMethodSupplier.create(genericMethods == null ? List.of() : genericMethods) ); diff --git a/projects/core/src/main/java/dan200/computercraft/core/apis/ComputerAccess.java b/projects/core/src/main/java/dan200/computercraft/core/apis/ComputerAccess.java index 1844db6c6..8184f3639 100644 --- a/projects/core/src/main/java/dan200/computercraft/core/apis/ComputerAccess.java +++ b/projects/core/src/main/java/dan200/computercraft/core/apis/ComputerAccess.java @@ -21,7 +21,7 @@ public abstract class ComputerAccess implements IComputerAccess { private static final Logger LOG = LoggerFactory.getLogger(ComputerAccess.class); private final IAPIEnvironment environment; - private final Set mounts = new HashSet<>(); + private final Set mounts = new HashSet<>(0); protected ComputerAccess(IAPIEnvironment environment) { this.environment = environment; diff --git a/projects/core/src/main/java/dan200/computercraft/core/computer/ApiLifecycle.java b/projects/core/src/main/java/dan200/computercraft/core/computer/ApiLifecycle.java new file mode 100644 index 000000000..e3920bd59 --- /dev/null +++ b/projects/core/src/main/java/dan200/computercraft/core/computer/ApiLifecycle.java @@ -0,0 +1,28 @@ +// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers +// +// SPDX-License-Identifier: MPL-2.0 + +package dan200.computercraft.core.computer; + +import dan200.computercraft.api.lua.ILuaAPI; + +/** + * Hooks for managing the lifecycle of an API. This allows adding additional logic to an API's {@link ILuaAPI#startup()} + * and {@link ILuaAPI#shutdown()} methods. + * + * @see ILuaAPI + * @see Computer#addApi(ILuaAPI, ApiLifecycle) + */ +public interface ApiLifecycle { + /** + * Called before the API's {@link ILuaAPI#startup()} method, may be used to set up resources. + */ + default void startup() { + } + + /** + * Called after the API's {@link ILuaAPI#shutdown()} method, may be used to tear down resources. + */ + default void shutdown() { + } +} diff --git a/projects/core/src/main/java/dan200/computercraft/core/computer/ApiWrapper.java b/projects/core/src/main/java/dan200/computercraft/core/computer/ApiWrapper.java index 535cb6b8d..94394724a 100644 --- a/projects/core/src/main/java/dan200/computercraft/core/computer/ApiWrapper.java +++ b/projects/core/src/main/java/dan200/computercraft/core/computer/ApiWrapper.java @@ -9,18 +9,14 @@ import dan200.computercraft.api.lua.ILuaAPI; import javax.annotation.Nullable; /** - * A wrapper for {@link ILuaAPI}s which optionally manages the lifecycle of a {@link ComputerSystem}. + * A wrapper for {@link ILuaAPI}s which provides an optional shutdown hook to clean up resources. + * + * @param api The original API. + * @param lifecycle The optional lifecycle hooks for this API. */ -final class ApiWrapper { - private final ILuaAPI api; - private final @Nullable ComputerSystem system; - - ApiWrapper(ILuaAPI api, @Nullable ComputerSystem system) { - this.api = api; - this.system = system; - } - +record ApiWrapper(ILuaAPI api, @Nullable ApiLifecycle lifecycle) { public void startup() { + if (lifecycle != null) lifecycle.startup(); api.startup(); } @@ -30,7 +26,7 @@ final class ApiWrapper { public void shutdown() { api.shutdown(); - if (system != null) system.unmountAll(); + if (lifecycle != null) lifecycle.shutdown(); } public ILuaAPI api() { diff --git a/projects/core/src/main/java/dan200/computercraft/core/computer/Computer.java b/projects/core/src/main/java/dan200/computercraft/core/computer/Computer.java index b2d4f1e13..9b853bab6 100644 --- a/projects/core/src/main/java/dan200/computercraft/core/computer/Computer.java +++ b/projects/core/src/main/java/dan200/computercraft/core/computer/Computer.java @@ -184,6 +184,10 @@ public class Computer { executor.addApi(api); } + public void addApi(ILuaAPI api, ApiLifecycle lifecycleHooks) { + executor.addApi(api, lifecycleHooks); + } + long getUniqueTaskId() { return lastTaskId.incrementAndGet(); } diff --git a/projects/core/src/main/java/dan200/computercraft/core/computer/ComputerExecutor.java b/projects/core/src/main/java/dan200/computercraft/core/computer/ComputerExecutor.java index 3bb1c551e..4abed1117 100644 --- a/projects/core/src/main/java/dan200/computercraft/core/computer/ComputerExecutor.java +++ b/projects/core/src/main/java/dan200/computercraft/core/computer/ComputerExecutor.java @@ -162,13 +162,6 @@ final class ComputerExecutor implements ComputerScheduler.Worker { addApi(new PeripheralAPI(environment, context.peripheralMethods())); addApi(new OSAPI(environment)); if (CoreConfig.httpEnabled) addApi(new HTTPAPI(environment)); - - // Load in the externally registered APIs. - for (var factory : context.apiFactories()) { - var system = new ComputerSystem(environment); - var api = factory.create(system); - if (api != null) apis.add(new ApiWrapper(api, system)); - } } @Override @@ -190,6 +183,10 @@ final class ComputerExecutor implements ComputerScheduler.Worker { apis.add(new ApiWrapper(api, null)); } + void addApi(ILuaAPI api, ApiLifecycle lifecycleHooks) { + apis.add(new ApiWrapper(api, lifecycleHooks)); + } + /** * Schedule this computer to be started if not already on. */ From b9eac4e5092f73c63b4e3362e2a11c2f7f9ec0aa Mon Sep 17 00:00:00 2001 From: Jonathan Coates Date: Wed, 31 Jul 2024 06:57:38 +0100 Subject: [PATCH 18/19] Computer components (#1915) This adds a new mechanism for attaching additional objects to a computer, allowing them to be queried by other mods. This is primarily designed for mods which add external APIs, allowing them to add APIs which depend on the computer's position or can interact with the turtle inventory. I will stress that the use-cases for custom APIs are few and far between. Almost all the time a peripheral would be the better option, and I am wary that this PR will encourage misuse of APIs. However, there are some legitimate use-cases, and I think we should enable them. - Add a new "ComputerComponent" class, and several built-in components (for turtle, pocket and command computers). - Add a method to `IComputerSystem` to read a component from the computer. We also add methods to get the level and position of the computer. - Move all our existing APIs (built-in turtle, pocket, command) to use the public API. --- .../computercraft/api/ComputerCraftAPI.java | 17 ++++- .../api/component/AdminComputer.java | 24 +++++++ .../api/component/ComputerComponent.java | 48 ++++++++++++++ .../api/component/ComputerComponents.java | 29 ++++++++ .../api/lua/IComputerSystem.java | 34 ++++++++++ .../computercraft/impl/ApiFactories.java | 1 - .../computercraft/shared/ModRegistry.java | 23 +++++++ .../shared/computer/apis/CommandAPI.java | 10 +-- .../computer/blocks/ComputerBlockEntity.java | 4 +- .../shared/computer/core/ComputerSystem.java | 51 ++++++++++++-- .../shared/computer/core/ServerComputer.java | 30 +++++---- .../pocket/core/PocketServerComputer.java | 7 +- .../pocket/items/PocketComputerItem.java | 3 - .../shared/turtle/apis/TurtleAPI.java | 5 +- .../turtle/blocks/TurtleBlockEntity.java | 8 +-- .../shared/util/ComponentMap.java | 66 +++++++++++++++++++ 16 files changed, 327 insertions(+), 33 deletions(-) create mode 100644 projects/common-api/src/main/java/dan200/computercraft/api/component/AdminComputer.java create mode 100644 projects/common-api/src/main/java/dan200/computercraft/api/component/ComputerComponent.java create mode 100644 projects/common-api/src/main/java/dan200/computercraft/api/component/ComputerComponents.java create mode 100644 projects/common/src/main/java/dan200/computercraft/shared/util/ComponentMap.java diff --git a/projects/common-api/src/main/java/dan200/computercraft/api/ComputerCraftAPI.java b/projects/common-api/src/main/java/dan200/computercraft/api/ComputerCraftAPI.java index fc3edb489..bf82da6cb 100644 --- a/projects/common-api/src/main/java/dan200/computercraft/api/ComputerCraftAPI.java +++ b/projects/common-api/src/main/java/dan200/computercraft/api/ComputerCraftAPI.java @@ -4,9 +4,11 @@ package dan200.computercraft.api; +import dan200.computercraft.api.component.ComputerComponent; import dan200.computercraft.api.filesystem.Mount; import dan200.computercraft.api.filesystem.WritableMount; import dan200.computercraft.api.lua.GenericSource; +import dan200.computercraft.api.lua.IComputerSystem; import dan200.computercraft.api.lua.ILuaAPI; import dan200.computercraft.api.lua.ILuaAPIFactory; import dan200.computercraft.api.media.IMedia; @@ -165,7 +167,20 @@ public final class ComputerCraftAPI { * Register a custom {@link ILuaAPI}, which may be added onto all computers without requiring a peripheral. *

* Before implementing this interface, consider alternative methods of providing methods. It is generally preferred - * to use peripherals to provide functionality to users. + * to use peripherals to provide functionality to users. If an API is required, you may want to consider + * using {@link ILuaAPI#getModuleName()} to expose this library as a module instead of as a global. + *

+ * This may be used with {@link IComputerSystem#getComponent(ComputerComponent)} to only attach APIs to specific + * computers. For example, one can add an additional API just to turtles with the following code: + * + *

{@code
+     * ComputerCraftAPI.registerAPIFactory(computer -> {
+     *   // Read the turtle component.
+     *   var turtle = computer.getComponent(ComputerComponents.TURTLE);
+     *   // If present then add our API.
+     *   return turtle == null ? null : new MyCustomTurtleApi(turtle);
+     * });
+     * }
* * @param factory The factory for your API subclass. * @see ILuaAPIFactory diff --git a/projects/common-api/src/main/java/dan200/computercraft/api/component/AdminComputer.java b/projects/common-api/src/main/java/dan200/computercraft/api/component/AdminComputer.java new file mode 100644 index 000000000..e17539d2c --- /dev/null +++ b/projects/common-api/src/main/java/dan200/computercraft/api/component/AdminComputer.java @@ -0,0 +1,24 @@ +// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers +// +// SPDX-License-Identifier: MPL-2.0 + +package dan200.computercraft.api.component; + +import net.minecraft.commands.CommandSourceStack; +import org.jetbrains.annotations.ApiStatus; + +/** + * A computer which has permission to perform administrative/op commands, such as the command computer. + */ +@ApiStatus.NonExtendable +public interface AdminComputer { + /** + * The permission level that this computer can operate at. + * + * @return The permission level for this computer. + * @see CommandSourceStack#hasPermission(int) + */ + default int permissionLevel() { + return 2; + } +} diff --git a/projects/common-api/src/main/java/dan200/computercraft/api/component/ComputerComponent.java b/projects/common-api/src/main/java/dan200/computercraft/api/component/ComputerComponent.java new file mode 100644 index 000000000..bc516cbc5 --- /dev/null +++ b/projects/common-api/src/main/java/dan200/computercraft/api/component/ComputerComponent.java @@ -0,0 +1,48 @@ +// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers +// +// SPDX-License-Identifier: MPL-2.0 + +package dan200.computercraft.api.component; + +import dan200.computercraft.api.lua.IComputerSystem; +import dan200.computercraft.api.lua.ILuaAPIFactory; + +/** + * A component attached to a computer. + *

+ * Components provide a mechanism to attach additional data to a computer, that can then be queried with + * {@link IComputerSystem#getComponent(ComputerComponent)}. + *

+ * This is largely designed for {@linkplain ILuaAPIFactory custom APIs}, allowing APIs to read additional properties + * of the computer, such as its position. + * + * @param The type of this component. + * @see ComputerComponents The built-in components. + */ +@SuppressWarnings("UnusedTypeParameter") +public final class ComputerComponent { + private final String id; + + private ComputerComponent(String id) { + this.id = id; + } + + /** + * Create a new computer component. + *

+ * Mods typically will not need to create their own components. + * + * @param namespace The namespace of this component. This should be the mod id. + * @param id The unique id of this component. + * @param The component + * @return The newly created component. + */ + public static ComputerComponent create(String namespace, String id) { + return new ComputerComponent<>(namespace + ":" + id); + } + + @Override + public String toString() { + return "ComputerComponent(" + id + ")"; + } +} diff --git a/projects/common-api/src/main/java/dan200/computercraft/api/component/ComputerComponents.java b/projects/common-api/src/main/java/dan200/computercraft/api/component/ComputerComponents.java new file mode 100644 index 000000000..9b391b5a4 --- /dev/null +++ b/projects/common-api/src/main/java/dan200/computercraft/api/component/ComputerComponents.java @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers +// +// SPDX-License-Identifier: MPL-2.0 + +package dan200.computercraft.api.component; + +import dan200.computercraft.api.ComputerCraftAPI; +import dan200.computercraft.api.pocket.IPocketAccess; +import dan200.computercraft.api.turtle.ITurtleAccess; + +/** + * The {@link ComputerComponent}s provided by ComputerCraft. + */ +public class ComputerComponents { + /** + * The {@link ITurtleAccess} associated with a turtle. + */ + public static final ComputerComponent TURTLE = ComputerComponent.create(ComputerCraftAPI.MOD_ID, "turtle"); + + /** + * The {@link IPocketAccess} associated with a pocket computer. + */ + public static final ComputerComponent POCKET = ComputerComponent.create(ComputerCraftAPI.MOD_ID, "pocket"); + + /** + * This component is only present on "command computers", and other computers with admin capabilities. + */ + public static final ComputerComponent ADMIN_COMPUTER = ComputerComponent.create(ComputerCraftAPI.MOD_ID, "admin_computer"); +} diff --git a/projects/common-api/src/main/java/dan200/computercraft/api/lua/IComputerSystem.java b/projects/common-api/src/main/java/dan200/computercraft/api/lua/IComputerSystem.java index f851d45cd..8d6de36e6 100644 --- a/projects/common-api/src/main/java/dan200/computercraft/api/lua/IComputerSystem.java +++ b/projects/common-api/src/main/java/dan200/computercraft/api/lua/IComputerSystem.java @@ -4,7 +4,10 @@ package dan200.computercraft.api.lua; +import dan200.computercraft.api.component.ComputerComponent; import dan200.computercraft.api.peripheral.IComputerAccess; +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerLevel; import org.jetbrains.annotations.ApiStatus; import javax.annotation.Nullable; @@ -15,6 +18,24 @@ import javax.annotation.Nullable; */ @ApiStatus.NonExtendable public interface IComputerSystem extends IComputerAccess { + /** + * Get the level this computer is currently in. + *

+ * This method is not guaranteed to remain the same (even for stationary computers). + * + * @return The computer's current level. + */ + ServerLevel getLevel(); + + /** + * Get the position this computer is currently at. + *

+ * This method is not guaranteed to remain the same (even for stationary computers). + * + * @return The computer's current position. + */ + BlockPos getPosition(); + /** * Get the label for this computer. * @@ -22,4 +43,17 @@ public interface IComputerSystem extends IComputerAccess { */ @Nullable String getLabel(); + + /** + * Get a component attached to this computer. + *

+ * No component is guaranteed to be on a computer, and so this method should always be guarded with a null check. + *

+ * This method will always return the same value for a given component, and so may be cached. + * + * @param component The component to query. + * @param The type of the component. + * @return The component, if present. + */ + @Nullable T getComponent(ComputerComponent component); } diff --git a/projects/common/src/main/java/dan200/computercraft/impl/ApiFactories.java b/projects/common/src/main/java/dan200/computercraft/impl/ApiFactories.java index 388854fce..adea80257 100644 --- a/projects/common/src/main/java/dan200/computercraft/impl/ApiFactories.java +++ b/projects/common/src/main/java/dan200/computercraft/impl/ApiFactories.java @@ -14,7 +14,6 @@ import java.util.Objects; /** * The global factory for {@link ILuaAPIFactory}s. * - * @see dan200.computercraft.core.ComputerContext.Builder#apiFactories(Collection) * @see dan200.computercraft.api.ComputerCraftAPI#registerAPIFactory(ILuaAPIFactory) */ public final class ApiFactories { diff --git a/projects/common/src/main/java/dan200/computercraft/shared/ModRegistry.java b/projects/common/src/main/java/dan200/computercraft/shared/ModRegistry.java index 8c9f64b78..12446bb61 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/ModRegistry.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/ModRegistry.java @@ -6,6 +6,7 @@ package dan200.computercraft.shared; import com.mojang.brigadier.arguments.ArgumentType; import dan200.computercraft.api.ComputerCraftAPI; +import dan200.computercraft.api.component.ComputerComponents; import dan200.computercraft.api.detail.DetailProvider; import dan200.computercraft.api.detail.VanillaDetailRegistries; import dan200.computercraft.api.media.IMedia; @@ -23,6 +24,7 @@ import dan200.computercraft.shared.common.ClearColourRecipe; import dan200.computercraft.shared.common.ColourableRecipe; import dan200.computercraft.shared.common.DefaultBundledRedstoneProvider; import dan200.computercraft.shared.common.HeldItemMenu; +import dan200.computercraft.shared.computer.apis.CommandAPI; import dan200.computercraft.shared.computer.blocks.CommandComputerBlock; import dan200.computercraft.shared.computer.blocks.ComputerBlock; import dan200.computercraft.shared.computer.blocks.ComputerBlockEntity; @@ -64,6 +66,7 @@ import dan200.computercraft.shared.peripheral.speaker.SpeakerBlockEntity; import dan200.computercraft.shared.platform.PlatformHelper; import dan200.computercraft.shared.platform.RegistrationHelper; import dan200.computercraft.shared.platform.RegistryEntry; +import dan200.computercraft.shared.pocket.apis.PocketAPI; import dan200.computercraft.shared.pocket.items.PocketComputerItem; import dan200.computercraft.shared.pocket.peripherals.PocketModem; import dan200.computercraft.shared.pocket.peripherals.PocketSpeaker; @@ -73,14 +76,17 @@ import dan200.computercraft.shared.recipe.CustomShapelessRecipe; import dan200.computercraft.shared.recipe.ImpostorShapedRecipe; import dan200.computercraft.shared.recipe.ImpostorShapelessRecipe; import dan200.computercraft.shared.turtle.FurnaceRefuelHandler; +import dan200.computercraft.shared.turtle.apis.TurtleAPI; import dan200.computercraft.shared.turtle.blocks.TurtleBlock; import dan200.computercraft.shared.turtle.blocks.TurtleBlockEntity; +import dan200.computercraft.shared.turtle.core.TurtleAccessInternal; import dan200.computercraft.shared.turtle.inventory.TurtleMenu; import dan200.computercraft.shared.turtle.items.TurtleItem; import dan200.computercraft.shared.turtle.recipes.TurtleOverlayRecipe; import dan200.computercraft.shared.turtle.recipes.TurtleRecipe; import dan200.computercraft.shared.turtle.recipes.TurtleUpgradeRecipe; import dan200.computercraft.shared.turtle.upgrades.*; +import dan200.computercraft.shared.util.ComponentMap; import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.synchronization.ArgumentTypeInfo; import net.minecraft.commands.synchronization.SingletonArgumentInfo; @@ -102,6 +108,7 @@ import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.material.MapColor; import net.minecraft.world.level.storage.loot.predicates.LootItemConditionType; +import java.util.Objects; import java.util.function.BiFunction; import java.util.function.Predicate; @@ -447,6 +454,22 @@ public final class ModRegistry { return null; }); + ComputerCraftAPI.registerAPIFactory(computer -> { + var turtle = computer.getComponent(ComputerComponents.TURTLE); + var metrics = Objects.requireNonNull(computer.getComponent(ComponentMap.METRICS)); + return turtle == null ? null : new TurtleAPI(metrics, (TurtleAccessInternal) turtle); + }); + + ComputerCraftAPI.registerAPIFactory(computer -> { + var pocket = computer.getComponent(ComputerComponents.POCKET); + return pocket == null ? null : new PocketAPI(pocket); + }); + + ComputerCraftAPI.registerAPIFactory(computer -> { + var admin = computer.getComponent(ComputerComponents.ADMIN_COMPUTER); + return admin == null ? null : new CommandAPI(computer, admin); + }); + VanillaDetailRegistries.ITEM_STACK.addProvider(ItemDetails::fill); VanillaDetailRegistries.BLOCK_IN_WORLD.addProvider(BlockDetails::fill); } 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 0ff8f5e8f..b9574340b 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 @@ -6,11 +6,11 @@ package dan200.computercraft.shared.computer.apis; import com.mojang.brigadier.tree.CommandNode; import com.mojang.brigadier.tree.LiteralCommandNode; +import dan200.computercraft.api.component.AdminComputer; import dan200.computercraft.api.detail.BlockReference; import dan200.computercraft.api.detail.VanillaDetailRegistries; import dan200.computercraft.api.lua.*; import dan200.computercraft.core.Logging; -import dan200.computercraft.shared.computer.core.ServerComputer; import dan200.computercraft.shared.util.NBTUtil; import net.minecraft.commands.CommandSource; import net.minecraft.commands.CommandSourceStack; @@ -35,11 +35,13 @@ import java.util.*; public class CommandAPI implements ILuaAPI { private static final Logger LOG = LoggerFactory.getLogger(CommandAPI.class); - private final ServerComputer computer; + private final IComputerSystem computer; + private final AdminComputer admin; private final OutputReceiver receiver = new OutputReceiver(); - public CommandAPI(ServerComputer computer) { + public CommandAPI(IComputerSystem computer, AdminComputer admin) { this.computer = computer; + this.admin = admin; } @Override @@ -287,7 +289,7 @@ public class CommandAPI implements ILuaAPI { return new CommandSourceStack(receiver, Vec3.atCenterOf(computer.getPosition()), Vec2.ZERO, - computer.getLevel(), 2, + computer.getLevel(), admin.permissionLevel(), name, Component.literal(name), computer.getLevel().getServer(), null ); diff --git a/projects/common/src/main/java/dan200/computercraft/shared/computer/blocks/ComputerBlockEntity.java b/projects/common/src/main/java/dan200/computercraft/shared/computer/blocks/ComputerBlockEntity.java index db508ab71..857b0a0e2 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/computer/blocks/ComputerBlockEntity.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/computer/blocks/ComputerBlockEntity.java @@ -12,6 +12,7 @@ import dan200.computercraft.shared.computer.core.ComputerState; import dan200.computercraft.shared.computer.core.ServerComputer; import dan200.computercraft.shared.computer.inventory.ComputerMenuWithoutInventory; import dan200.computercraft.shared.config.Config; +import dan200.computercraft.shared.util.ComponentMap; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.server.level.ServerLevel; @@ -34,7 +35,8 @@ public class ComputerBlockEntity extends AbstractComputerBlockEntity { protected ServerComputer createComputer(int id) { return new ServerComputer( (ServerLevel) getLevel(), getBlockPos(), id, label, - getFamily(), Config.computerTermWidth, Config.computerTermHeight + getFamily(), Config.computerTermWidth, Config.computerTermHeight, + ComponentMap.empty() ); } diff --git a/projects/common/src/main/java/dan200/computercraft/shared/computer/core/ComputerSystem.java b/projects/common/src/main/java/dan200/computercraft/shared/computer/core/ComputerSystem.java index 737be3c0d..b69901513 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/computer/core/ComputerSystem.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/computer/core/ComputerSystem.java @@ -4,28 +4,41 @@ package dan200.computercraft.shared.computer.core; +import dan200.computercraft.api.component.ComputerComponent; import dan200.computercraft.api.lua.IComputerSystem; import dan200.computercraft.api.lua.ILuaAPIFactory; -import dan200.computercraft.api.peripheral.IComputerAccess; import dan200.computercraft.api.peripheral.IPeripheral; import dan200.computercraft.core.apis.ComputerAccess; import dan200.computercraft.core.apis.IAPIEnvironment; import dan200.computercraft.core.computer.ApiLifecycle; +import dan200.computercraft.shared.util.ComponentMap; +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerLevel; import javax.annotation.Nullable; import java.util.Map; /** - * Implementation of {@link IComputerAccess}/{@link IComputerSystem} for usage by externally registered APIs. + * Implementation of {@link IComputerSystem} for usage by externally registered APIs. * * @see ILuaAPIFactory */ -class ComputerSystem extends ComputerAccess implements IComputerSystem, ApiLifecycle { +final class ComputerSystem extends ComputerAccess implements IComputerSystem, ApiLifecycle { + private final ServerComputer computer; private final IAPIEnvironment environment; + private final ComponentMap components; - ComputerSystem(IAPIEnvironment environment) { + private boolean active; + + ComputerSystem(ServerComputer computer, IAPIEnvironment environment, ComponentMap components) { super(environment); + this.computer = computer; this.environment = environment; + this.components = components; + } + + void activate() { + active = true; } @Override @@ -38,6 +51,31 @@ class ComputerSystem extends ComputerAccess implements IComputerSystem, ApiLifec return "computer"; } + @Override + public ServerLevel getLevel() { + if (!active) { + throw new IllegalStateException(""" + Cannot access level when constructing the API. Computers are not guaranteed to stay in one place and + APIs should not rely on the level remaining constant. Instead, call this method when needed. + """.replace('\n', ' ').strip() + ); + } + return computer.getLevel(); + } + + @Override + public BlockPos getPosition() { + if (!active) { + throw new IllegalStateException(""" + Cannot access computer position when constructing the API. Computers are not guaranteed to stay in one + place and APIs should not rely on the position remaining constant. Instead, call this method when + needed. + """.replace('\n', ' ').strip() + ); + } + return computer.getPosition(); + } + @Nullable @Override public String getLabel() { @@ -55,4 +93,9 @@ class ComputerSystem extends ComputerAccess implements IComputerSystem, ApiLifec public IPeripheral getAvailablePeripheral(String name) { return null; } + + @Override + public @Nullable T getComponent(ComputerComponent component) { + return components.get(component); + } } diff --git a/projects/common/src/main/java/dan200/computercraft/shared/computer/core/ServerComputer.java b/projects/common/src/main/java/dan200/computercraft/shared/computer/core/ServerComputer.java index 22da0cb5a..808f38cbb 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/computer/core/ServerComputer.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/computer/core/ServerComputer.java @@ -5,8 +5,9 @@ package dan200.computercraft.shared.computer.core; import dan200.computercraft.api.ComputerCraftAPI; +import dan200.computercraft.api.component.AdminComputer; +import dan200.computercraft.api.component.ComputerComponents; import dan200.computercraft.api.filesystem.WritableMount; -import dan200.computercraft.api.lua.ILuaAPI; import dan200.computercraft.api.peripheral.IPeripheral; import dan200.computercraft.api.peripheral.WorkMonitor; import dan200.computercraft.core.computer.Computer; @@ -14,7 +15,6 @@ import dan200.computercraft.core.computer.ComputerEnvironment; import dan200.computercraft.core.computer.ComputerSide; import dan200.computercraft.core.metrics.MetricsObserver; import dan200.computercraft.impl.ApiFactories; -import dan200.computercraft.shared.computer.apis.CommandAPI; import dan200.computercraft.shared.computer.menu.ComputerMenu; import dan200.computercraft.shared.computer.terminal.NetworkedTerminal; import dan200.computercraft.shared.computer.terminal.TerminalState; @@ -23,6 +23,7 @@ import dan200.computercraft.shared.network.NetworkMessage; import dan200.computercraft.shared.network.client.ClientNetworkContext; import dan200.computercraft.shared.network.client.ComputerTerminalClientMessage; import dan200.computercraft.shared.network.server.ServerNetworking; +import dan200.computercraft.shared.util.ComponentMap; import net.minecraft.core.BlockPos; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.entity.player.Player; @@ -50,7 +51,8 @@ public class ServerComputer implements InputHandler, ComputerEnvironment { private int ticksSincePing; public ServerComputer( - ServerLevel level, BlockPos position, int computerID, @Nullable String label, ComputerFamily family, int terminalWidth, int terminalHeight + ServerLevel level, BlockPos position, int computerID, @Nullable String label, ComputerFamily family, int terminalWidth, int terminalHeight, + ComponentMap baseComponents ) { this.level = level; this.position = position; @@ -61,17 +63,27 @@ public class ServerComputer implements InputHandler, ComputerEnvironment { terminal = new NetworkedTerminal(terminalWidth, terminalHeight, family != ComputerFamily.NORMAL, this::markTerminalChanged); metrics = context.metrics().createMetricObserver(this); + var componentBuilder = ComponentMap.builder(); + componentBuilder.add(ComponentMap.METRICS, metrics); + if (family == ComputerFamily.COMMAND) { + componentBuilder.add(ComputerComponents.ADMIN_COMPUTER, new AdminComputer() { + }); + } + componentBuilder.add(baseComponents); + var components = componentBuilder.build(); + computer = new Computer(context.computerContext(), this, terminal, computerID); computer.setLabel(label); // Load in the externally registered APIs. for (var factory : ApiFactories.getAll()) { - var system = new ComputerSystem(computer.getAPIEnvironment()); + var system = new ComputerSystem(this, computer.getAPIEnvironment(), components); var api = factory.create(system); - if (api != null) computer.addApi(api, system); - } + if (api == null) continue; - if (family == ComputerFamily.COMMAND) addAPI(new CommandAPI(this)); + system.activate(); + computer.addApi(api, system); + } } public ComputerFamily getFamily() { @@ -225,10 +237,6 @@ public class ServerComputer implements InputHandler, ComputerEnvironment { computer.getEnvironment().setBundledRedstoneInput(side, combination); } - public void addAPI(ILuaAPI api) { - computer.addApi(api); - } - public void setPeripheral(ComputerSide side, @Nullable IPeripheral peripheral) { computer.getEnvironment().setPeripheral(side, peripheral); } diff --git a/projects/common/src/main/java/dan200/computercraft/shared/pocket/core/PocketServerComputer.java b/projects/common/src/main/java/dan200/computercraft/shared/pocket/core/PocketServerComputer.java index 87450a0c8..eabe10323 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/pocket/core/PocketServerComputer.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/pocket/core/PocketServerComputer.java @@ -4,6 +4,7 @@ package dan200.computercraft.shared.pocket.core; +import dan200.computercraft.api.component.ComputerComponents; import dan200.computercraft.shared.computer.core.ComputerFamily; import dan200.computercraft.shared.computer.core.ComputerState; import dan200.computercraft.shared.computer.core.ServerComputer; @@ -12,6 +13,7 @@ import dan200.computercraft.shared.network.client.PocketComputerDataMessage; import dan200.computercraft.shared.network.client.PocketComputerDeletedClientMessage; import dan200.computercraft.shared.network.server.ServerNetworking; import dan200.computercraft.shared.pocket.items.PocketComputerItem; +import dan200.computercraft.shared.util.ComponentMap; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.level.ChunkPos; @@ -40,7 +42,10 @@ public final class PocketServerComputer extends ServerComputer { private Set tracking = Set.of(); PocketServerComputer(PocketBrain brain, PocketHolder holder, int computerID, @Nullable String label, ComputerFamily family) { - super(holder.level(), holder.blockPos(), computerID, label, family, Config.pocketTermWidth, Config.pocketTermHeight); + super( + holder.level(), holder.blockPos(), computerID, label, family, Config.pocketTermWidth, Config.pocketTermHeight, + ComponentMap.builder().add(ComputerComponents.POCKET, brain).build() + ); this.brain = brain; } diff --git a/projects/common/src/main/java/dan200/computercraft/shared/pocket/items/PocketComputerItem.java b/projects/common/src/main/java/dan200/computercraft/shared/pocket/items/PocketComputerItem.java index 8ba9d7542..08c698751 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/pocket/items/PocketComputerItem.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/pocket/items/PocketComputerItem.java @@ -20,7 +20,6 @@ import dan200.computercraft.shared.computer.core.ServerContext; import dan200.computercraft.shared.computer.items.IComputerItem; import dan200.computercraft.shared.config.Config; import dan200.computercraft.shared.network.container.ComputerContainerData; -import dan200.computercraft.shared.pocket.apis.PocketAPI; import dan200.computercraft.shared.pocket.core.PocketBrain; import dan200.computercraft.shared.pocket.core.PocketHolder; import dan200.computercraft.shared.pocket.core.PocketServerComputer; @@ -239,8 +238,6 @@ public class PocketComputerItem extends Item implements IComputerItem, IMedia, I tag.putInt(NBT_SESSION, registry.getSessionID()); tag.putUUID(NBT_INSTANCE, computer.register()); - computer.addAPI(new PocketAPI(brain)); - // Only turn on when initially creating the computer, rather than each tick. if (isMarkedOn(stack) && holder instanceof PocketHolder.PlayerHolder) computer.turnOn(); diff --git a/projects/common/src/main/java/dan200/computercraft/shared/turtle/apis/TurtleAPI.java b/projects/common/src/main/java/dan200/computercraft/shared/turtle/apis/TurtleAPI.java index d36bfd6f2..885235d37 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/turtle/apis/TurtleAPI.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/turtle/apis/TurtleAPI.java @@ -11,7 +11,6 @@ import dan200.computercraft.api.turtle.TurtleCommandResult; import dan200.computercraft.api.turtle.TurtleSide; import dan200.computercraft.core.metrics.Metrics; import dan200.computercraft.core.metrics.MetricsObserver; -import dan200.computercraft.shared.computer.core.ServerComputer; import dan200.computercraft.shared.peripheral.generic.methods.AbstractInventoryMethods; import dan200.computercraft.shared.turtle.core.*; @@ -68,8 +67,8 @@ public class TurtleAPI implements ILuaAPI { private final MetricsObserver metrics; private final TurtleAccessInternal turtle; - public TurtleAPI(ServerComputer computer, TurtleAccessInternal turtle) { - this.metrics = computer.getMetrics(); + public TurtleAPI(MetricsObserver metrics, TurtleAccessInternal turtle) { + this.metrics = metrics; this.turtle = turtle; } 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 8844d1be0..d166b0011 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 @@ -5,6 +5,7 @@ package dan200.computercraft.shared.turtle.blocks; import com.mojang.authlib.GameProfile; +import dan200.computercraft.api.component.ComputerComponents; import dan200.computercraft.api.peripheral.IPeripheral; import dan200.computercraft.api.turtle.ITurtleAccess; import dan200.computercraft.api.turtle.ITurtleUpgrade; @@ -17,9 +18,9 @@ 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.ComponentMap; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.core.NonNullList; @@ -75,10 +76,9 @@ public class TurtleBlockEntity extends AbstractComputerBlockEntity implements Ba protected ServerComputer createComputer(int id) { var computer = new ServerComputer( (ServerLevel) getLevel(), getBlockPos(), id, label, - getFamily(), Config.turtleTermWidth, - Config.turtleTermHeight + getFamily(), Config.turtleTermWidth, Config.turtleTermHeight, + ComponentMap.builder().add(ComputerComponents.TURTLE, brain).build() ); - computer.addAPI(new TurtleAPI(computer, brain)); brain.setupComputer(computer); return computer; } diff --git a/projects/common/src/main/java/dan200/computercraft/shared/util/ComponentMap.java b/projects/common/src/main/java/dan200/computercraft/shared/util/ComponentMap.java new file mode 100644 index 000000000..cd89ba4a0 --- /dev/null +++ b/projects/common/src/main/java/dan200/computercraft/shared/util/ComponentMap.java @@ -0,0 +1,66 @@ +// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers +// +// SPDX-License-Identifier: MPL-2.0 + +package dan200.computercraft.shared.util; + +import dan200.computercraft.api.component.ComputerComponent; +import dan200.computercraft.core.metrics.MetricsObserver; + +import javax.annotation.Nullable; +import java.util.HashMap; +import java.util.Map; + +/** + * An immutable map of components. + */ +public final class ComponentMap { + public static final ComputerComponent METRICS = ComputerComponent.create("computercraft", "metrics"); + + private static final ComponentMap EMPTY = new ComponentMap(Map.of()); + + private final Map, Object> components; + + private ComponentMap(Map, Object> components) { + this.components = components; + } + + @SuppressWarnings("unchecked") + public @Nullable T get(ComputerComponent component) { + return (T) components.get(component); + } + + public static ComponentMap empty() { + return EMPTY; + } + + public static Builder builder() { + return new Builder(); + } + + public static final class Builder { + private final Map, Object> components = new HashMap<>(); + + private Builder() { + } + + public Builder add(ComputerComponent component, T value) { + addImpl(component, value); + return this; + } + + public Builder add(ComponentMap components) { + for (var component : components.components.entrySet()) addImpl(component.getKey(), component.getValue()); + return this; + } + + private void addImpl(ComputerComponent component, Object value) { + if (components.containsKey(component)) throw new IllegalArgumentException(component + " is already set"); + components.put(component, value); + } + + public ComponentMap build() { + return new ComponentMap(Map.copyOf(components)); + } + } +} From 08d4f91c8bd1dd3c0a999799cd7b4ba327e8e1d3 Mon Sep 17 00:00:00 2001 From: Jonathan Coates Date: Wed, 31 Jul 2024 07:05:08 +0100 Subject: [PATCH 19/19] Bump CC:T to 1.112.0 --- CONTRIBUTING.md | 10 ++-------- gradle.properties | 2 +- .../data/computercraft/lua/rom/help/changelog.md | 12 ++++++++++++ .../data/computercraft/lua/rom/help/whatsnew.md | 16 ++++++++-------- 4 files changed, 23 insertions(+), 17 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5fc93c314..a6e13e1f5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -12,7 +12,6 @@ If you've any other questions, [just ask the community][community] or [open an i ## Table of Contents - [Reporting issues](#reporting-issues) - - [Translations](#translations) - [Setting up a development environment](#setting-up-a-development-environment) - [Developing CC: Tweaked](#developing-cc-tweaked) - [Writing documentation](#writing-documentation) @@ -21,17 +20,13 @@ If you've any other questions, [just ask the community][community] or [open an i If you have a bug, suggestion, or other feedback, the best thing to do is [file an issue][new-issue]. When doing so, do use the issue templates - they provide a useful hint on what information to provide. -## Translations -Translations are managed through [Weblate], an online interface for managing language strings. This is synced -automatically with GitHub, so please don't submit PRs adding/changing translations! - ## Setting up a development environment In order to develop CC: Tweaked, you'll need to download the source code and then run it. - Make sure you've got the following software installed: - - Java Development Kit (JDK). This can be downloaded from [Adoptium]. + - Java Development Kit 17 (JDK). This can be downloaded from [Adoptium]. - [Git](https://git-scm.com/). - - [NodeJS][node]. + - [NodeJS 20 or later][node]. - Download CC: Tweaked's source code: ``` @@ -101,7 +96,6 @@ about how you can build on that until you've covered everything! [community]: README.md#community "Get in touch with the community." [Adoptium]: https://adoptium.net/temurin/releases?version=17 "Download OpenJDK 17" [illuaminate]: https://github.com/SquidDev/illuaminate/ "Illuaminate on GitHub" -[weblate]: https://i18n.tweaked.cc/projects/cc-tweaked/minecraft/ "CC: Tweaked weblate instance" [docs]: https://tweaked.cc/ "CC: Tweaked documentation" [ldoc]: http://stevedonovan.github.io/ldoc/ "ldoc, a Lua documentation generator." [mc-test]: https://www.youtube.com/watch?v=vXaWOJTCYNg diff --git a/gradle.properties b/gradle.properties index adf404ab3..93d5e0dba 100644 --- a/gradle.properties +++ b/gradle.properties @@ -10,7 +10,7 @@ kotlin.jvm.target.validation.mode=error # Mod properties isUnstable=false -modVersion=1.111.0 +modVersion=1.112.0 # Minecraft properties: We want to configure this here so we can read it in settings.gradle mcVersion=1.20.1 diff --git a/projects/core/src/main/resources/data/computercraft/lua/rom/help/changelog.md b/projects/core/src/main/resources/data/computercraft/lua/rom/help/changelog.md index 863f6448e..3342468a4 100644 --- a/projects/core/src/main/resources/data/computercraft/lua/rom/help/changelog.md +++ b/projects/core/src/main/resources/data/computercraft/lua/rom/help/changelog.md @@ -1,3 +1,15 @@ +# New features in CC: Tweaked 1.112.0 + +* Report a custom error when using `!` instead of `not`. +* Update several translations (zyxkad, MineKID-LP). +* Add `cc.strings.split` function. + +Several bug fixes: +* Fix `drive.getAudioTitle` returning `nil` when no disk is inserted. +* Preserve item data when upgrading pocket computers. +* Add missing bounds check to `cc.strings.wrap` (Lupus950). +* Fix modems not moving with Create contraptions. + # New features in CC: Tweaked 1.111.0 * Update several translations (Ale32bit). diff --git a/projects/core/src/main/resources/data/computercraft/lua/rom/help/whatsnew.md b/projects/core/src/main/resources/data/computercraft/lua/rom/help/whatsnew.md index 90f960dd9..65e718be5 100644 --- a/projects/core/src/main/resources/data/computercraft/lua/rom/help/whatsnew.md +++ b/projects/core/src/main/resources/data/computercraft/lua/rom/help/whatsnew.md @@ -1,13 +1,13 @@ -New features in CC: Tweaked 1.111.0 +New features in CC: Tweaked 1.112.0 -* Update several translations (Ale32bit). -* Split up turtle textures into individual textures. -* Add `r+`/`w+` support to the `io` library. -* Warn when capabilities are not registered and Optifine is installed. +* Report a custom error when using `!` instead of `not`. +* Update several translations (zyxkad, MineKID-LP). +* Add `cc.strings.split` function. Several bug fixes: -* Allow planks to be used for building in "adventure" (dan200). -* Fix `disk.getAudioTitle()` returning untranslated strings for some modded discs. -* Fix crash when right clicking turtles in spectator. +* Fix `drive.getAudioTitle` returning `nil` when no disk is inserted. +* Preserve item data when upgrading pocket computers. +* Add missing bounds check to `cc.strings.wrap` (Lupus950). +* Fix modems not moving with Create contraptions. Type "help changelog" to see the full version history.