1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-01-10 09:20:28 +00:00

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.
This commit is contained in:
Jonathan Coates 2024-07-31 06:57:38 +01:00 committed by GitHub
parent dc3d8ea198
commit b9eac4e509
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 327 additions and 33 deletions

View File

@ -4,9 +4,11 @@
package dan200.computercraft.api; package dan200.computercraft.api;
import dan200.computercraft.api.component.ComputerComponent;
import dan200.computercraft.api.filesystem.Mount; import dan200.computercraft.api.filesystem.Mount;
import dan200.computercraft.api.filesystem.WritableMount; import dan200.computercraft.api.filesystem.WritableMount;
import dan200.computercraft.api.lua.GenericSource; import dan200.computercraft.api.lua.GenericSource;
import dan200.computercraft.api.lua.IComputerSystem;
import dan200.computercraft.api.lua.ILuaAPI; import dan200.computercraft.api.lua.ILuaAPI;
import dan200.computercraft.api.lua.ILuaAPIFactory; import dan200.computercraft.api.lua.ILuaAPIFactory;
import dan200.computercraft.api.media.IMedia; 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. * Register a custom {@link ILuaAPI}, which may be added onto all computers without requiring a peripheral.
* <p> * <p>
* Before implementing this interface, consider alternative methods of providing methods. It is generally preferred * 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 <em>required</em>, you may want to consider
* using {@link ILuaAPI#getModuleName()} to expose this library as a module instead of as a global.
* <p>
* 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:
*
* <pre>{@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);
* });
* }</pre>
* *
* @param factory The factory for your API subclass. * @param factory The factory for your API subclass.
* @see ILuaAPIFactory * @see ILuaAPIFactory

View File

@ -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;
}
}

View File

@ -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.
* <p>
* Components provide a mechanism to attach additional data to a computer, that can then be queried with
* {@link IComputerSystem#getComponent(ComputerComponent)}.
* <p>
* This is largely designed for {@linkplain ILuaAPIFactory custom APIs}, allowing APIs to read additional properties
* of the computer, such as its position.
*
* @param <T> The type of this component.
* @see ComputerComponents The built-in components.
*/
@SuppressWarnings("UnusedTypeParameter")
public final class ComputerComponent<T> {
private final String id;
private ComputerComponent(String id) {
this.id = id;
}
/**
* Create a new computer component.
* <p>
* 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 <T> The component
* @return The newly created component.
*/
public static <T> ComputerComponent<T> create(String namespace, String id) {
return new ComputerComponent<>(namespace + ":" + id);
}
@Override
public String toString() {
return "ComputerComponent(" + id + ")";
}
}

View File

@ -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<ITurtleAccess> TURTLE = ComputerComponent.create(ComputerCraftAPI.MOD_ID, "turtle");
/**
* The {@link IPocketAccess} associated with a pocket computer.
*/
public static final ComputerComponent<IPocketAccess> 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<AdminComputer> ADMIN_COMPUTER = ComputerComponent.create(ComputerCraftAPI.MOD_ID, "admin_computer");
}

View File

@ -4,7 +4,10 @@
package dan200.computercraft.api.lua; package dan200.computercraft.api.lua;
import dan200.computercraft.api.component.ComputerComponent;
import dan200.computercraft.api.peripheral.IComputerAccess; import dan200.computercraft.api.peripheral.IComputerAccess;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.ApiStatus;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@ -15,6 +18,24 @@ import javax.annotation.Nullable;
*/ */
@ApiStatus.NonExtendable @ApiStatus.NonExtendable
public interface IComputerSystem extends IComputerAccess { public interface IComputerSystem extends IComputerAccess {
/**
* Get the level this computer is currently in.
* <p>
* 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.
* <p>
* 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. * Get the label for this computer.
* *
@ -22,4 +43,17 @@ public interface IComputerSystem extends IComputerAccess {
*/ */
@Nullable @Nullable
String getLabel(); String getLabel();
/**
* Get a component attached to this computer.
* <p>
* No component is guaranteed to be on a computer, and so this method should always be guarded with a null check.
* <p>
* This method will always return the same value for a given component, and so may be cached.
*
* @param component The component to query.
* @param <T> The type of the component.
* @return The component, if present.
*/
<T> @Nullable T getComponent(ComputerComponent<T> component);
} }

View File

@ -14,7 +14,6 @@ import java.util.Objects;
/** /**
* The global factory for {@link ILuaAPIFactory}s. * The global factory for {@link ILuaAPIFactory}s.
* *
* @see dan200.computercraft.core.ComputerContext.Builder#apiFactories(Collection)
* @see dan200.computercraft.api.ComputerCraftAPI#registerAPIFactory(ILuaAPIFactory) * @see dan200.computercraft.api.ComputerCraftAPI#registerAPIFactory(ILuaAPIFactory)
*/ */
public final class ApiFactories { public final class ApiFactories {

View File

@ -6,6 +6,7 @@ package dan200.computercraft.shared;
import com.mojang.brigadier.arguments.ArgumentType; import com.mojang.brigadier.arguments.ArgumentType;
import dan200.computercraft.api.ComputerCraftAPI; import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.component.ComputerComponents;
import dan200.computercraft.api.detail.DetailProvider; import dan200.computercraft.api.detail.DetailProvider;
import dan200.computercraft.api.detail.VanillaDetailRegistries; import dan200.computercraft.api.detail.VanillaDetailRegistries;
import dan200.computercraft.api.media.IMedia; 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.ColourableRecipe;
import dan200.computercraft.shared.common.DefaultBundledRedstoneProvider; import dan200.computercraft.shared.common.DefaultBundledRedstoneProvider;
import dan200.computercraft.shared.common.HeldItemMenu; 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.CommandComputerBlock;
import dan200.computercraft.shared.computer.blocks.ComputerBlock; import dan200.computercraft.shared.computer.blocks.ComputerBlock;
import dan200.computercraft.shared.computer.blocks.ComputerBlockEntity; 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.PlatformHelper;
import dan200.computercraft.shared.platform.RegistrationHelper; import dan200.computercraft.shared.platform.RegistrationHelper;
import dan200.computercraft.shared.platform.RegistryEntry; 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.items.PocketComputerItem;
import dan200.computercraft.shared.pocket.peripherals.PocketModem; import dan200.computercraft.shared.pocket.peripherals.PocketModem;
import dan200.computercraft.shared.pocket.peripherals.PocketSpeaker; 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.ImpostorShapedRecipe;
import dan200.computercraft.shared.recipe.ImpostorShapelessRecipe; import dan200.computercraft.shared.recipe.ImpostorShapelessRecipe;
import dan200.computercraft.shared.turtle.FurnaceRefuelHandler; 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.TurtleBlock;
import dan200.computercraft.shared.turtle.blocks.TurtleBlockEntity; 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.inventory.TurtleMenu;
import dan200.computercraft.shared.turtle.items.TurtleItem; import dan200.computercraft.shared.turtle.items.TurtleItem;
import dan200.computercraft.shared.turtle.recipes.TurtleOverlayRecipe; import dan200.computercraft.shared.turtle.recipes.TurtleOverlayRecipe;
import dan200.computercraft.shared.turtle.recipes.TurtleRecipe; import dan200.computercraft.shared.turtle.recipes.TurtleRecipe;
import dan200.computercraft.shared.turtle.recipes.TurtleUpgradeRecipe; import dan200.computercraft.shared.turtle.recipes.TurtleUpgradeRecipe;
import dan200.computercraft.shared.turtle.upgrades.*; import dan200.computercraft.shared.turtle.upgrades.*;
import dan200.computercraft.shared.util.ComponentMap;
import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.synchronization.ArgumentTypeInfo; import net.minecraft.commands.synchronization.ArgumentTypeInfo;
import net.minecraft.commands.synchronization.SingletonArgumentInfo; 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.material.MapColor;
import net.minecraft.world.level.storage.loot.predicates.LootItemConditionType; import net.minecraft.world.level.storage.loot.predicates.LootItemConditionType;
import java.util.Objects;
import java.util.function.BiFunction; import java.util.function.BiFunction;
import java.util.function.Predicate; import java.util.function.Predicate;
@ -447,6 +454,22 @@ public final class ModRegistry {
return null; 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.ITEM_STACK.addProvider(ItemDetails::fill);
VanillaDetailRegistries.BLOCK_IN_WORLD.addProvider(BlockDetails::fill); VanillaDetailRegistries.BLOCK_IN_WORLD.addProvider(BlockDetails::fill);
} }

View File

@ -6,11 +6,11 @@ package dan200.computercraft.shared.computer.apis;
import com.mojang.brigadier.tree.CommandNode; import com.mojang.brigadier.tree.CommandNode;
import com.mojang.brigadier.tree.LiteralCommandNode; import com.mojang.brigadier.tree.LiteralCommandNode;
import dan200.computercraft.api.component.AdminComputer;
import dan200.computercraft.api.detail.BlockReference; import dan200.computercraft.api.detail.BlockReference;
import dan200.computercraft.api.detail.VanillaDetailRegistries; import dan200.computercraft.api.detail.VanillaDetailRegistries;
import dan200.computercraft.api.lua.*; import dan200.computercraft.api.lua.*;
import dan200.computercraft.core.Logging; import dan200.computercraft.core.Logging;
import dan200.computercraft.shared.computer.core.ServerComputer;
import dan200.computercraft.shared.util.NBTUtil; import dan200.computercraft.shared.util.NBTUtil;
import net.minecraft.commands.CommandSource; import net.minecraft.commands.CommandSource;
import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.CommandSourceStack;
@ -35,11 +35,13 @@ import java.util.*;
public class CommandAPI implements ILuaAPI { public class CommandAPI implements ILuaAPI {
private static final Logger LOG = LoggerFactory.getLogger(CommandAPI.class); 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(); private final OutputReceiver receiver = new OutputReceiver();
public CommandAPI(ServerComputer computer) { public CommandAPI(IComputerSystem computer, AdminComputer admin) {
this.computer = computer; this.computer = computer;
this.admin = admin;
} }
@Override @Override
@ -287,7 +289,7 @@ public class CommandAPI implements ILuaAPI {
return new CommandSourceStack(receiver, return new CommandSourceStack(receiver,
Vec3.atCenterOf(computer.getPosition()), Vec2.ZERO, Vec3.atCenterOf(computer.getPosition()), Vec2.ZERO,
computer.getLevel(), 2, computer.getLevel(), admin.permissionLevel(),
name, Component.literal(name), name, Component.literal(name),
computer.getLevel().getServer(), null computer.getLevel().getServer(), null
); );

View File

@ -12,6 +12,7 @@ import dan200.computercraft.shared.computer.core.ComputerState;
import dan200.computercraft.shared.computer.core.ServerComputer; import dan200.computercraft.shared.computer.core.ServerComputer;
import dan200.computercraft.shared.computer.inventory.ComputerMenuWithoutInventory; import dan200.computercraft.shared.computer.inventory.ComputerMenuWithoutInventory;
import dan200.computercraft.shared.config.Config; import dan200.computercraft.shared.config.Config;
import dan200.computercraft.shared.util.ComponentMap;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction; import net.minecraft.core.Direction;
import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerLevel;
@ -34,7 +35,8 @@ public class ComputerBlockEntity extends AbstractComputerBlockEntity {
protected ServerComputer createComputer(int id) { protected ServerComputer createComputer(int id) {
return new ServerComputer( return new ServerComputer(
(ServerLevel) getLevel(), getBlockPos(), id, label, (ServerLevel) getLevel(), getBlockPos(), id, label,
getFamily(), Config.computerTermWidth, Config.computerTermHeight getFamily(), Config.computerTermWidth, Config.computerTermHeight,
ComponentMap.empty()
); );
} }

View File

@ -4,28 +4,41 @@
package dan200.computercraft.shared.computer.core; package dan200.computercraft.shared.computer.core;
import dan200.computercraft.api.component.ComputerComponent;
import dan200.computercraft.api.lua.IComputerSystem; import dan200.computercraft.api.lua.IComputerSystem;
import dan200.computercraft.api.lua.ILuaAPIFactory; import dan200.computercraft.api.lua.ILuaAPIFactory;
import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.api.peripheral.IPeripheral; import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.core.apis.ComputerAccess; import dan200.computercraft.core.apis.ComputerAccess;
import dan200.computercraft.core.apis.IAPIEnvironment; import dan200.computercraft.core.apis.IAPIEnvironment;
import dan200.computercraft.core.computer.ApiLifecycle; 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 javax.annotation.Nullable;
import java.util.Map; 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 * @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 IAPIEnvironment environment;
private final ComponentMap components;
ComputerSystem(IAPIEnvironment environment) { private boolean active;
ComputerSystem(ServerComputer computer, IAPIEnvironment environment, ComponentMap components) {
super(environment); super(environment);
this.computer = computer;
this.environment = environment; this.environment = environment;
this.components = components;
}
void activate() {
active = true;
} }
@Override @Override
@ -38,6 +51,31 @@ class ComputerSystem extends ComputerAccess implements IComputerSystem, ApiLifec
return "computer"; 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 @Nullable
@Override @Override
public String getLabel() { public String getLabel() {
@ -55,4 +93,9 @@ class ComputerSystem extends ComputerAccess implements IComputerSystem, ApiLifec
public IPeripheral getAvailablePeripheral(String name) { public IPeripheral getAvailablePeripheral(String name) {
return null; return null;
} }
@Override
public <T> @Nullable T getComponent(ComputerComponent<T> component) {
return components.get(component);
}
} }

View File

@ -5,8 +5,9 @@
package dan200.computercraft.shared.computer.core; package dan200.computercraft.shared.computer.core;
import dan200.computercraft.api.ComputerCraftAPI; 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.filesystem.WritableMount;
import dan200.computercraft.api.lua.ILuaAPI;
import dan200.computercraft.api.peripheral.IPeripheral; import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.api.peripheral.WorkMonitor; import dan200.computercraft.api.peripheral.WorkMonitor;
import dan200.computercraft.core.computer.Computer; 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.computer.ComputerSide;
import dan200.computercraft.core.metrics.MetricsObserver; import dan200.computercraft.core.metrics.MetricsObserver;
import dan200.computercraft.impl.ApiFactories; import dan200.computercraft.impl.ApiFactories;
import dan200.computercraft.shared.computer.apis.CommandAPI;
import dan200.computercraft.shared.computer.menu.ComputerMenu; import dan200.computercraft.shared.computer.menu.ComputerMenu;
import dan200.computercraft.shared.computer.terminal.NetworkedTerminal; import dan200.computercraft.shared.computer.terminal.NetworkedTerminal;
import dan200.computercraft.shared.computer.terminal.TerminalState; 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.ClientNetworkContext;
import dan200.computercraft.shared.network.client.ComputerTerminalClientMessage; import dan200.computercraft.shared.network.client.ComputerTerminalClientMessage;
import dan200.computercraft.shared.network.server.ServerNetworking; import dan200.computercraft.shared.network.server.ServerNetworking;
import dan200.computercraft.shared.util.ComponentMap;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.player.Player;
@ -50,7 +51,8 @@ public class ServerComputer implements InputHandler, ComputerEnvironment {
private int ticksSincePing; private int ticksSincePing;
public ServerComputer( 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.level = level;
this.position = position; this.position = position;
@ -61,17 +63,27 @@ public class ServerComputer implements InputHandler, ComputerEnvironment {
terminal = new NetworkedTerminal(terminalWidth, terminalHeight, family != ComputerFamily.NORMAL, this::markTerminalChanged); terminal = new NetworkedTerminal(terminalWidth, terminalHeight, family != ComputerFamily.NORMAL, this::markTerminalChanged);
metrics = context.metrics().createMetricObserver(this); 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 = new Computer(context.computerContext(), this, terminal, computerID);
computer.setLabel(label); computer.setLabel(label);
// Load in the externally registered APIs. // Load in the externally registered APIs.
for (var factory : ApiFactories.getAll()) { for (var factory : ApiFactories.getAll()) {
var system = new ComputerSystem(computer.getAPIEnvironment()); var system = new ComputerSystem(this, computer.getAPIEnvironment(), components);
var api = factory.create(system); 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() { public ComputerFamily getFamily() {
@ -225,10 +237,6 @@ public class ServerComputer implements InputHandler, ComputerEnvironment {
computer.getEnvironment().setBundledRedstoneInput(side, combination); computer.getEnvironment().setBundledRedstoneInput(side, combination);
} }
public void addAPI(ILuaAPI api) {
computer.addApi(api);
}
public void setPeripheral(ComputerSide side, @Nullable IPeripheral peripheral) { public void setPeripheral(ComputerSide side, @Nullable IPeripheral peripheral) {
computer.getEnvironment().setPeripheral(side, peripheral); computer.getEnvironment().setPeripheral(side, peripheral);
} }

View File

@ -4,6 +4,7 @@
package dan200.computercraft.shared.pocket.core; 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.ComputerFamily;
import dan200.computercraft.shared.computer.core.ComputerState; import dan200.computercraft.shared.computer.core.ComputerState;
import dan200.computercraft.shared.computer.core.ServerComputer; 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.client.PocketComputerDeletedClientMessage;
import dan200.computercraft.shared.network.server.ServerNetworking; import dan200.computercraft.shared.network.server.ServerNetworking;
import dan200.computercraft.shared.pocket.items.PocketComputerItem; import dan200.computercraft.shared.pocket.items.PocketComputerItem;
import dan200.computercraft.shared.util.ComponentMap;
import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.ChunkPos;
@ -40,7 +42,10 @@ public final class PocketServerComputer extends ServerComputer {
private Set<ServerPlayer> tracking = Set.of(); private Set<ServerPlayer> tracking = Set.of();
PocketServerComputer(PocketBrain brain, PocketHolder holder, int computerID, @Nullable String label, ComputerFamily family) { 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; this.brain = brain;
} }

View File

@ -20,7 +20,6 @@ import dan200.computercraft.shared.computer.core.ServerContext;
import dan200.computercraft.shared.computer.items.IComputerItem; import dan200.computercraft.shared.computer.items.IComputerItem;
import dan200.computercraft.shared.config.Config; import dan200.computercraft.shared.config.Config;
import dan200.computercraft.shared.network.container.ComputerContainerData; 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.PocketBrain;
import dan200.computercraft.shared.pocket.core.PocketHolder; import dan200.computercraft.shared.pocket.core.PocketHolder;
import dan200.computercraft.shared.pocket.core.PocketServerComputer; 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.putInt(NBT_SESSION, registry.getSessionID());
tag.putUUID(NBT_INSTANCE, computer.register()); tag.putUUID(NBT_INSTANCE, computer.register());
computer.addAPI(new PocketAPI(brain));
// Only turn on when initially creating the computer, rather than each tick. // Only turn on when initially creating the computer, rather than each tick.
if (isMarkedOn(stack) && holder instanceof PocketHolder.PlayerHolder) computer.turnOn(); if (isMarkedOn(stack) && holder instanceof PocketHolder.PlayerHolder) computer.turnOn();

View File

@ -11,7 +11,6 @@ import dan200.computercraft.api.turtle.TurtleCommandResult;
import dan200.computercraft.api.turtle.TurtleSide; import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.core.metrics.Metrics; import dan200.computercraft.core.metrics.Metrics;
import dan200.computercraft.core.metrics.MetricsObserver; 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.peripheral.generic.methods.AbstractInventoryMethods;
import dan200.computercraft.shared.turtle.core.*; import dan200.computercraft.shared.turtle.core.*;
@ -68,8 +67,8 @@ public class TurtleAPI implements ILuaAPI {
private final MetricsObserver metrics; private final MetricsObserver metrics;
private final TurtleAccessInternal turtle; private final TurtleAccessInternal turtle;
public TurtleAPI(ServerComputer computer, TurtleAccessInternal turtle) { public TurtleAPI(MetricsObserver metrics, TurtleAccessInternal turtle) {
this.metrics = computer.getMetrics(); this.metrics = metrics;
this.turtle = turtle; this.turtle = turtle;
} }

View File

@ -5,6 +5,7 @@
package dan200.computercraft.shared.turtle.blocks; package dan200.computercraft.shared.turtle.blocks;
import com.mojang.authlib.GameProfile; import com.mojang.authlib.GameProfile;
import dan200.computercraft.api.component.ComputerComponents;
import dan200.computercraft.api.peripheral.IPeripheral; import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.api.turtle.ITurtleAccess; import dan200.computercraft.api.turtle.ITurtleAccess;
import dan200.computercraft.api.turtle.ITurtleUpgrade; 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.computer.core.ServerComputer;
import dan200.computercraft.shared.config.Config; import dan200.computercraft.shared.config.Config;
import dan200.computercraft.shared.container.BasicContainer; 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.core.TurtleBrain;
import dan200.computercraft.shared.turtle.inventory.TurtleMenu; import dan200.computercraft.shared.turtle.inventory.TurtleMenu;
import dan200.computercraft.shared.util.ComponentMap;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction; import net.minecraft.core.Direction;
import net.minecraft.core.NonNullList; import net.minecraft.core.NonNullList;
@ -75,10 +76,9 @@ public class TurtleBlockEntity extends AbstractComputerBlockEntity implements Ba
protected ServerComputer createComputer(int id) { protected ServerComputer createComputer(int id) {
var computer = new ServerComputer( var computer = new ServerComputer(
(ServerLevel) getLevel(), getBlockPos(), id, label, (ServerLevel) getLevel(), getBlockPos(), id, label,
getFamily(), Config.turtleTermWidth, getFamily(), Config.turtleTermWidth, Config.turtleTermHeight,
Config.turtleTermHeight ComponentMap.builder().add(ComputerComponents.TURTLE, brain).build()
); );
computer.addAPI(new TurtleAPI(computer, brain));
brain.setupComputer(computer); brain.setupComputer(computer);
return computer; return computer;
} }

View File

@ -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<MetricsObserver> METRICS = ComputerComponent.create("computercraft", "metrics");
private static final ComponentMap EMPTY = new ComponentMap(Map.of());
private final Map<ComputerComponent<?>, Object> components;
private ComponentMap(Map<ComputerComponent<?>, Object> components) {
this.components = components;
}
@SuppressWarnings("unchecked")
public <T> @Nullable T get(ComputerComponent<T> 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<ComputerComponent<?>, Object> components = new HashMap<>();
private Builder() {
}
public <T> Builder add(ComputerComponent<T> 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));
}
}
}