mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2025-11-03 15:13:07 +00:00
Merge branch 'mc-1.20.x' into mc-1.21.x
This commit is contained in:
@@ -45,6 +45,7 @@ dependencies {
|
||||
compileOnly(libs.mixin)
|
||||
compileOnly(libs.mixinExtra)
|
||||
compileOnly(libs.bundles.externalMods.common)
|
||||
compileOnly(variantOf(libs.create.forge) { classifier("slim") }) { isTransitive = false }
|
||||
clientCompileOnly(variantOf(libs.emi) { classifier("api") })
|
||||
|
||||
annotationProcessorEverywhere(libs.autoService)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -8,6 +8,7 @@ import com.mojang.brigadier.arguments.ArgumentType;
|
||||
import com.mojang.serialization.Codec;
|
||||
import com.mojang.serialization.MapCodec;
|
||||
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;
|
||||
@@ -26,6 +27,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,8 +76,10 @@ import dan200.computercraft.shared.recipe.function.CopyComponents;
|
||||
import dan200.computercraft.shared.recipe.function.RecipeFunction;
|
||||
import dan200.computercraft.shared.turtle.FurnaceRefuelHandler;
|
||||
import dan200.computercraft.shared.turtle.TurtleOverlay;
|
||||
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.TurtleUpgradeRecipe;
|
||||
@@ -82,6 +87,7 @@ import dan200.computercraft.shared.turtle.upgrades.TurtleCraftingTable;
|
||||
import dan200.computercraft.shared.turtle.upgrades.TurtleModem;
|
||||
import dan200.computercraft.shared.turtle.upgrades.TurtleSpeaker;
|
||||
import dan200.computercraft.shared.turtle.upgrades.TurtleTool;
|
||||
import dan200.computercraft.shared.util.ComponentMap;
|
||||
import dan200.computercraft.shared.util.DataComponentUtil;
|
||||
import dan200.computercraft.shared.util.NonNegativeId;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
@@ -114,6 +120,7 @@ import net.minecraft.world.level.block.state.BlockBehaviour;
|
||||
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;
|
||||
import java.util.function.UnaryOperator;
|
||||
@@ -579,6 +586,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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -295,7 +297,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
|
||||
);
|
||||
|
||||
@@ -39,7 +39,7 @@ import javax.annotation.Nullable;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
public abstract class AbstractComputerBlockEntity extends BlockEntity implements IComputerBlockEntity, Nameable, MenuProvider {
|
||||
public abstract class AbstractComputerBlockEntity extends BlockEntity implements Nameable, MenuProvider {
|
||||
private static final String NBT_ID = "ComputerId";
|
||||
private static final String NBT_LABEL = "Label";
|
||||
private static final String NBT_ON = "On";
|
||||
@@ -326,17 +326,14 @@ public abstract class AbstractComputerBlockEntity extends BlockEntity implements
|
||||
for (var dir : DirectionUtil.FACINGS) updateRedstoneTo(dir);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int getComputerID() {
|
||||
return computerID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final @Nullable String getLabel() {
|
||||
return label;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void setComputerID(int id) {
|
||||
if (getLevel().isClientSide || computerID == id) return;
|
||||
|
||||
@@ -344,7 +341,6 @@ public abstract class AbstractComputerBlockEntity extends BlockEntity implements
|
||||
BlockEntityHelpers.updateBlock(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void setLabel(@Nullable String label) {
|
||||
if (getLevel().isClientSide || Objects.equals(this.label, label)) return;
|
||||
|
||||
@@ -354,7 +350,6 @@ public abstract class AbstractComputerBlockEntity extends BlockEntity implements
|
||||
BlockEntityHelpers.updateBlock(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ComputerFamily getFamily() {
|
||||
return family;
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
// Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
|
||||
//
|
||||
// SPDX-License-Identifier: LicenseRef-CCPL
|
||||
|
||||
package dan200.computercraft.shared.computer.blocks;
|
||||
|
||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public interface IComputerBlockEntity {
|
||||
int getComputerID();
|
||||
|
||||
void setComputerID(int id);
|
||||
|
||||
@Nullable
|
||||
String getLabel();
|
||||
|
||||
void setLabel(@Nullable String label);
|
||||
|
||||
ComputerFamily getFamily();
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
// SPDX-FileCopyrightText: 2019 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
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.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 IComputerSystem} for usage by externally registered APIs.
|
||||
*
|
||||
* @see ILuaAPIFactory
|
||||
*/
|
||||
final class ComputerSystem extends ComputerAccess implements IComputerSystem, ApiLifecycle {
|
||||
private final ServerComputer computer;
|
||||
private final IAPIEnvironment environment;
|
||||
private final ComponentMap components;
|
||||
|
||||
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
|
||||
public void shutdown() {
|
||||
unmountAll();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAttachmentName() {
|
||||
return "computer";
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServerLevel getLevel() {
|
||||
if (!active) {
|
||||
throw new IllegalStateException("""
|
||||
Cannot access level when constructing the API. Computers are not guaranteed to stay in one place and
|
||||
APIs should not rely on the level remaining constant. Instead, call this method when needed.
|
||||
""".replace('\n', ' ').strip()
|
||||
);
|
||||
}
|
||||
return computer.getLevel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockPos getPosition() {
|
||||
if (!active) {
|
||||
throw new IllegalStateException("""
|
||||
Cannot access computer position when constructing the API. Computers are not guaranteed to stay in one
|
||||
place and APIs should not rely on the position remaining constant. Instead, call this method when
|
||||
needed.
|
||||
""".replace('\n', ' ').strip()
|
||||
);
|
||||
}
|
||||
return computer.getPosition();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getLabel() {
|
||||
return environment.getLabel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, IPeripheral> getAvailablePeripherals() {
|
||||
// TODO: Should this return peripherals on the current computer?
|
||||
return Map.of();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public IPeripheral getAvailablePeripheral(String name) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> @Nullable T getComponent(ComputerComponent<T> component) {
|
||||
return components.get(component);
|
||||
}
|
||||
}
|
||||
@@ -5,15 +5,16 @@
|
||||
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;
|
||||
import dan200.computercraft.core.computer.ComputerEnvironment;
|
||||
import dan200.computercraft.core.computer.ComputerSide;
|
||||
import dan200.computercraft.core.metrics.MetricsObserver;
|
||||
import dan200.computercraft.shared.computer.apis.CommandAPI;
|
||||
import dan200.computercraft.impl.ApiFactories;
|
||||
import dan200.computercraft.shared.computer.menu.ComputerMenu;
|
||||
import dan200.computercraft.shared.computer.terminal.NetworkedTerminal;
|
||||
import dan200.computercraft.shared.computer.terminal.TerminalState;
|
||||
@@ -22,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;
|
||||
@@ -48,7 +50,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;
|
||||
@@ -58,10 +61,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);
|
||||
|
||||
if (family == ComputerFamily.COMMAND) addAPI(new CommandAPI(this));
|
||||
// Load in the externally registered APIs.
|
||||
for (var factory : ApiFactories.getAll()) {
|
||||
var system = new ComputerSystem(this, computer.getAPIEnvironment(), components);
|
||||
var api = factory.create(system);
|
||||
if (api == null) continue;
|
||||
|
||||
system.activate();
|
||||
computer.addApi(api, system);
|
||||
}
|
||||
}
|
||||
|
||||
public ComputerFamily getFamily() {
|
||||
@@ -211,10 +231,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);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ import dan200.computercraft.core.lua.ILuaMachine;
|
||||
import dan200.computercraft.core.methods.MethodSupplier;
|
||||
import dan200.computercraft.core.methods.PeripheralMethod;
|
||||
import dan200.computercraft.impl.AbstractComputerCraftAPI;
|
||||
import dan200.computercraft.impl.ApiFactories;
|
||||
import dan200.computercraft.impl.GenericSources;
|
||||
import dan200.computercraft.shared.CommonHooks;
|
||||
import dan200.computercraft.shared.computer.metrics.GlobalMetrics;
|
||||
@@ -74,7 +73,6 @@ public final class ServerContext {
|
||||
.computerThreads(ConfigSpec.computerThreads.get())
|
||||
.mainThreadScheduler(mainThread)
|
||||
.luaFactory(luaMachine)
|
||||
.apiFactories(ApiFactories.getAll())
|
||||
.genericMethods(GenericSources.getAllMethods())
|
||||
.build();
|
||||
idAssigner = new IDAssigner(storageDir.resolve("ids.json"));
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
package dan200.computercraft.shared.data;
|
||||
|
||||
import dan200.computercraft.shared.ModRegistry;
|
||||
import dan200.computercraft.shared.computer.blocks.IComputerBlockEntity;
|
||||
import dan200.computercraft.shared.computer.blocks.AbstractComputerBlockEntity;
|
||||
import net.minecraft.world.level.storage.loot.LootContext;
|
||||
import net.minecraft.world.level.storage.loot.parameters.LootContextParam;
|
||||
import net.minecraft.world.level.storage.loot.parameters.LootContextParams;
|
||||
@@ -27,7 +27,7 @@ public final class HasComputerIdLootCondition implements LootItemCondition {
|
||||
@Override
|
||||
public boolean test(LootContext lootContext) {
|
||||
var tile = lootContext.getParamOrNull(LootContextParams.BLOCK_ENTITY);
|
||||
return tile instanceof IComputerBlockEntity computer && computer.getComputerID() >= 0;
|
||||
return tile instanceof AbstractComputerBlockEntity computer && computer.getComputerID() >= 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.shared.integration;
|
||||
|
||||
import com.simibubi.create.content.contraptions.BlockMovementChecks;
|
||||
import com.simibubi.create.content.contraptions.BlockMovementChecks.CheckResult;
|
||||
import dan200.computercraft.shared.peripheral.modem.wired.CableBlock;
|
||||
import dan200.computercraft.shared.peripheral.modem.wireless.WirelessModemBlock;
|
||||
|
||||
/**
|
||||
* Integration with Create.
|
||||
*/
|
||||
public final class CreateIntegration {
|
||||
public static final String ID = "create";
|
||||
|
||||
private CreateIntegration() {
|
||||
}
|
||||
|
||||
public static void setup() {
|
||||
// Allow modems to be treated as "attached" to their adjacent block.
|
||||
BlockMovementChecks.registerAttachedCheck((state, world, pos, direction) -> {
|
||||
var block = state.getBlock();
|
||||
if (block instanceof WirelessModemBlock) {
|
||||
return CheckResult.of(state.getValue(WirelessModemBlock.FACING) == direction);
|
||||
} else if (block instanceof CableBlock) {
|
||||
return CheckResult.of(state.getValue(CableBlock.MODEM).getFacing() == direction);
|
||||
} else {
|
||||
return CheckResult.PASS;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.tags.TagKey;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
|
||||
/**
|
||||
* Tags defined by external mods.
|
||||
@@ -26,9 +27,9 @@ public final class ExternalModTags {
|
||||
/**
|
||||
* Create's "brittle" tag, used to determine if this block needs to be moved before its neighbours.
|
||||
*
|
||||
* @see <a href="https://github.com/Creators-of-Create/Create/blob/mc1.20.1/dev/src/main/java/com/simibubi/create/content/contraptions/BlockMovementChecks.java">{@code BlockMovementChecks}</a>
|
||||
* @see com.simibubi.create.content.contraptions.BlockMovementChecks#isBrittle(BlockState)
|
||||
*/
|
||||
public static final TagKey<Block> CREATE_BRITTLE = make("create", "brittle");
|
||||
public static final TagKey<Block> CREATE_BRITTLE = make(CreateIntegration.ID, "brittle");
|
||||
|
||||
private static TagKey<Block> make(String mod, String name) {
|
||||
return TagKey.create(Registries.BLOCK, ResourceLocation.fromNamespaceAndPath(mod, name));
|
||||
|
||||
@@ -43,7 +43,7 @@ public record PocketComputerDataMessage(
|
||||
this(
|
||||
computer.getInstanceUUID(),
|
||||
computer.getState(),
|
||||
computer.getLight(),
|
||||
computer.getBrain().getLight(),
|
||||
sendTerminal ? Optional.of(computer.getTerminalState()) : Optional.empty()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -276,16 +276,18 @@ public abstract class SpeakerPeripheral implements IPeripheral {
|
||||
* Attempt to stream some audio data to the speaker.
|
||||
* <p>
|
||||
* This accepts a list of audio samples as amplitudes between -128 and 127. These are stored in an internal buffer
|
||||
* and played back at 48kHz. If this buffer is full, this function will return {@literal false}. You should wait for
|
||||
* a [`speaker_audio_empty`] event before trying again.
|
||||
* and played back at 48kHz. If this buffer is full, this function will return {@literal false}. Programs should
|
||||
* wait for a [`speaker_audio_empty`] event before trying to play audio again.
|
||||
* <p>
|
||||
* > [!NOTE]
|
||||
* > The speaker only buffers a single call to {@link #playAudio} at once. This means if you try to play a small
|
||||
* > number of samples, you'll have a lot of stutter. You should try to play as many samples in one call as possible
|
||||
* > (up to 128×1024), as this reduces the chances of audio stuttering or halting, especially when the server or
|
||||
* > computer is lagging.
|
||||
* The speaker only buffers a single call to {@link #playAudio} at once. This means if you try to play a small
|
||||
* number of samples, you'll have a lot of stutter. You should try to play as many samples in one call as possible
|
||||
* (up to 128×1024), as this reduces the chances of audio stuttering or halting, especially when the server or
|
||||
* computer is lagging.
|
||||
* <p>
|
||||
* [`speaker_audio`] provides a more complete guide to using speakers
|
||||
* While the speaker accepts 8-bit PCM audio, the audio stream is re-encoded before being played. This means that
|
||||
* the supplied samples may not be played out exactly.
|
||||
* <p>
|
||||
* [`speaker_audio`] provides a more complete guide to using speakers.
|
||||
*
|
||||
* @param context The Lua context.
|
||||
* @param audio The audio data to play.
|
||||
|
||||
@@ -6,10 +6,10 @@ package dan200.computercraft.shared.pocket.apis;
|
||||
|
||||
import dan200.computercraft.api.lua.ILuaAPI;
|
||||
import dan200.computercraft.api.lua.LuaFunction;
|
||||
import dan200.computercraft.api.pocket.IPocketAccess;
|
||||
import dan200.computercraft.api.pocket.IPocketUpgrade;
|
||||
import dan200.computercraft.api.upgrades.UpgradeData;
|
||||
import dan200.computercraft.impl.PocketUpgrades;
|
||||
import dan200.computercraft.shared.pocket.core.PocketServerComputer;
|
||||
import net.minecraft.core.NonNullList;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
@@ -34,10 +34,10 @@ import java.util.Objects;
|
||||
* @cc.module pocket
|
||||
*/
|
||||
public class PocketAPI implements ILuaAPI {
|
||||
private final PocketServerComputer computer;
|
||||
private final IPocketAccess pocket;
|
||||
|
||||
public PocketAPI(PocketServerComputer computer) {
|
||||
this.computer = computer;
|
||||
public PocketAPI(IPocketAccess pocket) {
|
||||
this.pocket = pocket;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -56,10 +56,10 @@ public class PocketAPI implements ILuaAPI {
|
||||
*/
|
||||
@LuaFunction(mainThread = true)
|
||||
public final Object[] equipBack() {
|
||||
var entity = computer.getEntity();
|
||||
var entity = pocket.getEntity();
|
||||
if (!(entity instanceof Player player)) return new Object[]{ false, "Cannot find player" };
|
||||
var inventory = player.getInventory();
|
||||
var previousUpgrade = computer.getUpgrade();
|
||||
var previousUpgrade = pocket.getUpgrade();
|
||||
|
||||
// Attempt to find the upgrade, starting in the main segment, and then looking in the opposite
|
||||
// one. We start from the position the item is currently in and loop round to the start.
|
||||
@@ -73,7 +73,7 @@ public class PocketAPI implements ILuaAPI {
|
||||
if (previousUpgrade != null) storeItem(player, previousUpgrade.getUpgradeItem());
|
||||
|
||||
// Set the new upgrade
|
||||
computer.setUpgrade(newUpgrade);
|
||||
pocket.setUpgrade(newUpgrade);
|
||||
|
||||
return new Object[]{ true };
|
||||
}
|
||||
@@ -87,13 +87,13 @@ public class PocketAPI implements ILuaAPI {
|
||||
*/
|
||||
@LuaFunction(mainThread = true)
|
||||
public final Object[] unequipBack() {
|
||||
var entity = computer.getEntity();
|
||||
var entity = pocket.getEntity();
|
||||
if (!(entity instanceof Player player)) return new Object[]{ false, "Cannot find player" };
|
||||
var previousUpgrade = computer.getUpgrade();
|
||||
var previousUpgrade = pocket.getUpgrade();
|
||||
|
||||
if (previousUpgrade == null) return new Object[]{ false, "Nothing to unequip" };
|
||||
|
||||
computer.setUpgrade(null);
|
||||
pocket.setUpgrade(null);
|
||||
|
||||
storeItem(player, previousUpgrade.getUpgradeItem());
|
||||
|
||||
@@ -111,7 +111,7 @@ public class PocketAPI implements ILuaAPI {
|
||||
for (var i = 0; i < inv.size(); i++) {
|
||||
var invStack = inv.get((i + start) % inv.size());
|
||||
if (!invStack.isEmpty()) {
|
||||
var newUpgrade = PocketUpgrades.instance().get(computer.getLevel().registryAccess(), invStack);
|
||||
var newUpgrade = PocketUpgrades.instance().get(pocket.getLevel().registryAccess(), invStack);
|
||||
|
||||
if (newUpgrade != null && !Objects.equals(newUpgrade, previous)) {
|
||||
// Consume an item from this stack and exit the loop
|
||||
|
||||
@@ -0,0 +1,176 @@
|
||||
// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.shared.pocket.core;
|
||||
|
||||
import dan200.computercraft.api.pocket.IPocketAccess;
|
||||
import dan200.computercraft.api.pocket.IPocketUpgrade;
|
||||
import dan200.computercraft.api.upgrades.UpgradeData;
|
||||
import dan200.computercraft.core.computer.ComputerSide;
|
||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||
import dan200.computercraft.shared.network.client.PocketComputerDataMessage;
|
||||
import dan200.computercraft.shared.network.server.ServerNetworking;
|
||||
import dan200.computercraft.shared.pocket.items.PocketComputerItem;
|
||||
import net.minecraft.core.component.DataComponentPatch;
|
||||
import net.minecraft.core.component.DataComponents;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.component.DyedItemColor;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Holds additional state for a pocket computer. This includes pocket computer upgrade,
|
||||
* {@linkplain IPocketAccess#getLight() light colour} and {@linkplain IPocketAccess#getColour() colour}.
|
||||
* <p>
|
||||
* This state is read when the brain is created, and written back to the holding item stack when the holding entity is
|
||||
* ticked (see {@link #updateItem(ItemStack)}).
|
||||
*/
|
||||
public final class PocketBrain implements IPocketAccess {
|
||||
private final PocketServerComputer computer;
|
||||
|
||||
private PocketHolder holder;
|
||||
private Vec3 position;
|
||||
|
||||
private boolean dirty = false;
|
||||
private @Nullable UpgradeData<IPocketUpgrade> upgrade;
|
||||
private int colour = -1;
|
||||
private int lightColour = -1;
|
||||
|
||||
public PocketBrain(PocketHolder holder, int computerID, @Nullable String label, ComputerFamily family, @Nullable UpgradeData<IPocketUpgrade> upgrade) {
|
||||
this.computer = new PocketServerComputer(this, holder, computerID, label, family);
|
||||
this.holder = holder;
|
||||
this.position = holder.pos();
|
||||
this.upgrade = upgrade;
|
||||
invalidatePeripheral();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the corresponding pocket computer for this brain.
|
||||
*
|
||||
* @return The pocket computer.
|
||||
*/
|
||||
public PocketServerComputer computer() {
|
||||
return computer;
|
||||
}
|
||||
|
||||
PocketHolder holder() {
|
||||
return holder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the position and holder for this computer.
|
||||
*
|
||||
* @param newHolder The new holder
|
||||
*/
|
||||
public void updateHolder(PocketHolder newHolder) {
|
||||
position = newHolder.pos();
|
||||
computer.setPosition(newHolder.level(), newHolder.blockPos());
|
||||
|
||||
var oldHolder = this.holder;
|
||||
if (holder.equals(newHolder)) return;
|
||||
holder = newHolder;
|
||||
|
||||
// If a new player has picked it up then rebroadcast the terminal to them
|
||||
var oldPlayer = oldHolder instanceof PocketHolder.PlayerHolder p ? p.entity() : null;
|
||||
if (newHolder instanceof PocketHolder.PlayerHolder player && player.entity() != oldPlayer) {
|
||||
ServerNetworking.sendToPlayer(new PocketComputerDataMessage(computer, true), player.entity());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write back properties of the pocket brain to the item.
|
||||
*
|
||||
* @param stack The pocket computer stack to update.
|
||||
* @return Whether the item was changed.
|
||||
*/
|
||||
public boolean updateItem(ItemStack stack) {
|
||||
if (!dirty) return false;
|
||||
this.dirty = false;
|
||||
|
||||
stack.set(DataComponents.DYED_COLOR, colour == -1 ? null : new DyedItemColor(colour, false));
|
||||
PocketComputerItem.setUpgrade(stack, upgrade);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServerLevel getLevel() {
|
||||
return computer.getLevel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vec3 getPosition() {
|
||||
// This method can be called from off-thread, and so we must use the cached position rather than rereading
|
||||
// from the holder.
|
||||
return position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Entity getEntity() {
|
||||
return holder instanceof PocketHolder.EntityHolder entity && holder.isValid(computer) ? entity.entity() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColour() {
|
||||
return colour;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColour(int colour) {
|
||||
if (this.colour == colour) return;
|
||||
dirty = true;
|
||||
this.colour = colour;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLight() {
|
||||
return lightColour;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLight(int colour) {
|
||||
if (colour < 0 || colour > 0xFFFFFF) colour = -1;
|
||||
lightColour = colour;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataComponentPatch getUpgradeData() {
|
||||
var upgrade = this.upgrade;
|
||||
return upgrade == null ? DataComponentPatch.EMPTY : upgrade.data();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUpgradeData(DataComponentPatch data) {
|
||||
var upgrade = this.upgrade;
|
||||
if (upgrade == null) return;
|
||||
this.upgrade = UpgradeData.of(upgrade.holder(), data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidatePeripheral() {
|
||||
var peripheral = upgrade == null ? null : upgrade.upgrade().createPeripheral(this);
|
||||
computer.setPeripheral(ComputerSide.BACK, peripheral);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable UpgradeData<IPocketUpgrade> getUpgrade() {
|
||||
return upgrade;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the upgrade for this pocket computer, also updating the item stack.
|
||||
* <p>
|
||||
* Note this method is not thread safe - it must be called from the server thread.
|
||||
*
|
||||
* @param upgrade The new upgrade to set it to, may be {@code null}.
|
||||
*/
|
||||
@Override
|
||||
public void setUpgrade(@Nullable UpgradeData<IPocketUpgrade> upgrade) {
|
||||
this.upgrade = upgrade;
|
||||
dirty = true;
|
||||
invalidatePeripheral();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.shared.pocket.core;
|
||||
|
||||
import dan200.computercraft.shared.computer.core.ServerComputer;
|
||||
import dan200.computercraft.shared.pocket.items.PocketComputerItem;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.item.ItemEntity;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
|
||||
/**
|
||||
* An object that holds a pocket computer item.
|
||||
*/
|
||||
public sealed interface PocketHolder permits PocketHolder.EntityHolder {
|
||||
/**
|
||||
* The level this holder is in.
|
||||
*
|
||||
* @return The holder's level.
|
||||
*/
|
||||
ServerLevel level();
|
||||
|
||||
/**
|
||||
* The position of this holder.
|
||||
*
|
||||
* @return The position of this holder.
|
||||
*/
|
||||
Vec3 pos();
|
||||
|
||||
/**
|
||||
* The block position of this holder.
|
||||
*
|
||||
* @return The position of this holder.
|
||||
*/
|
||||
BlockPos blockPos();
|
||||
|
||||
/**
|
||||
* Determine if this holder is still valid for a particular computer.
|
||||
*
|
||||
* @param computer The current computer.
|
||||
* @return Whether this holder is valid.
|
||||
*/
|
||||
boolean isValid(ServerComputer computer);
|
||||
|
||||
/**
|
||||
* Mark the pocket computer item as having changed.
|
||||
*/
|
||||
void setChanged();
|
||||
|
||||
/**
|
||||
* An {@link Entity} holding a pocket computer.
|
||||
*/
|
||||
sealed interface EntityHolder extends PocketHolder permits PocketHolder.PlayerHolder, PocketHolder.ItemEntityHolder {
|
||||
/**
|
||||
* Get the entity holding this pocket computer.
|
||||
*
|
||||
* @return The holding entity.
|
||||
*/
|
||||
Entity entity();
|
||||
|
||||
@Override
|
||||
default ServerLevel level() {
|
||||
return (ServerLevel) entity().level();
|
||||
}
|
||||
|
||||
@Override
|
||||
default Vec3 pos() {
|
||||
return entity().getEyePosition();
|
||||
}
|
||||
|
||||
@Override
|
||||
default BlockPos blockPos() {
|
||||
return entity().blockPosition();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A pocket computer in a player's slot.
|
||||
*
|
||||
* @param entity The current player.
|
||||
* @param slot The slot the pocket computer is in.
|
||||
*/
|
||||
record PlayerHolder(ServerPlayer entity, int slot) implements EntityHolder {
|
||||
@Override
|
||||
public boolean isValid(ServerComputer computer) {
|
||||
return entity().isAlive() && PocketComputerItem.isServerComputer(computer, entity().getInventory().getItem(this.slot()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setChanged() {
|
||||
entity.getInventory().setChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A pocket computer in an {@link ItemEntity}.
|
||||
*
|
||||
* @param entity The item entity.
|
||||
*/
|
||||
record ItemEntityHolder(ItemEntity entity) implements EntityHolder {
|
||||
@Override
|
||||
public boolean isValid(ServerComputer computer) {
|
||||
return entity().isAlive() && PocketComputerItem.isServerComputer(computer, this.entity().getItem());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setChanged() {
|
||||
entity.setItem(entity.getItem().copy());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,11 +4,7 @@
|
||||
|
||||
package dan200.computercraft.shared.pocket.core;
|
||||
|
||||
import dan200.computercraft.api.pocket.IPocketAccess;
|
||||
import dan200.computercraft.api.pocket.IPocketUpgrade;
|
||||
import dan200.computercraft.api.upgrades.UpgradeData;
|
||||
import dan200.computercraft.core.computer.ComputerSide;
|
||||
import dan200.computercraft.shared.ModRegistry;
|
||||
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;
|
||||
@@ -17,176 +13,81 @@ import dan200.computercraft.shared.network.client.PocketComputerDataMessage;
|
||||
import dan200.computercraft.shared.network.client.PocketComputerDeletedClientMessage;
|
||||
import dan200.computercraft.shared.network.server.ServerNetworking;
|
||||
import dan200.computercraft.shared.pocket.items.PocketComputerItem;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.component.DataComponentPatch;
|
||||
import net.minecraft.core.component.DataComponents;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import dan200.computercraft.shared.util.ComponentMap;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.entity.item.ItemEntity;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.component.DyedItemColor;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class PocketServerComputer extends ServerComputer implements IPocketAccess {
|
||||
private @Nullable IPocketUpgrade upgrade;
|
||||
private @Nullable Entity entity;
|
||||
private ItemStack stack = ItemStack.EMPTY;
|
||||
|
||||
private int lightColour = -1;
|
||||
/**
|
||||
* A {@link ServerComputer}-subclass for {@linkplain PocketComputerItem pocket computers}.
|
||||
* <p>
|
||||
* This extends default {@link ServerComputer} behaviour by also syncing pocket computer state to nearby players, and
|
||||
* syncing the terminal to the current player.
|
||||
* <p>
|
||||
* The actual pocket computer state (upgrade, light) is maintained in {@link PocketBrain}. The two classes are tightly
|
||||
* coupled, and maintain a reference to each other.
|
||||
*
|
||||
* @see PocketComputerDataMessage
|
||||
* @see PocketComputerDeletedClientMessage
|
||||
*/
|
||||
public final class PocketServerComputer extends ServerComputer {
|
||||
private final PocketBrain brain;
|
||||
|
||||
// The state the previous tick, used to determine if the state needs to be sent to the client.
|
||||
private int oldLightColour = -1;
|
||||
private @Nullable ComputerState oldComputerState;
|
||||
|
||||
private final Set<ServerPlayer> tracking = new HashSet<>();
|
||||
private Set<ServerPlayer> tracking = Set.of();
|
||||
|
||||
public PocketServerComputer(ServerLevel world, BlockPos position, int computerID, @Nullable String label, ComputerFamily family) {
|
||||
super(world, position, computerID, label, family, Config.pocketTermWidth, Config.pocketTermHeight);
|
||||
PocketServerComputer(PocketBrain brain, PocketHolder holder, int computerID, @Nullable String label, ComputerFamily family) {
|
||||
super(
|
||||
holder.level(), holder.blockPos(), computerID, label, family, Config.pocketTermWidth, Config.pocketTermHeight,
|
||||
ComponentMap.builder().add(ComputerComponents.POCKET, brain).build()
|
||||
);
|
||||
this.brain = brain;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Entity getEntity() {
|
||||
var entity = this.entity;
|
||||
if (entity == null || stack.isEmpty() || !entity.isAlive()) return null;
|
||||
|
||||
if (entity instanceof Player) {
|
||||
var inventory = ((Player) entity).getInventory();
|
||||
return inventory.items.contains(stack) || inventory.offhand.contains(stack) ? entity : null;
|
||||
} else if (entity instanceof LivingEntity living) {
|
||||
return living.getMainHandItem() == stack || living.getOffhandItem() == stack ? entity : null;
|
||||
} else if (entity instanceof ItemEntity itemEntity) {
|
||||
return itemEntity.getItem() == stack ? entity : null;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColour() {
|
||||
return DyedItemColor.getOrDefault(stack, -1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColour(int colour) {
|
||||
stack.set(DataComponents.DYED_COLOR, colour == -1 ? null : new DyedItemColor(colour, false));
|
||||
setItemChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLight() {
|
||||
return lightColour;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLight(int colour) {
|
||||
if (colour < 0 || colour > 0xFFFFFF) colour = -1;
|
||||
lightColour = colour;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataComponentPatch getUpgradeData() {
|
||||
var upgrade = PocketComputerItem.getUpgradeWithData(stack);
|
||||
return upgrade == null ? DataComponentPatch.EMPTY : upgrade.data();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUpgradeData(DataComponentPatch data) {
|
||||
var upgrade = PocketComputerItem.getUpgradeWithData(stack);
|
||||
if (upgrade == null) return;
|
||||
|
||||
PocketComputerItem.setUpgrade(stack, new UpgradeData<>(upgrade.holder(), data));
|
||||
setItemChanged();
|
||||
}
|
||||
|
||||
private void setItemChanged() {
|
||||
if (entity instanceof Player player) player.getInventory().setChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidatePeripheral() {
|
||||
var peripheral = upgrade == null ? null : upgrade.createPeripheral(this);
|
||||
setPeripheral(ComputerSide.BACK, peripheral);
|
||||
}
|
||||
|
||||
public @Nullable UpgradeData<IPocketUpgrade> getUpgrade() {
|
||||
return PocketComputerItem.getUpgradeWithData(stack);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the upgrade for this pocket computer, also updating the item stack.
|
||||
* <p>
|
||||
* Note this method is not thread safe - it must be called from the server thread.
|
||||
*
|
||||
* @param upgrade The new upgrade to set it to, may be {@code null}.
|
||||
*/
|
||||
public void setUpgrade(@Nullable UpgradeData<IPocketUpgrade> upgrade) {
|
||||
synchronized (this) {
|
||||
stack.set(ModRegistry.DataComponents.POCKET_UPGRADE.get(), upgrade);
|
||||
setItemChanged();
|
||||
this.upgrade = upgrade == null ? null : upgrade.upgrade();
|
||||
invalidatePeripheral();
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void updateValues(@Nullable Entity entity, ItemStack stack, @Nullable IPocketUpgrade upgrade) {
|
||||
if (entity != null) setPosition((ServerLevel) entity.level(), entity.blockPosition());
|
||||
|
||||
// If a new entity has picked it up then rebroadcast the terminal to them
|
||||
if (entity != this.entity && entity instanceof ServerPlayer) markTerminalChanged();
|
||||
|
||||
this.entity = entity;
|
||||
this.stack = stack;
|
||||
|
||||
if (this.upgrade != upgrade) {
|
||||
this.upgrade = upgrade;
|
||||
invalidatePeripheral();
|
||||
}
|
||||
public PocketBrain getBrain() {
|
||||
return brain;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void tickServer() {
|
||||
super.tickServer();
|
||||
|
||||
// Find any players which have gone missing and remove them from the tracking list.
|
||||
tracking.removeIf(player -> !player.isAlive() || player.level() != getLevel());
|
||||
// Get the new set of players tracking the current position.
|
||||
var newTracking = getLevel().getChunkSource().chunkMap.getPlayers(new ChunkPos(getPosition()), false);
|
||||
var trackingChanged = tracking.size() != newTracking.size() || !tracking.containsAll(newTracking);
|
||||
|
||||
// And now find any new players, add them to the tracking list, and broadcast state where appropriate.
|
||||
var state = getState();
|
||||
if (oldLightColour != lightColour || oldComputerState != state) {
|
||||
var light = brain.getLight();
|
||||
if (oldLightColour != light || oldComputerState != state) {
|
||||
oldComputerState = state;
|
||||
oldLightColour = lightColour;
|
||||
oldLightColour = light;
|
||||
|
||||
// Broadcast the state to all players
|
||||
tracking.addAll(getLevel().players());
|
||||
ServerNetworking.sendToPlayers(new PocketComputerDataMessage(this, false), tracking);
|
||||
} else {
|
||||
ServerNetworking.sendToPlayers(new PocketComputerDataMessage(this, false), newTracking);
|
||||
} else if (trackingChanged) {
|
||||
// Broadcast the state to new players.
|
||||
List<ServerPlayer> added = new ArrayList<>();
|
||||
for (var player : getLevel().players()) {
|
||||
if (tracking.add(player)) added.add(player);
|
||||
}
|
||||
var added = newTracking.stream().filter(x -> !tracking.contains(x)).toList();
|
||||
if (!added.isEmpty()) {
|
||||
ServerNetworking.sendToPlayers(new PocketComputerDataMessage(this, false), added);
|
||||
}
|
||||
}
|
||||
|
||||
if (trackingChanged) tracking = Set.copyOf(newTracking);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onTerminalChanged() {
|
||||
super.onTerminalChanged();
|
||||
|
||||
if (entity instanceof ServerPlayer player && entity.isAlive()) {
|
||||
if (brain.holder() instanceof PocketHolder.PlayerHolder holder && holder.isValid(this)) {
|
||||
// Broadcast the terminal to the current player.
|
||||
ServerNetworking.sendToPlayer(new PocketComputerDataMessage(this, true), player);
|
||||
ServerNetworking.sendToPlayer(new PocketComputerDataMessage(this, true), holder.entity());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,22 +14,26 @@ import dan200.computercraft.core.computer.ComputerSide;
|
||||
import dan200.computercraft.impl.PocketUpgrades;
|
||||
import dan200.computercraft.shared.ModRegistry;
|
||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||
import dan200.computercraft.shared.computer.core.ServerComputer;
|
||||
import dan200.computercraft.shared.computer.core.ServerComputerRegistry;
|
||||
import dan200.computercraft.shared.computer.core.ServerContext;
|
||||
import dan200.computercraft.shared.computer.items.ServerComputerReference;
|
||||
import dan200.computercraft.shared.config.Config;
|
||||
import dan200.computercraft.shared.network.container.ComputerContainerData;
|
||||
import dan200.computercraft.shared.pocket.apis.PocketAPI;
|
||||
import dan200.computercraft.shared.pocket.core.PocketBrain;
|
||||
import dan200.computercraft.shared.pocket.core.PocketHolder;
|
||||
import dan200.computercraft.shared.pocket.core.PocketServerComputer;
|
||||
import dan200.computercraft.shared.pocket.inventory.PocketComputerMenuProvider;
|
||||
import dan200.computercraft.shared.util.DataComponentUtil;
|
||||
import dan200.computercraft.shared.util.IDAssigner;
|
||||
import dan200.computercraft.shared.util.InventoryUtil;
|
||||
import dan200.computercraft.shared.util.NonNegativeId;
|
||||
import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.core.HolderLookup;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.Container;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.InteractionResult;
|
||||
import net.minecraft.world.InteractionResultHolder;
|
||||
@@ -53,12 +57,33 @@ public class PocketComputerItem extends Item implements IMedia {
|
||||
this.family = family;
|
||||
}
|
||||
|
||||
private boolean tick(ItemStack stack, Entity entity, PocketServerComputer computer) {
|
||||
var upgrade = getUpgrade(stack);
|
||||
/**
|
||||
* Tick a pocket computer.
|
||||
*
|
||||
* @param stack The current pocket computer stack.
|
||||
* @param holder The entity holding the pocket item.
|
||||
* @param brain The pocket computer brain.
|
||||
*/
|
||||
private void tick(ItemStack stack, PocketHolder holder, PocketBrain brain) {
|
||||
brain.updateHolder(holder);
|
||||
|
||||
computer.updateValues(entity, stack, upgrade);
|
||||
// Update pocket upgrade
|
||||
var upgrade = brain.getUpgrade();
|
||||
if (upgrade != null) upgrade.upgrade().update(brain, brain.computer().getPeripheral(ComputerSide.BACK));
|
||||
|
||||
var changed = false;
|
||||
if (updateItem(stack, brain)) holder.setChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy properties from the brain back to the item stack.
|
||||
*
|
||||
* @param stack The current pocket computer stack.
|
||||
* @param brain The current pocket brain.
|
||||
* @return Whether the item was changed.
|
||||
*/
|
||||
private boolean updateItem(ItemStack stack, PocketBrain brain) {
|
||||
var changed = brain.updateItem(stack);
|
||||
var computer = brain.computer();
|
||||
|
||||
// Sync label
|
||||
var label = computer.getLabel();
|
||||
@@ -73,21 +98,20 @@ public class PocketComputerItem extends Item implements IMedia {
|
||||
stack.set(ModRegistry.DataComponents.ON.get(), on);
|
||||
}
|
||||
|
||||
// Update pocket upgrade
|
||||
if (upgrade != null) upgrade.update(computer, computer.getPeripheral(ComputerSide.BACK));
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void inventoryTick(ItemStack stack, Level world, Entity entity, int slotNum, boolean selected) {
|
||||
if (world.isClientSide) return;
|
||||
Container inventory = entity instanceof Player player ? player.getInventory() : null;
|
||||
var computer = createServerComputer((ServerLevel) world, entity, inventory, stack);
|
||||
computer.keepAlive();
|
||||
// This (in vanilla at least) is only called for players. Don't bother to handle other entities.
|
||||
if (world.isClientSide || !(entity instanceof ServerPlayer player)) return;
|
||||
|
||||
var changed = tick(stack, entity, computer);
|
||||
if (changed && inventory != null) inventory.setChanged();
|
||||
// If we're in the inventory, create a computer and keep it alive.
|
||||
var holder = new PocketHolder.PlayerHolder(player, slotNum);
|
||||
var brain = getOrCreateBrain((ServerLevel) world, holder, stack);
|
||||
brain.computer().keepAlive();
|
||||
|
||||
tick(stack, holder, brain);
|
||||
}
|
||||
|
||||
@ForgeOverride
|
||||
@@ -95,8 +119,11 @@ public class PocketComputerItem extends Item implements IMedia {
|
||||
var level = entity.level();
|
||||
if (level.isClientSide || level.getServer() == null) return false;
|
||||
|
||||
// If we're an item entity, tick an already existing computer (as to update the position), but do not keep the
|
||||
// computer alive.
|
||||
var computer = getServerComputer(level.getServer(), stack);
|
||||
if (computer != null && tick(stack, entity, computer)) entity.setItem(stack.copy());
|
||||
if (computer != null) tick(stack, new PocketHolder.ItemEntityHolder(entity), computer.getBrain());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -104,14 +131,18 @@ public class PocketComputerItem extends Item implements IMedia {
|
||||
public InteractionResultHolder<ItemStack> use(Level world, Player player, InteractionHand hand) {
|
||||
var stack = player.getItemInHand(hand);
|
||||
if (!world.isClientSide) {
|
||||
var computer = createServerComputer((ServerLevel) world, player, player.getInventory(), stack);
|
||||
var holder = new PocketHolder.PlayerHolder((ServerPlayer) player, InventoryUtil.getHandSlot(player, hand));
|
||||
var brain = getOrCreateBrain((ServerLevel) world, holder, stack);
|
||||
var computer = brain.computer();
|
||||
computer.turnOn();
|
||||
|
||||
var stop = false;
|
||||
var upgrade = getUpgrade(stack);
|
||||
if (upgrade != null) {
|
||||
computer.updateValues(player, stack, upgrade);
|
||||
stop = upgrade.onRightClick(world, computer, computer.getPeripheral(ComputerSide.BACK));
|
||||
brain.updateHolder(holder);
|
||||
stop = upgrade.onRightClick(world, brain, computer.getPeripheral(ComputerSide.BACK));
|
||||
// Sync back just in case. We don't need to setChanged, as we'll return the item anyway.
|
||||
updateItem(stack, brain);
|
||||
}
|
||||
|
||||
if (!stop) {
|
||||
@@ -153,34 +184,42 @@ public class PocketComputerItem extends Item implements IMedia {
|
||||
|
||||
}
|
||||
|
||||
public PocketServerComputer createServerComputer(ServerLevel level, Entity entity, @Nullable Container inventory, ItemStack stack) {
|
||||
|
||||
private PocketBrain getOrCreateBrain(ServerLevel level, PocketHolder holder, ItemStack stack) {
|
||||
var registry = ServerContext.get(level.getServer()).registry();
|
||||
var computer = (PocketServerComputer) ServerComputerReference.get(stack, registry);
|
||||
if (computer == null) {
|
||||
var computerID = NonNegativeId.getOrCreate(level.getServer(), stack, ModRegistry.DataComponents.COMPUTER_ID.get(), IDAssigner.COMPUTER);
|
||||
computer = new PocketServerComputer(level, entity.blockPosition(), computerID, getLabel(stack), getFamily());
|
||||
|
||||
var instanceId = computer.register();
|
||||
stack.set(ModRegistry.DataComponents.COMPUTER.get(), new ServerComputerReference(registry.getSessionID(), instanceId));
|
||||
|
||||
var upgrade = getUpgrade(stack);
|
||||
|
||||
computer.updateValues(entity, stack, upgrade);
|
||||
computer.addAPI(new PocketAPI(computer));
|
||||
|
||||
// Only turn on when initially creating the computer, rather than each tick.
|
||||
if (isMarkedOn(stack) && entity instanceof Player) computer.turnOn();
|
||||
|
||||
if (inventory != null) inventory.setChanged();
|
||||
{
|
||||
var computer = getServerComputer(registry, stack);
|
||||
if (computer != null) return computer.getBrain();
|
||||
}
|
||||
|
||||
return computer;
|
||||
var computerID = NonNegativeId.getOrCreate(level.getServer(), stack, ModRegistry.DataComponents.COMPUTER_ID.get(), IDAssigner.COMPUTER);
|
||||
var brain = new PocketBrain(holder, computerID, getLabel(stack), getFamily(), getUpgradeWithData(stack));
|
||||
var computer = brain.computer();
|
||||
|
||||
stack.set(ModRegistry.DataComponents.COMPUTER.get(), new ServerComputerReference(registry.getSessionID(), computer.register()));
|
||||
|
||||
// Only turn on when initially creating the computer, rather than each tick.
|
||||
if (isMarkedOn(stack) && holder instanceof PocketHolder.PlayerHolder) computer.turnOn();
|
||||
|
||||
updateItem(stack, brain);
|
||||
|
||||
holder.setChanged();
|
||||
|
||||
return brain;
|
||||
}
|
||||
|
||||
public static boolean isServerComputer(ServerComputer computer, ItemStack stack) {
|
||||
return stack.getItem() instanceof PocketComputerItem
|
||||
&& getServerComputer(computer.getLevel().getServer(), stack) == computer;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static PocketServerComputer getServerComputer(ServerComputerRegistry registry, ItemStack stack) {
|
||||
return (PocketServerComputer) ServerComputerReference.get(stack, registry);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static PocketServerComputer getServerComputer(MinecraftServer server, ItemStack stack) {
|
||||
return (PocketServerComputer) ServerComputerReference.get(stack, ServerContext.get(server).registry());
|
||||
return getServerComputer(ServerContext.get(server).registry(), stack);
|
||||
}
|
||||
|
||||
public ComputerFamily getFamily() {
|
||||
|
||||
@@ -41,8 +41,6 @@ public class PocketModem extends AbstractPocketUpgrade {
|
||||
public void update(IPocketAccess access, @Nullable IPeripheral peripheral) {
|
||||
if (!(peripheral instanceof PocketModemPeripheral modem)) return;
|
||||
|
||||
modem.setLocation(access);
|
||||
|
||||
var state = modem.getModemState();
|
||||
if (state.pollChanged()) access.setLight(state.isOpen() ? 0xBA0000 : -1);
|
||||
}
|
||||
|
||||
@@ -14,31 +14,21 @@ import net.minecraft.world.phys.Vec3;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public class PocketModemPeripheral extends WirelessModemPeripheral {
|
||||
private @Nullable Level level = null;
|
||||
private Vec3 position = Vec3.ZERO;
|
||||
private final IPocketAccess access;
|
||||
|
||||
public PocketModemPeripheral(boolean advanced, IPocketAccess access) {
|
||||
super(new ModemState(), advanced);
|
||||
setLocation(access);
|
||||
}
|
||||
|
||||
void setLocation(IPocketAccess access) {
|
||||
var entity = access.getEntity();
|
||||
if (entity != null) {
|
||||
level = entity.level();
|
||||
position = entity.getEyePosition(1);
|
||||
}
|
||||
this.access = access;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Level getLevel() {
|
||||
if (level == null) throw new IllegalStateException("Using modem before position has been defined");
|
||||
return level;
|
||||
return access.getLevel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vec3 getPosition() {
|
||||
return position;
|
||||
return access.getPosition();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -8,15 +8,11 @@ import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
import dan200.computercraft.api.pocket.IPocketAccess;
|
||||
import dan200.computercraft.shared.peripheral.speaker.SpeakerPosition;
|
||||
import dan200.computercraft.shared.peripheral.speaker.UpgradeSpeakerPeripheral;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public class PocketSpeakerPeripheral extends UpgradeSpeakerPeripheral {
|
||||
private final IPocketAccess access;
|
||||
private @Nullable Level level;
|
||||
private Vec3 position = Vec3.ZERO;
|
||||
|
||||
public PocketSpeakerPeripheral(IPocketAccess access) {
|
||||
this.access = access;
|
||||
@@ -25,7 +21,7 @@ public class PocketSpeakerPeripheral extends UpgradeSpeakerPeripheral {
|
||||
@Override
|
||||
public SpeakerPosition getPosition() {
|
||||
var entity = access.getEntity();
|
||||
return entity == null ? SpeakerPosition.of(level, position) : SpeakerPosition.of(entity);
|
||||
return entity == null ? SpeakerPosition.of(access.getLevel(), access.getPosition()) : SpeakerPosition.of(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -35,12 +31,6 @@ public class PocketSpeakerPeripheral extends UpgradeSpeakerPeripheral {
|
||||
|
||||
@Override
|
||||
public void update() {
|
||||
var entity = access.getEntity();
|
||||
if (entity != null) {
|
||||
level = entity.level();
|
||||
position = entity.position();
|
||||
}
|
||||
|
||||
super.update();
|
||||
|
||||
access.setLight(madeSound() ? 0x3320fc : -1);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
@@ -21,9 +22,9 @@ import dan200.computercraft.shared.config.Config;
|
||||
import dan200.computercraft.shared.container.BasicContainer;
|
||||
import dan200.computercraft.shared.platform.PlatformHelper;
|
||||
import dan200.computercraft.shared.turtle.TurtleOverlay;
|
||||
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.HolderLookup;
|
||||
@@ -81,10 +82,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;
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,9 @@ import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.Container;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.entity.player.Inventory;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.phys.EntityHitResult;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
@@ -18,6 +21,20 @@ public final class InventoryUtil {
|
||||
private InventoryUtil() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the inventory slot for a given hand.
|
||||
*
|
||||
* @param player The player to get the slot from.
|
||||
* @param hand The hand to get.
|
||||
* @return The current slot.
|
||||
*/
|
||||
public static int getHandSlot(Player player, InteractionHand hand) {
|
||||
return switch (hand) {
|
||||
case MAIN_HAND -> player.getInventory().selected;
|
||||
case OFF_HAND -> Inventory.SLOT_OFFHAND;
|
||||
};
|
||||
}
|
||||
|
||||
public static @Nullable Container getEntityContainer(ServerLevel level, BlockPos pos, Direction side) {
|
||||
var vecStart = new Vec3(
|
||||
pos.getX() + 0.5 + 0.6 * side.getStepX(),
|
||||
|
||||
@@ -29,6 +29,7 @@ import dan200.computercraft.shared.util.WaterloggableHelpers
|
||||
import dan200.computercraft.test.core.assertArrayEquals
|
||||
import dan200.computercraft.test.core.computer.LuaTaskContext
|
||||
import dan200.computercraft.test.core.computer.getApi
|
||||
import dan200.computercraft.test.shared.ItemStackMatcher.isStack
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.core.registries.Registries
|
||||
import net.minecraft.gametest.framework.GameTest
|
||||
@@ -44,8 +45,7 @@ import net.minecraft.world.level.block.FenceBlock
|
||||
import net.minecraft.world.level.block.entity.BlockEntityType
|
||||
import net.minecraft.world.level.block.state.properties.BlockStateProperties
|
||||
import org.hamcrest.MatcherAssert.assertThat
|
||||
import org.hamcrest.Matchers.array
|
||||
import org.hamcrest.Matchers.instanceOf
|
||||
import org.hamcrest.Matchers.*
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertNotEquals
|
||||
import java.util.*
|
||||
@@ -693,6 +693,47 @@ class Turtle_Test {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* `turtle.craft` works as expected
|
||||
*/
|
||||
@GameTest
|
||||
fun Craft(helper: GameTestHelper) = helper.sequence {
|
||||
thenOnComputer {
|
||||
callPeripheral("left", "craft", 1).assertArrayEquals(true)
|
||||
}
|
||||
thenExecute {
|
||||
val turtle = helper.getBlockEntity(BlockPos(2, 2, 2), ModRegistry.BlockEntities.TURTLE_NORMAL.get())
|
||||
assertThat(
|
||||
"Inventory is as expected.",
|
||||
turtle.items,
|
||||
contains(
|
||||
isStack(Items.DIAMOND, 1), isStack(Items.DIAMOND, 1), isStack(Items.DIAMOND, 1), isStack(Items.DIAMOND_PICKAXE, 1),
|
||||
isStack(ItemStack.EMPTY), isStack(Items.STICK, 1), isStack(ItemStack.EMPTY), isStack(ItemStack.EMPTY),
|
||||
isStack(ItemStack.EMPTY), isStack(Items.STICK, 1), isStack(ItemStack.EMPTY), isStack(ItemStack.EMPTY),
|
||||
isStack(ItemStack.EMPTY), isStack(ItemStack.EMPTY), isStack(ItemStack.EMPTY), isStack(ItemStack.EMPTY),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* `turtle.equipLeft` equips a tool.
|
||||
*/
|
||||
@GameTest
|
||||
fun Equip_tool(helper: GameTestHelper) = helper.sequence {
|
||||
thenOnComputer {
|
||||
turtle.equipLeft().await().assertArrayEquals(true)
|
||||
}
|
||||
thenExecute {
|
||||
val turtle = helper.getBlockEntity(BlockPos(2, 2, 2), ModRegistry.BlockEntities.TURTLE_NORMAL.get())
|
||||
assertEquals(
|
||||
helper.level.registryAccess().registryOrThrow(ITurtleUpgrade.REGISTRY)
|
||||
.get(ResourceLocation.withDefaultNamespace("diamond_pickaxe")),
|
||||
turtle.getUpgrade(TurtleSide.LEFT),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render turtles as an item.
|
||||
*/
|
||||
|
||||
@@ -179,7 +179,6 @@ fun <T : Comparable<T>> GameTestHelper.assertBlockHas(pos: BlockPos, property: P
|
||||
fun GameTestHelper.getContainerAt(pos: BlockPos): Container =
|
||||
when (val container: BlockEntity = getBlockEntity(pos)) {
|
||||
is Container -> container
|
||||
null -> failVerbose("Expected a container at $pos, found nothing", pos)
|
||||
else -> failVerbose("Expected a container at $pos, found ${getName(container.type)}", pos)
|
||||
}
|
||||
|
||||
|
||||
137
projects/common/src/testMod/resources/data/cctest/structures/turtle_test.craft.snbt
generated
Normal file
137
projects/common/src/testMod/resources/data/cctest/structures/turtle_test.craft.snbt
generated
Normal file
@@ -0,0 +1,137 @@
|
||||
{
|
||||
DataVersion: 3465,
|
||||
size: [5, 5, 5],
|
||||
data: [
|
||||
{pos: [0, 0, 0], state: "minecraft:polished_andesite"},
|
||||
{pos: [0, 0, 1], state: "minecraft:polished_andesite"},
|
||||
{pos: [0, 0, 2], state: "minecraft:polished_andesite"},
|
||||
{pos: [0, 0, 3], state: "minecraft:polished_andesite"},
|
||||
{pos: [0, 0, 4], state: "minecraft:polished_andesite"},
|
||||
{pos: [1, 0, 0], state: "minecraft:polished_andesite"},
|
||||
{pos: [1, 0, 1], state: "minecraft:polished_andesite"},
|
||||
{pos: [1, 0, 2], state: "minecraft:polished_andesite"},
|
||||
{pos: [1, 0, 3], state: "minecraft:polished_andesite"},
|
||||
{pos: [1, 0, 4], state: "minecraft:polished_andesite"},
|
||||
{pos: [2, 0, 0], state: "minecraft:polished_andesite"},
|
||||
{pos: [2, 0, 1], state: "minecraft:polished_andesite"},
|
||||
{pos: [2, 0, 2], state: "minecraft:polished_andesite"},
|
||||
{pos: [2, 0, 3], state: "minecraft:polished_andesite"},
|
||||
{pos: [2, 0, 4], state: "minecraft:polished_andesite"},
|
||||
{pos: [3, 0, 0], state: "minecraft:polished_andesite"},
|
||||
{pos: [3, 0, 1], state: "minecraft:polished_andesite"},
|
||||
{pos: [3, 0, 2], state: "minecraft:polished_andesite"},
|
||||
{pos: [3, 0, 3], state: "minecraft:polished_andesite"},
|
||||
{pos: [3, 0, 4], state: "minecraft:polished_andesite"},
|
||||
{pos: [4, 0, 0], state: "minecraft:polished_andesite"},
|
||||
{pos: [4, 0, 1], state: "minecraft:polished_andesite"},
|
||||
{pos: [4, 0, 2], state: "minecraft:polished_andesite"},
|
||||
{pos: [4, 0, 3], state: "minecraft:polished_andesite"},
|
||||
{pos: [4, 0, 4], state: "minecraft:polished_andesite"},
|
||||
{pos: [0, 1, 0], state: "minecraft:air"},
|
||||
{pos: [0, 1, 1], state: "minecraft:air"},
|
||||
{pos: [0, 1, 2], state: "minecraft:air"},
|
||||
{pos: [0, 1, 3], state: "minecraft:air"},
|
||||
{pos: [0, 1, 4], state: "minecraft:air"},
|
||||
{pos: [1, 1, 0], state: "minecraft:air"},
|
||||
{pos: [1, 1, 1], state: "minecraft:air"},
|
||||
{pos: [1, 1, 2], state: "minecraft:air"},
|
||||
{pos: [1, 1, 3], state: "minecraft:air"},
|
||||
{pos: [1, 1, 4], state: "minecraft:air"},
|
||||
{pos: [2, 1, 0], state: "minecraft:air"},
|
||||
{pos: [2, 1, 1], state: "minecraft:air"},
|
||||
{pos: [2, 1, 2], state: "computercraft:turtle_normal{facing:north,waterlogged:false}", nbt: {ComputerId: 1, Fuel: 0, Items: [{Count: 2b, Slot: 0b, id: "minecraft:diamond"}, {Count: 2b, Slot: 1b, id: "minecraft:diamond"}, {Count: 2b, Slot: 2b, id: "minecraft:diamond"}, {Count: 2b, Slot: 5b, id: "minecraft:stick"}, {Count: 2b, Slot: 9b, id: "minecraft:stick"}], Label: "turtle_test.craft", LeftUpgrade: "minecraft:crafting_table", LeftUpgradeNbt: {}, On: 1b, Slot: 0, id: "computercraft:turtle_normal"}},
|
||||
{pos: [2, 1, 3], state: "minecraft:air"},
|
||||
{pos: [2, 1, 4], state: "minecraft:air"},
|
||||
{pos: [3, 1, 0], state: "minecraft:air"},
|
||||
{pos: [3, 1, 1], state: "minecraft:air"},
|
||||
{pos: [3, 1, 2], state: "minecraft:air"},
|
||||
{pos: [3, 1, 3], state: "minecraft:air"},
|
||||
{pos: [3, 1, 4], state: "minecraft:air"},
|
||||
{pos: [4, 1, 0], state: "minecraft:air"},
|
||||
{pos: [4, 1, 1], state: "minecraft:air"},
|
||||
{pos: [4, 1, 2], state: "minecraft:air"},
|
||||
{pos: [4, 1, 3], state: "minecraft:air"},
|
||||
{pos: [4, 1, 4], state: "minecraft:air"},
|
||||
{pos: [0, 2, 0], state: "minecraft:air"},
|
||||
{pos: [0, 2, 1], state: "minecraft:air"},
|
||||
{pos: [0, 2, 2], state: "minecraft:air"},
|
||||
{pos: [0, 2, 3], state: "minecraft:air"},
|
||||
{pos: [0, 2, 4], state: "minecraft:air"},
|
||||
{pos: [1, 2, 0], state: "minecraft:air"},
|
||||
{pos: [1, 2, 1], state: "minecraft:air"},
|
||||
{pos: [1, 2, 2], state: "minecraft:air"},
|
||||
{pos: [1, 2, 3], state: "minecraft:air"},
|
||||
{pos: [1, 2, 4], state: "minecraft:air"},
|
||||
{pos: [2, 2, 0], state: "minecraft:air"},
|
||||
{pos: [2, 2, 1], state: "minecraft:air"},
|
||||
{pos: [2, 2, 2], state: "minecraft:air"},
|
||||
{pos: [2, 2, 3], state: "minecraft:air"},
|
||||
{pos: [2, 2, 4], state: "minecraft:air"},
|
||||
{pos: [3, 2, 0], state: "minecraft:air"},
|
||||
{pos: [3, 2, 1], state: "minecraft:air"},
|
||||
{pos: [3, 2, 2], state: "minecraft:air"},
|
||||
{pos: [3, 2, 3], state: "minecraft:air"},
|
||||
{pos: [3, 2, 4], state: "minecraft:air"},
|
||||
{pos: [4, 2, 0], state: "minecraft:air"},
|
||||
{pos: [4, 2, 1], state: "minecraft:air"},
|
||||
{pos: [4, 2, 2], state: "minecraft:air"},
|
||||
{pos: [4, 2, 3], state: "minecraft:air"},
|
||||
{pos: [4, 2, 4], state: "minecraft:air"},
|
||||
{pos: [0, 3, 0], state: "minecraft:air"},
|
||||
{pos: [0, 3, 1], state: "minecraft:air"},
|
||||
{pos: [0, 3, 2], state: "minecraft:air"},
|
||||
{pos: [0, 3, 3], state: "minecraft:air"},
|
||||
{pos: [0, 3, 4], state: "minecraft:air"},
|
||||
{pos: [1, 3, 0], state: "minecraft:air"},
|
||||
{pos: [1, 3, 1], state: "minecraft:air"},
|
||||
{pos: [1, 3, 2], state: "minecraft:air"},
|
||||
{pos: [1, 3, 3], state: "minecraft:air"},
|
||||
{pos: [1, 3, 4], state: "minecraft:air"},
|
||||
{pos: [2, 3, 0], state: "minecraft:air"},
|
||||
{pos: [2, 3, 1], state: "minecraft:air"},
|
||||
{pos: [2, 3, 2], state: "minecraft:air"},
|
||||
{pos: [2, 3, 3], state: "minecraft:air"},
|
||||
{pos: [2, 3, 4], state: "minecraft:air"},
|
||||
{pos: [3, 3, 0], state: "minecraft:air"},
|
||||
{pos: [3, 3, 1], state: "minecraft:air"},
|
||||
{pos: [3, 3, 2], state: "minecraft:air"},
|
||||
{pos: [3, 3, 3], state: "minecraft:air"},
|
||||
{pos: [3, 3, 4], state: "minecraft:air"},
|
||||
{pos: [4, 3, 0], state: "minecraft:air"},
|
||||
{pos: [4, 3, 1], state: "minecraft:air"},
|
||||
{pos: [4, 3, 2], state: "minecraft:air"},
|
||||
{pos: [4, 3, 3], state: "minecraft:air"},
|
||||
{pos: [4, 3, 4], state: "minecraft:air"},
|
||||
{pos: [0, 4, 0], state: "minecraft:air"},
|
||||
{pos: [0, 4, 1], state: "minecraft:air"},
|
||||
{pos: [0, 4, 2], state: "minecraft:air"},
|
||||
{pos: [0, 4, 3], state: "minecraft:air"},
|
||||
{pos: [0, 4, 4], state: "minecraft:air"},
|
||||
{pos: [1, 4, 0], state: "minecraft:air"},
|
||||
{pos: [1, 4, 1], state: "minecraft:air"},
|
||||
{pos: [1, 4, 2], state: "minecraft:air"},
|
||||
{pos: [1, 4, 3], state: "minecraft:air"},
|
||||
{pos: [1, 4, 4], state: "minecraft:air"},
|
||||
{pos: [2, 4, 0], state: "minecraft:air"},
|
||||
{pos: [2, 4, 1], state: "minecraft:air"},
|
||||
{pos: [2, 4, 2], state: "minecraft:air"},
|
||||
{pos: [2, 4, 3], state: "minecraft:air"},
|
||||
{pos: [2, 4, 4], state: "minecraft:air"},
|
||||
{pos: [3, 4, 0], state: "minecraft:air"},
|
||||
{pos: [3, 4, 1], state: "minecraft:air"},
|
||||
{pos: [3, 4, 2], state: "minecraft:air"},
|
||||
{pos: [3, 4, 3], state: "minecraft:air"},
|
||||
{pos: [3, 4, 4], state: "minecraft:air"},
|
||||
{pos: [4, 4, 0], state: "minecraft:air"},
|
||||
{pos: [4, 4, 1], state: "minecraft:air"},
|
||||
{pos: [4, 4, 2], state: "minecraft:air"},
|
||||
{pos: [4, 4, 3], state: "minecraft:air"},
|
||||
{pos: [4, 4, 4], state: "minecraft:air"}
|
||||
],
|
||||
entities: [],
|
||||
palette: [
|
||||
"minecraft:polished_andesite",
|
||||
"minecraft:air",
|
||||
"computercraft:turtle_normal{facing:north,waterlogged:false}"
|
||||
]
|
||||
}
|
||||
137
projects/common/src/testMod/resources/data/cctest/structures/turtle_test.equip_tool.snbt
generated
Normal file
137
projects/common/src/testMod/resources/data/cctest/structures/turtle_test.equip_tool.snbt
generated
Normal file
@@ -0,0 +1,137 @@
|
||||
{
|
||||
DataVersion: 3465,
|
||||
size: [5, 5, 5],
|
||||
data: [
|
||||
{pos: [0, 0, 0], state: "minecraft:polished_andesite"},
|
||||
{pos: [0, 0, 1], state: "minecraft:polished_andesite"},
|
||||
{pos: [0, 0, 2], state: "minecraft:polished_andesite"},
|
||||
{pos: [0, 0, 3], state: "minecraft:polished_andesite"},
|
||||
{pos: [0, 0, 4], state: "minecraft:polished_andesite"},
|
||||
{pos: [1, 0, 0], state: "minecraft:polished_andesite"},
|
||||
{pos: [1, 0, 1], state: "minecraft:polished_andesite"},
|
||||
{pos: [1, 0, 2], state: "minecraft:polished_andesite"},
|
||||
{pos: [1, 0, 3], state: "minecraft:polished_andesite"},
|
||||
{pos: [1, 0, 4], state: "minecraft:polished_andesite"},
|
||||
{pos: [2, 0, 0], state: "minecraft:polished_andesite"},
|
||||
{pos: [2, 0, 1], state: "minecraft:polished_andesite"},
|
||||
{pos: [2, 0, 2], state: "minecraft:polished_andesite"},
|
||||
{pos: [2, 0, 3], state: "minecraft:polished_andesite"},
|
||||
{pos: [2, 0, 4], state: "minecraft:polished_andesite"},
|
||||
{pos: [3, 0, 0], state: "minecraft:polished_andesite"},
|
||||
{pos: [3, 0, 1], state: "minecraft:polished_andesite"},
|
||||
{pos: [3, 0, 2], state: "minecraft:polished_andesite"},
|
||||
{pos: [3, 0, 3], state: "minecraft:polished_andesite"},
|
||||
{pos: [3, 0, 4], state: "minecraft:polished_andesite"},
|
||||
{pos: [4, 0, 0], state: "minecraft:polished_andesite"},
|
||||
{pos: [4, 0, 1], state: "minecraft:polished_andesite"},
|
||||
{pos: [4, 0, 2], state: "minecraft:polished_andesite"},
|
||||
{pos: [4, 0, 3], state: "minecraft:polished_andesite"},
|
||||
{pos: [4, 0, 4], state: "minecraft:polished_andesite"},
|
||||
{pos: [0, 1, 0], state: "minecraft:air"},
|
||||
{pos: [0, 1, 1], state: "minecraft:air"},
|
||||
{pos: [0, 1, 2], state: "minecraft:air"},
|
||||
{pos: [0, 1, 3], state: "minecraft:air"},
|
||||
{pos: [0, 1, 4], state: "minecraft:air"},
|
||||
{pos: [1, 1, 0], state: "minecraft:air"},
|
||||
{pos: [1, 1, 1], state: "minecraft:air"},
|
||||
{pos: [1, 1, 2], state: "minecraft:air"},
|
||||
{pos: [1, 1, 3], state: "minecraft:air"},
|
||||
{pos: [1, 1, 4], state: "minecraft:air"},
|
||||
{pos: [2, 1, 0], state: "minecraft:air"},
|
||||
{pos: [2, 1, 1], state: "minecraft:air"},
|
||||
{pos: [2, 1, 2], state: "computercraft:turtle_normal{facing:north,waterlogged:false}", nbt: {ComputerId: 1, Fuel: 0, Items: [{Count: 1b, Slot: 0b, id: "minecraft:diamond_pickaxe"}], Label: "turtle_test.equip_tool", On: 1b, Slot: 0, id: "computercraft:turtle_normal"}},
|
||||
{pos: [2, 1, 3], state: "minecraft:air"},
|
||||
{pos: [2, 1, 4], state: "minecraft:air"},
|
||||
{pos: [3, 1, 0], state: "minecraft:air"},
|
||||
{pos: [3, 1, 1], state: "minecraft:air"},
|
||||
{pos: [3, 1, 2], state: "minecraft:air"},
|
||||
{pos: [3, 1, 3], state: "minecraft:air"},
|
||||
{pos: [3, 1, 4], state: "minecraft:air"},
|
||||
{pos: [4, 1, 0], state: "minecraft:air"},
|
||||
{pos: [4, 1, 1], state: "minecraft:air"},
|
||||
{pos: [4, 1, 2], state: "minecraft:air"},
|
||||
{pos: [4, 1, 3], state: "minecraft:air"},
|
||||
{pos: [4, 1, 4], state: "minecraft:air"},
|
||||
{pos: [0, 2, 0], state: "minecraft:air"},
|
||||
{pos: [0, 2, 1], state: "minecraft:air"},
|
||||
{pos: [0, 2, 2], state: "minecraft:air"},
|
||||
{pos: [0, 2, 3], state: "minecraft:air"},
|
||||
{pos: [0, 2, 4], state: "minecraft:air"},
|
||||
{pos: [1, 2, 0], state: "minecraft:air"},
|
||||
{pos: [1, 2, 1], state: "minecraft:air"},
|
||||
{pos: [1, 2, 2], state: "minecraft:air"},
|
||||
{pos: [1, 2, 3], state: "minecraft:air"},
|
||||
{pos: [1, 2, 4], state: "minecraft:air"},
|
||||
{pos: [2, 2, 0], state: "minecraft:air"},
|
||||
{pos: [2, 2, 1], state: "minecraft:air"},
|
||||
{pos: [2, 2, 2], state: "minecraft:air"},
|
||||
{pos: [2, 2, 3], state: "minecraft:air"},
|
||||
{pos: [2, 2, 4], state: "minecraft:air"},
|
||||
{pos: [3, 2, 0], state: "minecraft:air"},
|
||||
{pos: [3, 2, 1], state: "minecraft:air"},
|
||||
{pos: [3, 2, 2], state: "minecraft:air"},
|
||||
{pos: [3, 2, 3], state: "minecraft:air"},
|
||||
{pos: [3, 2, 4], state: "minecraft:air"},
|
||||
{pos: [4, 2, 0], state: "minecraft:air"},
|
||||
{pos: [4, 2, 1], state: "minecraft:air"},
|
||||
{pos: [4, 2, 2], state: "minecraft:air"},
|
||||
{pos: [4, 2, 3], state: "minecraft:air"},
|
||||
{pos: [4, 2, 4], state: "minecraft:air"},
|
||||
{pos: [0, 3, 0], state: "minecraft:air"},
|
||||
{pos: [0, 3, 1], state: "minecraft:air"},
|
||||
{pos: [0, 3, 2], state: "minecraft:air"},
|
||||
{pos: [0, 3, 3], state: "minecraft:air"},
|
||||
{pos: [0, 3, 4], state: "minecraft:air"},
|
||||
{pos: [1, 3, 0], state: "minecraft:air"},
|
||||
{pos: [1, 3, 1], state: "minecraft:air"},
|
||||
{pos: [1, 3, 2], state: "minecraft:air"},
|
||||
{pos: [1, 3, 3], state: "minecraft:air"},
|
||||
{pos: [1, 3, 4], state: "minecraft:air"},
|
||||
{pos: [2, 3, 0], state: "minecraft:air"},
|
||||
{pos: [2, 3, 1], state: "minecraft:air"},
|
||||
{pos: [2, 3, 2], state: "minecraft:air"},
|
||||
{pos: [2, 3, 3], state: "minecraft:air"},
|
||||
{pos: [2, 3, 4], state: "minecraft:air"},
|
||||
{pos: [3, 3, 0], state: "minecraft:air"},
|
||||
{pos: [3, 3, 1], state: "minecraft:air"},
|
||||
{pos: [3, 3, 2], state: "minecraft:air"},
|
||||
{pos: [3, 3, 3], state: "minecraft:air"},
|
||||
{pos: [3, 3, 4], state: "minecraft:air"},
|
||||
{pos: [4, 3, 0], state: "minecraft:air"},
|
||||
{pos: [4, 3, 1], state: "minecraft:air"},
|
||||
{pos: [4, 3, 2], state: "minecraft:air"},
|
||||
{pos: [4, 3, 3], state: "minecraft:air"},
|
||||
{pos: [4, 3, 4], state: "minecraft:air"},
|
||||
{pos: [0, 4, 0], state: "minecraft:air"},
|
||||
{pos: [0, 4, 1], state: "minecraft:air"},
|
||||
{pos: [0, 4, 2], state: "minecraft:air"},
|
||||
{pos: [0, 4, 3], state: "minecraft:air"},
|
||||
{pos: [0, 4, 4], state: "minecraft:air"},
|
||||
{pos: [1, 4, 0], state: "minecraft:air"},
|
||||
{pos: [1, 4, 1], state: "minecraft:air"},
|
||||
{pos: [1, 4, 2], state: "minecraft:air"},
|
||||
{pos: [1, 4, 3], state: "minecraft:air"},
|
||||
{pos: [1, 4, 4], state: "minecraft:air"},
|
||||
{pos: [2, 4, 0], state: "minecraft:air"},
|
||||
{pos: [2, 4, 1], state: "minecraft:air"},
|
||||
{pos: [2, 4, 2], state: "minecraft:air"},
|
||||
{pos: [2, 4, 3], state: "minecraft:air"},
|
||||
{pos: [2, 4, 4], state: "minecraft:air"},
|
||||
{pos: [3, 4, 0], state: "minecraft:air"},
|
||||
{pos: [3, 4, 1], state: "minecraft:air"},
|
||||
{pos: [3, 4, 2], state: "minecraft:air"},
|
||||
{pos: [3, 4, 3], state: "minecraft:air"},
|
||||
{pos: [3, 4, 4], state: "minecraft:air"},
|
||||
{pos: [4, 4, 0], state: "minecraft:air"},
|
||||
{pos: [4, 4, 1], state: "minecraft:air"},
|
||||
{pos: [4, 4, 2], state: "minecraft:air"},
|
||||
{pos: [4, 4, 3], state: "minecraft:air"},
|
||||
{pos: [4, 4, 4], state: "minecraft:air"}
|
||||
],
|
||||
entities: [],
|
||||
palette: [
|
||||
"minecraft:polished_andesite",
|
||||
"minecraft:air",
|
||||
"computercraft:turtle_normal{facing:north,waterlogged:false}"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user