From 11acafdae4fcc2c521b2121ca099c40d3ebdd4e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20N=C3=BCcke?= Date: Thu, 13 Jan 2022 13:19:31 +0100 Subject: [PATCH] Added network tunnel devices. --- .../cil/oc2/api/bus/device/DeviceTypes.java | 1 + .../java/li/cil/oc2/client/ClientSetup.java | 6 +- .../oc2/client/gui/NetworkTunnelScreen.java | 78 ++++++++++++ .../java/li/cil/oc2/client/gui/Sprites.java | 4 + .../java/li/cil/oc2/client/gui/Textures.java | 2 + src/main/java/li/cil/oc2/common/Config.java | 1 + .../oc2/common/bus/device/DeviceTypes.java | 1 + .../device/item/NetworkTunnelItemDevice.java | 112 +++++++++++++++++ .../common/bus/device/provider/Providers.java | 2 + .../NetworkTunnelCardItemDeviceProvider.java | 28 +++++ ...NetworkTunnelModuleItemDeviceProvider.java | 28 +++++ .../cil/oc2/common/container/Containers.java | 1 + .../oc2/common/container/DeviceTypeSlot.java | 40 ++++++ .../container/NetworkTunnelContainer.java | 116 ++++++++++++++++++ .../java/li/cil/oc2/common/item/Items.java | 2 + .../oc2/common/item/NetworkTunnelItem.java | 78 ++++++++++++ .../li/cil/oc2/common/network/Network.java | 1 + .../message/NetworkTunnelLinkMessage.java | 52 ++++++++ .../java/li/cil/oc2/common/tags/ItemTags.java | 1 + .../li/cil/oc2/data/ModItemModelProvider.java | 2 + .../li/cil/oc2/data/ModItemTagsProvider.java | 14 ++- .../li/cil/oc2/data/ModRecipesProvider.java | 22 ++++ .../assets/oc2/doc/en_us/item/index.md | 4 +- .../oc2/doc/en_us/item/network_tunnel_card.md | 12 ++ .../doc/en_us/item/network_tunnel_module.md | 12 ++ src/main/resources/assets/oc2/lang/en_us.json | 7 ++ .../oc2/models/item/network_tunnel_card.json | 6 + .../models/item/network_tunnel_module.json | 6 + .../oc2/textures/gui/icon/network_tunnel.png | Bin 0 -> 2056 bytes .../gui/widget/network_tunnel_link_button.png | Bin 0 -> 2191 bytes .../gui/widget/network_tunnel_screen.png | Bin 0 -> 4401 bytes .../oc2/textures/item/network_tunnel_card.png | Bin 0 -> 2145 bytes .../textures/item/network_tunnel_module.png | Bin 0 -> 2321 bytes .../oc2.common/network_tunnel_card.json | 34 +++++ .../oc2.common/network_tunnel_module.json | 34 +++++ .../data/oc2/recipes/network_tunnel_card.json | 24 ++++ .../oc2/recipes/network_tunnel_module.json | 24 ++++ .../oc2/tags/items/device_needs_reboot.json | 4 +- .../data/oc2/tags/items/devices/card.json | 3 +- .../tags/items/devices/network_tunnel.json | 7 ++ .../oc2/tags/items/devices/robot_module.json | 3 +- 41 files changed, 761 insertions(+), 11 deletions(-) create mode 100644 src/main/java/li/cil/oc2/client/gui/NetworkTunnelScreen.java create mode 100644 src/main/java/li/cil/oc2/common/bus/device/item/NetworkTunnelItemDevice.java create mode 100644 src/main/java/li/cil/oc2/common/bus/device/provider/item/NetworkTunnelCardItemDeviceProvider.java create mode 100644 src/main/java/li/cil/oc2/common/bus/device/provider/item/NetworkTunnelModuleItemDeviceProvider.java create mode 100644 src/main/java/li/cil/oc2/common/container/DeviceTypeSlot.java create mode 100644 src/main/java/li/cil/oc2/common/container/NetworkTunnelContainer.java create mode 100644 src/main/java/li/cil/oc2/common/item/NetworkTunnelItem.java create mode 100644 src/main/java/li/cil/oc2/common/network/message/NetworkTunnelLinkMessage.java create mode 100644 src/main/resources/assets/oc2/doc/en_us/item/network_tunnel_card.md create mode 100644 src/main/resources/assets/oc2/doc/en_us/item/network_tunnel_module.md create mode 100644 src/main/resources/assets/oc2/models/item/network_tunnel_card.json create mode 100644 src/main/resources/assets/oc2/models/item/network_tunnel_module.json create mode 100644 src/main/resources/assets/oc2/textures/gui/icon/network_tunnel.png create mode 100644 src/main/resources/assets/oc2/textures/gui/widget/network_tunnel_link_button.png create mode 100644 src/main/resources/assets/oc2/textures/gui/widget/network_tunnel_screen.png create mode 100644 src/main/resources/assets/oc2/textures/item/network_tunnel_card.png create mode 100644 src/main/resources/assets/oc2/textures/item/network_tunnel_module.png create mode 100644 src/main/resources/data/oc2/advancements/recipes/oc2.common/network_tunnel_card.json create mode 100644 src/main/resources/data/oc2/advancements/recipes/oc2.common/network_tunnel_module.json create mode 100644 src/main/resources/data/oc2/recipes/network_tunnel_card.json create mode 100644 src/main/resources/data/oc2/recipes/network_tunnel_module.json create mode 100644 src/main/resources/data/oc2/tags/items/devices/network_tunnel.json diff --git a/src/main/java/li/cil/oc2/api/bus/device/DeviceTypes.java b/src/main/java/li/cil/oc2/api/bus/device/DeviceTypes.java index a87df022..2366f250 100644 --- a/src/main/java/li/cil/oc2/api/bus/device/DeviceTypes.java +++ b/src/main/java/li/cil/oc2/api/bus/device/DeviceTypes.java @@ -14,4 +14,5 @@ public final class DeviceTypes { @ObjectHolder("card") public static DeviceType CARD = null; @ObjectHolder("robot_module") public static DeviceType ROBOT_MODULE = null; @ObjectHolder("floppy") public static DeviceType FLOPPY = null; + @ObjectHolder("network_tunnel") public static DeviceType NETWORK_TUNNEL = null; } diff --git a/src/main/java/li/cil/oc2/client/ClientSetup.java b/src/main/java/li/cil/oc2/client/ClientSetup.java index da369414..be774840 100644 --- a/src/main/java/li/cil/oc2/client/ClientSetup.java +++ b/src/main/java/li/cil/oc2/client/ClientSetup.java @@ -1,10 +1,7 @@ package li.cil.oc2.client; import li.cil.oc2.api.bus.device.DeviceType; -import li.cil.oc2.client.gui.ComputerContainerScreen; -import li.cil.oc2.client.gui.ComputerTerminalScreen; -import li.cil.oc2.client.gui.RobotContainerScreen; -import li.cil.oc2.client.gui.RobotTerminalScreen; +import li.cil.oc2.client.gui.*; import li.cil.oc2.client.item.CustomItemColors; import li.cil.oc2.client.item.CustomItemModelProperties; import li.cil.oc2.client.model.BusCableModelLoader; @@ -55,6 +52,7 @@ public final class ClientSetup { MenuScreens.register(Containers.COMPUTER_TERMINAL.get(), ComputerTerminalScreen::new); MenuScreens.register(Containers.ROBOT.get(), RobotContainerScreen::new); MenuScreens.register(Containers.ROBOT_TERMINAL.get(), RobotTerminalScreen::new); + MenuScreens.register(Containers.NETWORK_TUNNEL.get(), NetworkTunnelScreen::new); ItemBlockRenderTypes.setRenderLayer(Blocks.BUS_CABLE.get(), renderType -> true); Minecraft.getInstance().getBlockColors().register(new BusCableBlockColor(), Blocks.BUS_CABLE.get()); diff --git a/src/main/java/li/cil/oc2/client/gui/NetworkTunnelScreen.java b/src/main/java/li/cil/oc2/client/gui/NetworkTunnelScreen.java new file mode 100644 index 00000000..46a7385b --- /dev/null +++ b/src/main/java/li/cil/oc2/client/gui/NetworkTunnelScreen.java @@ -0,0 +1,78 @@ +package li.cil.oc2.client.gui; + +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.vertex.PoseStack; +import li.cil.oc2.client.gui.widget.ImageButton; +import li.cil.oc2.common.container.NetworkTunnelContainer; +import li.cil.oc2.common.network.Network; +import li.cil.oc2.common.network.message.NetworkTunnelLinkMessage; +import net.minecraft.client.renderer.GameRenderer; +import net.minecraft.network.chat.Component; +import net.minecraft.world.entity.player.Inventory; + +import static li.cil.oc2.common.util.TranslationUtils.text; + +public final class NetworkTunnelScreen extends AbstractModContainerScreen { + private static final int LINK_BUTTON_LEFT = 48; + private static final int LINK_BUTTON_TOP = 78; + + private static final Component LINK_BUTTON_CAPTION = text("gui.{mod}.network_tunnel.link"); + + private ImageButton linkButton; + + /////////////////////////////////////////////////////////////////// + + public NetworkTunnelScreen(final NetworkTunnelContainer container, final Inventory inventory, final Component title) { + super(container, inventory, title); + + imageWidth = Sprites.NETWORK_TUNNEL_SCREEN.width; + imageHeight = Sprites.NETWORK_TUNNEL_SCREEN.height; + inventoryLabelY = imageHeight - 94; + } + + /////////////////////////////////////////////////////////////////// + + @Override + public void render(final PoseStack stack, final int mouseX, final int mouseY, final float partialTicks) { + renderBackground(stack); + + linkButton.active = getMenu().hasLinkSlotItem(); + + super.render(stack, mouseX, mouseY, partialTicks); + renderTooltip(stack, mouseX, mouseY); + } + + /////////////////////////////////////////////////////////////////// + + @Override + protected void init() { + super.init(); + + linkButton = addRenderableWidget(new ImageButton( + leftPos + LINK_BUTTON_LEFT, topPos + LINK_BUTTON_TOP, + Sprites.NETWORK_TUNNEL_LINK_BUTTON_INACTIVE.width, Sprites.NETWORK_TUNNEL_LINK_BUTTON_INACTIVE.height, + Sprites.NETWORK_TUNNEL_LINK_BUTTON_INACTIVE, + Sprites.NETWORK_TUNNEL_LINK_BUTTON_ACTIVE) { + @Override + public void onPress() { + super.onPress(); + createTunnel(); + } + }).withMessage(LINK_BUTTON_CAPTION); + } + + @Override + protected void renderBg(final PoseStack stack, final float partialTicks, final int mouseX, final int mouseY) { + RenderSystem.setShader(GameRenderer::getPositionTexShader); + RenderSystem.setShaderColor(1, 1, 1, 1); + + Sprites.NETWORK_TUNNEL_SCREEN.draw(stack, leftPos, topPos); + } + + /////////////////////////////////////////////////////////////////// + + private void createTunnel() { + final NetworkTunnelLinkMessage message = new NetworkTunnelLinkMessage(getMenu().containerId); + Network.INSTANCE.sendToServer(message); + } +} diff --git a/src/main/java/li/cil/oc2/client/gui/Sprites.java b/src/main/java/li/cil/oc2/client/gui/Sprites.java index 771c9938..e20dbc18 100644 --- a/src/main/java/li/cil/oc2/client/gui/Sprites.java +++ b/src/main/java/li/cil/oc2/client/gui/Sprites.java @@ -10,6 +10,7 @@ public final class Sprites { public static final Sprite TERMINAL_SCREEN = new Sprite(TERMINAL_SCREEN_TEXTURE); public static final Sprite BUS_INTERFACE_SCREEN = new Sprite(BUS_INTERFACE_SCREEN_TEXTURE); public static final Sprite NETWORK_INTERFACE_CARD_SCREEN = new Sprite(NETWORK_INTERFACE_CARD_SCREEN_TEXTURE); + public static final Sprite NETWORK_TUNNEL_SCREEN = new Sprite(NETWORK_TUNNEL_SCREEN_TEXTURE); public static final Sprite TERMINAL_FOCUSED = new Sprite(TERMINAL_FOCUSED_TEXTURE); public static final Sprite SLOT_SELECTION = new Sprite(SLOT_SELECTION_TEXTURE, 18, 18, 0, 0); @@ -34,6 +35,9 @@ public final class Sprites { public static final Sprite INVENTORY_BUTTON_INACTIVE = new Sprite(INVENTORY_BUTTON_TEXTURE, 12, 12, 1, 1); public static final Sprite INVENTORY_BUTTON_ACTIVE = new Sprite(INVENTORY_BUTTON_TEXTURE, 12, 12, 15, 1); + public static final Sprite NETWORK_TUNNEL_LINK_BUTTON_INACTIVE = new Sprite(NETWORK_TUNNEL_LINK_BUTTON_TEXTURE, 80, 20, 0, 0); + public static final Sprite NETWORK_TUNNEL_LINK_BUTTON_ACTIVE = new Sprite(NETWORK_TUNNEL_LINK_BUTTON_TEXTURE, 80, 20, 0, 20); + public static final Sprite CONFIRM_PRESSED = new Sprite(CONFIRM_BUTTON_TEXTURE, 12, 12, 14, 1); public static final Sprite CONFIRM_BASE = new Sprite(CONFIRM_BUTTON_TEXTURE, 12, 12, 1, 1); public static final Sprite CANCEL_PRESSED = new Sprite(CANCEL_BUTTON_TEXTURE, 12, 12, 14, 1); diff --git a/src/main/java/li/cil/oc2/client/gui/Textures.java b/src/main/java/li/cil/oc2/client/gui/Textures.java index 9786d594..957953fa 100644 --- a/src/main/java/li/cil/oc2/client/gui/Textures.java +++ b/src/main/java/li/cil/oc2/client/gui/Textures.java @@ -8,6 +8,7 @@ public final class Textures { public static final Texture TERMINAL_SCREEN_TEXTURE = new Texture("textures/gui/widget/terminal_screen.png", 336, 208); public static final Texture BUS_INTERFACE_SCREEN_TEXTURE = new Texture("textures/gui/widget/bus_interface_screen.png", 240, 30); public static final Texture NETWORK_INTERFACE_CARD_SCREEN_TEXTURE = new Texture("textures/gui/widget/network_interface_card_screen.png", 176, 130); + public static final Texture NETWORK_TUNNEL_SCREEN_TEXTURE = new Texture("textures/gui/widget/network_tunnel_screen.png", 176, 197); public static final Texture TERMINAL_FOCUSED_TEXTURE = new Texture("textures/gui/overlay/terminal_focused.png", 336, 208); public static final Texture SLOT_SELECTION_TEXTURE = new Texture("textures/gui/overlay/slot_selection.png", 18, 270); @@ -27,4 +28,5 @@ public final class Textures { public static final Texture POWER_BUTTON_TEXTURE = new Texture("textures/gui/widget/power_button.png", 42, 14); public static final Texture INPUT_BUTTON_TEXTURE = new Texture("textures/gui/widget/input_button.png", 42, 14); public static final Texture INVENTORY_BUTTON_TEXTURE = new Texture("textures/gui/widget/inventory_button.png", 28, 14); + public static final Texture NETWORK_TUNNEL_LINK_BUTTON_TEXTURE = new Texture("textures/gui/widget/network_tunnel_link_button.png", 80, 40); } diff --git a/src/main/java/li/cil/oc2/common/Config.java b/src/main/java/li/cil/oc2/common/Config.java index 1515fc16..6c3ae36a 100644 --- a/src/main/java/li/cil/oc2/common/Config.java +++ b/src/main/java/li/cil/oc2/common/Config.java @@ -32,6 +32,7 @@ public final class Config { @Path("energy.items") public static int soundCardEnergyPerTick = 1; @Path("energy.items") public static int blockOperationsModuleEnergyPerTick = 2; @Path("energy.items") public static int inventoryOperationsModuleEnergyPerTick = 1; + @Path("energy.items") public static int networkTunnelEnergyPerTick = 2; @Path("gameplay") public static ResourceLocation blockOperationsModuleToolTier = TierSortingRegistry.getName(Tiers.DIAMOND); diff --git a/src/main/java/li/cil/oc2/common/bus/device/DeviceTypes.java b/src/main/java/li/cil/oc2/common/bus/device/DeviceTypes.java index df06c348..231254c4 100644 --- a/src/main/java/li/cil/oc2/common/bus/device/DeviceTypes.java +++ b/src/main/java/li/cil/oc2/common/bus/device/DeviceTypes.java @@ -32,6 +32,7 @@ public final class DeviceTypes { register(ItemTags.DEVICES_CARD); register(ItemTags.DEVICES_ROBOT_MODULE); register(ItemTags.DEVICES_FLOPPY); + register(ItemTags.DEVICES_NETWORK_TUNNEL); } /////////////////////////////////////////////////////////////////// diff --git a/src/main/java/li/cil/oc2/common/bus/device/item/NetworkTunnelItemDevice.java b/src/main/java/li/cil/oc2/common/bus/device/item/NetworkTunnelItemDevice.java new file mode 100644 index 00000000..ea057546 --- /dev/null +++ b/src/main/java/li/cil/oc2/common/bus/device/item/NetworkTunnelItemDevice.java @@ -0,0 +1,112 @@ +package li.cil.oc2.common.bus.device.item; + +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; +import li.cil.oc2.api.bus.device.vm.VMDeviceLoadResult; +import li.cil.oc2.api.bus.device.vm.context.VMContext; +import li.cil.oc2.api.capabilities.NetworkInterface; +import li.cil.oc2.common.item.NetworkTunnelItem; +import li.cil.oc2.common.util.TickUtils; +import net.minecraft.core.Direction; +import net.minecraft.world.item.ItemStack; +import net.minecraftforge.common.capabilities.Capability; +import net.minecraftforge.common.util.LazyOptional; +import net.minecraftforge.event.TickEvent; +import net.minecraftforge.event.server.ServerStoppedEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.common.Mod; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.time.Duration; +import java.util.*; + +public final class NetworkTunnelItemDevice extends AbstractNetworkInterfaceItemDevice { + public NetworkTunnelItemDevice(final ItemStack identity) { + super(identity); + } + + /////////////////////////////////////////////////////////////// + + @NotNull + @Override + public LazyOptional getCapability(final Capability cap, @Nullable final Direction side) { + return LazyOptional.empty(); + } + + @Override + public VMDeviceLoadResult mount(final VMContext context) { + final VMDeviceLoadResult result = super.mount(context); + if (result.wasSuccessful()) { + NetworkTunnelItem.getTunnelId(identity).ifPresent(id -> + TunnelManager.registerEndpoint(id, getNetworkInterface())); + } + return result; + } + + @Override + public void unmount() { + super.unmount(); + TunnelManager.unregisterEndpoint(getNetworkInterface()); + } + + /////////////////////////////////////////////////////////////// + + @Mod.EventBusSubscriber + private static final class TunnelManager { + private static final int BYTES_PER_TICK = 32 * 1024 / TickUtils.toTicks(Duration.ofSeconds(1)); // bytes / sec -> bytes / tick + private static final int MIN_ETHERNET_FRAME_SIZE = 42; + + private static final BiMap> TUNNELS = HashBiMap.create(); + + public static void registerEndpoint(final UUID id, final NetworkInterface networkInterface) { + TUNNELS.computeIfAbsent(id, unused -> new HashSet<>()) + .add(networkInterface); + } + + public static void unregisterEndpoint(final NetworkInterface networkInterface) { + for (final Set tunnel : TUNNELS.values()) { + tunnel.remove(networkInterface); + } + } + + @SubscribeEvent + public static void handleServerTick(final TickEvent.ServerTickEvent event) { + if (event.phase == TickEvent.Phase.START) { + pumpMessages(); + } + } + + @SubscribeEvent + public static void handleServerStopped(final ServerStoppedEvent event) { + TUNNELS.clear(); + } + + private static void pumpMessages() { + final Iterator> iterator = TUNNELS.values().iterator(); + while (iterator.hasNext()) { + final Set tunnel = iterator.next(); + if (tunnel.isEmpty()) { + iterator.remove(); + } else { + pumpMessages(tunnel); + } + } + } + + private static void pumpMessages(final Collection tunnel) { + for (final NetworkInterface source : tunnel) { + int byteBudget = BYTES_PER_TICK; + byte[] frame; + while ((frame = source.readEthernetFrame()) != null && byteBudget > 0) { + byteBudget -= Math.max(frame.length, MIN_ETHERNET_FRAME_SIZE); // Avoid bogus packets messing with us. + for (final NetworkInterface destination : tunnel) { + if (destination != source) { + destination.writeEthernetFrame(source, frame, 1); + } + } + } + } + } + } +} 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 78cf0c00..69111f39 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 @@ -39,10 +39,12 @@ public final class Providers { ITEM_DEVICE_PROVIDERS.register("flash_memory_custom", FlashMemoryWithExternalDataItemDeviceProvider::new); ITEM_DEVICE_PROVIDERS.register("redstone_interface_card", RedstoneInterfaceCardItemDeviceProvider::new); ITEM_DEVICE_PROVIDERS.register("network_interface_card", NetworkInterfaceCardItemDeviceProvider::new); + ITEM_DEVICE_PROVIDERS.register("network_tunnel_card", NetworkTunnelCardItemDeviceProvider::new); ITEM_DEVICE_PROVIDERS.register("file_import_export_card", FileImportExportCardItemDeviceProvider::new); ITEM_DEVICE_PROVIDERS.register("sound_card", SoundCardItemDeviceProvider::new); ITEM_DEVICE_PROVIDERS.register("inventory_operations_module", InventoryOperationsModuleDeviceProvider::new); ITEM_DEVICE_PROVIDERS.register("block_operations_module", BlockOperationsModuleDeviceProvider::new); + ITEM_DEVICE_PROVIDERS.register("network_tunnel_module", NetworkTunnelModuleItemDeviceProvider::new); } } diff --git a/src/main/java/li/cil/oc2/common/bus/device/provider/item/NetworkTunnelCardItemDeviceProvider.java b/src/main/java/li/cil/oc2/common/bus/device/provider/item/NetworkTunnelCardItemDeviceProvider.java new file mode 100644 index 00000000..db2c1e25 --- /dev/null +++ b/src/main/java/li/cil/oc2/common/bus/device/provider/item/NetworkTunnelCardItemDeviceProvider.java @@ -0,0 +1,28 @@ +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.Config; +import li.cil.oc2.common.bus.device.item.NetworkTunnelItemDevice; +import li.cil.oc2.common.bus.device.provider.util.AbstractItemDeviceProvider; +import li.cil.oc2.common.item.Items; + +import java.util.Optional; + +public final class NetworkTunnelCardItemDeviceProvider extends AbstractItemDeviceProvider { + public NetworkTunnelCardItemDeviceProvider() { + super(Items.NETWORK_TUNNEL_CARD); + } + + /////////////////////////////////////////////////////////////////// + + @Override + protected Optional getItemDevice(final ItemDeviceQuery query) { + return Optional.of(new NetworkTunnelItemDevice(query.getItemStack())); + } + + @Override + protected int getItemDeviceEnergyConsumption(final ItemDeviceQuery query) { + return Config.networkTunnelEnergyPerTick; + } +} diff --git a/src/main/java/li/cil/oc2/common/bus/device/provider/item/NetworkTunnelModuleItemDeviceProvider.java b/src/main/java/li/cil/oc2/common/bus/device/provider/item/NetworkTunnelModuleItemDeviceProvider.java new file mode 100644 index 00000000..e42586f9 --- /dev/null +++ b/src/main/java/li/cil/oc2/common/bus/device/provider/item/NetworkTunnelModuleItemDeviceProvider.java @@ -0,0 +1,28 @@ +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.Config; +import li.cil.oc2.common.bus.device.item.NetworkTunnelItemDevice; +import li.cil.oc2.common.bus.device.provider.util.AbstractItemDeviceProvider; +import li.cil.oc2.common.item.Items; + +import java.util.Optional; + +public final class NetworkTunnelModuleItemDeviceProvider extends AbstractItemDeviceProvider { + public NetworkTunnelModuleItemDeviceProvider() { + super(Items.NETWORK_TUNNEL_MODULE); + } + + /////////////////////////////////////////////////////////////////// + + @Override + protected Optional getItemDevice(final ItemDeviceQuery query) { + return Optional.of(new NetworkTunnelItemDevice(query.getItemStack())); + } + + @Override + protected int getItemDeviceEnergyConsumption(final ItemDeviceQuery query) { + return Config.networkTunnelEnergyPerTick; + } +} diff --git a/src/main/java/li/cil/oc2/common/container/Containers.java b/src/main/java/li/cil/oc2/common/container/Containers.java index 34fb7bfb..670ebbc9 100644 --- a/src/main/java/li/cil/oc2/common/container/Containers.java +++ b/src/main/java/li/cil/oc2/common/container/Containers.java @@ -16,6 +16,7 @@ public final class Containers { public static final RegistryObject> COMPUTER_TERMINAL = CONTAINERS.register("computer_terminal", () -> IForgeMenuType.create(ComputerTerminalContainer::createClient)); public static final RegistryObject> ROBOT = CONTAINERS.register("robot", () -> IForgeMenuType.create(RobotInventoryContainer::createClient)); public static final RegistryObject> ROBOT_TERMINAL = CONTAINERS.register("robot_terminal", () -> IForgeMenuType.create(RobotTerminalContainer::createClient)); + public static final RegistryObject> NETWORK_TUNNEL = CONTAINERS.register("network_tunnel", () -> IForgeMenuType.create(NetworkTunnelContainer::createClient)); /////////////////////////////////////////////////////////////////// diff --git a/src/main/java/li/cil/oc2/common/container/DeviceTypeSlot.java b/src/main/java/li/cil/oc2/common/container/DeviceTypeSlot.java new file mode 100644 index 00000000..1f4a89df --- /dev/null +++ b/src/main/java/li/cil/oc2/common/container/DeviceTypeSlot.java @@ -0,0 +1,40 @@ +package li.cil.oc2.common.container; + +import com.mojang.datafixers.util.Pair; +import li.cil.oc2.api.bus.device.DeviceType; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.Container; +import net.minecraft.world.inventory.InventoryMenu; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.ItemStack; +import org.jetbrains.annotations.NotNull; + +import javax.annotation.Nullable; + +public final class DeviceTypeSlot extends Slot { + private final DeviceType deviceType; + + public DeviceTypeSlot(final Container container, final DeviceType deviceType, final int index, final int x, final int y) { + super(container, index, x, y); + this.deviceType = deviceType; + } + + public DeviceType getDeviceType() { + return deviceType; + } + + @Override + public boolean mayPlace(@NotNull final ItemStack stack) { + return super.mayPlace(stack) && stack.is(deviceType.getTag()); + } + + @Nullable + @Override + public Pair getNoItemIcon() { + if (hasItem()) { + return super.getNoItemIcon(); + } else { + return Pair.of(InventoryMenu.BLOCK_ATLAS, deviceType.getBackgroundIcon()); + } + } +} diff --git a/src/main/java/li/cil/oc2/common/container/NetworkTunnelContainer.java b/src/main/java/li/cil/oc2/common/container/NetworkTunnelContainer.java new file mode 100644 index 00000000..80d92ef6 --- /dev/null +++ b/src/main/java/li/cil/oc2/common/container/NetworkTunnelContainer.java @@ -0,0 +1,116 @@ +package li.cil.oc2.common.container; + +import li.cil.oc2.api.bus.device.DeviceTypes; +import li.cil.oc2.common.item.NetworkTunnelItem; +import li.cil.oc2.common.tags.ItemTags; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.chat.Component; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.Container; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.item.ItemStack; +import net.minecraftforge.network.NetworkHooks; + +import java.util.UUID; + +public final class NetworkTunnelContainer extends AbstractContainer { + public static void createServer(final ServerPlayer player, final InteractionHand hand) { + NetworkHooks.openGui(player, new MenuProvider() { + @Override + public Component getDisplayName() { + return player.getItemInHand(hand).getItem().getDescription(); + } + + @Override + public AbstractContainerMenu createMenu(final int id, final Inventory inventory, final Player player) { + return new NetworkTunnelContainer(id, player, hand); + } + }, b -> b.writeEnum(hand)); + } + + public static NetworkTunnelContainer createClient(final int id, final Inventory inventory, final FriendlyByteBuf data) { + final InteractionHand hand = data.readEnum(InteractionHand.class); + final Player player = inventory.player; + return new NetworkTunnelContainer(id, player, hand); + } + + /////////////////////////////////////////////////////////////////// + + private final Player player; + private final InteractionHand hand; + private final Container linkSlot = new SimpleContainer(1); + + /////////////////////////////////////////////////////////////////// + + private NetworkTunnelContainer(final int id, final Player player, final InteractionHand hand) { + super(Containers.NETWORK_TUNNEL.get(), id); + this.player = player; + this.hand = hand; + + createPlayerInventoryAndHotbarSlots(player.getInventory(), 8, 115); + + addSlot(new LockedSlot(player.getInventory(), getHandSlot(), 80, 25)); + addSlot(new DeviceTypeSlot(linkSlot, DeviceTypes.NETWORK_TUNNEL, 0, 80, 51)); + } + + /////////////////////////////////////////////////////////////////// + + public boolean hasLinkSlotItem() { + return !linkSlot.getItem(0).isEmpty(); + } + + public void createTunnel() { + final ItemStack tunnelA = player.getItemInHand(hand); + final ItemStack tunnelB = linkSlot.getItem(0); + + if (tunnelA.isEmpty() || + tunnelB.isEmpty() || + !tunnelA.is(ItemTags.DEVICES_NETWORK_TUNNEL) || + !tunnelB.is(ItemTags.DEVICES_NETWORK_TUNNEL)) { + return; + } + + final UUID id = UUID.randomUUID(); + NetworkTunnelItem.setTunnelId(tunnelA, id); + NetworkTunnelItem.setTunnelId(tunnelB, id); + } + + @Override + public void removed(final Player player) { + super.removed(player); + + if (!player.getLevel().isClientSide()) { + clearContainer(player, linkSlot); + } + } + + @Override + public boolean stillValid(final Player player) { + return player.getItemInHand(hand).is(ItemTags.DEVICES_NETWORK_TUNNEL); + } + + /////////////////////////////////////////////////////////////////// + + @Override + protected boolean isSlotLocked(final Inventory inventory, final int slot) { + return inventory.getItem(slot) == player.getItemInHand(hand); + } + + /////////////////////////////////////////////////////////////////// + + private int getHandSlot() { + final Inventory inventory = player.getInventory(); + for (int slot = 0; slot < inventory.getContainerSize(); slot++) { + if (inventory.getItem(slot) == player.getItemInHand(hand)) { + return slot; + } + } + + return -1; + } +} 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 5506705a..c10bbdb8 100644 --- a/src/main/java/li/cil/oc2/common/item/Items.java +++ b/src/main/java/li/cil/oc2/common/item/Items.java @@ -64,11 +64,13 @@ public final class Items { public static final RegistryObject REDSTONE_INTERFACE_CARD = register("redstone_interface_card"); public static final RegistryObject NETWORK_INTERFACE_CARD = register("network_interface_card", NetworkInterfaceCardItem::new); + public static final RegistryObject NETWORK_TUNNEL_CARD = register("network_tunnel_card", NetworkTunnelItem::new); public static final RegistryObject FILE_IMPORT_EXPORT_CARD = register("file_import_export_card"); public static final RegistryObject SOUND_CARD = register("sound_card"); public static final RegistryObject INVENTORY_OPERATIONS_MODULE = register("inventory_operations_module"); public static final RegistryObject BLOCK_OPERATIONS_MODULE = register("block_operations_module", BlockOperationsModule::new); + public static final RegistryObject NETWORK_TUNNEL_MODULE = register("network_tunnel_module", NetworkTunnelItem::new); public static final RegistryObject TRANSISTOR = register("transistor", ModItem::new); public static final RegistryObject CIRCUIT_BOARD = register("circuit_board", ModItem::new); diff --git a/src/main/java/li/cil/oc2/common/item/NetworkTunnelItem.java b/src/main/java/li/cil/oc2/common/item/NetworkTunnelItem.java new file mode 100644 index 00000000..ab525a54 --- /dev/null +++ b/src/main/java/li/cil/oc2/common/item/NetworkTunnelItem.java @@ -0,0 +1,78 @@ +package li.cil.oc2.common.item; + +import li.cil.oc2.common.container.NetworkTunnelContainer; +import li.cil.oc2.common.util.ItemStackUtils; +import li.cil.oc2.common.util.TextFormatUtils; +import net.minecraft.ChatFormatting; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; +import net.minecraft.network.chat.TranslatableComponent; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.util.StringUtil; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResultHolder; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.TooltipFlag; +import net.minecraft.world.level.Level; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +import static li.cil.oc2.common.util.TranslationUtils.key; + +public final class NetworkTunnelItem extends ModItem { + private static final String TUNNEL_ID_TAG_NAME = "tunnel"; + private static final String TUNNEL_ID_TEXT = key("tooltip.{mod}.network_tunnel_id"); + + /////////////////////////////////////////////////////////////////// + + public NetworkTunnelItem() { + super(createProperties().stacksTo(1)); + } + + /////////////////////////////////////////////////////////////////// + + public static Optional getTunnelId(final ItemStack stack) { + final CompoundTag tag = ItemStackUtils.getModDataTag(stack); + if (tag.hasUUID(TUNNEL_ID_TAG_NAME)) { + return Optional.of(tag.getUUID(TUNNEL_ID_TAG_NAME)); + } else { + return Optional.empty(); + } + } + + public static void setTunnelId(final ItemStack stack, final UUID value) { + ItemStackUtils.getOrCreateModDataTag(stack).putUUID(TUNNEL_ID_TAG_NAME, value); + } + + /////////////////////////////////////////////////////////////////// + + @Override + public void appendHoverText(final ItemStack stack, @Nullable final Level level, final List tooltip, final TooltipFlag flag) { + super.appendHoverText(stack, level, tooltip, flag); + getTunnelId(stack).ifPresent(id -> { + final String idString = StringUtil.truncateStringIfNecessary(id.toString(), 8 + 3, true); + final MutableComponent idComponent = TextFormatUtils.withFormat(idString, ChatFormatting.GREEN); + tooltip.add(new TranslatableComponent(TUNNEL_ID_TEXT, idComponent).withStyle(ChatFormatting.GRAY)); + }); + } + + @Override + public InteractionResultHolder use(final Level level, final Player player, final InteractionHand hand) { + if (!level.isClientSide() && player instanceof ServerPlayer serverPlayer) { + openContainerScreen(serverPlayer, hand); + } + + return InteractionResultHolder.sidedSuccess(player.getItemInHand(hand), level.isClientSide()); + } + + /////////////////////////////////////////////////////////////////// + + private void openContainerScreen(final ServerPlayer player, final InteractionHand hand) { + NetworkTunnelContainer.createServer(player, hand); + } +} 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 f797953a..c671110c 100644 --- a/src/main/java/li/cil/oc2/common/network/Network.java +++ b/src/main/java/li/cil/oc2/common/network/Network.java @@ -68,6 +68,7 @@ public final class Network { registerMessage(BusCableFacadeMessage.class, BusCableFacadeMessage::new, NetworkDirection.PLAY_TO_CLIENT); registerMessage(NetworkInterfaceCardConfigurationMessage.class, NetworkInterfaceCardConfigurationMessage::new, NetworkDirection.PLAY_TO_SERVER); + registerMessage(NetworkTunnelLinkMessage.class, NetworkTunnelLinkMessage::new, NetworkDirection.PLAY_TO_SERVER); } public static void sendToClientsTrackingChunk(final T message, final LevelChunk chunk) { diff --git a/src/main/java/li/cil/oc2/common/network/message/NetworkTunnelLinkMessage.java b/src/main/java/li/cil/oc2/common/network/message/NetworkTunnelLinkMessage.java new file mode 100644 index 00000000..019b95b2 --- /dev/null +++ b/src/main/java/li/cil/oc2/common/network/message/NetworkTunnelLinkMessage.java @@ -0,0 +1,52 @@ +package li.cil.oc2.common.network.message; + +import li.cil.oc2.common.container.NetworkTunnelContainer; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraftforge.network.NetworkEvent; + +public final class NetworkTunnelLinkMessage extends AbstractMessage { + private int containerId; + + /////////////////////////////////////////////////////////////////// + + public NetworkTunnelLinkMessage(final int containerId) { + this.containerId = containerId; + } + + public NetworkTunnelLinkMessage(final FriendlyByteBuf buffer) { + super(buffer); + } + + /////////////////////////////////////////////////////////////////// + + @Override + public void fromBytes(final FriendlyByteBuf buffer) { + containerId = buffer.readVarInt(); + } + + @Override + public void toBytes(final FriendlyByteBuf buffer) { + buffer.writeVarInt(containerId); + } + + /////////////////////////////////////////////////////////////////// + + @Override + protected void handleMessage(final NetworkEvent.Context context) { + final ServerPlayer player = context.getSender(); + if (player == null) { + return; + } + + final AbstractContainerMenu container = player.containerMenu; + if (container.containerId != containerId) { + return; + } + + if (container instanceof NetworkTunnelContainer networkTunnelContainer) { + networkTunnelContainer.createTunnel(); + } + } +} diff --git a/src/main/java/li/cil/oc2/common/tags/ItemTags.java b/src/main/java/li/cil/oc2/common/tags/ItemTags.java index a2293ed4..4dcb8bd5 100644 --- a/src/main/java/li/cil/oc2/common/tags/ItemTags.java +++ b/src/main/java/li/cil/oc2/common/tags/ItemTags.java @@ -15,6 +15,7 @@ public final class ItemTags { public static final Tags.IOptionalNamedTag DEVICES_CARD = tag("devices/card"); public static final Tags.IOptionalNamedTag DEVICES_ROBOT_MODULE = tag("devices/robot_module"); public static final Tags.IOptionalNamedTag DEVICES_FLOPPY = tag("devices/floppy"); + public static final Tags.IOptionalNamedTag DEVICES_NETWORK_TUNNEL = tag("devices/network_tunnel"); public static final Tags.IOptionalNamedTag CABLES = tag("cables"); public static final Tags.IOptionalNamedTag WRENCHES = tag("wrenches"); diff --git a/src/main/java/li/cil/oc2/data/ModItemModelProvider.java b/src/main/java/li/cil/oc2/data/ModItemModelProvider.java index 5f101913..a46e269a 100644 --- a/src/main/java/li/cil/oc2/data/ModItemModelProvider.java +++ b/src/main/java/li/cil/oc2/data/ModItemModelProvider.java @@ -43,9 +43,11 @@ public final class ModItemModelProvider extends ItemModelProvider { simple(Items.NETWORK_INTERFACE_CARD); simple(Items.FILE_IMPORT_EXPORT_CARD); simple(Items.SOUND_CARD); + simple(Items.NETWORK_TUNNEL_CARD); simple(Items.INVENTORY_OPERATIONS_MODULE); simple(Items.BLOCK_OPERATIONS_MODULE); + simple(Items.NETWORK_TUNNEL_MODULE); simple(Items.TRANSISTOR); simple(Items.CIRCUIT_BOARD); diff --git a/src/main/java/li/cil/oc2/data/ModItemTagsProvider.java b/src/main/java/li/cil/oc2/data/ModItemTagsProvider.java index 649f2556..be4167a6 100644 --- a/src/main/java/li/cil/oc2/data/ModItemTagsProvider.java +++ b/src/main/java/li/cil/oc2/data/ModItemTagsProvider.java @@ -53,11 +53,17 @@ public final class ModItemTagsProvider extends ItemTagsProvider { Items.REDSTONE_INTERFACE_CARD.get(), Items.NETWORK_INTERFACE_CARD.get(), Items.FILE_IMPORT_EXPORT_CARD.get(), - Items.SOUND_CARD.get() + Items.SOUND_CARD.get(), + Items.NETWORK_TUNNEL_CARD.get() ); tag(DEVICES_ROBOT_MODULE).add( Items.INVENTORY_OPERATIONS_MODULE.get(), - Items.BLOCK_OPERATIONS_MODULE.get() + Items.BLOCK_OPERATIONS_MODULE.get(), + Items.NETWORK_TUNNEL_MODULE.get() + ); + tag(DEVICES_NETWORK_TUNNEL).add( + Items.NETWORK_TUNNEL_CARD.get(), + Items.NETWORK_TUNNEL_MODULE.get() ); tag(WRENCHES).add(Items.WRENCH.get()); @@ -73,7 +79,9 @@ public final class ModItemTagsProvider extends ItemTagsProvider { Items.FLASH_MEMORY.get(), Items.FLASH_MEMORY_CUSTOM.get(), Items.NETWORK_INTERFACE_CARD.get(), - Items.DISK_DRIVE.get() + Items.DISK_DRIVE.get(), + Items.NETWORK_TUNNEL_CARD.get(), + Items.NETWORK_TUNNEL_MODULE.get() ); } } diff --git a/src/main/java/li/cil/oc2/data/ModRecipesProvider.java b/src/main/java/li/cil/oc2/data/ModRecipesProvider.java index 546f04b3..ffdf2f63 100644 --- a/src/main/java/li/cil/oc2/data/ModRecipesProvider.java +++ b/src/main/java/li/cil/oc2/data/ModRecipesProvider.java @@ -273,6 +273,17 @@ public final class ModRecipesProvider extends RecipeProvider { .unlockedBy("has_computer", inventoryChange(Items.COMPUTER.get())) .save(consumer); + ShapedRecipeBuilder + .shaped(Items.NETWORK_TUNNEL_CARD.get()) + .pattern("IET") + .pattern(" B ") + .define('E', Tags.Items.ENDER_PEARLS) + .define('I', Tags.Items.INGOTS_IRON) + .define('T', Items.TRANSISTOR.get()) + .define('B', Items.CIRCUIT_BOARD.get()) + .unlockedBy("has_computer", inventoryChange(Items.COMPUTER.get())) + .save(consumer); + ShapedRecipeBuilder .shaped(Items.FILE_IMPORT_EXPORT_CARD.get()) .pattern("IET") @@ -329,6 +340,17 @@ public final class ModRecipesProvider extends RecipeProvider { .unlockedBy("has_robot", inventoryChange(Items.ROBOT.get())) .save(consumer); + ShapedRecipeBuilder + .shaped(Items.NETWORK_TUNNEL_MODULE.get()) + .pattern("TEG") + .pattern(" B ") + .define('T', Items.TRANSISTOR.get()) + .define('E', Tags.Items.ENDER_PEARLS) + .define('G', Tags.Items.INGOTS_GOLD) + .define('B', Items.CIRCUIT_BOARD.get()) + .unlockedBy("has_robot", inventoryChange(Items.ROBOT.get())) + .save(consumer); + ShapedRecipeBuilder .shaped(Items.TRANSISTOR.get(), 8) diff --git a/src/main/resources/assets/oc2/doc/en_us/item/index.md b/src/main/resources/assets/oc2/doc/en_us/item/index.md index 56d68cea..af8fe154 100644 --- a/src/main/resources/assets/oc2/doc/en_us/item/index.md +++ b/src/main/resources/assets/oc2/doc/en_us/item/index.md @@ -11,7 +11,9 @@ This index lists all documented items. If you're looking for a block, see the [b - [Memory](memory.md) - [Network Cable](network_cable.md) - [Network Interface Card](network_interface_card.md) +- [Network Tunnel Card](network_tunnel_card.md) +- [Network Tunnel Module](network_tunnel_module.md) - [Redstone Interface Card](redstone_interface_card.md) - [Robot](robot.md) - [Scrench](wrench.md) -- [Sound Card](sound_card.md) \ No newline at end of file +- [Sound Card](sound_card.md) diff --git a/src/main/resources/assets/oc2/doc/en_us/item/network_tunnel_card.md b/src/main/resources/assets/oc2/doc/en_us/item/network_tunnel_card.md new file mode 100644 index 00000000..9cbb1ab0 --- /dev/null +++ b/src/main/resources/assets/oc2/doc/en_us/item/network_tunnel_card.md @@ -0,0 +1,12 @@ +# Network Tunnel Card +![Tunnel vision?](item:oc2:network_tunnel_card) + +The network tunnel card allows [computers](../block/computer.md) to send messages to and receive messages from another tunnel device (tunnel cards and [tunnel modules](network_tunnel_module.md)) linked to the card. + +Computers *have to be shut down* before installing or removing this component. Installing it while the computer is running will have no effect, removing it may lead to system errors. + +To link two tunnel devices, open their configuration interface (use while holding), and insert the other tunnel device to link to. This allows linking any two network tunnel devices. + +When using the default Linux distribution, this device will provide a regular ethernet device. Network setup can either be performed manually, or using the convenience script `setup-network.lua`. This script provides the option to either use a fixed-address setup, or a DHCP setup. For a DHCP setup, exactly one computer in the network must act as a DHCP server. + +After initial setup, use the command `ifconfig` to see the currently used IP address. diff --git a/src/main/resources/assets/oc2/doc/en_us/item/network_tunnel_module.md b/src/main/resources/assets/oc2/doc/en_us/item/network_tunnel_module.md new file mode 100644 index 00000000..affbb0e9 --- /dev/null +++ b/src/main/resources/assets/oc2/doc/en_us/item/network_tunnel_module.md @@ -0,0 +1,12 @@ +# Network Tunnel Module +![Tunnel vision?](item:oc2:network_tunnel_module) + +The network tunnel module allows [robots](robot.md) to send messages to and receive messages from another tunnel device (tunnel modules and [tunnel cards](network_tunnel_card.md)) linked to the module. + +Robots *have to be shut down* before installing or removing this component. Installing it while the robot is running will have no effect, removing it may lead to system errors. + +To link two tunnel devices, open their configuration interface (use while holding), and insert the other tunnel device to link to. This allows linking any two network tunnel devices. + +When using the default Linux distribution, this device will provide a regular ethernet device. Network setup can either be performed manually, or using the convenience script `setup-network.lua`. This script provides the option to either use a fixed-address setup, or a DHCP setup. For a DHCP setup, exactly one computer in the network must act as a DHCP server. + +After initial setup, use the command `ifconfig` to see the currently used IP address. diff --git a/src/main/resources/assets/oc2/lang/en_us.json b/src/main/resources/assets/oc2/lang/en_us.json index af6246f0..6784922a 100644 --- a/src/main/resources/assets/oc2/lang/en_us.json +++ b/src/main/resources/assets/oc2/lang/en_us.json @@ -40,6 +40,8 @@ "item.oc2.network_interface_card": "Network Interface Card", "item.oc2.network_interface_card.desc": "Sends and receives network packets via attached Network Connectors.", "item.oc2.network_interface_card.is_configured": "Has connectivity configuration.", + "item.oc2.network_tunnel_card": "Network Tunnel Card", + "item.oc2.network_tunnel_card.desc": "Sends and receives network packets to a linked device.", "item.oc2.file_import_export_card": "File Import/Export Card", "item.oc2.file_import_export_card.desc": "Imports and exports files from and into your real file system.", "item.oc2.robot": "Robot", @@ -50,6 +52,8 @@ "item.oc2.block_operations_module.desc": "Breaks and places blocks.", "item.oc2.sound_card": "Sound Card", "item.oc2.sound_card.desc": "Plays various sounds from its highly realistic sound bank.", + "item.oc2.network_tunnel_module": "Network Tunnel Module", + "item.oc2.network_tunnel_module.desc": "Sends and receives network packets to a linked device.", "item.oc2.transistor": "Transistor", "item.oc2.transistor.desc": "Crafting material.", @@ -92,6 +96,8 @@ "gui.oc2.network_interface_card.connectivity.disabled": "Disabled", "gui.oc2.network_interface_card.info": "Drag to rotate. Click faces to toggle connectivity.", + "gui.oc2.network_tunnel.link": "Link", + "manual.oc2.home": "Home", "manual.oc2.blocks": "Blocks", "manual.oc2.items": "Items", @@ -110,6 +116,7 @@ "tooltip.oc2.energyConsumption": "Energy Consumption: %s/t", "tooltip.oc2.confirm": "Confirm", "tooltip.oc2.cancel": "Cancel", + "tooltip.oc2.network_tunnel_id": "Tunnel: %s", "subtitles.oc2.computer": "Computer fans running", "subtitles.oc2.floppy": "Floppy access", diff --git a/src/main/resources/assets/oc2/models/item/network_tunnel_card.json b/src/main/resources/assets/oc2/models/item/network_tunnel_card.json new file mode 100644 index 00000000..b9588a59 --- /dev/null +++ b/src/main/resources/assets/oc2/models/item/network_tunnel_card.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:item/generated", + "textures": { + "layer0": "oc2:item/network_tunnel_card" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/oc2/models/item/network_tunnel_module.json b/src/main/resources/assets/oc2/models/item/network_tunnel_module.json new file mode 100644 index 00000000..c0490936 --- /dev/null +++ b/src/main/resources/assets/oc2/models/item/network_tunnel_module.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:item/generated", + "textures": { + "layer0": "oc2:item/network_tunnel_module" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/oc2/textures/gui/icon/network_tunnel.png b/src/main/resources/assets/oc2/textures/gui/icon/network_tunnel.png new file mode 100644 index 0000000000000000000000000000000000000000..71170948862e177d88f3168a0f3e3663d2b15168 GIT binary patch literal 2056 zcmah~X;2eq7~WKE5k;xm;?)w@3q`WoU?N!)7)ZD*%S40<=v0~2g#(T-?A z#?}L;wu2PwG4-e-MW{j*s0wNwrS)hBX%&%CYgI%lO1=7Rf*^wR$L{8PpZ9$B$-8-) z_-G&RvEC2_`NYPk6Ts7#zr5ZAcmMWh1OyG5O(iCC$#@*5r7a>tN2ik_r^N8`dKv|V=>yOp2kkTpP`BGer+f<~HipgZid71Hv+DwyHu7kruy%kOjB(RVi zA#_^IRu*$AVRv2({N~LFEOfhYCMBGVYlJGAA%#+rR3wH&y@d)!r^gc1QC-2{q=bze zXTuQ0;c$o?!6KS5AgEj}N5m3DA_)Q>L9ENl5zZhhJCRS}$)P4$EkoHjina>*oJ2Zp z=aeuEaA8++(n3(zfo7dc}0ZeF8a3|B5<1(#IXp z*M9{kVe8Ks$N%5mV(B#o%SA2$M0AnbYlPR2qiXQ)Ym% z3DFV4*hrNOmCIy75|O6?o<}U!LKw(6(rVz0-Nk!=`IPQuJ3pY=&MPFzN&!kF?z--f z_n6~G_Iu^5WwXvXC>d1OA1%FNtH?MHspV0EtDPbE!>+D)m=PvEm zKaaAso^uck8EybCSP6&g^%UU2CDiaj7owsNk+gS!!$^W;U@+7@m^2ZUVUl1Bm30k< z^xKMCaIb8|gTAg6R4khLycbVIcQ3CXxvLii!vFc7v!p=!@aS=PVOxM1JT|a}z&b;^ z_Zc{JZ$#1x)*u7+-GO&>CI}L&pnyjEXcK}0*4x+D2OcHHA79jHOfP6|YARVdUWS$* z3^AkLxt5=84OJbg+s6;IIzBj+R8bs|(e`)EqM9UmVC43e07G+jRp@0LFRee2=Hyo#<{T6miol%yyZS$6|wtg3UCC|%#`^kq}Ym1fo zz5ixBOFhLz%&$2+N%8H@)Np5lGqe1gfd4e1R$LU2rM=lUJ#16q?t42< zrcN3KnKqRK-d}ySIyu&qLb&A0hPb(%6>B?3M8mWEHkL2Y`(YEZA7-GnA%d)-==4ju zZy^Ipx5YnAoBYYfLoL&rlja!rtb=jEft``Niq=V|Wky8hq9E(SPrc^YS&Zr(VC*!!H{;!h9U+>)Q4Qb<|WObTRgYa`D#il76 z2dZ0`dlyqor+={Qx^rRqs;0w+%MbQwrqzb5akl#Z?(%}lEUSMjXjcf%X0{d$J~kSy z^VP=fKNypgbU0t|&!0b47KdD4g^tjwr+xb1eN)h-k0ZQV-+;5(HSLNqIm<1LHy#y{ zDK{#Iy?rHQP3{Ww`OWpwH!GJNswp2nA#LyJ{03h^A)9x%BkyV3vEh;;+k4~easK=V NJT@|3T^x~?{U6T_;1B=+ literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/oc2/textures/gui/widget/network_tunnel_link_button.png b/src/main/resources/assets/oc2/textures/gui/widget/network_tunnel_link_button.png new file mode 100644 index 0000000000000000000000000000000000000000..ad7cf6e4f7497f692328936d437cc34cefa8878c GIT binary patch literal 2191 zcmai0X;c$e6dr^g5y85{DK6uPib668NF*b{!wy0yU{DNhLoy*FNhV|_CZVXOU~vJD zJ5~r<1r%+;6_=`kBO)p&iq)!E6-8V?QL7Z{q4p(Ez^d(!N%G!z@AvNg?tOQ%Iw~^6 z!R{+N2!b5KLIY#KcNBPBZTo@Grhd<6Ly*k@QWVF;2_rBGrDWq$DuH0@m1=-Pkk1Ug z8kZyy44gnDk}3hxda?|GNvQyt<0a$>)d7T@3{BAxvr-~Ol9VI~Uy97|we!(qfIvww zIILGHR5Yd+AQoN>{5Ef+2y8K7k_1ScFbWQ!Gz9F$_F{7oUpv@GBb8w>fx+F*U?o80 z45P+SRHxIib)IZWlZbNpd_KzYKs`KIz=A~^R1B_Xsp#=$iY|^of|h7VHA7M=*vyG1 zP+CTSAOMHE$q7B#%dVok(+5JJW(4K3IcQ&VhLnBA+>Cr=u9i~_MawC5PXZqt(F1?v z2;}>;iyl{h%2+7;Z*!&cgEVL+XbBLaThtF?`sV;zWKa`m3_(*`jf4nVLa3PW7A4HO zU{R!=P{ak2NkwVh$-~;-g9Cb#!)0+jMBHf@XDa6D z<;LM+oGvvi*5EEkaR&cCdviy_KEOjL#6n4$p)>}|R#%(PB9gmST}uUN>2DaeR2jo1 zW|0JlT0=>-5<+T83($3;G$mtnxQ6gc1O*l#eli&e7?3aP`3qL3@S>SoDk`LIq}z1`HYTu$yBb$SR!# zW%;NaV8CtAZm4yuz(-Xrtd}R`Jh*@Vz=}~kZs~Dv1=nt+@}l~dsV(4n)v;Dxcy;WF z64&Hc&E*TrWBG1D1&ywW57JD&_k_Yjwby<(EU10gk?Jd`eQcHFk}rLm;dj2tPVD+b zcPYeiUfU%1Ba8BP?EHS$kDd*yZMDxkXYZ;k5y*=FOnxt}*7(mUuXFL)T_pC?$LLc^ zf5E4{5!?YUF#uI!ImAIgvXYte+|*{2$(Jcyks|7krU zv^rK4w0HY@ugNL?!5JHGy)RpOeN)BV#oCo)zZ&cCu+*_7_hl?EYn^pPJ7!aFZG61A zqhqO)Vc6+;*HYTbPYl}Q=*2QQBsyO)>_2xTF6hvOBNI2h)YunC%gfT5Di_x-yUhPN z{d`X9^$9I4{yWZ2-)geblZh{oU?v{CF9xVX=h6@~=&bp%+Tu3I8PupPEYMFR|MpSN z>BtJs;Hv#=w~F`Gi554NEd7GV`^D~+&F20PZz85^4xYCA%rLm1DBe*Ne2yLAju%Od zYc_vbn6PRUQonX+;HbB+b4N9&b}Vc$q8r>!FZj~sMsw+vhwn^^;K^I2Z^=%6XOq4n zy8e9QCGGrvP-Z2vDebrK=mjisx@y&(N~eL&*~HTk`PRs(lB|QSm-WsLh4FQd2F8Up zx8G13VcX%JHBOnYNBW+1U+WQF8LbF^dD62X^A5*eTi))!HuPeh>%GR;%J86pR~&}; zn%t}X7lc>OcYSCd?KtN6knr#~gMZcXA79lDwmGx`ciLvmh^)(sxy%0c&CH{Ql3$wN z8s|~tJJZ|i(<6`PkvTSyxOn!&vXhz0`bRUxn`0`EHr6zKjoxj3_U>6rRN3xRq2Mf$+{QWHFQSR7ke)bNzSM!(9+4G6;7jF zV#S?T^LV3g7ukImlG<9iwQzFb>g6o%#NwIxSLM9iS0yj*kA$557*n+ecxEm+S#0aX z&5PVKZ^z20bMNbRledrNEi0cf21y&p literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/oc2/textures/gui/widget/network_tunnel_screen.png b/src/main/resources/assets/oc2/textures/gui/widget/network_tunnel_screen.png new file mode 100644 index 0000000000000000000000000000000000000000..10f6d91bd78dff8010be463f28265c202c604fb1 GIT binary patch literal 4401 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 J5{Dq^{{YW1y?y`y literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/oc2/textures/item/network_tunnel_card.png b/src/main/resources/assets/oc2/textures/item/network_tunnel_card.png new file mode 100644 index 0000000000000000000000000000000000000000..2beec361eeb8dae985d5d501c6b5c16748a020e7 GIT binary patch literal 2145 zcmai02~ZPP7~UXSMRBM&#X&oSRTRZ!H{r~hz(5F>hCvQFbSfd)kcA{0lEowpNC&Kp z_klwZP*Aj91sw)NTI7)8QAeRF(;^r6J;_rCx9-nV~tQ&?!A zwUxaU1VPrpLH^<3*M`2PP66Kzuii-@X!0^5GKPu~ufU|FiY<|m2{_xJ(f}NS_&x@W zM4F6K%mh4JD4dwLsznI;n|Spdg~!Pz0%F(lI3o zWGW?qVSqD7g5w5aEV^1Z(mo&pp%H|`Mv)KAH42gj7 z2lP#x!64C0WGoi{zqv~FQ5!l+vbsxX?uy3?oI$}w4^Lm zipxx?P5P%%9Vw^u5-skV2o6{P`^x16;K9fYqlM1ouz4fwjymWSI0yzB=gQg^kuG)d-NSlgjU zD7e?zd1}Kv4^Gt?FD1t+NA3w0d!f2dzx;AkO}TSQ|I>!G4N*K7(V=eV#CutF z-gm{~ik9oIjNi5VGnDQvXz5`jFDR7_Z1lbO&??UPk^V}c-I~EgODp3`ix1=xiHYX~=_Rh1OfI^8;ONP79)qH}67+c{nO zq<@J|VbSrIM=r%Jm<=Tto^l!3a=$(%I5}2g_O#2ZS}nK39JAh} zaGJds=`%P>?&N%hOsXgi{WsnzGq1LL$-Sr$#m_roF{ApZ=-B?9?u*m>0yge$e^;N; zmfLhEDK*Ez-og5QmEE(#7f~KtcT8$}gIRV=D(XoZ8p@bsv^~4#dfH$^&CDZq?yNfN z#JR1;ljkdAL=~4R7v{du&L~@{sLy)XoYb=ZD({!UDkz!*&PWjK?;Gj0?e=b;<$jXc=QB6YqOxs7h?`~k?rV8L zLYuO${fZm=T4zsYb?p(hb{16Lu(2L?d^;;tt&QrATer4vpsk~$q%MB5aP__i{CS4; zJzbj4%sn=K(;RP*wT@5|YH?b1;>w!PGMu}#`To+mQy_ukqw2Vut;y94LwTfyJ;Xe0 z+1dSiYk}tR%#9I+hn^;7WTd<8iw{4Iue4=1Uu(W~CM0C=We)XZUG1q3ABy~drbf5upoP=7%*i}k#~6&yDZTo7)ph#KAXpUYU+x#5 F^&g$y2?hWF literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/oc2/textures/item/network_tunnel_module.png b/src/main/resources/assets/oc2/textures/item/network_tunnel_module.png new file mode 100644 index 0000000000000000000000000000000000000000..4e76f87457eebbc3a905b0b5d352e42bd9b83a8d GIT binary patch literal 2321 zcmai0dsGu=7N3-Ei>dUq)gC<5E#n9^K$FSDCCHfI0zp8Gr3eZXlp&cUVO$jO3&mowX<;@W04VhCZCkK zEH;2ckZOj@M(E5W59gCc%Bn_w`t&FQQ+hQrKT<2#+TuwQmE>W`xt=-cI*(Zwtw(0W zh*d5e5Lif_fL#{a%Hb|G;^W1^Z{Zk4V4n(aRwJ3(R5+etNjOp#DU%~HVpzrM4S1Sn z=8!Rv)QE}aZ8(ZLolco^s*GWcC>9+Zjmj0MLLmhjQqFDV376E$O%^ByIW#1vV<{U? zF;-aMB=Q*tuSO7n!$ageWf)<%azob#i9iJe#bk2yL3O=uI24i}mNX{m$M&6`GFy%wQd`kcDxj;la zgvw=DDd1W$Vv;$6sgWoJij6>7kA;L(uAG1#z%#NPmi9ga8CID9HbvRG)H0hHDCnA>S2cL-&%j;+3l1ILa6lN|k)#zYMiy+q z+f~qY2of!$Ko2}pdkB)ObF6ERI6+MOY+;uvzoPs6`F(FsRATjqr_q?W)N#eA>T9%w&z-MygwwznWg-F``A4%w!^ zu{`$FMRB&|N9X5>fjI+HUa!mBwxxPg&Bs%JSmE!udUsw;%O161_bvOq>^3%Te)HK7 z)s7FdV_j)3PyN=4#?b#%*Xr7iPdRSY$|ME4D?f+NsI2)$d2;**MR+Od{tqJptl_;;nxqJ9Uq&G{`abjilX z3*p@vvrM0?MYN(rA13VDyf!k-6E}1D`p$bt3p+NP_->J-G&m?Ypr<~tud+Wwx$0fN z6F2dvyZovyE%JH`C%DHQ$vNj4Xl{6ZXJDkXDZu#38Ta0Q)@3FfXsvr~Lq9uq_nW4p z#TQ!^wHJL6{f}j*DhfJ6`}*RxoSgoClgLFG|A)-v-vTeQF}9>E4uYN=Cp;pkqG|%D zQCG4iHhtRaZ`Y@o#}VU~ZzZAsG`h1>?o%Cepb_PDi4p=BpFoq29o w;jXJ9=)JTf__upk)s(^h19wl}%PaEhU-%`nF00`i;h#)$!W_+>xV+;31L