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

Access capabilities using PlatformHelper

We provide a new abstraction: ComponentAccess, which is a view of
capabilities (or block lookups for Fabric) for adjacent blocks.
This commit is contained in:
Jonathan Coates 2022-11-08 17:44:06 +00:00
parent 320007dbc6
commit 7d47b219c5
No known key found for this signature in database
GPG Key ID: B9E431FF07C98D06
10 changed files with 190 additions and 43 deletions

View File

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

View File

@ -11,11 +11,11 @@
import net.minecraft.client.gui.screens.Screen; import net.minecraft.client.gui.screens.Screen;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import net.minecraftforge.common.util.NonNullSupplier;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.util.List; import java.util.List;
import java.util.function.IntSupplier; import java.util.function.IntSupplier;
import java.util.function.Supplier;
/** /**
* Version of {@link net.minecraft.client.gui.components.ImageButton} which allows changing some properties * Version of {@link net.minecraft.client.gui.components.ImageButton} which allows changing some properties
@ -29,7 +29,7 @@ public class DynamicImageButton extends Button {
private final int yDiffTex; private final int yDiffTex;
private final int textureWidth; private final int textureWidth;
private final int textureHeight; private final int textureHeight;
private final NonNullSupplier<List<Component>> tooltip; private final Supplier<List<Component>> tooltip;
public DynamicImageButton( public DynamicImageButton(
Screen screen, int x, int y, int width, int height, int xTexStart, int yTexStart, int yDiffTex, Screen screen, int x, int y, int width, int height, int xTexStart, int yTexStart, int yDiffTex,
@ -47,7 +47,7 @@ public DynamicImageButton(
public DynamicImageButton( public DynamicImageButton(
Screen screen, int x, int y, int width, int height, IntSupplier xTexStart, int yTexStart, int yDiffTex, Screen screen, int x, int y, int width, int height, IntSupplier xTexStart, int yTexStart, int yDiffTex,
ResourceLocation texture, int textureWidth, int textureHeight, ResourceLocation texture, int textureWidth, int textureHeight,
OnPress onPress, NonNullSupplier<List<Component>> tooltip OnPress onPress, Supplier<List<Component>> tooltip
) { ) {
super(x, y, width, height, Component.empty(), onPress); super(x, y, width, height, Component.empty(), onPress);
this.screen = screen; this.screen = screen;

View File

@ -5,25 +5,28 @@
*/ */
package dan200.computercraft.shared.computer.blocks; package dan200.computercraft.shared.computer.blocks;
import com.google.common.base.Strings;
import dan200.computercraft.api.ComputerCraftAPI; import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.core.computer.ComputerSide; import dan200.computercraft.core.computer.ComputerSide;
import dan200.computercraft.shared.BundledRedstone; import dan200.computercraft.shared.BundledRedstone;
import dan200.computercraft.shared.Peripherals;
import dan200.computercraft.shared.common.TileGeneric; import dan200.computercraft.shared.common.TileGeneric;
import dan200.computercraft.shared.computer.core.ComputerFamily; import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.computer.core.ComputerState; import dan200.computercraft.shared.computer.core.ComputerState;
import dan200.computercraft.shared.computer.core.ServerComputer; import dan200.computercraft.shared.computer.core.ServerComputer;
import dan200.computercraft.shared.computer.core.ServerContext; import dan200.computercraft.shared.computer.core.ServerContext;
import dan200.computercraft.shared.network.container.ComputerContainerData; import dan200.computercraft.shared.network.container.ComputerContainerData;
import dan200.computercraft.shared.platform.ComponentAccess;
import dan200.computercraft.shared.platform.PlatformHelper;
import dan200.computercraft.shared.util.DirectionUtil; import dan200.computercraft.shared.util.DirectionUtil;
import dan200.computercraft.shared.util.IDAssigner; import dan200.computercraft.shared.util.IDAssigner;
import dan200.computercraft.shared.util.RedstoneUtil; import dan200.computercraft.shared.util.RedstoneUtil;
import joptsimple.internal.Strings;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction; import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket; import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.*; import net.minecraft.world.*;
import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
@ -32,7 +35,6 @@
import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.BlockHitResult; import net.minecraft.world.phys.BlockHitResult;
import net.minecraftforge.common.util.NonNullConsumer;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@ -51,7 +53,7 @@ public abstract class TileComputerBase extends TileGeneric implements IComputerT
private boolean fresh = false; private boolean fresh = false;
private int invalidSides = 0; private int invalidSides = 0;
private final NonNullConsumer<Object>[] invalidate; private final ComponentAccess<IPeripheral> peripherals = PlatformHelper.get().createPeripheralAccess(d -> invalidSides |= 1 << d.ordinal());
private LockCode lockCode = LockCode.NO_LOCK; private LockCode lockCode = LockCode.NO_LOCK;
@ -60,14 +62,6 @@ public abstract class TileComputerBase extends TileGeneric implements IComputerT
public TileComputerBase(BlockEntityType<? extends TileGeneric> type, BlockPos pos, BlockState state, ComputerFamily family) { public TileComputerBase(BlockEntityType<? extends TileGeneric> type, BlockPos pos, BlockState state, ComputerFamily family) {
super(type, pos, state); super(type, pos, state);
this.family = family; this.family = family;
// We cache these so we can guarantee we only ever register one listener for adjacent capabilities.
@SuppressWarnings({ "unchecked", "rawtypes" })
var invalidate = this.invalidate = new NonNullConsumer[6];
for (var direction : Direction.values()) {
var mask = 1 << direction.ordinal();
invalidate[direction.ordinal()] = o -> invalidSides |= mask;
}
} }
protected void unload() { protected void unload() {
@ -231,8 +225,7 @@ private void refreshPeripheral(@Nonnull ServerComputer computer, Direction dir)
var localDir = remapToLocalSide(dir); var localDir = remapToLocalSide(dir);
if (isPeripheralBlockedOnSide(localDir)) return; if (isPeripheralBlockedOnSide(localDir)) return;
var offsetSide = dir.getOpposite(); var peripheral = peripherals.get((ServerLevel) getLevel(), getBlockPos(), dir);
var peripheral = Peripherals.getPeripheral(getLevel(), getBlockPos().relative(dir), offsetSide, invalidate[dir.ordinal()]);
computer.setPeripheral(localDir, peripheral); computer.setPeripheral(localDir, peripheral);
} }

View File

@ -6,9 +6,9 @@
package dan200.computercraft.shared.peripheral.modem.wired; package dan200.computercraft.shared.peripheral.modem.wired;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import dan200.computercraft.api.ForgeComputerCraftAPI;
import dan200.computercraft.shared.ModRegistry; import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.common.BlockGeneric; import dan200.computercraft.shared.common.BlockGeneric;
import dan200.computercraft.shared.platform.PlatformHelper;
import dan200.computercraft.shared.util.WaterloggableHelpers; import dan200.computercraft.shared.util.WaterloggableHelpers;
import dan200.computercraft.shared.util.WorldUtil; import dan200.computercraft.shared.util.WorldUtil;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
@ -76,10 +76,10 @@ public static boolean canConnectIn(BlockState state, Direction direction) {
return state.getValue(BlockCable.CABLE) && state.getValue(BlockCable.MODEM).getFacing() != direction; return state.getValue(BlockCable.CABLE) && state.getValue(BlockCable.MODEM).getFacing() != direction;
} }
public static boolean doesConnectVisually(BlockState state, BlockGetter world, BlockPos pos, Direction direction) { public static boolean doesConnectVisually(BlockState state, Level level, BlockPos pos, Direction direction) {
if (!state.getValue(CABLE)) return false; if (!state.getValue(CABLE)) return false;
if (state.getValue(MODEM).getFacing() == direction) return true; if (state.getValue(MODEM).getFacing() == direction) return true;
return ForgeComputerCraftAPI.getWiredElementAt(world, pos.relative(direction), direction.getOpposite()).isPresent(); return PlatformHelper.get().hasWiredElementIn(level, pos, direction);
} }
@Nonnull @Nonnull
@ -171,7 +171,7 @@ public BlockState updateShape(@Nonnull BlockState state, @Nonnull Direction side
return getFluidState(state).createLegacyBlock(); return getFluidState(state).createLegacyBlock();
} }
return state.setValue(CONNECTIONS.get(side), doesConnectVisually(state, world, pos, side)); return world instanceof Level level ? state.setValue(CONNECTIONS.get(side), doesConnectVisually(state, level, pos, side)) : state;
} }
@Override @Override

View File

@ -6,7 +6,6 @@
package dan200.computercraft.shared.peripheral.modem.wired; package dan200.computercraft.shared.peripheral.modem.wired;
import com.google.common.base.Objects; import com.google.common.base.Objects;
import dan200.computercraft.api.ForgeComputerCraftAPI;
import dan200.computercraft.api.network.wired.IWiredElement; import dan200.computercraft.api.network.wired.IWiredElement;
import dan200.computercraft.api.network.wired.IWiredNode; import dan200.computercraft.api.network.wired.IWiredNode;
import dan200.computercraft.api.peripheral.IPeripheral; import dan200.computercraft.api.peripheral.IPeripheral;
@ -14,12 +13,15 @@
import dan200.computercraft.shared.command.text.ChatHelpers; import dan200.computercraft.shared.command.text.ChatHelpers;
import dan200.computercraft.shared.common.TileGeneric; import dan200.computercraft.shared.common.TileGeneric;
import dan200.computercraft.shared.peripheral.modem.ModemState; import dan200.computercraft.shared.peripheral.modem.ModemState;
import dan200.computercraft.shared.platform.ComponentAccess;
import dan200.computercraft.shared.platform.PlatformHelper;
import dan200.computercraft.shared.util.DirectionUtil; import dan200.computercraft.shared.util.DirectionUtil;
import dan200.computercraft.shared.util.TickScheduler; import dan200.computercraft.shared.util.TickScheduler;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction; import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult; import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.player.Player;
@ -30,8 +32,6 @@
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.BlockHitResult; import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.Vec3; import net.minecraft.world.phys.Vec3;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.common.util.NonNullConsumer;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@ -99,7 +99,7 @@ public Object getTarget() {
} }
}; };
private final NonNullConsumer<LazyOptional<IWiredElement>> connectedNodeChanged = x -> connectionsChanged(); private final ComponentAccess<IWiredElement> connectedElements = PlatformHelper.get().createWiredElementAccess(x -> connectionsChanged());
public TileCable(BlockEntityType<? extends TileCable> type, BlockPos pos, BlockState state) { public TileCable(BlockEntityType<? extends TileCable> type, BlockPos pos, BlockState state) {
super(type, pos, state); super(type, pos, state);
@ -284,11 +284,10 @@ void connectionsChanged() {
var offset = current.relative(facing); var offset = current.relative(facing);
if (!world.isLoaded(offset)) continue; if (!world.isLoaded(offset)) continue;
var element = ForgeComputerCraftAPI.getWiredElementAt(world, offset, facing.getOpposite()); var element = connectedElements.get((ServerLevel) world, current, facing);
if (!element.isPresent()) continue; if (element == null) continue;
element.addListener(connectedNodeChanged); var node = element.getNode();
var node = element.orElseThrow(NullPointerException::new).getNode();
if (BlockCable.canConnectIn(state, facing)) { if (BlockCable.canConnectIn(state, facing)) {
// If we can connect to it then do so // If we can connect to it then do so
this.node.connectTo(node); this.node.connectTo(node);

View File

@ -6,19 +6,21 @@
package dan200.computercraft.shared.peripheral.modem.wired; package dan200.computercraft.shared.peripheral.modem.wired;
import com.google.common.base.Objects; import com.google.common.base.Objects;
import dan200.computercraft.api.ForgeComputerCraftAPI;
import dan200.computercraft.api.network.wired.IWiredElement; import dan200.computercraft.api.network.wired.IWiredElement;
import dan200.computercraft.api.network.wired.IWiredNode; import dan200.computercraft.api.network.wired.IWiredNode;
import dan200.computercraft.api.peripheral.IPeripheral; import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.shared.command.text.ChatHelpers; import dan200.computercraft.shared.command.text.ChatHelpers;
import dan200.computercraft.shared.common.TileGeneric; import dan200.computercraft.shared.common.TileGeneric;
import dan200.computercraft.shared.peripheral.modem.ModemState; import dan200.computercraft.shared.peripheral.modem.ModemState;
import dan200.computercraft.shared.platform.ComponentAccess;
import dan200.computercraft.shared.platform.PlatformHelper;
import dan200.computercraft.shared.util.DirectionUtil; import dan200.computercraft.shared.util.DirectionUtil;
import dan200.computercraft.shared.util.TickScheduler; import dan200.computercraft.shared.util.TickScheduler;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction; import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult; import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.player.Player;
@ -27,8 +29,6 @@
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.BlockHitResult; import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.Vec3; import net.minecraft.world.phys.Vec3;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.common.util.NonNullConsumer;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@ -89,7 +89,7 @@ public Vec3 getPosition() {
private final WiredModemElement element = new FullElement(this); private final WiredModemElement element = new FullElement(this);
private final IWiredNode node = element.getNode(); private final IWiredNode node = element.getNode();
private final NonNullConsumer<LazyOptional<IWiredElement>> connectedNodeChanged = x -> connectionsChanged(); private final ComponentAccess<IWiredElement> connectedElements = PlatformHelper.get().createWiredElementAccess(x -> connectionsChanged());
private int invalidSides = 0; private int invalidSides = 0;
@ -252,11 +252,10 @@ private void connectionsChanged() {
var offset = current.relative(facing); var offset = current.relative(facing);
if (!world.isLoaded(offset)) continue; if (!world.isLoaded(offset)) continue;
var element = ForgeComputerCraftAPI.getWiredElementAt(world, offset, facing.getOpposite()); var element = connectedElements.get((ServerLevel) getLevel(), getBlockPos(), facing);
if (!element.isPresent()) continue; if (element == null) return;
element.addListener(connectedNodeChanged); node.connectTo(element.getNode());
node.connectTo(element.orElseThrow(NullPointerException::new).getNode());
} }
} }

View File

@ -7,14 +7,15 @@
import dan200.computercraft.api.peripheral.IPeripheral; import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.shared.ModRegistry; import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.Peripherals;
import dan200.computercraft.shared.computer.core.ServerContext; import dan200.computercraft.shared.computer.core.ServerContext;
import dan200.computercraft.shared.platform.ComponentAccess;
import dan200.computercraft.shared.platform.PlatformHelper;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction; import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.Tag; import net.minecraft.nbt.Tag;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.Level; import net.minecraft.world.level.Level;
import net.minecraftforge.common.util.NonNullConsumer;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@ -35,10 +36,10 @@ public final class WiredModemLocalPeripheral {
private String type; private String type;
private IPeripheral peripheral; private IPeripheral peripheral;
private final NonNullConsumer<Object> invalidate; private final ComponentAccess<IPeripheral> peripherals;
public WiredModemLocalPeripheral(@Nonnull Runnable invalidate) { public WiredModemLocalPeripheral(@Nonnull Runnable invalidate) {
this.invalidate = x -> invalidate.run(); peripherals = PlatformHelper.get().createPeripheralAccess(x -> invalidate.run());
} }
/** /**
@ -126,7 +127,7 @@ private IPeripheral getPeripheralFrom(Level world, BlockPos pos, Direction direc
var block = world.getBlockState(offset).getBlock(); var block = world.getBlockState(offset).getBlock();
if (block == ModRegistry.Blocks.WIRED_MODEM_FULL.get() || block == ModRegistry.Blocks.CABLE.get()) return null; if (block == ModRegistry.Blocks.WIRED_MODEM_FULL.get() || block == ModRegistry.Blocks.CABLE.get()) return null;
var peripheral = Peripherals.getPeripheral(world, offset, direction.getOpposite(), invalidate); var peripheral = peripherals.get((ServerLevel) world, pos, direction);
return peripheral instanceof WiredModemPeripheral ? null : peripheral; return peripheral instanceof WiredModemPeripheral ? null : peripheral;
} }
} }

View File

@ -0,0 +1,33 @@
/*
* 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.platform;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.server.level.ServerLevel;
import javax.annotation.Nullable;
/**
* A (possibly cached) provider of a component at a specific location.
*
* @param <T> The type of the component.
*/
public interface ComponentAccess<T> {
/**
* Get a peripheral for the current block.
* <p>
* Both {@code level} and {@code pos} must be constant for the lifetime of the store.
*
* @param level The current level.
* @param pos The position of the block fetching the peripheral, for instance the computer or modem.
* @param direction The direction the peripheral is in.
* @return The peripheral, or {@literal null} if not found.
* @throws IllegalStateException If the level or position have changed.
*/
@Nullable
T get(ServerLevel level, BlockPos pos, Direction direction);
}

View File

@ -5,10 +5,13 @@
*/ */
package dan200.computercraft.shared.platform; package dan200.computercraft.shared.platform;
import dan200.computercraft.api.network.wired.IWiredElement;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.shared.network.NetworkMessage; import dan200.computercraft.shared.network.NetworkMessage;
import dan200.computercraft.shared.network.client.ClientNetworkContext; import dan200.computercraft.shared.network.client.ClientNetworkContext;
import dan200.computercraft.shared.network.container.ContainerData; import dan200.computercraft.shared.network.container.ContainerData;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.Registry; import net.minecraft.core.Registry;
import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceKey;
@ -20,6 +23,7 @@
import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.AbstractContainerMenu; import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.inventory.MenuType; import net.minecraft.world.inventory.MenuType;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.entity.BlockEntityType;
@ -30,6 +34,7 @@
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.Collection; import java.util.Collection;
import java.util.function.BiFunction; import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function; import java.util.function.Function;
/** /**
@ -146,4 +151,34 @@ static PlatformHelper get() {
* @param chunk The chunk players must be tracking. * @param chunk The chunk players must be tracking.
*/ */
void sendToAllTracking(NetworkMessage<ClientNetworkContext> message, LevelChunk chunk); void sendToAllTracking(NetworkMessage<ClientNetworkContext> message, LevelChunk chunk);
/**
* Create a {@link ComponentAccess} for surrounding peripherals.
*
* @param invalidate The function to call when a neighbouring peripheral potentially changes. This <em>MAY NOT</em>
* include all changes, and so block updates should still be listened to.
* @return The peripheral component access.
*/
ComponentAccess<IPeripheral> createPeripheralAccess(Consumer<Direction> invalidate);
/**
* Create a {@link ComponentAccess} for surrounding wired nodes.
*
* @param invalidate The function to call when a neighbouring wired node potentially changes. This <em>MAY NOT</em>
* include all changes, and so block updates should still be listened to.
* @return The peripheral component access.
*/
ComponentAccess<IWiredElement> createWiredElementAccess(Consumer<Direction> invalidate);
/**
* Determine if there is a wired element in the given direction. This is equivalent to
* {@code createWiredElementAt(x -> {}).get(level, pos, dir) != null}, but is intended for when we don't need the
* cache.
*
* @param level The current level.
* @param pos The <em>current</em> block's position.
* @param direction The direction to check in.
* @return Whether there is a wired element in the given direction.
*/
boolean hasWiredElementIn(Level level, BlockPos pos, Direction direction);
} }

View File

@ -7,10 +7,16 @@
import com.google.auto.service.AutoService; import com.google.auto.service.AutoService;
import dan200.computercraft.api.ComputerCraftAPI; import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.network.wired.IWiredElement;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.shared.Capabilities;
import dan200.computercraft.shared.Peripherals;
import dan200.computercraft.shared.network.NetworkMessage; import dan200.computercraft.shared.network.NetworkMessage;
import dan200.computercraft.shared.network.client.ClientNetworkContext; import dan200.computercraft.shared.network.client.ClientNetworkContext;
import dan200.computercraft.shared.network.container.ContainerData; import dan200.computercraft.shared.network.container.ContainerData;
import dan200.computercraft.shared.util.CapabilityUtil;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.Registry; import net.minecraft.core.Registry;
import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.FriendlyByteBuf;
@ -24,13 +30,16 @@
import net.minecraft.world.inventory.AbstractContainerMenu; import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.inventory.MenuType; import net.minecraft.world.inventory.MenuType;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.LevelChunk; import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.world.phys.Vec3; import net.minecraft.world.phys.Vec3;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.common.extensions.IForgeMenuType; import net.minecraftforge.common.extensions.IForgeMenuType;
import net.minecraftforge.common.util.NonNullConsumer;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import net.minecraftforge.network.NetworkHooks; import net.minecraftforge.network.NetworkHooks;
import net.minecraftforge.registries.DeferredRegister; import net.minecraftforge.registries.DeferredRegister;
@ -43,6 +52,7 @@
import java.util.Iterator; import java.util.Iterator;
import java.util.Set; import java.util.Set;
import java.util.function.BiFunction; import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Supplier; import java.util.function.Supplier;
@ -118,6 +128,24 @@ public void sendToAllTracking(NetworkMessage<ClientNetworkContext> message, Leve
NetworkHandler.sendToAllTracking(message, chunk); NetworkHandler.sendToAllTracking(message, chunk);
} }
@Override
public ComponentAccess<IPeripheral> createPeripheralAccess(Consumer<Direction> invalidate) {
return new PeripheralAccess(invalidate);
}
@Override
public ComponentAccess<IWiredElement> createWiredElementAccess(Consumer<Direction> invalidate) {
return new CapabilityAccess<>(Capabilities.CAPABILITY_WIRED_ELEMENT, invalidate);
}
@Override
public boolean hasWiredElementIn(Level level, BlockPos pos, Direction direction) {
if (!level.isLoaded(pos)) return false;
var blockEntity = level.getBlockEntity(pos.relative(direction));
return blockEntity != null && blockEntity.getCapability(Capabilities.CAPABILITY_WIRED_ELEMENT, direction.getOpposite()).isPresent();
}
@Nullable @Nullable
@Override @Override
public CompoundTag getShareTag(ItemStack item) { public CompoundTag getShareTag(ItemStack item) {
@ -190,4 +218,62 @@ public T get() {
return object.get(); return object.get();
} }
} }
private abstract static class ComponentAccessImpl<T> implements ComponentAccess<T> {
private final NonNullConsumer<Object>[] invalidators;
private @Nullable Level level;
private @Nullable BlockPos pos;
ComponentAccessImpl(Consumer<Direction> invalidate) {
// Generate a cache of invalidation functions so we can guarantee we only ever have one registered per
// capability - there's no way to remove these callbacks!
@SuppressWarnings({ "unchecked", "rawtypes" })
var invalidators = this.invalidators = new NonNullConsumer[6];
for (var dir : Direction.values()) invalidators[dir.ordinal()] = x -> invalidate.accept(dir);
}
@Nullable
protected abstract T get(ServerLevel world, BlockPos pos, Direction side, NonNullConsumer<Object> invalidate);
@Nullable
@Override
public T get(ServerLevel level, BlockPos pos, Direction direction) {
if (this.level != null && this.level != level) throw new IllegalStateException("Level has changed");
if (this.pos != null && this.pos != pos) throw new IllegalStateException("Position has changed");
this.level = level;
this.pos = pos;
return get(level, pos.relative(direction), direction.getOpposite(), invalidators[direction.ordinal()]);
}
}
private static class PeripheralAccess extends ComponentAccessImpl<IPeripheral> {
PeripheralAccess(Consumer<Direction> invalidate) {
super(invalidate);
}
@Nullable
@Override
protected IPeripheral get(ServerLevel world, BlockPos pos, Direction side, NonNullConsumer<Object> invalidate) {
return Peripherals.getPeripheral(world, pos, side, invalidate);
}
}
private static class CapabilityAccess<T> extends ComponentAccessImpl<T> {
private final Capability<T> capability;
CapabilityAccess(Capability<T> capability, Consumer<Direction> invalidate) {
super(invalidate);
this.capability = capability;
}
@Nullable
@Override
protected T get(ServerLevel world, BlockPos pos, Direction side, NonNullConsumer<Object> invalidate) {
if (!world.isLoaded(pos)) return null;
var blockEntity = world.getBlockEntity(pos);
return blockEntity != null ? CapabilityUtil.unwrap(blockEntity.getCapability(capability, side), invalidate) : null;
}
}
} }