diff --git a/src/main/java/li/cil/oc2/api/capabilities/BundledEmitter.java b/src/main/java/li/cil/oc2/api/capabilities/BundledEmitter.java new file mode 100644 index 00000000..bb844019 --- /dev/null +++ b/src/main/java/li/cil/oc2/api/capabilities/BundledEmitter.java @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: MIT */ + +package li.cil.oc2.api.capabilities; + +/** + * This interface may be provided as a capability by item components to signal + * to the containing {@link net.minecraft.world.level.block.entity.BlockEntity} that they wish + * to emit redstone signals via a Project Red bundled cable. This is used by the built-in + * redstone interface card, for example. + */ +public interface BundledEmitter { + /** + * Returns the bundled output levels for the side this interface was returned for. + * + * @return the bundled output levels. + */ + byte[] getBundledOutput(); +} diff --git a/src/main/java/li/cil/oc2/common/Constants.java b/src/main/java/li/cil/oc2/common/Constants.java index b20119b5..f35e9732 100644 --- a/src/main/java/li/cil/oc2/common/Constants.java +++ b/src/main/java/li/cil/oc2/common/Constants.java @@ -22,6 +22,8 @@ public final class Constants { public static final Direction.Axis[] AXES = Direction.Axis.values(); public static final int BLOCK_FACE_COUNT = DIRECTIONS.length; + public static final int BUNDLE_COLOR_COUNT = 16; + /////////////////////////////////////////////////////////////////// public static final String BLOCK_ENTITY_TAG_NAME_IN_ITEM = "BlockEntityTag"; diff --git a/src/main/java/li/cil/oc2/common/block/ComputerBlock.java b/src/main/java/li/cil/oc2/common/block/ComputerBlock.java index 82366842..9f2d0224 100644 --- a/src/main/java/li/cil/oc2/common/block/ComputerBlock.java +++ b/src/main/java/li/cil/oc2/common/block/ComputerBlock.java @@ -13,6 +13,7 @@ import li.cil.oc2.common.integration.Wrenches; import li.cil.oc2.common.item.Items; import li.cil.oc2.common.tags.ItemTags; +import li.cil.oc2.common.util.HorizontalBlockUtils; import li.cil.oc2.common.util.TooltipUtils; import li.cil.oc2.common.util.VoxelShapeUtils; import net.minecraft.core.BlockPos; @@ -107,8 +108,9 @@ public int getSignal(final BlockState state, final BlockGetter blockGetter, fina var level = blockEntity.getLevel(); if (level != null) { // Redstone requests info for faces with external perspective. Capabilities treat - // the Direction from internal perspective, so flip it. - var cap = level.getCapability(Capabilities.RedstoneEmitter.BLOCK, blockEntity.getBlockPos(), null, blockEntity, side.getOpposite()); + // the Direction from an internal and local perspective, so flip it, and transform it from global to + // local. + var cap = level.getCapability(Capabilities.RedstoneEmitter.BLOCK, blockEntity.getBlockPos(), null, blockEntity, HorizontalBlockUtils.toLocal(state, side.getOpposite())); return Optional.ofNullable(cap) .map(RedstoneEmitter::getRedstoneOutput) .orElse(0); diff --git a/src/main/java/li/cil/oc2/common/blockentity/ComputerBlockEntity.java b/src/main/java/li/cil/oc2/common/blockentity/ComputerBlockEntity.java index 897b422e..2c75b6da 100644 --- a/src/main/java/li/cil/oc2/common/blockentity/ComputerBlockEntity.java +++ b/src/main/java/li/cil/oc2/common/blockentity/ComputerBlockEntity.java @@ -7,8 +7,11 @@ import li.cil.oc2.api.bus.device.Device; import li.cil.oc2.api.bus.device.DeviceTypes; import li.cil.oc2.api.bus.device.provider.ItemDeviceQuery; +import li.cil.oc2.api.capabilities.BundledEmitter; +import li.cil.oc2.api.capabilities.RedstoneEmitter; import li.cil.oc2.api.capabilities.TerminalUserProvider; import li.cil.oc2.client.audio.LoopingSoundManager; +import li.cil.oc2.common.Constants; import li.cil.oc2.common.block.Blocks; import li.cil.oc2.common.components.DataComponents; import li.cil.oc2.common.components.RestrictedContainer; @@ -368,6 +371,18 @@ protected void unloadServer(final boolean isRemove) { busElement.scheduleScan(); } + @Nullable + public byte[] getBundledSignal(Direction direction) { + if (level != null) { + var cap = level.getCapability(Capabilities.BundledEmitter.BLOCK, getBlockPos(), null, this, HorizontalBlockUtils.toLocal(getBlockState(), direction)); + return Optional.ofNullable(cap) + .map(BundledEmitter::getBundledOutput) + .orElse(new byte[Constants.BUNDLE_COLOR_COUNT]); + } + + return new byte[Constants.BUNDLE_COLOR_COUNT]; + } + /////////////////////////////////////////////////////////////////// private void sendToClientsTrackingComputer(final CustomPacketPayload message) { diff --git a/src/main/java/li/cil/oc2/common/blockentity/RedstoneInterfaceBlockEntity.java b/src/main/java/li/cil/oc2/common/blockentity/RedstoneInterfaceBlockEntity.java index bfb3923f..7285ff38 100644 --- a/src/main/java/li/cil/oc2/common/blockentity/RedstoneInterfaceBlockEntity.java +++ b/src/main/java/li/cil/oc2/common/blockentity/RedstoneInterfaceBlockEntity.java @@ -46,7 +46,7 @@ public final class RedstoneInterfaceBlockEntity extends ModBlockEntity implement /////////////////////////////////////////////////////////////////// private final byte[] output = new byte[Constants.BLOCK_FACE_COUNT]; - private final byte[][] bundled_output = new byte[Constants.BLOCK_FACE_COUNT][16]; + private final byte[][] bundled_output = new byte[Constants.BLOCK_FACE_COUNT][Constants.BUNDLE_COLOR_COUNT]; /////////////////////////////////////////////////////////////////// @@ -146,10 +146,12 @@ public byte[] getBundledInput(@Parameter(SIDE) @Nullable final Side side) { BundledRedstone bundledRedstone = BundledRedstone.getInstance(); if (bundledRedstone.isAvailable()) { - return bundledRedstone.getBundledInput(this.level, this.getBlockPos(), side.getDirection().getOpposite()); - } else { - return new byte[Constants.BLOCK_FACE_COUNT]; + final Direction direction = HorizontalBlockUtils.toGlobal(getBlockState(), side); + final byte[] input = bundledRedstone.getBundledInput(this.level, this.getBlockPos(), direction); + if (input != null) return input; } + + return new byte[Constants.BUNDLE_COLOR_COUNT]; } @Callback(name = GET_BUNDLED_OUTPUT) @@ -167,7 +169,7 @@ public void setBundledOutput(@Parameter(SIDE) @Nullable final Side side, @Parame if (side == null) throw new IllegalArgumentException(); boolean changed = false; - final int index = side.getDirection().getOpposite().get3DDataValue(); + final int index = side.getDirection().get3DDataValue(); final byte clampedValue = (byte) Mth.clamp(value, 0, 255); final byte clampedColor = (byte) Mth.clamp(color, 0, 15); /*for (int i=0; i < values.length; i++) { @@ -199,7 +201,7 @@ public void setBundledOutputs(@Parameter(SIDE) @Nullable final Side side, @Param if (side == null) throw new IllegalArgumentException(); boolean changed = false; - final int index = side.getDirection().getOpposite().get3DDataValue(); + final int index = side.getDirection().get3DDataValue(); for (int i=0; i < values.length; i++) { final byte clampedValue = (byte) Mth.clamp(values[i], 0, 255); if (clampedValue != bundled_output[index][i]) { @@ -285,7 +287,9 @@ private void notifyNeighbor(final Direction direction) { @Nullable public byte[] getBundledSignal(Direction direction) { - final int index = direction.get3DDataValue(); + final Direction local_direction = HorizontalBlockUtils.toLocal(getBlockState(), direction); + assert local_direction != null; + final int index = local_direction.get3DDataValue(); return this.bundled_output[index]; } diff --git a/src/main/java/li/cil/oc2/common/bus/device/rpc/item/RedstoneInterfaceCardItemDevice.java b/src/main/java/li/cil/oc2/common/bus/device/rpc/item/RedstoneInterfaceCardItemDevice.java index b99fc80f..42ed9f0e 100644 --- a/src/main/java/li/cil/oc2/common/bus/device/rpc/item/RedstoneInterfaceCardItemDevice.java +++ b/src/main/java/li/cil/oc2/common/bus/device/rpc/item/RedstoneInterfaceCardItemDevice.java @@ -6,12 +6,14 @@ import li.cil.oc2.api.bus.device.object.Callback; import li.cil.oc2.api.bus.device.object.DocumentedDevice; import li.cil.oc2.api.bus.device.object.Parameter; +import li.cil.oc2.api.capabilities.BundledEmitter; import li.cil.oc2.api.capabilities.RedstoneEmitter; import li.cil.oc2.api.util.Side; import li.cil.oc2.common.Constants; import li.cil.oc2.common.block.Blocks; import li.cil.oc2.common.blockentity.ComputerBlockEntity; import li.cil.oc2.common.capabilities.Capabilities; +import li.cil.oc2.common.integration.util.BundledRedstone; import li.cil.oc2.common.util.HorizontalBlockUtils; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; @@ -23,6 +25,7 @@ import net.minecraft.world.level.Level; import net.minecraft.world.level.block.entity.BlockEntity; import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.ModList; import net.neoforged.fml.common.EventBusSubscriber; import net.neoforged.neoforge.capabilities.RegisterCapabilitiesEvent; @@ -32,18 +35,27 @@ @EventBusSubscriber(modid = API.MOD_ID) public final class RedstoneInterfaceCardItemDevice extends AbstractItemRPCDevice implements DocumentedDevice { private static final String OUTPUT_TAG_NAME = "output"; + private static final String BUNDLED_TAG_NAME = "bundled"; private static final String GET_REDSTONE_INPUT = "getRedstoneInput"; private static final String GET_REDSTONE_OUTPUT = "getRedstoneOutput"; private static final String SET_REDSTONE_OUTPUT = "setRedstoneOutput"; + private static final String GET_BUNDLED_INPUT = "getBundledInput"; + private static final String GET_BUNDLED_OUTPUT = "getBundledOutput"; + private static final String SET_BUNDLED_OUTPUT = "setBundledOutput"; + private static final String SET_BUNDLED_OUTPUTS = "setBundledOutputs"; private static final String SIDE = "side"; private static final String VALUE = "value"; + private static final String VALUES = "values"; + private static final String COLOUR = "colour"; /////////////////////////////////////////////////////////////////// private final BlockEntity blockEntity; - private final RedstoneEmitter[] capabilities; + private final RedstoneEmitter[] re_capabilities; + private final BundledEmitter[] be_capabilities; private final byte[] output = new byte[Constants.BLOCK_FACE_COUNT]; + private final byte[][] bundled_output = new byte[Constants.BLOCK_FACE_COUNT][Constants.BUNDLE_COLOR_COUNT]; /////////////////////////////////////////////////////////////////// @@ -51,10 +63,12 @@ public RedstoneInterfaceCardItemDevice(final ItemStack identity, final BlockEnti super(identity, "redstone"); this.blockEntity = blockEntity; - capabilities = new RedstoneEmitter[Constants.BLOCK_FACE_COUNT]; + re_capabilities = new RedstoneEmitter[Constants.BLOCK_FACE_COUNT]; + be_capabilities = new BundledEmitter[Constants.BLOCK_FACE_COUNT]; for (int i = 0; i < Constants.BLOCK_FACE_COUNT; i++) { final int indexForClosure = i; - capabilities[i] = () -> output[indexForClosure]; + re_capabilities[i] = () -> output[indexForClosure]; + be_capabilities[i] = () -> bundled_output[indexForClosure]; } } @@ -72,7 +86,24 @@ public static void registerCapabilities(RegisterCapabilitiesEvent event) { if (self == null) return null; final int index = side.get3DDataValue(); - return self.capabilities[index]; + return self.re_capabilities[index]; + } + return null; + }, + Blocks.COMPUTER.get() + ); + + event.registerBlock( + Capabilities.BundledEmitter.BLOCK, + (level, pos, state, be, side) -> { + if (side == null) return null; + + if (be instanceof final ComputerBlockEntity computer) { + RedstoneInterfaceCardItemDevice self = computer.getFirstDevice(RedstoneInterfaceCardItemDevice.class); + if (self == null) return null; + + final int index = side.get3DDataValue(); + return self.be_capabilities[index]; } return null; }, @@ -84,6 +115,12 @@ public static void registerCapabilities(RegisterCapabilitiesEvent event) { public CompoundTag serializeNBT(HolderLookup.Provider provider) { final CompoundTag tag = new CompoundTag(); tag.putByteArray(OUTPUT_TAG_NAME, output); + CompoundTag tag_bundled_output = new CompoundTag(); + for (Direction dir : Direction.values()) { + tag_bundled_output.putByteArray(dir.getName(), bundled_output[dir.get3DDataValue()]); + } + tag.put(BUNDLED_TAG_NAME, tag_bundled_output); + return tag; } @@ -91,6 +128,13 @@ public CompoundTag serializeNBT(HolderLookup.Provider provider) { public void deserializeNBT(HolderLookup.Provider provider, final CompoundTag tag) { final byte[] serializedOutput = tag.getByteArray(OUTPUT_TAG_NAME); System.arraycopy(serializedOutput, 0, output, 0, Math.min(serializedOutput.length, output.length)); + + final CompoundTag tag_bundled_output = tag.getCompound(BUNDLED_TAG_NAME); + for (Direction dir : Direction.values()) { + final byte[] serializedBundledOutput = tag_bundled_output.getByteArray(dir.getName()); + byte[] dest_output = bundled_output[dir.get3DDataValue()]; + System.arraycopy(serializedBundledOutput, 0, dest_output, 0, Math.min(serializedBundledOutput.length, dest_output.length)); + } } @Callback(name = GET_REDSTONE_INPUT) @@ -141,6 +185,87 @@ public void setRedstoneOutput(@Parameter(SIDE) @Nullable final Side side, @Param } } + @Nullable + @Callback(name = GET_BUNDLED_INPUT) + public byte[] getBundledInput(@Parameter(SIDE) @Nullable final Side side) { + if(!ModList.get().isLoaded("projectred_transmission")) throw new IllegalStateException(); + if (side == null) throw new IllegalArgumentException(); + + final Level level = blockEntity.getLevel(); + if (level != null) { + BundledRedstone bundledRedstone = BundledRedstone.getInstance(); + if (bundledRedstone.isAvailable()) { + final Direction direction = HorizontalBlockUtils.toGlobal(blockEntity.getBlockState(), side); + final byte[] input = bundledRedstone.getBundledInput(level, blockEntity.getBlockPos(), direction); + if (input != null) return input; + } + } + + return new byte[Constants.BUNDLE_COLOR_COUNT]; + } + + @Callback(name = GET_BUNDLED_OUTPUT) + public byte[] getBundledOutput(@Parameter(SIDE) @Nullable final Side side) { + if(!ModList.get().isLoaded("projectred_transmission")) throw new IllegalStateException(); + if (side == null) throw new IllegalArgumentException(); + + final int index = side.getDirection().get3DDataValue(); + return bundled_output[index]; + } + + @Callback(name = SET_BUNDLED_OUTPUT) + public void setBundledOutput(@Parameter(SIDE) @Nullable final Side side, @Parameter(VALUE) final int value, @Parameter(COLOUR) final int color) { + if(!ModList.get().isLoaded("projectred_transmission")) throw new IllegalStateException(); + if (side == null) throw new IllegalArgumentException(); + + boolean changed = false; + final int index = side.getDirection().get3DDataValue(); + final byte clampedValue = (byte) Mth.clamp(value, 0, 255); + final byte clampedColor = (byte) Mth.clamp(color, 0, 15); + /*for (int i=0; i < values.length; i++) { + final byte clampedValue = (byte) Mth.clamp(values[i], 0, 255); + if (clampedValue != bundled_output[index][i]) { + bundled_output[index][i] = clampedValue; + changed = true; + } + }*/ + + if (bundled_output[index][clampedColor] != clampedValue) { + changed = true; + bundled_output[index][clampedColor] = clampedValue; + } + + if (changed) { + final Direction direction = HorizontalBlockUtils.toGlobal(blockEntity.getBlockState(), side); + if (direction != null) { + notifyNeighbor(direction); + } + } + } + + @Callback(name = SET_BUNDLED_OUTPUTS) + public void setBundledOutputs(@Parameter(SIDE) @Nullable final Side side, @Parameter(VALUES) final int[] values) { + if(!ModList.get().isLoaded("projectred_transmission")) throw new IllegalStateException(); + if (side == null) throw new IllegalArgumentException(); + + boolean changed = false; + final int index = side.getDirection().get3DDataValue(); + for (int i=0; i < values.length; i++) { + final byte clampedValue = (byte) Mth.clamp(values[i], 0, 255); + if (clampedValue != bundled_output[index][i]) { + bundled_output[index][i] = clampedValue; + changed = true; + } + } + + if (changed) { + final Direction direction = HorizontalBlockUtils.toGlobal(blockEntity.getBlockState(), side); + if (direction != null) { + notifyNeighbor(direction); + } + } + } + @Override public void getDeviceDocumentation(final DocumentedDevice.DeviceVisitor visitor) { visitor.visitCallback(GET_REDSTONE_INPUT) @@ -165,6 +290,29 @@ public void getDeviceDocumentation(final DocumentedDevice.DeviceVisitor visitor) "the side depends on the orientation of the device's container.") .parameterDescription(SIDE, "the side to write the output level to.") .parameterDescription(VALUE, "the output level to set, will be clamped to [0, 15]."); + + if(ModList.get().isLoaded("projectred_transmission")) + { + visitor.visitCallback(GET_BUNDLED_INPUT) + .description("Get the current bundled level received on the specified side.") + .parameterDescription(SIDE, "the side to read the bundled input level from"); + visitor.visitCallback(GET_BUNDLED_OUTPUT) + .description("Get the current bundled level sent out on the specified side.") + .parameterDescription(SIDE, "the side to read the bundled output level from"); + visitor.visitCallback(SET_BUNDLED_OUTPUT) + .description("Set the new bundled level transmitted for a specific color on the specified side.\n" + + "Sides may be specified by name or zero-based index. Please note that " + + "the side depends on the orientation of the device.") + .parameterDescription(SIDE, "the side to write the output level to.") + .parameterDescription(VALUE, "the output level to set, will be clamped to [0, 255].") + .parameterDescription(COLOUR, "the colour wire this sets, as int [0, 15]"); + visitor.visitCallback(SET_BUNDLED_OUTPUTS) + .description("Set the new bundled levels transmitted on the specified side.\n" + + "Sides may be specified by name or zero-based index. Please note that " + + "the side depends on the orientation of the device.") + .parameterDescription(SIDE, "the side to write the output level to.") + .parameterDescription(VALUES, "the output levels to set in array form, each value will be clamped to [0, 255], 16 entries."); + } } /////////////////////////////////////////////////////////////////// diff --git a/src/main/java/li/cil/oc2/common/capabilities/Capabilities.java b/src/main/java/li/cil/oc2/common/capabilities/Capabilities.java index be846ef2..c468fdac 100644 --- a/src/main/java/li/cil/oc2/common/capabilities/Capabilities.java +++ b/src/main/java/li/cil/oc2/common/capabilities/Capabilities.java @@ -26,6 +26,9 @@ public static final class Device { public static final class RedstoneEmitter { public static final BlockCapability BLOCK = BlockCapability.createSided(ResourceLocation.fromNamespaceAndPath(API.MOD_ID, "redstone_emitter"), li.cil.oc2.api.capabilities.RedstoneEmitter.class); } + public static final class BundledEmitter { + public static final BlockCapability BLOCK = BlockCapability.createSided(ResourceLocation.fromNamespaceAndPath(API.MOD_ID, "bundled_emitter"), li.cil.oc2.api.capabilities.BundledEmitter.class); + } public static final class NetworkInterface { public static final BlockCapability BLOCK = BlockCapability.createSided(ResourceLocation.fromNamespaceAndPath(API.MOD_ID, "network_interface"), li.cil.oc2.api.capabilities.NetworkInterface.class); } diff --git a/src/main/java/li/cil/oc2/common/integration/projectred/BundledCableHandler.java b/src/main/java/li/cil/oc2/common/integration/projectred/BundledCableHandler.java index 2136701b..d9ab25aa 100644 --- a/src/main/java/li/cil/oc2/common/integration/projectred/BundledCableHandler.java +++ b/src/main/java/li/cil/oc2/common/integration/projectred/BundledCableHandler.java @@ -1,5 +1,6 @@ package li.cil.oc2.common.integration.projectred; +import li.cil.oc2.common.blockentity.ComputerBlockEntity; import li.cil.oc2.common.blockentity.RedstoneInterfaceBlockEntity; import li.cil.oc2.common.integration.util.BundledRedstone; import mrtjp.projectred.api.IBundledTileInteraction; @@ -29,13 +30,13 @@ private BundledCableHandler(ITransmissionAPI transmissionAPI) { @Override public boolean isValidInteractionFor(final Level level, final BlockPos blockPos, final Direction direction) { BlockEntity entity = level.getBlockEntity(blockPos); - return (entity instanceof RedstoneInterfaceBlockEntity); + return (entity instanceof RedstoneInterfaceBlockEntity) || (entity instanceof ComputerBlockEntity); } @Override public boolean canConnectBundled(final Level level, final BlockPos blockPos, final Direction direction) { BlockEntity entity = level.getBlockEntity(blockPos); - return (entity instanceof RedstoneInterfaceBlockEntity); + return (entity instanceof RedstoneInterfaceBlockEntity) || (entity instanceof ComputerBlockEntity); } @Nullable @@ -44,11 +45,14 @@ public byte[] getBundledSignal(final Level level, final BlockPos blockPos, final BlockEntity entity = level.getBlockEntity(blockPos); if (entity instanceof RedstoneInterfaceBlockEntity rs) { return rs.getBundledSignal(direction); + } else if (entity instanceof ComputerBlockEntity cbe) { + return cbe.getBundledSignal(direction); } else { return null; } } + @Nullable public byte[] getBundledInput(final Level level, final BlockPos blockPos, final Direction direction) { return transmissionAPI.getBundledInput(level, blockPos, direction); }