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