diff --git a/src/main/java/li/cil/oc2/api/bus/device/capabilities/NetworkInterface.java b/src/main/java/li/cil/oc2/api/bus/device/capabilities/NetworkInterface.java new file mode 100644 index 00000000..aec56a03 --- /dev/null +++ b/src/main/java/li/cil/oc2/api/bus/device/capabilities/NetworkInterface.java @@ -0,0 +1,50 @@ +package li.cil.oc2.api.bus.device.capabilities; + +import li.cil.oc2.api.bus.device.ItemDevice; + +import javax.annotation.Nullable; + +/** + * This interface provides interaction with the network bus. + *

+ * Network connectors will check for this capability on blocks they are placed on. + * If found, they will actively poll frames via {@link #readEthernetFrame()} and push + * forwarded frames via {@link #writeEthernetFrame(NetworkInterface, byte[], int)}. + *

+ * As with all capabilities, this capability can be provided by {@link ItemDevice}s. + */ +public interface NetworkInterface { + /** + * Tries to read an ethernet frame from this network interface. + *

+ * The frame should> be a Layer 2 Ethernet frame. + *

+ * When no data is available, {@code null} should be returned. + * + * @return a pending frame or {@code null}. + */ + @Nullable + byte[] readEthernetFrame(); + + /** + * Tries to write an ethernet frame to this network interface. + *

+ * The frame should be a Layer 2 Ethernet frame, but this is + * not guaranteed. Implementations should not rely on this, and if relying + * on this at least add appropriate validation and discard the frame otherwise. + *

+ * If the device is not ready to receive data, it may ignore the call. + *

+ * The {@code timeToLive} parameter is not to be confused with the IP protocol's + * TTL field. This parameter is used when pushing frames through the network bus + * to prevent infinite loops in case of cycles. Pure consumers can ignore this + * argument. Any implementation forwarding directly (by pushing it to some other + * {@link NetworkInterface} implementation) should call {@code writeEthernetFrame} + * with the time to live reduced by some value, usually by one. + * + * @param source the device that last forwarded the frame. + * @param frame the frame offered to the network interface. + * @param timeToLive the number of hops remaining before the frame should be discarded. + */ + void writeEthernetFrame(NetworkInterface source, byte[] frame, final int timeToLive); +} diff --git a/src/main/java/li/cil/oc2/client/ClientSetup.java b/src/main/java/li/cil/oc2/client/ClientSetup.java index 683d5ec8..5b1ab237 100644 --- a/src/main/java/li/cil/oc2/client/ClientSetup.java +++ b/src/main/java/li/cil/oc2/client/ClientSetup.java @@ -1,6 +1,7 @@ package li.cil.oc2.client; import li.cil.oc2.client.gui.ComputerContainerScreen; +import li.cil.oc2.client.renderer.NetworkCableRenderer; import li.cil.oc2.client.renderer.tileentity.ComputerTileEntityRenderer; import li.cil.oc2.common.container.Containers; import li.cil.oc2.common.tileentity.TileEntities; @@ -10,6 +11,8 @@ import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent; public final class ClientSetup { public static void run(final FMLClientSetupEvent event) { + NetworkCableRenderer.initialize(); + ScreenManager.registerFactory(Containers.COMPUTER_CONTAINER.get(), ComputerContainerScreen::new); ClientRegistry.bindTileEntityRenderer(TileEntities.COMPUTER_TILE_ENTITY.get(), ComputerTileEntityRenderer::new); diff --git a/src/main/java/li/cil/oc2/client/renderer/NetworkCableRenderer.java b/src/main/java/li/cil/oc2/client/renderer/NetworkCableRenderer.java new file mode 100644 index 00000000..8d80d031 --- /dev/null +++ b/src/main/java/li/cil/oc2/client/renderer/NetworkCableRenderer.java @@ -0,0 +1,230 @@ +package li.cil.oc2.client.renderer; + +import com.mojang.blaze3d.matrix.MatrixStack; +import com.mojang.blaze3d.vertex.IVertexBuilder; +import li.cil.oc2.common.tileentity.NetworkConnectorTileEntity; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.ActiveRenderInfo; +import net.minecraft.client.renderer.IRenderTypeBuffer; +import net.minecraft.client.renderer.LightTexture; +import net.minecraft.client.renderer.RenderType; +import net.minecraft.client.renderer.culling.ClippingHelper; +import net.minecraft.util.math.AxisAlignedBB; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.MathHelper; +import net.minecraft.util.math.vector.Matrix4f; +import net.minecraft.util.math.vector.Vector3d; +import net.minecraft.util.math.vector.Vector3f; +import net.minecraft.world.LightType; +import net.minecraft.world.World; +import net.minecraftforge.client.event.RenderWorldLastEvent; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.eventbus.api.SubscribeEvent; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Objects; + +public final class NetworkCableRenderer { + private static final int MAX_RENDER_DISTANCE = 100; + private static final int CABLE_VERTEX_COUNT = 9; + private static final float CABLE_THICKNESS = 0.025f; + private static final float CABLE_HANG_MIN = 0.1f; + private static final float CABLE_HANG_MAX = 0.5f; + private static final float CABLE_MAX_LENGTH = 8f; + private static final Vector3f CABLE_COLOR = new Vector3f(0.0f, 0.33f, 0.4f); + + private static final ArrayList connectors = new ArrayList<>(); + private static final ArrayList connections = new ArrayList<>(); + private static boolean isDirty; + + /////////////////////////////////////////////////////////////////// + + public static void initialize() { + MinecraftForge.EVENT_BUS.addListener(NetworkCableRenderer::handleRenderWorld); + } + + public static void addNetworkConnector(final NetworkConnectorTileEntity connector) { + connectors.add(connector); + invalidateConnections(); + } + + public static void invalidateConnections() { + isDirty = true; + } + + @SubscribeEvent + public static void handleRenderWorld(final RenderWorldLastEvent event) { + validateConnectors(); + validatePairs(); + + if (connections.isEmpty()) { + return; + } + + final World world = Minecraft.getInstance().world; + if (world == null) { + return; + } + + final MatrixStack matrixStack = event.getMatrixStack(); + + final ActiveRenderInfo activeRenderInfo = Minecraft.getInstance().gameRenderer.getActiveRenderInfo(); + final Vector3d eye = activeRenderInfo.getProjectedView(); + + final ClippingHelper frustum = new ClippingHelper(matrixStack.getLast().getMatrix(), event.getProjectionMatrix()); + frustum.setCameraPosition(eye.getX(), eye.getY(), eye.getZ()); + + matrixStack.push(); + matrixStack.translate(-eye.getX(), -eye.getY(), -eye.getZ()); + + final Matrix4f viewMatrix = matrixStack.getLast().getMatrix(); + + final RenderType renderType = OpenComputersRenderType.getNetworkCable(); + final IRenderTypeBuffer.Impl bufferSource = Minecraft.getInstance().getRenderTypeBuffers().getBufferSource(); + + final float r = CABLE_COLOR.getX(); + final float g = CABLE_COLOR.getY(); + final float b = CABLE_COLOR.getZ(); + + for (final Connection connection : connections) { + final Vector3d p0 = connection.from; + final Vector3d p1 = connection.to; + + if (!p0.isWithinDistanceOf(eye, MAX_RENDER_DISTANCE) && !p1.isWithinDistanceOf(eye, MAX_RENDER_DISTANCE)) { + continue; + } + + // We may easily get false positives here for diagonal cables, but it's good enough for now. + if (!frustum.isBoundingBoxInFrustum(connection.bounds)) { + continue; + } + + final Vector3d p2 = addNoisyMovement(lerp(p0, p1, 0.5f).subtract(0, computeCableHang(p0, p1), 0), connection.hashCode()); + + final IVertexBuilder buffer = bufferSource.getBuffer(renderType); + + for (int i = 0; i < CABLE_VERTEX_COUNT; i++) { + final float t = i / (CABLE_VERTEX_COUNT - 1f); + final Vector3d p = quadraticBezier(p0, p1, p2, t); + final Vector3d n = getExtrusionVector(eye, p, connection.forward); + + final BlockPos blockPos = new BlockPos(p); + final int blockLight = world.getLightFor(LightType.BLOCK, blockPos); + final int skyLight = world.getLightFor(LightType.SKY, blockPos); + final int packedLight = LightTexture.packLight(blockLight, skyLight); + + final Vector3f v0 = new Vector3f(p.subtract(n)); + final Vector3f v1 = new Vector3f(p.add(n)); + + buffer.pos(viewMatrix, v0.getX(), v0.getY(), v0.getZ()) + .color(r, g, b, 1f) + .lightmap(packedLight) + .endVertex(); + buffer.pos(viewMatrix, v1.getX(), v1.getY(), v1.getZ()) + .color(r, g, b, 1f) + .lightmap(packedLight) + .endVertex(); + } + + bufferSource.finish(renderType); + } + + matrixStack.pop(); + } + + /////////////////////////////////////////////////////////////////// + + private static Vector3d lerp(final Vector3d a, final Vector3d b, final float t) { + return a.add(b.subtract(a).scale(t)); // a + (b - a)*t = a*(1-t) + b*t + } + + private static Vector3d quadraticBezier(final Vector3d a, final Vector3d b, final Vector3d c, final float t) { + final Vector3d a1 = lerp(a, c, t); + final Vector3d b1 = lerp(c, b, t); + return lerp(a1, b1, t); + } + + private static Vector3d getExtrusionVector(final Vector3d eye, final Vector3d v, final Vector3d forward) { + return forward.crossProduct(eye.subtract(v)).normalize().scale(CABLE_THICKNESS); + } + + private static float computeCableHang(final Vector3d a, final Vector3d b) { + final double length = a.distanceTo(b); + final double hangFactor = MathHelper.clamp(length / CABLE_MAX_LENGTH, 0, 1); + return (float) (CABLE_HANG_MIN + (CABLE_HANG_MAX - CABLE_HANG_MIN) * hangFactor); + } + + private static Vector3d addNoisyMovement(final Vector3d c, final int seed) { + final float relTime = ((System.currentTimeMillis() + seed) % 10000) / 10000f; + final float relRadialTime = relTime * 2 * (float) Math.PI; + + return c.add(0.1f * MathHelper.cos(relRadialTime), 0.025f + 0.025f * MathHelper.sin(relRadialTime), 0.1f * MathHelper.cos(relRadialTime)); + } + + private static void validateConnectors() { + for (int i = connectors.size() - 1; i >= 0; i--) { + final NetworkConnectorTileEntity connector = connectors.get(i); + if (connector.isRemoved()) { + connectors.remove(i); + invalidateConnections(); + } + } + } + + private static void validatePairs() { + if (!isDirty) { + return; + } + + isDirty = false; + connections.clear(); + + final HashSet seen = new HashSet<>(); + for (final NetworkConnectorTileEntity connector : connectors) { + final BlockPos position = connector.getPos(); + for (final BlockPos connectedPosition : connector.getConnectedPositions()) { + final Connection connection = new Connection(position, connectedPosition); + if (seen.add(connection)) { + connections.add(connection); + } + } + } + } + + /////////////////////////////////////////////////////////////////// + + private static final class Connection { + public final BlockPos fromPos, toPos; + public final Vector3d from, to, forward; + public final AxisAlignedBB bounds; + + private Connection(final BlockPos fromPos, final BlockPos toPos) { + if (fromPos.compareTo(toPos) > 0) { + this.fromPos = toPos; + this.toPos = fromPos; + } else { + this.fromPos = fromPos; + this.toPos = toPos; + } + + from = Vector3d.copyCentered(fromPos); + to = Vector3d.copyCentered(toPos); + forward = to.subtract(from).normalize(); + bounds = new AxisAlignedBB(from, to).grow(0, CABLE_HANG_MAX, 0); + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + final Connection that = (Connection) o; + return fromPos.equals(that.fromPos) && toPos.equals(that.toPos); + } + + @Override + public int hashCode() { + return Objects.hash(fromPos, toPos); + } + } +} diff --git a/src/main/java/li/cil/oc2/client/renderer/OpenComputersRenderType.java b/src/main/java/li/cil/oc2/client/renderer/OpenComputersRenderType.java index 859cc436..4875fc53 100644 --- a/src/main/java/li/cil/oc2/client/renderer/OpenComputersRenderType.java +++ b/src/main/java/li/cil/oc2/client/renderer/OpenComputersRenderType.java @@ -1,6 +1,7 @@ package li.cil.oc2.client.renderer; import li.cil.oc2.api.API; +import net.minecraft.client.renderer.RenderState; import net.minecraft.client.renderer.RenderType; import net.minecraft.client.renderer.vertex.DefaultVertexFormats; import net.minecraft.client.renderer.vertex.VertexFormat; @@ -23,6 +24,19 @@ public abstract class OpenComputersRenderType extends RenderType { state); } + public static RenderType getNetworkCable() { + final State state = State.getBuilder() + .transparency(RenderState.NO_TRANSPARENCY) + .diffuseLighting(RenderState.DIFFUSE_LIGHTING_ENABLED) + .lightmap(RenderState.LIGHTMAP_ENABLED) + .build(false); + return RenderType.makeType(API.MOD_ID + ":network_cable", + DefaultVertexFormats.POSITION_COLOR_LIGHTMAP, + GL11.GL_QUAD_STRIP, + 256, + state); + } + /////////////////////////////////////////////////////////////////// private OpenComputersRenderType(final String name, final VertexFormat format, final int drawMode, final int bufferSize, final boolean useDelegate, final boolean needsSorting, final Runnable setupTask, final Runnable clearTask) { diff --git a/src/main/java/li/cil/oc2/common/Constants.java b/src/main/java/li/cil/oc2/common/Constants.java index e675a5b8..c7f056c8 100644 --- a/src/main/java/li/cil/oc2/common/Constants.java +++ b/src/main/java/li/cil/oc2/common/Constants.java @@ -16,17 +16,22 @@ public final class Constants { public static final String COMPUTER_BLOCK_NAME = "computer"; public static final String BUS_CABLE_BLOCK_NAME = "bus_cable"; + public static final String NETWORK_CONNECTOR_BLOCK_NAME = "network_connector"; public static final String REDSTONE_INTERFACE_BLOCK_NAME = "redstone_interface"; public static final String SCREEN_BLOCK_NAME = "screen"; /////////////////////////////////////////////////////////////////// public static final String WRENCH_ITEM_NAME = "wrench"; + public static final String BUS_INTERFACE_ITEM_NAME = "bus_interface"; + public static final String NETWORK_CABLE_NAME = "network_cable"; + public static final String FLASH_MEMORY_ITEM_NAME = "flash_memory"; public static final String MEMORY_ITEM_NAME = "memory"; public static final String HARD_DRIVE_ITEM_NAME = "hard_drive"; public static final String REDSTONE_INTERFACE_CARD_NAME = "redstone_interface_card"; + public static final String NETWORK_INTERFACE_CARD_NAME = "network_interface_card"; /////////////////////////////////////////////////////////////////// @@ -46,4 +51,10 @@ public final class Constants { public static final String COMPUTER_BUS_STATE_INCOMPLETE = "gui.oc2.computer.bus_state.incomplete"; public static final String COMPUTER_BUS_STATE_TOO_COMPLEX = "gui.oc2.computer.bus_state.too_complex"; public static final String COMPUTER_BUS_STATE_MULTIPLE_CONTROLLERS = "gui.oc2.computer.bus_state.multiple_controllers"; + + /////////////////////////////////////////////////////////////////// + + public static final String CONNECTOR_ERROR_FULL = "message.oc2.connector.error.full"; + public static final String CONNECTOR_ERROR_TOO_FAR = "message.oc2.connector.error.too_far"; + public static final String CONNECTOR_ERROR_OBSTRUCTED = "message.oc2.connector.error.obstructed"; } diff --git a/src/main/java/li/cil/oc2/common/block/Blocks.java b/src/main/java/li/cil/oc2/common/block/Blocks.java index d2a9f470..c50e0657 100644 --- a/src/main/java/li/cil/oc2/common/block/Blocks.java +++ b/src/main/java/li/cil/oc2/common/block/Blocks.java @@ -15,6 +15,7 @@ public final class Blocks { public static final RegistryObject COMPUTER_BLOCK = BLOCKS.register(Constants.COMPUTER_BLOCK_NAME, ComputerBlock::new); public static final RegistryObject BUS_CABLE_BLOCK = BLOCKS.register(Constants.BUS_CABLE_BLOCK_NAME, BusCableBlock::new); + public static final RegistryObject NETWORK_CONNECTOR_BLOCK = BLOCKS.register(Constants.NETWORK_CONNECTOR_BLOCK_NAME, NetworkConnectorBlock::new); public static final RegistryObject REDSTONE_INTERFACE_BLOCK = BLOCKS.register(Constants.REDSTONE_INTERFACE_BLOCK_NAME, RedstoneInterfaceBlock::new); public static final RegistryObject SCREEN_BLOCK = BLOCKS.register(Constants.SCREEN_BLOCK_NAME, ScreenBlock::new); diff --git a/src/main/java/li/cil/oc2/common/block/NetworkConnectorBlock.java b/src/main/java/li/cil/oc2/common/block/NetworkConnectorBlock.java new file mode 100644 index 00000000..92b982d7 --- /dev/null +++ b/src/main/java/li/cil/oc2/common/block/NetworkConnectorBlock.java @@ -0,0 +1,101 @@ +package li.cil.oc2.common.block; + +import li.cil.oc2.common.tileentity.NetworkConnectorTileEntity; +import li.cil.oc2.common.tileentity.TileEntities; +import net.minecraft.block.Block; +import net.minecraft.block.BlockState; +import net.minecraft.block.HorizontalFaceBlock; +import net.minecraft.block.SoundType; +import net.minecraft.block.material.Material; +import net.minecraft.state.StateContainer; +import net.minecraft.state.properties.AttachFace; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.Direction; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.shapes.ISelectionContext; +import net.minecraft.util.math.shapes.VoxelShape; +import net.minecraft.world.IBlockReader; +import net.minecraft.world.World; + +import javax.annotation.Nullable; +import java.util.Objects; + +public final class NetworkConnectorBlock extends HorizontalFaceBlock { + private static final VoxelShape NEG_Z_SHAPE = Block.makeCuboidShape(5, 5, 7, 11, 11, 16); + private static final VoxelShape POS_Z_SHAPE = Block.makeCuboidShape(5, 5, 0, 11, 11, 9); + private static final VoxelShape NEG_X_SHAPE = Block.makeCuboidShape(7, 5, 5, 16, 11, 11); + private static final VoxelShape POS_X_SHAPE = Block.makeCuboidShape(0, 5, 5, 9, 11, 11); + private static final VoxelShape NEG_Y_SHAPE = Block.makeCuboidShape(5, 0, 5, 11, 9, 11); + private static final VoxelShape POS_Y_SHAPE = Block.makeCuboidShape(5, 7, 5, 11, 16, 11); + + /////////////////////////////////////////////////////////////////// + + public NetworkConnectorBlock() { + super(Properties + .create(Material.IRON) + .sound(SoundType.METAL) + .hardnessAndResistance(1.5f, 6.0f)); + setDefaultState(getStateContainer().getBaseState() + .with(HORIZONTAL_FACING, Direction.NORTH) + .with(FACE, AttachFace.WALL)); + } + + /////////////////////////////////////////////////////////////////// + + public static Direction getFacing(final BlockState state) { + return HorizontalFaceBlock.getFacing(state); + } + + @Override + public boolean hasTileEntity(final BlockState state) { + return true; + } + + @Nullable + @Override + public TileEntity createTileEntity(final BlockState state, final IBlockReader world) { + return TileEntities.NETWORK_CONNECTOR_TILE_ENTITY.get().create(); + } + + @SuppressWarnings("deprecation") + @Override + public void neighborChanged(final BlockState state, final World world, final BlockPos pos, final Block changedBlock, final BlockPos changedBlockPos, final boolean isMoving) { + if (Objects.equals(changedBlockPos, pos.offset(getFacing(state).getOpposite()))) { + final TileEntity tileEntity = world.getTileEntity(pos); + if (tileEntity instanceof NetworkConnectorTileEntity) { + final NetworkConnectorTileEntity connector = (NetworkConnectorTileEntity) tileEntity; + connector.setLocalInterfaceChanged(); + } + } + } + + @SuppressWarnings("deprecation") + @Override + public VoxelShape getShape(final BlockState state, final IBlockReader world, final BlockPos pos, final ISelectionContext context) { + switch (state.get(FACE)) { + case WALL: + switch (state.get(HORIZONTAL_FACING)) { + case EAST: + return POS_X_SHAPE; + case WEST: + return NEG_X_SHAPE; + case SOUTH: + return POS_Z_SHAPE; + case NORTH: + default: + return NEG_Z_SHAPE; + } + case CEILING: + return POS_Y_SHAPE; + case FLOOR: + default: + return NEG_Y_SHAPE; + } + } + + /////////////////////////////////////////////////////////////////// + + protected void fillStateContainer(final StateContainer.Builder builder) { + builder.add(FACE, HORIZONTAL_FACING); + } +} diff --git a/src/main/java/li/cil/oc2/common/bus/device/item/NetworkInterfaceCardItemDevice.java b/src/main/java/li/cil/oc2/common/bus/device/item/NetworkInterfaceCardItemDevice.java new file mode 100644 index 00000000..046038f8 --- /dev/null +++ b/src/main/java/li/cil/oc2/common/bus/device/item/NetworkInterfaceCardItemDevice.java @@ -0,0 +1,149 @@ +package li.cil.oc2.common.bus.device.item; + +import li.cil.oc2.api.bus.device.ItemDevice; +import li.cil.oc2.api.bus.device.capabilities.NetworkInterface; +import li.cil.oc2.api.bus.device.vm.*; +import li.cil.oc2.common.bus.device.util.IdentityProxy; +import li.cil.oc2.common.bus.device.util.OptionalAddress; +import li.cil.oc2.common.bus.device.util.OptionalInterrupt; +import li.cil.oc2.common.capabilities.Capabilities; +import li.cil.oc2.common.serialization.NBTSerialization; +import li.cil.oc2.common.util.NBTTagIds; +import li.cil.sedna.device.virtio.VirtIONetworkDevice; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.CompoundNBT; +import net.minecraft.util.Direction; +import net.minecraftforge.common.capabilities.Capability; +import net.minecraftforge.common.capabilities.ICapabilityProvider; +import net.minecraftforge.common.util.LazyOptional; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public final class NetworkInterfaceCardItemDevice extends IdentityProxy implements VMDevice, VMDeviceLifecycleListener, ItemDevice, ICapabilityProvider { + private static final String DEVICE_TAG_NAME = "device"; + private static final String ADDRESS_NBT_TAG_NAME = "address"; + private static final String INTERRUPT_NBT_TAG_NAME = "interrupt"; + + /////////////////////////////////////////////////////////////// + + private VirtIONetworkDevice device; + private final NetworkInterface networkInterface = new NetworkInterfaceImpl(); + private boolean isRunning; + + private final OptionalAddress address = new OptionalAddress(); + private final OptionalInterrupt interrupt = new OptionalInterrupt(); + private CompoundNBT deviceNbt; + + /////////////////////////////////////////////////////////////// + + public NetworkInterfaceCardItemDevice(final ItemStack identity) { + super(identity); + } + + /////////////////////////////////////////////////////////////// + + @NotNull + @Override + public LazyOptional getCapability(@NotNull final Capability cap, @Nullable final Direction side) { + if (cap == Capabilities.NETWORK_INTERFACE && side != null) { + return LazyOptional.of(() -> networkInterface).cast(); + } + + return LazyOptional.empty(); + } + + @Override + public VMDeviceLoadResult load(final VMContext context) { + device = new VirtIONetworkDevice(context.getMemoryMap()); + + if (!address.claim(context, device)) { + return VMDeviceLoadResult.fail(); + } + + if (interrupt.claim(context)) { + device.getInterrupt().set(interrupt.getAsInt(), context.getInterruptController()); + } else { + return VMDeviceLoadResult.fail(); + } + + if (deviceNbt != null) { + NBTSerialization.deserialize(deviceNbt, device); + } + + return VMDeviceLoadResult.success(); + } + + @Override + public void handleLifecycleEvent(final VMDeviceLifecycleEventType event) { + switch (event) { + case RESUMED_RUNNING: + isRunning = true; + break; + case UNLOAD: + unload(); + break; + } + } + + @Override + public CompoundNBT serializeNBT() { + final CompoundNBT tag = new CompoundNBT(); + + if (device != null) { + deviceNbt = NBTSerialization.serialize(device); + } + if (deviceNbt != null) { + tag.put(DEVICE_TAG_NAME, deviceNbt); + } + if (address.isPresent()) { + tag.putLong(ADDRESS_NBT_TAG_NAME, address.getAsLong()); + } + if (interrupt.isPresent()) { + tag.putInt(INTERRUPT_NBT_TAG_NAME, interrupt.getAsInt()); + } + + return tag; + } + + @Override + public void deserializeNBT(final CompoundNBT tag) { + if (tag.contains(DEVICE_TAG_NAME, NBTTagIds.TAG_COMPOUND)) { + deviceNbt = tag.getCompound(DEVICE_TAG_NAME); + } + if (tag.contains(ADDRESS_NBT_TAG_NAME, NBTTagIds.TAG_LONG)) { + address.set(tag.getLong(ADDRESS_NBT_TAG_NAME)); + } + if (tag.contains(INTERRUPT_NBT_TAG_NAME, NBTTagIds.TAG_INT)) { + interrupt.set(tag.getInt(INTERRUPT_NBT_TAG_NAME)); + } + } + + /////////////////////////////////////////////////////////////// + + private void unload() { + device = null; + isRunning = false; + address.clear(); + interrupt.clear(); + } + + /////////////////////////////////////////////////////////////// + + private final class NetworkInterfaceImpl implements NetworkInterface { + @Override + public byte[] readEthernetFrame() { + if (device != null && isRunning) { + return device.readEthernetFrame(); + } else { + return null; + } + } + + @Override + public void writeEthernetFrame(final NetworkInterface source, final byte[] frame, final int timeToLive) { + if (device != null && isRunning) { + device.writeEthernetFrame(frame); + } + } + } +} diff --git a/src/main/java/li/cil/oc2/common/bus/device/provider/Providers.java b/src/main/java/li/cil/oc2/common/bus/device/provider/Providers.java index 8aa80832..d9877746 100644 --- a/src/main/java/li/cil/oc2/common/bus/device/provider/Providers.java +++ b/src/main/java/li/cil/oc2/common/bus/device/provider/Providers.java @@ -4,10 +4,7 @@ import li.cil.oc2.api.API; import li.cil.oc2.api.bus.device.provider.BlockDeviceProvider; import li.cil.oc2.api.bus.device.provider.ItemDeviceProvider; import li.cil.oc2.common.bus.device.provider.block.*; -import li.cil.oc2.common.bus.device.provider.item.FlashMemoryItemDeviceProvider; -import li.cil.oc2.common.bus.device.provider.item.HardDriveItemDeviceProvider; -import li.cil.oc2.common.bus.device.provider.item.MemoryItemDeviceProvider; -import li.cil.oc2.common.bus.device.provider.item.RedstoneInterfaceCardItemDeviceProvider; +import li.cil.oc2.common.bus.device.provider.item.*; import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; import net.minecraftforge.registries.DeferredRegister; import net.minecraftforge.registries.IForgeRegistry; @@ -38,6 +35,7 @@ public final class Providers { ITEM_DEVICE_PROVIDERS.register("item_hard_drive", HardDriveItemDeviceProvider::new); ITEM_DEVICE_PROVIDERS.register("item_flash_memory", FlashMemoryItemDeviceProvider::new); ITEM_DEVICE_PROVIDERS.register("item_redstone_interface_card", RedstoneInterfaceCardItemDeviceProvider::new); + ITEM_DEVICE_PROVIDERS.register("item_network_interface_card", NetworkInterfaceCardItemDeviceProvider::new); BLOCK_DEVICE_PROVIDERS.register(FMLJavaModLoadingContext.get().getModEventBus()); ITEM_DEVICE_PROVIDERS.register(FMLJavaModLoadingContext.get().getModEventBus()); diff --git a/src/main/java/li/cil/oc2/common/bus/device/provider/item/NetworkInterfaceCardItemDeviceProvider.java b/src/main/java/li/cil/oc2/common/bus/device/provider/item/NetworkInterfaceCardItemDeviceProvider.java new file mode 100644 index 00000000..f73edc41 --- /dev/null +++ b/src/main/java/li/cil/oc2/common/bus/device/provider/item/NetworkInterfaceCardItemDeviceProvider.java @@ -0,0 +1,23 @@ +package li.cil.oc2.common.bus.device.provider.item; + +import li.cil.oc2.api.bus.device.ItemDevice; +import li.cil.oc2.api.bus.device.provider.ItemDeviceQuery; +import li.cil.oc2.common.bus.device.item.NetworkInterfaceCardItemDevice; +import li.cil.oc2.common.bus.device.provider.util.AbstractItemDeviceProvider; +import li.cil.oc2.common.item.Items; + +import java.util.Optional; + +public final class NetworkInterfaceCardItemDeviceProvider extends AbstractItemDeviceProvider { + public NetworkInterfaceCardItemDeviceProvider() { + super(Items.NETWORK_INTERFACE_CARD_ITEM); + } + + /////////////////////////////////////////////////////////////////// + + @Override + protected Optional getItemDevice(final ItemDeviceQuery query) { + return query.getContainerTileEntity().map(tileEntity -> + new NetworkInterfaceCardItemDevice(query.getItemStack())); + } +} 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 1151d578..c0e4a330 100644 --- a/src/main/java/li/cil/oc2/common/capabilities/Capabilities.java +++ b/src/main/java/li/cil/oc2/common/capabilities/Capabilities.java @@ -1,6 +1,7 @@ package li.cil.oc2.common.capabilities; import li.cil.oc2.api.bus.DeviceBusElement; +import li.cil.oc2.api.bus.device.capabilities.NetworkInterface; import li.cil.oc2.api.bus.device.capabilities.RedstoneEmitter; import net.minecraftforge.common.capabilities.Capability; import net.minecraftforge.common.capabilities.CapabilityInject; @@ -25,11 +26,15 @@ public final class Capabilities { @CapabilityInject(RedstoneEmitter.class) public static Capability REDSTONE_EMITTER = null; + @CapabilityInject(NetworkInterface.class) + public static Capability NETWORK_INTERFACE = null; + /////////////////////////////////////////////////////////////////// public static void initialize() { register(DeviceBusElement.class); register(RedstoneEmitter.class); + register(NetworkInterface.class); } /////////////////////////////////////////////////////////////////// diff --git a/src/main/java/li/cil/oc2/common/item/Items.java b/src/main/java/li/cil/oc2/common/item/Items.java index 6275da18..65eeac3d 100644 --- a/src/main/java/li/cil/oc2/common/item/Items.java +++ b/src/main/java/li/cil/oc2/common/item/Items.java @@ -20,17 +20,22 @@ public final class Items { public static final RegistryObject COMPUTER_ITEM = register(Constants.COMPUTER_BLOCK_NAME, Blocks.COMPUTER_BLOCK); public static final RegistryObject BUS_CABLE_ITEM = register(Constants.BUS_CABLE_BLOCK_NAME, Blocks.BUS_CABLE_BLOCK); + public static final RegistryObject NETWORK_CONNECTOR_ITEM = register(Constants.NETWORK_CONNECTOR_BLOCK_NAME, Blocks.NETWORK_CONNECTOR_BLOCK); public static final RegistryObject REDSTONE_INTERFACE_ITEM = register(Constants.REDSTONE_INTERFACE_BLOCK_NAME, Blocks.REDSTONE_INTERFACE_BLOCK); public static final RegistryObject SCREEN_ITEM = register(Constants.SCREEN_BLOCK_NAME, Blocks.SCREEN_BLOCK); /////////////////////////////////////////////////////////////////// - public static final RegistryObject BUS_INTERFACE_ITEM = register(Constants.BUS_INTERFACE_ITEM_NAME, BusInterfaceItem::new); public static final RegistryObject WRENCH_ITEM = register(Constants.WRENCH_ITEM_NAME, WrenchItem::new); + + public static final RegistryObject BUS_INTERFACE_ITEM = register(Constants.BUS_INTERFACE_ITEM_NAME, BusInterfaceItem::new); + public static final RegistryObject NETWORK_CABLE_ITEM = register(Constants.NETWORK_CABLE_NAME, NetworkCableItem::new); + public static final RegistryObject MEMORY_ITEM = register(Constants.MEMORY_ITEM_NAME, MemoryItem::new, new Item.Properties()); public static final RegistryObject HARD_DRIVE_ITEM = register(Constants.HARD_DRIVE_ITEM_NAME, HardDriveItem::new, new Item.Properties()); public static final RegistryObject FLASH_MEMORY_ITEM = register(Constants.FLASH_MEMORY_ITEM_NAME, FlashMemoryItem::new, new Item.Properties()); public static final RegistryObject REDSTONE_INTERFACE_CARD_ITEM = register(Constants.REDSTONE_INTERFACE_CARD_NAME); + public static final RegistryObject NETWORK_INTERFACE_CARD_ITEM = register(Constants.NETWORK_INTERFACE_CARD_NAME); /////////////////////////////////////////////////////////////////// diff --git a/src/main/java/li/cil/oc2/common/item/NetworkCableItem.java b/src/main/java/li/cil/oc2/common/item/NetworkCableItem.java new file mode 100644 index 00000000..eea1007d --- /dev/null +++ b/src/main/java/li/cil/oc2/common/item/NetworkCableItem.java @@ -0,0 +1,113 @@ +package li.cil.oc2.common.item; + +import li.cil.oc2.common.Constants; +import li.cil.oc2.common.tileentity.NetworkConnectorTileEntity; +import li.cil.oc2.common.tileentity.NetworkConnectorTileEntity.ConnectionResult; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.entity.player.ServerPlayerEntity; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.item.ItemUseContext; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.ActionResult; +import net.minecraft.util.ActionResultType; +import net.minecraft.util.Hand; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.text.TranslationTextComponent; +import net.minecraft.world.World; + +import java.util.Objects; +import java.util.WeakHashMap; + +public final class NetworkCableItem extends Item { + private static final WeakHashMap LINK_STARTS = new WeakHashMap<>(); + + /////////////////////////////////////////////////////////////////// + + public NetworkCableItem(final Properties properties) { + super(properties); + } + + /////////////////////////////////////////////////////////////////// + + @Override + public ActionResult onItemRightClick(final World world, final PlayerEntity player, final Hand hand) { + if (player.isSneaking()) { + if (player instanceof ServerPlayerEntity) { + LINK_STARTS.remove(player); + } + + return ActionResult.resultSuccess(player.getHeldItem(hand)); + } + + return super.onItemRightClick(world, player, hand); + } + + @Override + public ActionResultType onItemUse(final ItemUseContext context) { + final PlayerEntity player = context.getPlayer(); + if (player == null) { + return super.onItemUse(context); + } + + final ItemStack stack = player.getHeldItem(context.getHand()); + if (stack.isEmpty() || stack.getItem() != this) { + return super.onItemUse(context); + } + + final World world = context.getWorld(); + final BlockPos currentPos = context.getPos(); + + final TileEntity currentTileEntity = world.getTileEntity(currentPos); + if (!(currentTileEntity instanceof NetworkConnectorTileEntity)) { + return super.onItemUse(context); + } + + if (!world.isRemote() && player instanceof ServerPlayerEntity) { + final BlockPos startPos = LINK_STARTS.remove(player); + if (startPos == null || Objects.equals(startPos, currentPos)) { + if (((NetworkConnectorTileEntity) currentTileEntity).canConnectMore()) { + LINK_STARTS.put((ServerPlayerEntity) player, currentPos); + } else { + player.sendStatusMessage(new TranslationTextComponent(Constants.CONNECTOR_ERROR_FULL), true); + } + } else { + final TileEntity startTileEntity = world.getTileEntity(startPos); + if (!(startTileEntity instanceof NetworkConnectorTileEntity)) { + // Starting connector was removed in the meantime. + return super.onItemUse(context); + } + + final NetworkConnectorTileEntity connectorA = (NetworkConnectorTileEntity) startTileEntity; + final NetworkConnectorTileEntity connectorB = (NetworkConnectorTileEntity) currentTileEntity; + + final ConnectionResult connectionResult = NetworkConnectorTileEntity.connect(connectorA, connectorB); + switch (connectionResult) { + case SUCCESS: + if (!player.isCreative()) { + stack.shrink(1); + } + break; + + case FAILURE: + LINK_STARTS.put((ServerPlayerEntity) player, startPos); + break; + case FAILURE_FULL: + LINK_STARTS.put((ServerPlayerEntity) player, startPos); + player.sendStatusMessage(new TranslationTextComponent(Constants.CONNECTOR_ERROR_FULL), true); + break; + case FAILURE_TOO_FAR: + LINK_STARTS.put((ServerPlayerEntity) player, startPos); + player.sendStatusMessage(new TranslationTextComponent(Constants.CONNECTOR_ERROR_TOO_FAR), true); + break; + case FAILURE_OBSTRUCTED: + LINK_STARTS.put((ServerPlayerEntity) player, startPos); + player.sendStatusMessage(new TranslationTextComponent(Constants.CONNECTOR_ERROR_OBSTRUCTED), true); + break; + } + } + } + + return ActionResultType.SUCCESS; + } +} diff --git a/src/main/java/li/cil/oc2/common/network/Network.java b/src/main/java/li/cil/oc2/common/network/Network.java index 1cea193c..e0ac7745 100644 --- a/src/main/java/li/cil/oc2/common/network/Network.java +++ b/src/main/java/li/cil/oc2/common/network/Network.java @@ -61,6 +61,12 @@ public final class Network { .decoder(ComputerPowerMessage::new) .consumer(ComputerPowerMessage::handleMessage) .add(); + + INSTANCE.messageBuilder(NetworkConnectorConnectionsMessage.class, getNextPacketId(), NetworkDirection.PLAY_TO_CLIENT) + .encoder(NetworkConnectorConnectionsMessage::toBytes) + .decoder(NetworkConnectorConnectionsMessage::new) + .consumer(NetworkConnectorConnectionsMessage::handleMessage) + .add(); } public static void sendToClientsTrackingChunk(final T message, final Chunk chunk) { diff --git a/src/main/java/li/cil/oc2/common/network/message/NetworkConnectorConnectionsMessage.java b/src/main/java/li/cil/oc2/common/network/message/NetworkConnectorConnectionsMessage.java new file mode 100644 index 00000000..c675a2c8 --- /dev/null +++ b/src/main/java/li/cil/oc2/common/network/message/NetworkConnectorConnectionsMessage.java @@ -0,0 +1,52 @@ +package li.cil.oc2.common.network.message; + +import li.cil.oc2.common.network.MessageUtils; +import li.cil.oc2.common.tileentity.NetworkConnectorTileEntity; +import net.minecraft.network.PacketBuffer; +import net.minecraft.util.math.BlockPos; +import net.minecraftforge.fml.network.NetworkEvent; + +import java.util.ArrayList; +import java.util.function.Supplier; + +public final class NetworkConnectorConnectionsMessage { + private BlockPos pos; + private ArrayList connectedPositions; + + /////////////////////////////////////////////////////////////////// + + public NetworkConnectorConnectionsMessage(final NetworkConnectorTileEntity connector) { + this.pos = connector.getPos(); + this.connectedPositions = new ArrayList<>(connector.getConnectedPositions()); + } + + public NetworkConnectorConnectionsMessage(final PacketBuffer buffer) { + fromBytes(buffer); + } + + /////////////////////////////////////////////////////////////////// + + public static boolean handleMessage(final NetworkConnectorConnectionsMessage message, final Supplier context) { + context.get().enqueueWork(() -> MessageUtils.withClientTileEntityAt(message.pos, NetworkConnectorTileEntity.class, + (tileEntity) -> tileEntity.setConnectedPositionsClient(message.connectedPositions))); + return true; + } + + public void fromBytes(final PacketBuffer buffer) { + pos = buffer.readBlockPos(); + connectedPositions = new ArrayList<>(); + final int positionCount = buffer.readVarInt(); + for (int i = 0; i < positionCount; i++) { + final BlockPos pos = buffer.readBlockPos(); + connectedPositions.add(pos); + } + } + + public static void toBytes(final NetworkConnectorConnectionsMessage message, final PacketBuffer buffer) { + buffer.writeBlockPos(message.pos); + buffer.writeVarInt(message.connectedPositions.size()); + for (final BlockPos pos : message.connectedPositions) { + buffer.writeBlockPos(pos); + } + } +} diff --git a/src/main/java/li/cil/oc2/common/tileentity/NetworkConnectorTileEntity.java b/src/main/java/li/cil/oc2/common/tileentity/NetworkConnectorTileEntity.java new file mode 100644 index 00000000..1d40700e --- /dev/null +++ b/src/main/java/li/cil/oc2/common/tileentity/NetworkConnectorTileEntity.java @@ -0,0 +1,452 @@ +package li.cil.oc2.common.tileentity; + +import li.cil.oc2.api.bus.device.capabilities.NetworkInterface; +import li.cil.oc2.client.renderer.NetworkCableRenderer; +import li.cil.oc2.common.Constants; +import li.cil.oc2.common.block.NetworkConnectorBlock; +import li.cil.oc2.common.capabilities.Capabilities; +import li.cil.oc2.common.item.Items; +import li.cil.oc2.common.network.Network; +import li.cil.oc2.common.network.message.NetworkConnectorConnectionsMessage; +import li.cil.oc2.common.util.ItemStackUtils; +import li.cil.oc2.common.util.NBTTagIds; +import li.cil.oc2.common.util.ServerScheduler; +import net.minecraft.block.BlockState; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.CompoundNBT; +import net.minecraft.nbt.ListNBT; +import net.minecraft.nbt.NBTUtil; +import net.minecraft.tileentity.ITickableTileEntity; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.Direction; +import net.minecraft.util.math.*; +import net.minecraft.util.math.vector.Vector3d; +import net.minecraft.world.World; +import net.minecraft.world.chunk.Chunk; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; +import net.minecraftforge.common.util.LazyOptional; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; + +public final class NetworkConnectorTileEntity extends AbstractTileEntity implements ITickableTileEntity { + public enum ConnectionResult { + SUCCESS, + FAILURE, + FAILURE_FULL, + FAILURE_TOO_FAR, + FAILURE_OBSTRUCTED, + ALREADY_CONNECTED + } + + private static final String CONNECTIONS_TAG_NAME = "connections"; + private static final String IS_OWNER_TAG_NAME = "is_owner"; + + private static final int RETRY_UNLOADED_CHUNK_INTERVAL = 5 * Constants.TICK_SECONDS; + private static final int MAX_CONNECTION_COUNT = 2; + private static final int MAX_CONNECTION_DISTANCE = 16; + private static final int INITIAL_PACKET_TIME_TO_LIVE = 8; + private static final int BYTES_PER_SECOND = 64 * 1024; + private static final int BYTES_PER_TICK = BYTES_PER_SECOND / Constants.TICK_SECONDS; + private static final int MIN_ETHERNET_FRAME_SIZE = 42; + + /////////////////////////////////////////////////////////////////// + + private final NetworkConnectorNetworkInterface networkInterface = new NetworkConnectorNetworkInterface(); + + private LazyOptional localInterface = LazyOptional.empty(); + private boolean isLocalConnectionDirty = true; + + private final HashSet connectorPositions = new HashSet<>(); + private final HashSet ownedCables = new HashSet<>(); + private final HashSet dirtyConnectors = new HashSet<>(); + private final HashMap connectors = new HashMap<>(); + + /////////////////////////////////////////////////////////////////// + + public NetworkConnectorTileEntity() { + super(TileEntities.NETWORK_CONNECTOR_TILE_ENTITY.get()); + } + + /////////////////////////////////////////////////////////////////// + + public static ConnectionResult connect(final NetworkConnectorTileEntity connectorA, final NetworkConnectorTileEntity connectorB) { + if (connectorA == connectorB || connectorA.isRemoved() || connectorB.isRemoved()) { + return ConnectionResult.FAILURE; + } + + final World world = connectorA.getWorld(); + if (world == null || world.isRemote()) { + return ConnectionResult.FAILURE; + } + + if (connectorB.getWorld() != world) { + return ConnectionResult.FAILURE; + } + + if (!connectorA.canConnectMore() || !connectorB.canConnectMore()) { + return ConnectionResult.FAILURE_FULL; + } + + final BlockPos posA = connectorA.getPos(); + final BlockPos posB = connectorB.getPos(); + + if (!posA.withinDistance(posB, MAX_CONNECTION_DISTANCE)) { + return ConnectionResult.FAILURE_TOO_FAR; + } + + if (isObstructed(world, posA, posB)) { + return ConnectionResult.FAILURE_OBSTRUCTED; + } + + if (connectorA.connectorPositions.add(posB)) { + connectorA.dirtyConnectors.add(posB); + connectorA.onConnectedPositionsChanged(); + } + + if (connectorB.connectorPositions.add(posA)) { + connectorB.dirtyConnectors.add(posA); + connectorB.onConnectedPositionsChanged(); + } + + final ConnectionResult result; + if (connectorA.ownedCables.contains(posB) || connectorB.ownedCables.contains(posA)) { + connectorA.ownedCables.add(posB); + connectorB.ownedCables.remove(posA); + result = ConnectionResult.ALREADY_CONNECTED; + } else { + connectorA.ownedCables.add(posB); + result = ConnectionResult.SUCCESS; + } + + connectorA.markDirty(); + connectorB.markDirty(); + + return result; + } + + public void disconnectFrom(final BlockPos pos) { + dirtyConnectors.remove(pos); + connectors.remove(pos); + + if (ownedCables.remove(pos)) { + final World world = getWorld(); + if (world != null) { + final Vector3d middle = Vector3d.copyCentered(getPos().add(pos)).scale(0.5f); + ItemStackUtils.spawnAsEntity(world, middle, new ItemStack(Items.NETWORK_CABLE_ITEM.get())); + } + } + + if (!isRemoved()) { + if (connectorPositions.remove(pos)) { + onConnectedPositionsChanged(); + } + + markDirty(); + } + } + + public boolean canConnectMore() { + return connectorPositions.size() < MAX_CONNECTION_COUNT; + } + + public Collection getConnectedPositions() { + return connectorPositions; + } + + public void setLocalInterfaceChanged() { + isLocalConnectionDirty = true; + } + + @OnlyIn(Dist.CLIENT) + public void setConnectedPositionsClient(final ArrayList positions) { + connectorPositions.clear(); + connectorPositions.addAll(positions); + NetworkCableRenderer.invalidateConnections(); + } + + @Override + public void tick() { + if (isLocalConnectionDirty) { + isLocalConnectionDirty = false; + resolveLocalInterface(); + } + + if (!dirtyConnectors.isEmpty()) { + final ArrayList list = new ArrayList<>(dirtyConnectors); + dirtyConnectors.clear(); + for (final BlockPos connectedPosition : list) { + resolveConnectedInterface(connectedPosition); + } + } + + final NetworkInterface src = localInterface.orElse(NullNetworkInterface.INSTANCE); + + int byteBudget = BYTES_PER_TICK; + byte[] frame; + while ((frame = src.readEthernetFrame()) != null && byteBudget > 0) { + byteBudget -= Math.max(frame.length, MIN_ETHERNET_FRAME_SIZE); // Avoid bogus packets messing with us. + networkInterface.writeEthernetFrame(src, frame, INITIAL_PACKET_TIME_TO_LIVE); + } + } + + @Override + public CompoundNBT getUpdateTag() { + final CompoundNBT tag = super.getUpdateTag(); + + final ListNBT connections = new ListNBT(); + for (final BlockPos position : connectorPositions) { + final CompoundNBT connectionTag = NBTUtil.writeBlockPos(position); + connections.add(connectionTag); + } + tag.put(CONNECTIONS_TAG_NAME, connections); + + return tag; + } + + @Override + public void handleUpdateTag(final BlockState state, final CompoundNBT tag) { + super.handleUpdateTag(state, tag); + + final ListNBT connections = tag.getList(CONNECTIONS_TAG_NAME, NBTTagIds.TAG_COMPOUND); + for (int i = 0; i < Math.min(connections.size(), MAX_CONNECTION_COUNT); i++) { + final CompoundNBT connectionTag = connections.getCompound(i); + final BlockPos position = NBTUtil.readBlockPos(connectionTag); + connectorPositions.add(position); + dirtyConnectors.add(position); + } + } + + @Override + public CompoundNBT write(CompoundNBT tag) { + tag = super.write(tag); + + final ListNBT connections = new ListNBT(); + for (final BlockPos position : connectorPositions) { + final CompoundNBT connectionTag = NBTUtil.writeBlockPos(position); + if (ownedCables.contains(position)) { + connectionTag.putBoolean(IS_OWNER_TAG_NAME, true); + } + connections.add(connectionTag); + } + tag.put(CONNECTIONS_TAG_NAME, connections); + + return tag; + } + + @Override + public void read(final BlockState state, final CompoundNBT tag) { + super.read(state, tag); + + final ListNBT connections = tag.getList(CONNECTIONS_TAG_NAME, NBTTagIds.TAG_COMPOUND); + for (int i = 0; i < Math.min(connections.size(), MAX_CONNECTION_COUNT); i++) { + final CompoundNBT connectionTag = connections.getCompound(i); + final BlockPos position = NBTUtil.readBlockPos(connectionTag); + connectorPositions.add(position); + dirtyConnectors.add(position); + if (connectionTag.getBoolean(IS_OWNER_TAG_NAME)) { + ownedCables.add(position); + } + } + } + + @Override + protected void loadClient() { + super.loadClient(); + + NetworkCableRenderer.addNetworkConnector(this); + } + + @Override + public void remove() { + super.remove(); + + // When we're being removed we want to break the actual link to any connected + // connectors. This will also cause cables to be dropped. + final ArrayList list = new ArrayList<>(connectors.values()); + connectors.clear(); + for (final NetworkConnectorTileEntity connector : list) { + disconnectFrom(connector.getPos()); + connector.disconnectFrom(getPos()); + } + } + + @Override + protected void unloadServer() { + super.unloadServer(); + + // When unloading, we just want to remove the reference to this tile entity + // from connected connectors; we don't want to actually break the link. + final BlockPos pos = getPos(); + for (final NetworkConnectorTileEntity connector : connectors.values()) { + connector.connectors.remove(pos); + if (connector.connectorPositions.contains(pos)) { + connector.dirtyConnectors.add(pos); + } + } + } + + /////////////////////////////////////////////////////////////////// + + @Override + protected void collectCapabilities(final CapabilityCollector collector, @org.jetbrains.annotations.Nullable final Direction direction) { + collector.offer(Capabilities.NETWORK_INTERFACE, networkInterface); + } + + /////////////////////////////////////////////////////////////////// + + private void resolveLocalInterface() { + localInterface = LazyOptional.empty(); + + if (isRemoved()) { + return; + } + + final World world = getWorld(); + if (world == null || world.isRemote()) { + return; + } + + final Direction facing = NetworkConnectorBlock.getFacing(getBlockState()); + final BlockPos sourcePos = getPos().offset(facing.getOpposite()); + + final ChunkPos sourceChunk = new ChunkPos(sourcePos); + if (!world.chunkExists(sourceChunk.x, sourceChunk.z)) { + ServerScheduler.schedule(world, this::setLocalInterfaceChanged, RETRY_UNLOADED_CHUNK_INTERVAL); + return; + } + + final TileEntity tileEntity = world.getTileEntity(sourcePos); + if (tileEntity == null) { + return; + } + + localInterface = tileEntity.getCapability(Capabilities.NETWORK_INTERFACE, facing); + if (localInterface.isPresent()) { + localInterface.addListener(unused -> setLocalInterfaceChanged()); + } + } + + private void resolveConnectedInterface(final BlockPos connectedPosition) { + connectors.remove(connectedPosition); + + if (isRemoved()) { + return; + } + + final World world = getWorld(); + if (world == null || world.isRemote()) { + return; + } + + final ChunkPos destinationChunk = new ChunkPos(connectedPosition); + if (!world.chunkExists(destinationChunk.x, destinationChunk.z)) { + ServerScheduler.schedule(world, () -> dirtyConnectors.add(connectedPosition), RETRY_UNLOADED_CHUNK_INTERVAL); + return; + } + + final TileEntity tileEntity = world.getTileEntity(connectedPosition); + if (!(tileEntity instanceof NetworkConnectorTileEntity)) { + disconnectFrom(connectedPosition); + return; + } + + final NetworkConnectorTileEntity connector = (NetworkConnectorTileEntity) tileEntity; + + if (!connectedPosition.withinDistance(getPos(), MAX_CONNECTION_DISTANCE)) { + disconnectFrom(connectedPosition); + connector.disconnectFrom(getPos()); + return; + } + + if (isObstructed(world, getPos(), connectedPosition)) { + disconnectFrom(connectedPosition); + connector.disconnectFrom(getPos()); + return; + } + + connectors.put(connectedPosition, connector); + } + + private static boolean isObstructed(final World world, final BlockPos a, final BlockPos b) { + final Vector3d va = Vector3d.copyCentered(a); + final Vector3d vb = Vector3d.copyCentered(b); + final Vector3d ab = vb.subtract(va).normalize().scale(0.5); + + // Because of floating point inaccuracies the raytrace is not necessarily + // symmetric. In particular when grazing corners perfectly, e.g. two connectors + // attached to the same block at a 90 degree angle. So we check both ways. + final BlockRayTraceResult hitAB = world.rayTraceBlocks(new RayTraceContext( + va.add(ab), + vb.subtract(ab), + RayTraceContext.BlockMode.COLLIDER, + RayTraceContext.FluidMode.NONE, + null + )); + final BlockRayTraceResult hitBA = world.rayTraceBlocks(new RayTraceContext( + vb.subtract(ab), + va.add(ab), + RayTraceContext.BlockMode.COLLIDER, + RayTraceContext.FluidMode.NONE, + null + )); + + return hitAB.getType() != RayTraceResult.Type.MISS || + hitBA.getType() != RayTraceResult.Type.MISS; + } + + private void onConnectedPositionsChanged() { + final World world = getWorld(); + if (world != null && !world.isRemote()) { + final NetworkConnectorConnectionsMessage message = new NetworkConnectorConnectionsMessage(this); + final Chunk chunk = world.getChunkAt(getPos()); + Network.sendToClientsTrackingChunk(message, chunk); + } + } + + /////////////////////////////////////////////////////////////////// + + private static final class NullNetworkInterface implements NetworkInterface { + public static final NetworkInterface INSTANCE = new NullNetworkInterface(); + + @Override + public byte[] readEthernetFrame() { + return null; + } + + @Override + public void writeEthernetFrame(final NetworkInterface source, final byte[] frame, final int timeToLive) { + } + } + + private final class NetworkConnectorNetworkInterface implements NetworkInterface { + @Override + public byte[] readEthernetFrame() { + return null; + } + + @Override + public void writeEthernetFrame(final NetworkInterface source, final byte[] frame, final int timeToLive) { + if (timeToLive <= 0) { + return; + } + + localInterface.ifPresent(dst -> { + if (dst == source) { + return; + } + dst.writeEthernetFrame(this, frame, timeToLive - 1); + }); + + for (final NetworkConnectorTileEntity dst : connectors.values()) { + if (dst.isRemoved() || dst.networkInterface == source) { + continue; + } + dst.networkInterface.writeEthernetFrame(this, frame, timeToLive - 1); + } + } + } +} diff --git a/src/main/java/li/cil/oc2/common/tileentity/TileEntities.java b/src/main/java/li/cil/oc2/common/tileentity/TileEntities.java index 166aba00..0c6a2484 100644 --- a/src/main/java/li/cil/oc2/common/tileentity/TileEntities.java +++ b/src/main/java/li/cil/oc2/common/tileentity/TileEntities.java @@ -1,7 +1,7 @@ package li.cil.oc2.common.tileentity; -import li.cil.oc2.common.Constants; import li.cil.oc2.api.API; +import li.cil.oc2.common.Constants; import li.cil.oc2.common.block.Blocks; import net.minecraft.block.Block; import net.minecraft.tileentity.TileEntity; @@ -21,6 +21,7 @@ public final class TileEntities { public static final RegistryObject> REDSTONE_INTERFACE_TILE_ENTITY = register(Constants.REDSTONE_INTERFACE_BLOCK_NAME, Blocks.REDSTONE_INTERFACE_BLOCK, RedstoneInterfaceTileEntity::new); public static final RegistryObject> BUS_CABLE_TILE_ENTITY = register(Constants.BUS_CABLE_BLOCK_NAME, Blocks.BUS_CABLE_BLOCK, BusCableTileEntity::new); public static final RegistryObject> COMPUTER_TILE_ENTITY = register(Constants.COMPUTER_BLOCK_NAME, Blocks.COMPUTER_BLOCK, ComputerTileEntity::new); + public static final RegistryObject> NETWORK_CONNECTOR_TILE_ENTITY = register(Constants.NETWORK_CONNECTOR_BLOCK_NAME, Blocks.NETWORK_CONNECTOR_BLOCK, NetworkConnectorTileEntity::new); /////////////////////////////////////////////////////////////////// diff --git a/src/main/java/li/cil/oc2/data/BlockStates.java b/src/main/java/li/cil/oc2/data/BlockStates.java index 65486a9a..2f091b98 100644 --- a/src/main/java/li/cil/oc2/data/BlockStates.java +++ b/src/main/java/li/cil/oc2/data/BlockStates.java @@ -31,6 +31,18 @@ public class BlockStates extends BlockStateProvider { horizontalBlock(Blocks.COMPUTER_BLOCK, Items.COMPUTER_ITEM); horizontalBlock(Blocks.REDSTONE_INTERFACE_BLOCK, Items.REDSTONE_INTERFACE_ITEM); horizontalBlock(Blocks.SCREEN_BLOCK, Items.SCREEN_ITEM); + horizontalFaceBlock(Blocks.NETWORK_CONNECTOR_BLOCK, Items.NETWORK_CONNECTOR_ITEM) + .transforms() + .transform(ModelBuilder.Perspective.GUI) + .rotation(30, 315, 0) + .translation(0, 2, 0) + .scale(0.75f, 0.75f, 0.75f) + .end() + .transform(ModelBuilder.Perspective.FIXED) + .rotation(270, 0, 0) + .translation(0, 0, -5) + .end() + .end(); registerCableStates(); } @@ -187,8 +199,13 @@ public class BlockStates extends BlockStateProvider { .end(); } - private void horizontalBlock(final RegistryObject block, final RegistryObject item) { + private ItemModelBuilder horizontalBlock(final RegistryObject block, final RegistryObject item) { horizontalBlock(block.get(), models().getBuilder(block.getId().getPath())); - itemModels().getBuilder(item.getId().getPath()).parent(models().getExistingFile(block.getId())); + return itemModels().getBuilder(item.getId().getPath()).parent(models().getExistingFile(block.getId())); + } + + private ItemModelBuilder horizontalFaceBlock(final RegistryObject block, final RegistryObject item) { + horizontalFaceBlock(block.get(), models().getBuilder(block.getId().getPath())); + return itemModels().getBuilder(item.getId().getPath()).parent(models().getExistingFile(block.getId())); } } diff --git a/src/main/java/li/cil/oc2/data/ItemModels.java b/src/main/java/li/cil/oc2/data/ItemModels.java index 4e803756..a7e468ef 100644 --- a/src/main/java/li/cil/oc2/data/ItemModels.java +++ b/src/main/java/li/cil/oc2/data/ItemModels.java @@ -1,7 +1,7 @@ package li.cil.oc2.data; -import li.cil.oc2.common.Constants; import li.cil.oc2.api.API; +import li.cil.oc2.common.Constants; import li.cil.oc2.common.item.HardDriveItem; import li.cil.oc2.common.item.Items; import li.cil.oc2.common.item.MemoryItem; @@ -22,6 +22,8 @@ public final class ItemModels extends ItemModelProvider { protected void registerModels() { simple(Items.WRENCH_ITEM, "items/wrench"); + simple(Items.NETWORK_CABLE_ITEM, "items/network_cable"); + simple(Items.MEMORY_ITEM, "items/memory1") .override() .predicate(MemoryItem.CAPACITY_PROPERTY, 4 * Constants.MEGABYTE) @@ -31,7 +33,6 @@ public final class ItemModels extends ItemModelProvider { .predicate(MemoryItem.CAPACITY_PROPERTY, 8 * Constants.MEGABYTE) .model(simple(Items.MEMORY_ITEM, "items/memory3", "3")) .end(); - simple(Items.HARD_DRIVE_ITEM, "items/hard_drive1") .override() .predicate(HardDriveItem.CAPACITY_PROPERTY, 4 * Constants.MEGABYTE) @@ -41,9 +42,9 @@ public final class ItemModels extends ItemModelProvider { .predicate(HardDriveItem.CAPACITY_PROPERTY, 8 * Constants.MEGABYTE) .model(simple(Items.HARD_DRIVE_ITEM, "items/hard_drive3", "3")) .end(); - simple(Items.FLASH_MEMORY_ITEM, "items/flash_memory"); simple(Items.REDSTONE_INTERFACE_CARD_ITEM, "items/redstone_interface_card"); + simple(Items.NETWORK_INTERFACE_CARD_ITEM, "items/network_interface_card"); } private ItemModelBuilder simple(final RegistryObject item, final String texturePath) { diff --git a/src/main/java/li/cil/oc2/data/LootTables.java b/src/main/java/li/cil/oc2/data/LootTables.java index e9aecb68..5c5321d9 100644 --- a/src/main/java/li/cil/oc2/data/LootTables.java +++ b/src/main/java/li/cil/oc2/data/LootTables.java @@ -45,6 +45,7 @@ public final class LootTables extends LootTableProvider { registerDropSelfLootTable(Blocks.BUS_CABLE_BLOCK.get()); registerDropSelfLootTable(Blocks.REDSTONE_INTERFACE_BLOCK.get()); registerDropSelfLootTable(Blocks.SCREEN_BLOCK.get()); + registerDropSelfLootTable(Blocks.NETWORK_CONNECTOR_BLOCK.get()); registerLootTable(Blocks.COMPUTER_BLOCK.get(), ModBlockLootTables::droppingWithInventory); } diff --git a/src/main/resources/assets/oc2/blockstates/network_connector.json b/src/main/resources/assets/oc2/blockstates/network_connector.json new file mode 100644 index 00000000..a4003de3 --- /dev/null +++ b/src/main/resources/assets/oc2/blockstates/network_connector.json @@ -0,0 +1,57 @@ +{ + "variants": { + "face=floor,facing=north": { + "model": "oc2:block/network_connector" + }, + "face=wall,facing=north": { + "model": "oc2:block/network_connector", + "x": 90 + }, + "face=ceiling,facing=north": { + "model": "oc2:block/network_connector", + "x": 180, + "y": 180 + }, + "face=floor,facing=south": { + "model": "oc2:block/network_connector", + "y": 180 + }, + "face=wall,facing=south": { + "model": "oc2:block/network_connector", + "x": 90, + "y": 180 + }, + "face=ceiling,facing=south": { + "model": "oc2:block/network_connector", + "x": 180 + }, + "face=floor,facing=west": { + "model": "oc2:block/network_connector", + "y": 270 + }, + "face=wall,facing=west": { + "model": "oc2:block/network_connector", + "x": 90, + "y": 270 + }, + "face=ceiling,facing=west": { + "model": "oc2:block/network_connector", + "x": 180, + "y": 90 + }, + "face=floor,facing=east": { + "model": "oc2:block/network_connector", + "y": 90 + }, + "face=wall,facing=east": { + "model": "oc2:block/network_connector", + "x": 90, + "y": 90 + }, + "face=ceiling,facing=east": { + "model": "oc2:block/network_connector", + "x": 180, + "y": 270 + } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/oc2/lang/en_us.json b/src/main/resources/assets/oc2/lang/en_us.json index 8bd4189b..f894bed2 100644 --- a/src/main/resources/assets/oc2/lang/en_us.json +++ b/src/main/resources/assets/oc2/lang/en_us.json @@ -3,15 +3,18 @@ "block.oc2.computer": "Computer", "block.oc2.bus_cable": "Bus Cable", + "block.oc2.network_connector": "Network Connector", "block.oc2.redstone_interface": "Redstone Interface", "block.oc2.screen": "Screen", "item.oc2.wrench": "Scrench", "item.oc2.bus_interface": "Bus Interface", + "item.oc2.network_cable": "Network Cable", "item.oc2.memory": "Memory", "item.oc2.hard_drive": "Hard Drive", "item.oc2.flash_memory": "Flash Memory", "item.oc2.redstone_interface_card": "Redstone Interface Card", + "item.oc2.network_interface_card": "Network Interface Card", "gui.oc2.computer.boot_error.unknown": "Unknown Error", "gui.oc2.computer.boot_error.no_memory": "Insufficient Memory", @@ -27,5 +30,9 @@ "gui.oc2.device_type.memory": "Memory", "gui.oc2.device_type.hard_drive": "Hard Drive", "gui.oc2.device_type.flash_memory": "Flash Memory", - "gui.oc2.device_type.card": "Card" + "gui.oc2.device_type.card": "Card", + + "message.oc2.connector.error.full": "Cannot attach more cables.", + "message.oc2.connector.error.too_far": "Distance between connectors is too large.", + "message.oc2.connector.error.obstructed": "No clear line of sight between connectors." } \ No newline at end of file diff --git a/src/main/resources/assets/oc2/models/block/network_connector.json b/src/main/resources/assets/oc2/models/block/network_connector.json new file mode 100644 index 00000000..b86ee7a2 --- /dev/null +++ b/src/main/resources/assets/oc2/models/block/network_connector.json @@ -0,0 +1 @@ +{"parent":"block/block","textures":{"east":"oc2:blocks/network_connector/network_connector_east","west":"oc2:blocks/network_connector/network_connector_west","up":"oc2:blocks/network_connector/network_connector_up","down":"oc2:blocks/network_connector/network_connector_down","north":"oc2:blocks/network_connector/network_connector_north","south":"oc2:blocks/network_connector/network_connector_south","atlas0":"oc2:blocks/network_connector/network_connector_atlas0","particle":"#north"},"elements":[{"from":[5,0,5],"to":[11,1,11],"faces":{"east":{"texture":"east"},"west":{"texture":"west"},"up":{"texture":"up"},"down":{"texture":"down","cullface":"down"},"north":{"texture":"north"},"south":{"texture":"south"}}},{"from":[6,1,10],"to":[10,3,11],"faces":{"east":{"texture":"east"},"west":{"texture":"west"},"up":{"texture":"atlas0","uv":[0.0,2.5,1.0,2.75]},"south":{"texture":"south"}}},{"from":[5,1,6],"to":[11,3,10],"faces":{"east":{"texture":"east"},"west":{"texture":"west"},"up":{"texture":"atlas0","uv":[0.0,0.0,1.5,1.0]},"north":{"texture":"north"},"south":{"texture":"south"}}},{"from":[6,1,5],"to":[10,3,6],"faces":{"east":{"texture":"east"},"west":{"texture":"west"},"up":{"texture":"atlas0","uv":[0.0,2.75,1.0,3.0]},"north":{"texture":"north"}}},{"from":[6,3,10],"to":[7,4,11],"faces":{"east":{"texture":"atlas0","uv":[0.0,3.5,0.25,3.75]},"west":{"texture":"west"},"up":{"texture":"up"},"down":{"texture":"down"},"south":{"texture":"south"}}},{"from":[9,3,10],"to":[10,4,11],"faces":{"east":{"texture":"east"},"west":{"texture":"atlas0","uv":[0.0,3.75,0.25,4.0]},"up":{"texture":"up"},"down":{"texture":"down"},"south":{"texture":"south"}}},{"from":[5,3,9],"to":[11,4,10],"faces":{"east":{"texture":"east"},"west":{"texture":"west"},"up":{"texture":"up"},"down":{"texture":"down"},"north":{"texture":"atlas0","uv":[0.0,1.0,1.5,1.25]},"south":{"texture":"south"}}},{"from":[6,3,7],"to":[10,4,9],"faces":{"east":{"texture":"east"},"west":{"texture":"west"},"up":{"texture":"up"},"down":{"texture":"down"},"north":{"texture":"north"},"south":{"texture":"south"}}},{"from":[5,3,6],"to":[11,4,7],"faces":{"east":{"texture":"east"},"west":{"texture":"west"},"up":{"texture":"up"},"down":{"texture":"down"},"north":{"texture":"north"},"south":{"texture":"atlas0","uv":[0.0,1.25,1.5,1.5]}}},{"from":[6,3,5],"to":[7,4,6],"faces":{"east":{"texture":"atlas0","uv":[0.0,4.0,0.25,4.25]},"west":{"texture":"west"},"up":{"texture":"up"},"down":{"texture":"down"},"north":{"texture":"north"}}},{"from":[9,3,5],"to":[10,4,6],"faces":{"east":{"texture":"east"},"west":{"texture":"atlas0","uv":[0.0,4.25,0.25,4.5]},"up":{"texture":"up"},"down":{"texture":"down"},"north":{"texture":"north"}}},{"from":[6,4,10],"to":[10,7,11],"faces":{"east":{"texture":"east"},"west":{"texture":"west"},"up":{"texture":"up"},"down":{"texture":"atlas0","uv":[0.0,3.0,1.0,3.25]},"south":{"texture":"south"}}},{"from":[5,4,6],"to":[11,7,10],"faces":{"east":{"texture":"east"},"west":{"texture":"west"},"up":{"texture":"up"},"down":{"texture":"atlas0","uv":[0.0,1.5,1.5,2.5]},"north":{"texture":"north"},"south":{"texture":"south"}}},{"from":[6,4,5],"to":[10,7,6],"faces":{"east":{"texture":"east"},"west":{"texture":"west"},"up":{"texture":"up"},"down":{"texture":"atlas0","uv":[0.0,3.25,1.0,3.5]},"north":{"texture":"north"}}},{"from":[6,7,6],"to":[10,8,10],"faces":{"east":{"texture":"east"},"west":{"texture":"west"},"up":{"texture":"up"},"north":{"texture":"north"},"south":{"texture":"south"}}},{"from":[7,8,7],"to":[9,9,9],"faces":{"east":{"texture":"east"},"west":{"texture":"west"},"up":{"texture":"up"},"north":{"texture":"north"},"south":{"texture":"south"}}}]} \ No newline at end of file diff --git a/src/main/resources/assets/oc2/models/item/network_cable.json b/src/main/resources/assets/oc2/models/item/network_cable.json new file mode 100644 index 00000000..71b0cfc9 --- /dev/null +++ b/src/main/resources/assets/oc2/models/item/network_cable.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:item/handheld", + "textures": { + "layer0": "oc2:items/network_cable" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/oc2/models/item/network_connector.json b/src/main/resources/assets/oc2/models/item/network_connector.json new file mode 100644 index 00000000..43e30736 --- /dev/null +++ b/src/main/resources/assets/oc2/models/item/network_connector.json @@ -0,0 +1,32 @@ +{ + "parent": "oc2:block/network_connector", + "display": { + "gui": { + "rotation": [ + 30, + 315, + 0 + ], + "translation": [ + 0, 2, 0 + ], + "scale": [ + 0.75, + 0.75, + 0.75 + ] + }, + "fixed": { + "rotation": [ + 270, + 0, + 0 + ], + "translation": [ + 0, + 0, + -5 + ] + } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/oc2/models/item/network_interface_card.json b/src/main/resources/assets/oc2/models/item/network_interface_card.json new file mode 100644 index 00000000..2c99f043 --- /dev/null +++ b/src/main/resources/assets/oc2/models/item/network_interface_card.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:item/handheld", + "textures": { + "layer0": "oc2:items/network_interface_card" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/oc2/textures/blocks/network_connector/network_connector_atlas0.png b/src/main/resources/assets/oc2/textures/blocks/network_connector/network_connector_atlas0.png new file mode 100644 index 00000000..adcefdf8 Binary files /dev/null and b/src/main/resources/assets/oc2/textures/blocks/network_connector/network_connector_atlas0.png differ diff --git a/src/main/resources/assets/oc2/textures/blocks/network_connector/network_connector_down.png b/src/main/resources/assets/oc2/textures/blocks/network_connector/network_connector_down.png new file mode 100644 index 00000000..735d80f4 Binary files /dev/null and b/src/main/resources/assets/oc2/textures/blocks/network_connector/network_connector_down.png differ diff --git a/src/main/resources/assets/oc2/textures/blocks/network_connector/network_connector_east.png b/src/main/resources/assets/oc2/textures/blocks/network_connector/network_connector_east.png new file mode 100644 index 00000000..ba43447d Binary files /dev/null and b/src/main/resources/assets/oc2/textures/blocks/network_connector/network_connector_east.png differ diff --git a/src/main/resources/assets/oc2/textures/blocks/network_connector/network_connector_north.png b/src/main/resources/assets/oc2/textures/blocks/network_connector/network_connector_north.png new file mode 100644 index 00000000..9673caa1 Binary files /dev/null and b/src/main/resources/assets/oc2/textures/blocks/network_connector/network_connector_north.png differ diff --git a/src/main/resources/assets/oc2/textures/blocks/network_connector/network_connector_south.png b/src/main/resources/assets/oc2/textures/blocks/network_connector/network_connector_south.png new file mode 100644 index 00000000..0fb23e5d Binary files /dev/null and b/src/main/resources/assets/oc2/textures/blocks/network_connector/network_connector_south.png differ diff --git a/src/main/resources/assets/oc2/textures/blocks/network_connector/network_connector_up.png b/src/main/resources/assets/oc2/textures/blocks/network_connector/network_connector_up.png new file mode 100644 index 00000000..7cbf5a74 Binary files /dev/null and b/src/main/resources/assets/oc2/textures/blocks/network_connector/network_connector_up.png differ diff --git a/src/main/resources/assets/oc2/textures/blocks/network_connector/network_connector_west.png b/src/main/resources/assets/oc2/textures/blocks/network_connector/network_connector_west.png new file mode 100644 index 00000000..52128bc4 Binary files /dev/null and b/src/main/resources/assets/oc2/textures/blocks/network_connector/network_connector_west.png differ diff --git a/src/main/resources/assets/oc2/textures/items/network_cable.png b/src/main/resources/assets/oc2/textures/items/network_cable.png new file mode 100644 index 00000000..cf527462 Binary files /dev/null and b/src/main/resources/assets/oc2/textures/items/network_cable.png differ diff --git a/src/main/resources/assets/oc2/textures/items/network_interface_card.png b/src/main/resources/assets/oc2/textures/items/network_interface_card.png new file mode 100644 index 00000000..6a0df6f1 Binary files /dev/null and b/src/main/resources/assets/oc2/textures/items/network_interface_card.png differ diff --git a/src/main/resources/data/oc2/loot_tables/blocks/network_connector.json b/src/main/resources/data/oc2/loot_tables/blocks/network_connector.json new file mode 100644 index 00000000..74f40252 --- /dev/null +++ b/src/main/resources/data/oc2/loot_tables/blocks/network_connector.json @@ -0,0 +1,19 @@ +{ + "type": "minecraft:block", + "pools": [ + { + "rolls": 1, + "entries": [ + { + "type": "minecraft:item", + "name": "oc2:network_connector" + } + ], + "conditions": [ + { + "condition": "minecraft:survives_explosion" + } + ] + } + ] +} \ No newline at end of file