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));
+ }
+ }
+}