From b9eac4e5092f73c63b4e3362e2a11c2f7f9ec0aa Mon Sep 17 00:00:00 2001 From: Jonathan Coates Date: Wed, 31 Jul 2024 06:57:38 +0100 Subject: [PATCH] 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 fc3edb4897..bf82da6cb8 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 static PacketNetwork getWirelessNetwork(MinecraftServer server) { * 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 0000000000..e17539d2c0 --- /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 0000000000..bc516cbc53 --- /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 0000000000..9b391b5a4d --- /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 f851d45cd0..8d6de36e64 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 @@ */ @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 388854fce4..adea80257f 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 @@ /** * 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 8c9f64b788..12446bb61c 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 @@ 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.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.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.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.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 static void register() { 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 0ff8f5e8f0..b9574340b3 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 @@ 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 @@ 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 @@ private CommandSourceStack getSource() { 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 db508ab71a..857b0a0e25 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.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 ComputerBlockEntity(BlockEntityType type, 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 737be3c0d8..b699015131 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 @@ public String getAttachmentName() { 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 @@ public Map getAvailablePeripherals() { 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 22da0cb5ac..808f38cbb7 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.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.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 ServerComputer( 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 void setBundledRedstoneInput(ComputerSide side, int combination) { 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 87450a0c83..eabe10323a 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.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 8ba9d75428..08c698751a 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.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 @@ private PocketBrain getOrCreateBrain(ServerLevel level, PocketHolder holder, Ite 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 d36bfd6f2f..885235d374 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.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 8844d1be00..d166b00116 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.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 @@ boolean hasMoved() { 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 0000000000..cd89ba4a02 --- /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)); + } + } +}