1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2024-06-25 22:53:22 +00:00

Attach capabilities to our BlockEntities externally

Previously we overrode getCapability on our BlockEntity implementations.
While this is the Proper way to do things, it's obviously impossible to
do in a multi-loader environment.

We now subscribe to the AttachCapabilitiesEvent and add our caps that
way. This does require[^1] some nasty invalidation of caps in a couple
of places, which I'm not wild about.

[^1]: I'm not actually sure it does: we invalidate peripherals and wired
elements when neighbours change, so the explicit invalidation probably
isn't useful.
This commit is contained in:
Jonathan Coates 2022-11-06 22:26:07 +00:00
parent 53abe5e56e
commit e8f9cdd221
No known key found for this signature in database
GPG Key ID: B9E431FF07C98D06
23 changed files with 651 additions and 458 deletions

View File

@ -113,6 +113,7 @@ minecraft {
mods.register("cctest") {
source(sourceSets["testMod"])
source(sourceSets["testFixtures"])
// FIXME: We need this for running in-dev but not from Gradle: source(project(":core").sourceSets.testFixtures.get())
}
}

View File

@ -64,7 +64,7 @@ public static ResourceLocation getTurtleOverlayModel(ResourceLocation overlay, b
@Override
public void render(@Nonnull TileTurtle turtle, float partialTicks, @Nonnull PoseStack transform, @Nonnull MultiBufferSource buffers, int lightmapCoord, int overlayLight) {
// Render the label
var label = turtle.createProxy().getLabel();
var label = turtle.getLabel();
var hit = renderer.cameraHitResult;
if (label != null && hit.getType() == HitResult.Type.BLOCK && turtle.getBlockPos().equals(((BlockHitResult) hit).getBlockPos())) {
var mc = Minecraft.getInstance();

View File

@ -0,0 +1,86 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.shared;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.shared.computer.blocks.TileComputer;
import dan200.computercraft.shared.peripheral.commandblock.CommandBlockPeripheral;
import dan200.computercraft.shared.peripheral.diskdrive.TileDiskDrive;
import dan200.computercraft.shared.peripheral.modem.wired.TileCable;
import dan200.computercraft.shared.peripheral.modem.wired.TileWiredModemFull;
import dan200.computercraft.shared.peripheral.modem.wireless.TileWirelessModem;
import dan200.computercraft.shared.peripheral.monitor.TileMonitor;
import dan200.computercraft.shared.peripheral.printer.TilePrinter;
import dan200.computercraft.shared.peripheral.speaker.TileSpeaker;
import dan200.computercraft.shared.turtle.blocks.TileTurtle;
import dan200.computercraft.shared.util.CapabilityProvider;
import dan200.computercraft.shared.util.SidedCapabilityProvider;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.CommandBlockEntity;
import net.minecraftforge.event.AttachCapabilitiesEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.items.wrapper.InvWrapper;
import net.minecraftforge.items.wrapper.SidedInvWrapper;
import static dan200.computercraft.shared.Capabilities.CAPABILITY_PERIPHERAL;
import static net.minecraftforge.common.capabilities.ForgeCapabilities.ITEM_HANDLER;
@Mod.EventBusSubscriber(modid = ComputerCraftAPI.MOD_ID)
public class ForgeCommonHooks {
private static final ResourceLocation PERIPHERAL = new ResourceLocation(ComputerCraftAPI.MOD_ID, "peripheral");
private static final ResourceLocation WIRED_ELEMENT = new ResourceLocation(ComputerCraftAPI.MOD_ID, "wired_node");
private static final ResourceLocation INVENTORY = new ResourceLocation(ComputerCraftAPI.MOD_ID, "inventory");
/**
* Attach capabilities to our block entities.
*
* @param event The {@link AttachCapabilitiesEvent} event.
*/
@SubscribeEvent
public static void onCapability(AttachCapabilitiesEvent<BlockEntity> event) {
var blockEntity = event.getObject();
if (blockEntity instanceof TileComputer computer) {
CapabilityProvider.attach(event, PERIPHERAL, CAPABILITY_PERIPHERAL, computer::peripheral);
} else if (blockEntity instanceof TileTurtle turtle) {
CapabilityProvider.attach(event, INVENTORY, ITEM_HANDLER, () -> new InvWrapper(turtle));
var peripheral = CapabilityProvider.attach(event, PERIPHERAL, CAPABILITY_PERIPHERAL, turtle::peripheral);
turtle.onMoved(peripheral::invalidate);
} else if (blockEntity instanceof TileDiskDrive diskDrive) {
CapabilityProvider.attach(event, INVENTORY, ITEM_HANDLER, () -> new InvWrapper(diskDrive));
CapabilityProvider.attach(event, PERIPHERAL, CAPABILITY_PERIPHERAL, diskDrive::peripheral);
} else if (blockEntity instanceof TileCable cable) {
var peripheralHandler = SidedCapabilityProvider.attach(event, PERIPHERAL, Capabilities.CAPABILITY_PERIPHERAL, cable::getPeripheral);
var elementHandler = SidedCapabilityProvider.attach(event, WIRED_ELEMENT, Capabilities.CAPABILITY_WIRED_ELEMENT, cable::getWiredElement);
cable.onModemChanged(() -> {
peripheralHandler.invalidate();
elementHandler.invalidate();
});
} else if (blockEntity instanceof TileWiredModemFull modem) {
SidedCapabilityProvider.attach(event, PERIPHERAL, Capabilities.CAPABILITY_PERIPHERAL, modem::getPeripheral);
CapabilityProvider.attach(event, WIRED_ELEMENT, Capabilities.CAPABILITY_WIRED_ELEMENT, modem::getElement);
} else if (blockEntity instanceof TileWirelessModem modem) {
var peripheral = SidedCapabilityProvider.attach(event, PERIPHERAL, CAPABILITY_PERIPHERAL, modem::getPeripheral);
modem.onModemChanged(peripheral::invalidate);
} else if (blockEntity instanceof TileMonitor monitor) {
CapabilityProvider.attach(event, PERIPHERAL, CAPABILITY_PERIPHERAL, monitor::peripheral);
} else if (blockEntity instanceof TileSpeaker speaker) {
CapabilityProvider.attach(event, PERIPHERAL, CAPABILITY_PERIPHERAL, speaker::peripheral);
} else if (blockEntity instanceof TilePrinter printer) {
CapabilityProvider.attach(event, PERIPHERAL, Capabilities.CAPABILITY_PERIPHERAL, printer::peripheral);
// We don't need to invalidate here as the block's can't be rotated on the X axis!
SidedCapabilityProvider.attach(
event, INVENTORY, ITEM_HANDLER,
s -> s == null ? new InvWrapper(printer) : new SidedInvWrapper(printer, s)
);
} else if (ComputerCraft.enableCommandBlock && blockEntity instanceof CommandBlockEntity commandBlock) {
CapabilityProvider.attach(event, PERIPHERAL, CAPABILITY_PERIPHERAL, () -> new CommandBlockPeripheral(commandBlock));
}
}
}

View File

@ -22,11 +22,11 @@
*/
public class ComputerPeripheral implements IPeripheral {
private final String type;
private final ComputerProxy computer;
private final TileComputerBase owner;
public ComputerPeripheral(String type, ComputerProxy computer) {
public ComputerPeripheral(String type, TileComputerBase owner) {
this.type = type;
this.computer = computer;
this.owner = owner;
}
@Nonnull
@ -40,7 +40,12 @@ public String getType() {
*/
@LuaFunction
public final void turnOn() {
computer.turnOn();
var computer = owner.getServerComputer();
if (computer == null) {
owner.startOn = true;
} else {
computer.turnOn();
}
}
/**
@ -48,7 +53,12 @@ public final void turnOn() {
*/
@LuaFunction
public final void shutdown() {
computer.shutdown();
var computer = owner.getServerComputer();
if (computer == null) {
owner.startOn = false;
} else {
computer.shutdown();
}
}
/**
@ -56,7 +66,12 @@ public final void shutdown() {
*/
@LuaFunction
public final void reboot() {
computer.reboot();
var computer = owner.getServerComputer();
if (computer == null) {
owner.startOn = true;
} else {
computer.reboot();
}
}
/**
@ -67,7 +82,8 @@ public final void reboot() {
*/
@LuaFunction
public final int getID() {
return computer.getID();
var computer = owner.getServerComputer();
return computer == null ? owner.getComputerID() : computer.getID();
}
/**
@ -77,7 +93,8 @@ public final int getID() {
*/
@LuaFunction
public final boolean isOn() {
return computer.isOn();
var computer = owner.getServerComputer();
return computer != null && computer.isOn();
}
/**
@ -89,17 +106,18 @@ public final boolean isOn() {
@Nullable
@LuaFunction
public final String getLabel() {
return computer.getLabel();
var computer = owner.getServerComputer();
return computer == null ? owner.getLabel() : computer.getLabel();
}
@Override
public boolean equals(IPeripheral other) {
return other instanceof ComputerPeripheral && computer == ((ComputerPeripheral) other).computer;
return other instanceof ComputerPeripheral computerPeripheral && owner == computerPeripheral.owner;
}
@Nonnull
@Override
public Object getTarget() {
return computer.getTile();
return owner;
}
}

View File

@ -1,72 +0,0 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.shared.computer.blocks;
import dan200.computercraft.shared.computer.core.ServerComputer;
import java.util.function.Supplier;
/**
* A proxy object for computer objects, delegating to {@link ServerComputer} or {@link TileComputer} where appropriate.
*/
public final class ComputerProxy {
private final Supplier<TileComputerBase> get;
public ComputerProxy(Supplier<TileComputerBase> get) {
this.get = get;
}
TileComputerBase getTile() {
return get.get();
}
public void turnOn() {
var tile = getTile();
var computer = tile.getServerComputer();
if (computer == null) {
tile.startOn = true;
} else {
computer.turnOn();
}
}
public void shutdown() {
var tile = getTile();
var computer = tile.getServerComputer();
if (computer == null) {
tile.startOn = false;
} else {
computer.shutdown();
}
}
public void reboot() {
var tile = getTile();
var computer = tile.getServerComputer();
if (computer == null) {
tile.startOn = true;
} else {
computer.reboot();
}
}
public int getID() {
var tile = getTile();
var computer = tile.getServerComputer();
return computer == null ? tile.getComputerID() : computer.getID();
}
public boolean isOn() {
var computer = getTile().getServerComputer();
return computer != null && computer.isOn();
}
public String getLabel() {
var tile = getTile();
var computer = tile.getServerComputer();
return computer == null ? tile.getLabel() : computer.getLabel();
}
}

View File

@ -13,7 +13,6 @@
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.util.CapabilityUtil;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.server.level.ServerLevel;
@ -22,17 +21,12 @@
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.common.util.LazyOptional;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import static dan200.computercraft.shared.Capabilities.CAPABILITY_PERIPHERAL;
public class TileComputer extends TileComputerBase {
private ComputerProxy proxy;
private LazyOptional<IPeripheral> peripheral;
private IPeripheral peripheral;
public TileComputer(BlockEntityType<? extends TileComputer> type, BlockPos pos, BlockState state, ComputerFamily family) {
super(type, pos, state, family);
@ -81,25 +75,8 @@ public AbstractContainerMenu createMenu(int id, @Nonnull Inventory inventory, @N
return new ComputerMenuWithoutInventory(ModRegistry.Menus.COMPUTER.get(), id, inventory, this::isUsableByPlayer, createServerComputer(), getFamily());
}
@Nonnull
@Override
public <T> LazyOptional<T> getCapability(@Nonnull Capability<T> cap, @Nullable Direction side) {
if (cap == CAPABILITY_PERIPHERAL) {
if (peripheral == null) {
peripheral = LazyOptional.of(() -> {
if (proxy == null) proxy = new ComputerProxy(() -> this);
return new ComputerPeripheral("computer", proxy);
});
}
return peripheral.cast();
}
return super.getCapability(cap, side);
}
@Override
public void invalidateCaps() {
super.invalidateCaps();
peripheral = CapabilityUtil.invalidate(peripheral);
public IPeripheral peripheral() {
if (peripheral != null) return peripheral;
return peripheral = new ComputerPeripheral("computer", this);
}
}

View File

@ -5,26 +5,12 @@
*/
package dan200.computercraft.shared.peripheral.commandblock;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.shared.computer.apis.CommandAPI;
import dan200.computercraft.shared.util.CapabilityUtil;
import net.minecraft.core.Direction;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.CommandBlockEntity;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.common.capabilities.ICapabilityProvider;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.event.AttachCapabilitiesEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import static dan200.computercraft.shared.Capabilities.CAPABILITY_PERIPHERAL;
/**
* This peripheral allows you to interact with command blocks.
@ -36,12 +22,8 @@
*
* @cc.module command
*/
@Mod.EventBusSubscriber
public class CommandBlockPeripheral implements IPeripheral, ICapabilityProvider {
private static final ResourceLocation CAP_ID = new ResourceLocation(ComputerCraft.MOD_ID, "command_block");
public class CommandBlockPeripheral implements IPeripheral {
private final CommandBlockEntity commandBlock;
private LazyOptional<IPeripheral> self;
public CommandBlockPeripheral(CommandBlockEntity commandBlock) {
this.commandBlock = commandBlock;
@ -98,28 +80,4 @@ public boolean equals(IPeripheral other) {
public Object getTarget() {
return commandBlock;
}
@Nonnull
@Override
public <T> LazyOptional<T> getCapability(@Nonnull Capability<T> cap, @Nullable Direction side) {
if (cap == CAPABILITY_PERIPHERAL) {
if (self == null) self = LazyOptional.of(() -> this);
return self.cast();
}
return LazyOptional.empty();
}
private void invalidate() {
self = CapabilityUtil.invalidate(self);
}
@SubscribeEvent
public static void onCapability(AttachCapabilitiesEvent<BlockEntity> event) {
var tile = event.getObject();
if (ComputerCraft.enableCommandBlock && tile instanceof CommandBlockEntity commandBlock) {
var peripheral = new CommandBlockPeripheral(commandBlock);
event.addCapability(CAP_ID, peripheral);
event.addListener(peripheral::invalidate);
}
}
}

View File

@ -12,7 +12,6 @@
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.shared.MediaProviders;
import dan200.computercraft.shared.common.TileGeneric;
import dan200.computercraft.shared.util.CapabilityUtil;
import dan200.computercraft.shared.util.DefaultInventory;
import dan200.computercraft.shared.util.InventoryUtil;
import dan200.computercraft.shared.util.RecordUtil;
@ -30,19 +29,12 @@
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.common.capabilities.ForgeCapabilities;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.items.IItemHandlerModifiable;
import net.minecraftforge.items.wrapper.InvWrapper;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.HashMap;
import java.util.Map;
import static dan200.computercraft.shared.Capabilities.CAPABILITY_PERIPHERAL;
public final class TileDiskDrive extends TileGeneric implements DefaultInventory, Nameable, MenuProvider {
private static final String NBT_NAME = "CustomName";
private static final String NBT_ITEM = "Item";
@ -58,8 +50,7 @@ private static class MountInfo {
@Nonnull
private ItemStack diskStack = ItemStack.EMPTY;
private LazyOptional<IItemHandlerModifiable> itemHandlerCap;
private LazyOptional<IPeripheral> peripheralCap;
private IPeripheral peripheral;
private IMount diskMount = null;
private boolean recordQueued = false;
@ -77,13 +68,6 @@ public void destroy() {
if (recordPlaying) stopRecord();
}
@Override
public void invalidateCaps() {
super.invalidateCaps();
itemHandlerCap = CapabilityUtil.invalidate(itemHandlerCap);
peripheralCap = CapabilityUtil.invalidate(peripheralCap);
}
@Override
public boolean isUsable(Player player) {
return super.isUsable(player) && BaseContainerBlockEntity.canUnlock(player, lockCode, getDisplayName());
@ -434,22 +418,6 @@ private void stopRecord() {
RecordUtil.playRecord(null, null, getLevel(), getBlockPos());
}
@Nonnull
@Override
public <T> LazyOptional<T> getCapability(@Nonnull Capability<T> cap, @Nullable final Direction side) {
if (cap == ForgeCapabilities.ITEM_HANDLER) {
if (itemHandlerCap == null) itemHandlerCap = LazyOptional.of(() -> new InvWrapper(this));
return itemHandlerCap.cast();
}
if (cap == CAPABILITY_PERIPHERAL) {
if (peripheralCap == null) peripheralCap = LazyOptional.of(() -> new DiskDrivePeripheral(this));
return peripheralCap.cast();
}
return super.getCapability(cap, side);
}
@Override
public boolean hasCustomName() {
return customName != null;
@ -478,4 +446,9 @@ public Component getDisplayName() {
public AbstractContainerMenu createMenu(int id, @Nonnull Inventory inventory, @Nonnull Player player) {
return new ContainerDiskDrive(id, inventory, this);
}
public IPeripheral peripheral() {
if (peripheral != null) return peripheral;
return peripheral = new DiskDrivePeripheral(this);
}
}

View File

@ -14,7 +14,6 @@
import dan200.computercraft.shared.command.text.ChatHelpers;
import dan200.computercraft.shared.common.TileGeneric;
import dan200.computercraft.shared.peripheral.modem.ModemState;
import dan200.computercraft.shared.util.CapabilityUtil;
import dan200.computercraft.shared.util.DirectionUtil;
import dan200.computercraft.shared.util.TickScheduler;
import net.minecraft.core.BlockPos;
@ -31,7 +30,6 @@
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.common.util.NonNullConsumer;
@ -39,9 +37,6 @@
import javax.annotation.Nullable;
import java.util.Collections;
import static dan200.computercraft.shared.Capabilities.CAPABILITY_PERIPHERAL;
import static dan200.computercraft.shared.Capabilities.CAPABILITY_WIRED_ELEMENT;
public class TileCable extends TileGeneric {
private static final String NBT_PERIPHERAL_ENABLED = "PeirpheralAccess";
@ -72,13 +67,13 @@ protected void detachPeripheral(String name) {
private boolean invalidPeripheral;
private boolean peripheralAccessAllowed;
private final WiredModemLocalPeripheral peripheral = new WiredModemLocalPeripheral(this::queueRefreshPeripheral);
private @Nullable Runnable modemChanged;
private boolean destroyed = false;
private boolean connectionsFormed = false;
private final WiredModemElement cable = new CableElement();
private LazyOptional<IWiredElement> elementCap;
private final IWiredNode node = cable.getNode();
private final TickScheduler.Token tickToken = new TickScheduler.Token(this);
private final WiredModemPeripheral modem = new WiredModemPeripheral(
@ -103,7 +98,6 @@ public Object getTarget() {
return TileCable.this;
}
};
private LazyOptional<IPeripheral> modemCap;
private final NonNullConsumer<LazyOptional<IWiredElement>> connectedNodeChanged = x -> connectionsChanged();
@ -139,13 +133,6 @@ public void setRemoved() {
onRemove();
}
@Override
public void invalidateCaps() {
super.invalidateCaps();
elementCap = CapabilityUtil.invalidate(elementCap);
modemCap = CapabilityUtil.invalidate(modemCap);
}
@Override
public void clearRemoved() {
super.clearRemoved(); // TODO: Replace with onLoad
@ -157,11 +144,9 @@ public void clearRemoved() {
public void setBlockState(@Nonnull BlockState state) {
var direction = getMaybeDirection();
super.setBlockState(state);
if (getMaybeDirection() != direction) {
// We invalidate both the modem and element if the modem's direction is different.
modemCap = CapabilityUtil.invalidate(modemCap);
elementCap = CapabilityUtil.invalidate(elementCap);
}
// We invalidate both the modem and element if the modem's direction is different.
if (getMaybeDirection() != direction && modemChanged != null) modemChanged.run();
}
@Nullable
@ -316,7 +301,7 @@ void connectionsChanged() {
void modemChanged() {
// Tell anyone who cares that the connection state has changed
elementCap = CapabilityUtil.invalidate(elementCap);
if (modemChanged != null) modemChanged.run();
if (getLevel().isClientSide) return;
@ -359,22 +344,20 @@ private void updateConnectedPeripherals() {
node.updatePeripherals(peripherals);
}
@Nonnull
@Override
public <T> LazyOptional<T> getCapability(@Nonnull Capability<T> capability, @Nullable Direction side) {
if (capability == CAPABILITY_WIRED_ELEMENT) {
if (destroyed || !BlockCable.canConnectIn(getBlockState(), side)) return LazyOptional.empty();
if (elementCap == null) elementCap = LazyOptional.of(() -> cable);
return elementCap.cast();
}
@Nullable
public IWiredElement getWiredElement(@Nullable Direction direction) {
if (destroyed) return null;
return direction == null || BlockCable.canConnectIn(getBlockState(), direction) ? cable : null;
}
if (capability == CAPABILITY_PERIPHERAL) {
if (side != null && getMaybeDirection() != side) return LazyOptional.empty();
if (modemCap == null) modemCap = LazyOptional.of(() -> modem);
return modemCap.cast();
}
@Nullable
public IPeripheral getPeripheral(@Nullable Direction direction) {
if (destroyed) return null;
return direction == null || getMaybeDirection() == direction ? modem : null;
}
return super.getCapability(capability, side);
public void onModemChanged(Runnable callback) {
modemChanged = callback;
}
boolean hasCable() {

View File

@ -13,9 +13,7 @@
import dan200.computercraft.shared.command.text.ChatHelpers;
import dan200.computercraft.shared.common.TileGeneric;
import dan200.computercraft.shared.peripheral.modem.ModemState;
import dan200.computercraft.shared.util.CapabilityUtil;
import dan200.computercraft.shared.util.DirectionUtil;
import dan200.computercraft.shared.util.SidedCaps;
import dan200.computercraft.shared.util.TickScheduler;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
@ -29,7 +27,6 @@
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.common.util.NonNullConsumer;
@ -37,8 +34,6 @@
import javax.annotation.Nullable;
import java.util.*;
import static dan200.computercraft.shared.Capabilities.CAPABILITY_PERIPHERAL;
import static dan200.computercraft.shared.Capabilities.CAPABILITY_WIRED_ELEMENT;
import static dan200.computercraft.shared.peripheral.modem.wired.BlockWiredModemFull.MODEM_ON;
import static dan200.computercraft.shared.peripheral.modem.wired.BlockWiredModemFull.PERIPHERAL_ON;
@ -82,7 +77,6 @@ public Vec3 getPosition() {
}
private final WiredModemPeripheral[] modems = new WiredModemPeripheral[6];
private final SidedCaps<IPeripheral> modemCaps = SidedCaps.ofNonNull(this::getPeripheral);
private boolean peripheralAccessAllowed = false;
private final WiredModemLocalPeripheral[] peripherals = new WiredModemLocalPeripheral[6];
@ -93,7 +87,6 @@ public Vec3 getPosition() {
private final TickScheduler.Token tickToken = new TickScheduler.Token(this);
private final ModemState modemState = new ModemState(() -> TickScheduler.schedule(tickToken));
private final WiredModemElement element = new FullElement(this);
private LazyOptional<IWiredElement> elementCap;
private final IWiredNode node = element.getNode();
private final NonNullConsumer<LazyOptional<IWiredElement>> connectedNodeChanged = x -> connectionsChanged();
@ -130,13 +123,6 @@ public void onChunkUnloaded() {
doRemove();
}
@Override
public void invalidateCaps() {
super.invalidateCaps();
elementCap = CapabilityUtil.invalidate(elementCap);
modemCaps.invalidate();
}
@Override
public void setRemoved() {
super.setRemoved();
@ -327,24 +313,14 @@ private void updateConnectedPeripherals() {
node.updatePeripherals(peripherals);
}
@Nonnull
@Override
public <T> LazyOptional<T> getCapability(@Nonnull Capability<T> capability, @Nullable Direction side) {
if (capability == CAPABILITY_WIRED_ELEMENT) {
if (elementCap == null) elementCap = LazyOptional.of(() -> element);
return elementCap.cast();
}
if (capability == CAPABILITY_PERIPHERAL) return modemCaps.get(side).cast();
return super.getCapability(capability, side);
}
public IWiredElement getElement() {
return element;
}
private WiredModemPeripheral getPeripheral(@Nonnull Direction side) {
@Nullable
public WiredModemPeripheral getPeripheral(@Nullable Direction side) {
if (side == null) return null;
var peripheral = modems[side.ordinal()];
if (peripheral != null) return peripheral;

View File

@ -9,7 +9,6 @@
import dan200.computercraft.shared.common.TileGeneric;
import dan200.computercraft.shared.peripheral.modem.ModemPeripheral;
import dan200.computercraft.shared.peripheral.modem.ModemState;
import dan200.computercraft.shared.util.CapabilityUtil;
import dan200.computercraft.shared.util.TickScheduler;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
@ -17,14 +16,10 @@
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.common.util.LazyOptional;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import static dan200.computercraft.shared.Capabilities.CAPABILITY_PERIPHERAL;
public class TileWirelessModem extends TileGeneric {
private static class Peripheral extends WirelessModemPeripheral {
private final TileWirelessModem entity;
@ -62,7 +57,7 @@ public Object getTarget() {
private final ModemPeripheral modem;
private boolean destroyed = false;
private LazyOptional<IPeripheral> modemCap;
private @Nullable Runnable modemChanged;
private final TickScheduler.Token tickToken = new TickScheduler.Token(this);
public TileWirelessModem(BlockEntityType<? extends TileWirelessModem> type, BlockPos pos, BlockState state, boolean advanced) {
@ -90,7 +85,7 @@ public void destroy() {
public void setBlockState(@Nonnull BlockState state) {
var direction = getDirection();
super.setBlockState(state);
if (getDirection() != direction) modemCap = CapabilityUtil.invalidate(modemCap);
if (getDirection() != direction && modemChanged != null) modemChanged.run();
}
@Override
@ -111,15 +106,13 @@ private void updateBlockState() {
}
}
@Nonnull
@Override
public <T> LazyOptional<T> getCapability(@Nonnull Capability<T> cap, @Nullable Direction side) {
if (cap == CAPABILITY_PERIPHERAL) {
if (side != null && getDirection() != side) return LazyOptional.empty();
if (modemCap == null) modemCap = LazyOptional.of(() -> modem);
return modemCap.cast();
}
@Nullable
public IPeripheral getPeripheral(@Nullable Direction direction) {
if (destroyed) return null;
return direction == null || getDirection() == direction ? modem : null;
}
return super.getCapability(cap, side);
public void onModemChanged(Runnable callback) {
modemChanged = callback;
}
}

View File

@ -12,7 +12,6 @@
import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.shared.common.TileGeneric;
import dan200.computercraft.shared.computer.terminal.TerminalState;
import dan200.computercraft.shared.util.CapabilityUtil;
import dan200.computercraft.shared.util.TickScheduler;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
@ -25,8 +24,6 @@
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.common.util.LazyOptional;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@ -34,8 +31,6 @@
import java.util.Set;
import java.util.function.Consumer;
import static dan200.computercraft.shared.Capabilities.CAPABILITY_PERIPHERAL;
public class TileMonitor extends TileGeneric {
public static final double RENDER_BORDER = 2.0 / 16.0;
public static final double RENDER_MARGIN = 0.5 / 16.0;
@ -51,7 +46,6 @@ public class TileMonitor extends TileGeneric {
private ServerMonitor serverMonitor;
private ClientMonitor clientMonitor;
private MonitorPeripheral peripheral;
private LazyOptional<IPeripheral> peripheralCap;
private final Set<IComputerAccess> computers = new HashSet<>();
private boolean needsUpdate = false;
@ -160,24 +154,6 @@ public void blockTick() {
if (serverMonitor.pollTerminalChanged()) MonitorWatcher.enqueue(this);
}
@Override
public void invalidateCaps() {
super.invalidateCaps();
peripheralCap = CapabilityUtil.invalidate(peripheralCap);
}
@Nonnull
@Override
public <T> LazyOptional<T> getCapability(@Nonnull Capability<T> cap, @Nullable Direction side) {
if (cap == CAPABILITY_PERIPHERAL) {
createServerMonitor(); // Ensure the monitor is created before doing anything else.
if (peripheral == null) peripheral = new MonitorPeripheral(this);
if (peripheralCap == null) peripheralCap = LazyOptional.of(() -> peripheral);
return peripheralCap.cast();
}
return super.getCapability(cap, side);
}
@Nullable
@VisibleForTesting
public ServerMonitor getCachedServerMonitor() {
@ -542,6 +518,11 @@ private void eachComputer(Consumer<IComputerAccess> fun) {
}
}
public IPeripheral peripheral() {
if (peripheral != null) return peripheral;
return peripheral = new MonitorPeripheral(this);
}
void addComputer(IComputerAccess computer) {
computers.add(computer);
}

View File

@ -10,7 +10,9 @@
import dan200.computercraft.shared.common.TileGeneric;
import dan200.computercraft.shared.computer.terminal.NetworkedTerminal;
import dan200.computercraft.shared.media.items.ItemPrintout;
import dan200.computercraft.shared.util.*;
import dan200.computercraft.shared.util.ColourUtils;
import dan200.computercraft.shared.util.DefaultSidedInventory;
import dan200.computercraft.shared.util.WorldUtil;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.NonNullList;
@ -27,18 +29,10 @@
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.common.capabilities.ForgeCapabilities;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.items.IItemHandler;
import net.minecraftforge.items.wrapper.InvWrapper;
import net.minecraftforge.items.wrapper.SidedInvWrapper;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import static dan200.computercraft.shared.Capabilities.CAPABILITY_PERIPHERAL;
public final class TilePrinter extends TileGeneric implements DefaultSidedInventory, Nameable, MenuProvider {
private static final String NBT_NAME = "CustomName";
private static final String NBT_PRINTING = "Printing";
@ -54,9 +48,7 @@ public final class TilePrinter extends TileGeneric implements DefaultSidedInvent
private LockCode lockCode = LockCode.NO_LOCK;
private final NonNullList<ItemStack> inventory = NonNullList.withSize(SLOTS, ItemStack.EMPTY);
private final SidedCaps<IItemHandler> itemHandlerCaps =
SidedCaps.ofNullable(facing -> facing == null ? new InvWrapper(this) : new SidedInvWrapper(this, facing));
private LazyOptional<IPeripheral> peripheralCap;
private @Nullable IPeripheral peripheral;
private final NetworkedTerminal page = new NetworkedTerminal(ItemPrintout.LINE_MAX_LENGTH, ItemPrintout.LINES_PER_PAGE, true);
private String pageTitle = "";
@ -71,13 +63,6 @@ public void destroy() {
ejectContents();
}
@Override
public void invalidateCaps() {
super.invalidateCaps();
itemHandlerCaps.invalidate();
peripheralCap = CapabilityUtil.invalidate(peripheralCap);
}
@Override
public boolean isUsable(Player player) {
return super.isUsable(player) && BaseContainerBlockEntity.canUnlock(player, lockCode, getDisplayName());
@ -387,16 +372,9 @@ private void updateBlockState(boolean top, boolean bottom) {
getLevel().setBlockAndUpdate(getBlockPos(), state.setValue(BlockPrinter.TOP, top).setValue(BlockPrinter.BOTTOM, bottom));
}
@Nonnull
@Override
public <T> LazyOptional<T> getCapability(@Nonnull Capability<T> capability, @Nullable Direction facing) {
if (capability == ForgeCapabilities.ITEM_HANDLER) return itemHandlerCaps.get(facing).cast();
if (capability == CAPABILITY_PERIPHERAL) {
if (peripheralCap == null) peripheralCap = LazyOptional.of(() -> new PrinterPeripheral(this));
return peripheralCap.cast();
}
return super.getCapability(capability, facing);
public IPeripheral peripheral() {
if (peripheral == null) peripheral = new PrinterPeripheral(this);
return peripheral;
}
@Override

View File

@ -9,23 +9,16 @@
import dan200.computercraft.shared.common.TileGeneric;
import dan200.computercraft.shared.network.client.SpeakerStopClientMessage;
import dan200.computercraft.shared.platform.PlatformHelper;
import dan200.computercraft.shared.util.CapabilityUtil;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.common.util.LazyOptional;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import static dan200.computercraft.shared.Capabilities.CAPABILITY_PERIPHERAL;
public class TileSpeaker extends TileGeneric {
private final SpeakerPeripheral peripheral;
private LazyOptional<IPeripheral> peripheralCap;
public TileSpeaker(BlockEntityType<TileSpeaker> type, BlockPos pos, BlockState state) {
super(type, pos, state);
@ -44,21 +37,8 @@ public void setRemoved() {
}
}
@Nonnull
@Override
public <T> LazyOptional<T> getCapability(@Nonnull Capability<T> cap, @Nullable Direction side) {
if (cap == CAPABILITY_PERIPHERAL) {
if (peripheralCap == null) peripheralCap = LazyOptional.of(() -> peripheral);
return peripheralCap.cast();
}
return super.getCapability(cap, side);
}
@Override
public void invalidateCaps() {
super.invalidateCaps();
peripheralCap = CapabilityUtil.invalidate(peripheralCap);
public IPeripheral peripheral() {
return peripheral;
}
private static final class Peripheral extends SpeakerPeripheral {

View File

@ -14,7 +14,6 @@
import dan200.computercraft.core.computer.ComputerSide;
import dan200.computercraft.shared.common.TileGeneric;
import dan200.computercraft.shared.computer.blocks.ComputerPeripheral;
import dan200.computercraft.shared.computer.blocks.ComputerProxy;
import dan200.computercraft.shared.computer.blocks.TileComputerBase;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.computer.core.ComputerState;
@ -43,18 +42,11 @@
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.common.capabilities.ForgeCapabilities;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.items.IItemHandlerModifiable;
import net.minecraftforge.items.wrapper.InvWrapper;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Collections;
import static dan200.computercraft.shared.Capabilities.CAPABILITY_PERIPHERAL;
public class TileTurtle extends TileComputerBase implements ITurtleTile, DefaultInventory {
public static final int INVENTORY_SIZE = 16;
public static final int INVENTORY_WIDTH = 4;
@ -68,12 +60,11 @@ enum MoveState {
private final NonNullList<ItemStack> inventory = NonNullList.withSize(INVENTORY_SIZE, ItemStack.EMPTY);
private final NonNullList<ItemStack> previousInventory = NonNullList.withSize(INVENTORY_SIZE, ItemStack.EMPTY);
private final IItemHandlerModifiable itemHandler = new InvWrapper(this);
private LazyOptional<IItemHandlerModifiable> itemHandlerCap;
private boolean inventoryChanged = false;
private TurtleBrain brain = new TurtleBrain(this);
private MoveState moveState = MoveState.NOT_MOVED;
private LazyOptional<IPeripheral> peripheral;
private IPeripheral peripheral;
private Runnable onMoved;
public TileTurtle(BlockEntityType<? extends TileGeneric> type, BlockPos pos, BlockState state, ComputerFamily family) {
super(type, pos, state, family);
@ -95,10 +86,6 @@ protected ServerComputer createComputer(int id) {
return computer;
}
public ComputerProxy createProxy() {
return brain.getProxy();
}
@Override
public void destroy() {
if (!hasMoved()) {
@ -130,13 +117,6 @@ protected void unload() {
}
}
@Override
public void invalidateCaps() {
super.invalidateCaps();
itemHandlerCap = CapabilityUtil.invalidate(itemHandlerCap);
peripheral = CapabilityUtil.invalidate(peripheral);
}
@Nonnull
@Override
public InteractionResult onActivate(Player player, InteractionHand hand, BlockHitResult hit) {
@ -469,30 +449,18 @@ public void transferStateFrom(TileTurtle copy) {
// Mark the other turtle as having moved, and so its peripheral is dead.
copy.moveState = MoveState.MOVED;
copy.peripheral = CapabilityUtil.invalidate(copy.peripheral);
if (onMoved != null) onMoved.run();
}
public IItemHandlerModifiable getItemHandler() {
return itemHandler;
@Nullable
public IPeripheral peripheral() {
if (hasMoved()) return null;
if (peripheral != null) return peripheral;
return peripheral = new ComputerPeripheral("turtle", this);
}
@Nonnull
@Override
public <T> LazyOptional<T> getCapability(@Nonnull Capability<T> cap, @Nullable Direction side) {
if (cap == ForgeCapabilities.ITEM_HANDLER) {
if (itemHandlerCap == null) itemHandlerCap = LazyOptional.of(() -> new InvWrapper(this));
return itemHandlerCap.cast();
}
if (cap == CAPABILITY_PERIPHERAL) {
if (hasMoved()) return LazyOptional.empty();
if (peripheral == null) {
peripheral = LazyOptional.of(() -> new ComputerPeripheral("turtle", createProxy()));
}
return peripheral.cast();
}
return super.getCapability(cap, side);
public void onMoved(Runnable onMoved) {
this.onMoved = onMoved;
}
@Nullable

View File

@ -15,7 +15,6 @@
import dan200.computercraft.core.computer.ComputerSide;
import dan200.computercraft.core.util.Colour;
import dan200.computercraft.shared.TurtleUpgrades;
import dan200.computercraft.shared.computer.blocks.ComputerProxy;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.computer.core.ServerComputer;
import dan200.computercraft.shared.turtle.blocks.TileTurtle;
@ -65,7 +64,6 @@ public class TurtleBrain implements ITurtleAccess {
public static final Predicate<Entity> PUSHABLE_ENTITY = entity -> !entity.isSpectator() && entity.getPistonPushReaction() != PushReaction.IGNORE;
private TileTurtle owner;
private ComputerProxy proxy;
private GameProfile owningPlayer;
private final Container inventory = (InventoryDelegate) () -> owner;
@ -101,11 +99,6 @@ public TileTurtle getOwner() {
return owner;
}
public ComputerProxy getProxy() {
if (proxy == null) proxy = new ComputerProxy(() -> owner);
return proxy;
}
public ComputerFamily getFamily() {
return owner.getFamily();
}

View File

@ -0,0 +1,69 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.shared.util;
import net.minecraft.core.Direction;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.common.capabilities.ICapabilityProvider;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.event.AttachCapabilitiesEvent;
import javax.annotation.Nullable;
import java.util.Objects;
import java.util.function.BooleanSupplier;
import java.util.function.Supplier;
/**
* A basic {@link ICapabilityProvider} which provides a single capability, returning the same instance for every
* direction.
* <p>
* This is designed for use with {@link AttachCapabilitiesEvent}, to attach individual capabilities to a specific
* block entity.
*
* @param <T> The capability to provide.
*/
public final class CapabilityProvider<T> implements ICapabilityProvider {
private final Capability<T> cap;
private final Supplier<T> supplier;
private final BooleanSupplier isRemoved;
private @Nullable LazyOptional<T> instance;
private CapabilityProvider(Capability<T> cap, Supplier<T> supplier, BooleanSupplier isRemoved) {
this.cap = Objects.requireNonNull(cap, "Capability cannot be null");
this.supplier = supplier;
this.isRemoved = isRemoved;
}
public static <T> CapabilityProvider<T> attach(AttachCapabilitiesEvent<?> event, ResourceLocation id, Capability<T> cap, Supplier<T> instance) {
BooleanSupplier isRemoved
= event.getObject() instanceof BlockEntity be ? be::isRemoved
: event.getObject() instanceof Entity entity ? entity::isRemoved
: () -> true;
var provider = new CapabilityProvider<>(cap, instance, isRemoved);
event.addCapability(id, provider);
event.addListener(provider::invalidate);
return provider;
}
public void invalidate() {
instance = CapabilityUtil.invalidate(instance);
}
@Override
public <U> LazyOptional<U> getCapability(Capability<U> cap, @Nullable Direction side) {
if (cap != this.cap || isRemoved.getAsBoolean()) return LazyOptional.empty();
var instance = this.instance;
if (instance == null) {
var created = supplier.get();
instance = this.instance = created == null ? LazyOptional.empty() : LazyOptional.of(() -> created);
}
return instance.cast();
}
}

View File

@ -49,11 +49,6 @@ public static <T> T unwrap(LazyOptional<T> p, NonNullConsumer<? super LazyOption
return p.orElseThrow(NullPointerException::new);
}
@Nullable
public static <T> T unwrapUnsafe(LazyOptional<T> p) {
return !p.isPresent() ? null : p.orElseThrow(NullPointerException::new);
}
/**
* Find a capability, preferring the internal/null side but falling back to a given side if a mod doesn't support
* the internal one.

View File

@ -0,0 +1,72 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.shared.util;
import net.minecraft.core.Direction;
import net.minecraft.resources.ResourceLocation;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.common.capabilities.ICapabilityProvider;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.event.AttachCapabilitiesEvent;
import javax.annotation.Nullable;
import java.util.Objects;
/**
* A {@link ICapabilityProvider} which provides a different single capability, with different instances for each
* direction.
* <p>
* This is designed for use with {@link AttachCapabilitiesEvent}, to attach individual capabilities to a specific
* block entity.
*
* @param <T> The capability to provide.
* @see CapabilityProvider
*/
public final class SidedCapabilityProvider<T> implements ICapabilityProvider {
private final Capability<T> cap;
private final Provider<T> supplier;
private @Nullable LazyOptional<T>[] instances;
private SidedCapabilityProvider(Capability<T> cap, Provider<T> supplier) {
this.cap = Objects.requireNonNull(cap, "Capability cannot be null");
this.supplier = supplier;
}
public static <T> SidedCapabilityProvider<T> attach(AttachCapabilitiesEvent<?> event, ResourceLocation id, Capability<T> cap, Provider<T> supplier) {
var provider = new SidedCapabilityProvider<>(cap, supplier);
event.addCapability(id, provider);
event.addListener(provider::invalidate);
return provider;
}
public void invalidate() {
CapabilityUtil.invalidate(instances);
}
@Override
@SuppressWarnings({ "unchecked", "rawtypes" })
public <U> LazyOptional<U> getCapability(Capability<U> cap, @Nullable Direction side) {
if (cap != this.cap) return LazyOptional.empty();
var instances = this.instances;
if (instances == null) instances = this.instances = new LazyOptional[6];
var index = side == null ? 6 : side.ordinal();
var instance = instances[index];
if (instance == null) {
var created = supplier.get(side);
instance = instances[index] = created == null ? LazyOptional.empty() : LazyOptional.of(() -> created);
}
return instance.cast();
}
public interface Provider<T> {
@Nullable
T get(@Nullable Direction direction);
}
}

View File

@ -1,60 +0,0 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.shared.util;
import net.minecraft.core.Direction;
import net.minecraftforge.common.util.LazyOptional;
import javax.annotation.Nullable;
import java.util.function.Function;
/**
* Provides a constant (but invalidate-able) capability for each side.
*
* @param <T> The type of the produced capability.
*/
public final class SidedCaps<T> {
private final Function<Direction, T> factory;
private final boolean allowNull;
private T[] values;
private LazyOptional<T>[] caps;
private SidedCaps(Function<Direction, T> factory, boolean allowNull) {
this.factory = factory;
this.allowNull = allowNull;
}
public static <T> SidedCaps<T> ofNonNull(Function<Direction, T> factory) {
return new SidedCaps<>(factory, false);
}
public static <T> SidedCaps<T> ofNullable(Function<Direction, T> factory) {
return new SidedCaps<>(factory, true);
}
@SuppressWarnings({ "unchecked", "rawtypes" })
public LazyOptional<T> get(@Nullable Direction direction) {
if (direction == null && !allowNull) return LazyOptional.empty();
var index = direction == null ? 6 : direction.ordinal();
var caps = this.caps;
if (caps == null) {
caps = this.caps = new LazyOptional[allowNull ? 7 : 6];
values = (T[]) new Object[caps.length];
}
var cap = caps[index];
return cap == null ? caps[index] = LazyOptional.of(() -> {
var values = this.values;
var value = values[index];
return value == null ? values[index] = factory.apply(direction) : value;
}) : cap;
}
public void invalidate() {
if (caps != null) CapabilityUtil.invalidate(caps);
}
}

View File

@ -29,8 +29,7 @@
import net.minecraft.world.level.block.Blocks
import net.minecraft.world.level.block.FenceBlock
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.*
@ -355,7 +354,8 @@ fun Attack_entity(helper: GameTestHelper) = helper.sequence {
}
thenExecute {
helper.assertEntityNotPresent(EntityType.SHEEP)
val count = helper.getBlockEntity(turtlePos, ModRegistry.BlockEntities.TURTLE_NORMAL.get()).countItem(Items.WHITE_WOOL)
val count = helper.getBlockEntity(turtlePos, ModRegistry.BlockEntities.TURTLE_NORMAL.get())
.countItem(Items.WHITE_WOOL)
if (count == 0) helper.fail("Expected turtle to have white wool", turtlePos)
}
}
@ -371,7 +371,56 @@ fun Attack_entity_destroy(helper: GameTestHelper) = helper.sequence {
thenWaitUntil { helper.assertBlockPresent(Blocks.AIR, BlockPos(2, 2, 2)) }
}
// TODO: Ghost peripherals?
/**
* Ensure a turtle never sees itself as a peripheral.
*
* @see <https://github.com/dan200/ComputerCraft/issues/131>
*/
@GameTest
fun No_ghost_peripheral(helper: GameTestHelper) = helper.sequence {
val events = mutableListOf<String>()
thenOnComputer {
for (i in 0 until 3) {
if ((i % 2) == 0) turtle.up() else turtle.down()
do {
val event = pullEvent()[0] as String
events.add(event)
} while (event != "turtle_response")
}
}
}
/**
* Ensure a turtle attaches and detaches peripherals as it moves.
*/
@GameTest
fun Peripheral_change(helper: GameTestHelper) = helper.sequence {
val events = mutableListOf<Pair<String, String>>()
thenStartComputer("listen") {
while (true) {
val event = pullEvent()
if (event[0] == "peripheral" || event[0] == "peripheral_detach") {
events.add((event[0] as String) to (event[1] as String))
}
}
}
thenOnComputer("turtle") {
turtle.forward().await().assertArrayEquals(true, message = "Moved turtle forward")
turtle.back().await().assertArrayEquals(true, message = "Moved turtle forward")
}
thenIdle(1)
thenExecute {
assertEquals(
listOf(
"peripheral_detach" to "right",
"peripheral" to "right",
),
events,
)
}
}
// TODO: Turtle sucking from items
}

View File

@ -0,0 +1,137 @@
{
DataVersion: 3120,
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:south,waterlogged:false}", nbt: {ComputerId: 1, Fuel: 80, Items: [], Label: "turtle_test.no_ghost_peripheral", On: 1b, Owner: {LowerId: -6876936588741668278L, Name: "Dev", UpperId: 4039158846114182220L}, 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:south,waterlogged:false}"
]
}

View File

@ -0,0 +1,138 @@
{
DataVersion: 3120,
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:south,waterlogged:false}", nbt: {ComputerId: 1, Fuel: 80, Items: [], Label: "turtle_test.peripheral_change.turtle", On: 1b, Owner: {LowerId: -6876936588741668278L, Name: "Dev", UpperId: 4039158846114182220L}, 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: "computercraft:computer_advanced{facing:north,state:blinking}", nbt: {ComputerId: 1, Label: "turtle_test.peripheral_change.listen", On: 1b, id: "computercraft:computer_advanced"}},
{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:south,waterlogged:false}",
"computercraft:computer_advanced{facing:north,state:blinking}"
]
}