diff --git a/src/main/java/li/cil/oc2/client/gui/NetworkInterfaceCardScreen.java b/src/main/java/li/cil/oc2/client/gui/NetworkInterfaceCardScreen.java new file mode 100644 index 00000000..094bd662 --- /dev/null +++ b/src/main/java/li/cil/oc2/client/gui/NetworkInterfaceCardScreen.java @@ -0,0 +1,294 @@ +package li.cil.oc2.client.gui; + +import com.mojang.blaze3d.platform.GlStateManager; +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.VertexConsumer; +import com.mojang.math.Quaternion; +import com.mojang.math.Vector3f; +import li.cil.oc2.client.gui.widget.Texture; +import li.cil.oc2.client.renderer.ModRenderType; +import li.cil.oc2.common.item.Items; +import li.cil.oc2.common.item.NetworkInterfaceCardItem; +import li.cil.oc2.common.network.Network; +import li.cil.oc2.common.network.message.NetworkInterfaceCardConfigurationMessage; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.client.renderer.block.model.ItemTransforms; +import net.minecraft.client.renderer.entity.ItemRenderer; +import net.minecraft.client.renderer.texture.OverlayTexture; +import net.minecraft.client.resources.model.BakedModel; +import net.minecraft.client.resources.sounds.SimpleSoundInstance; +import net.minecraft.core.Direction; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.TranslatableComponent; +import net.minecraft.sounds.SoundEvents; +import net.minecraft.util.Mth; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.InventoryMenu; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.Vec3; + +import javax.annotation.Nullable; + +import static li.cil.oc2.common.util.TranslationUtils.key; +import static li.cil.oc2.common.util.TranslationUtils.text; + +public final class NetworkInterfaceCardScreen extends Screen { + private static final String SIDE_STATE_TEXT = key("gui.{mod}.network_interface_card.side_state"); + private static final Component CONNECTIVITY_ENABLED_TEXT = text("gui.{mod}.network_interface_card.connectivity.enabled"); + private static final Component CONNECTIVITY_DISABLED_TEXT = text("gui.{mod}.network_interface_card.connectivity.disabled"); + private static final Component INFO_TEXT = text("gui.{mod}.network_interface_card.info"); + + public static final int UI_WIDTH = Sprites.NETWORK_INTERFACE_CARD_SCREEN.width; + public static final int UI_HEIGHT = Sprites.NETWORK_INTERFACE_CARD_SCREEN.height; + public static final int BLOCK_LEFT = UI_WIDTH / 2; + public static final int BLOCK_TOP = 53; + public static final int INFO_TEXT_LEFT = 8; + public static final int INFO_TEXT_TOP = 104; + public static final int INFO_TEXT_WIDTH = UI_WIDTH - 16; + public static final int MAX_BLOCK_PITCH = 30; + + /////////////////////////////////////////////////////////////////// + + private final Player player; + private final InteractionHand hand; + + private final ComputerBlockItemRenderer computerBlockItemRenderer = new ComputerBlockItemRenderer(); + + private Vector3f blockRotation = new Vector3f(-30, 45, 0); + private int left, top; + @Nullable private Direction focusedSide; + private boolean isDraggingBlock, hasDraggedBlock; + private double dragStartX, dragStartY; + + /////////////////////////////////////////////////////////////////// + + public NetworkInterfaceCardScreen(final Player player, final InteractionHand hand) { + super(Items.NETWORK_INTERFACE_CARD.get().getDescription()); + this.player = player; + this.hand = hand; + } + + /////////////////////////////////////////////////////////////////// + + @Override + protected void init() { + super.init(); + + left = (width - UI_WIDTH) / 2; + top = (height - UI_HEIGHT) / 2; + } + + @Override + public void tick() { + super.tick(); + + final ItemStack heldItem = player.getItemInHand(hand); + if (!heldItem.is(Items.NETWORK_INTERFACE_CARD.get())) { + onClose(); + } + } + + @Override + public boolean mouseClicked(final double mouseX, final double mouseY, final int button) { + final boolean result = super.mouseClicked(mouseX, mouseY, button); + + if (!result && isMouseInBlockArea(mouseX, mouseY) && button == 0) { + isDraggingBlock = true; + hasDraggedBlock = false; + dragStartX = mouseX; + dragStartY = mouseY; + } + + return result; + } + + @Override + public boolean mouseReleased(final double mouseX, final double mouseY, final int button) { + if (isDraggingBlock && button == 0) { + isDraggingBlock = false; + if (!hasDraggedBlock && focusedSide != null) { + final NetworkInterfaceCardConfigurationMessage message = new NetworkInterfaceCardConfigurationMessage(hand, focusedSide, !getConfiguration(focusedSide)); + Network.INSTANCE.sendToServer(message); + Minecraft.getInstance().getSoundManager() + .play(SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK, 1)); + } + } + + return super.mouseReleased(mouseX, mouseY, button); + } + + @Override + public boolean mouseDragged(final double mouseX, final double mouseY, final int activeButton, final double deltaX, final double deltaY) { + if (isDraggingBlock && activeButton == 0) { + if (!hasDraggedBlock) { + final double dx = mouseX - dragStartX; + final double dy = mouseY - dragStartY; + final double delta = Math.sqrt(dx * dx + dy * dy); + hasDraggedBlock = delta > 3; + } + if (hasDraggedBlock) { + blockRotation = new Vector3f( + Mth.clamp(blockRotation.x() - (float) deltaY, -MAX_BLOCK_PITCH, MAX_BLOCK_PITCH), + Mth.wrapDegrees(blockRotation.y() + (float) deltaX), + blockRotation.z() + ); + } + } + + return true; + } + + @Override + public void render(final PoseStack stack, final int mouseX, final int mouseY, final float partialTicks) { + renderBackground(stack); + Sprites.NETWORK_INTERFACE_CARD_SCREEN.draw(stack, left, top); + + super.render(stack, mouseX, mouseY, partialTicks); + + final int blockX = left + BLOCK_LEFT; + final int blockY = top + BLOCK_TOP; + focusedSide = computerBlockItemRenderer.getFocusedSide(blockX - mouseX, blockY - mouseY, blockRotation); + computerBlockItemRenderer.render(blockX, blockY, blockRotation); + + if (focusedSide != null) { + final Component enabledComponent = getConfiguration(focusedSide) ? CONNECTIVITY_ENABLED_TEXT : CONNECTIVITY_DISABLED_TEXT; + final TranslatableComponent tooltip = new TranslatableComponent(SIDE_STATE_TEXT, enabledComponent); + renderTooltip(stack, tooltip, mouseX, mouseY); + } + + font.drawWordWrap(INFO_TEXT, left + INFO_TEXT_LEFT, top + INFO_TEXT_TOP, INFO_TEXT_WIDTH, 0xAAAAAA); + } + + @Override + public boolean isPauseScreen() { + return false; + } + + /////////////////////////////////////////////////////////////////// + + private boolean isMouseInBlockArea(final double mouseX, final double mouseY) { + return mouseX >= left + 37 && mouseX <= left + (37 + 102) && + mouseY >= top + 10 && mouseY <= top + (10 + 102); + } + + private boolean getConfiguration(@Nullable final Direction side) { + return side != null && NetworkInterfaceCardItem.getSideConfiguration(player.getItemInHand(hand), side); + } + + /////////////////////////////////////////////////////////////////// + + private final class ComputerBlockItemRenderer { + public static final int BLOCK_RENDER_SIZE = 48; + + private final ItemStack computerItemStack = new ItemStack(Items.COMPUTER.get()); + private final ItemRenderer itemRenderer = Minecraft.getInstance().getItemRenderer(); + private final BakedModel model = itemRenderer.getModel(computerItemStack, null, null, 0); + + @Nullable + private Direction getFocusedSide(final float mouseX, final float mouseY, final Vector3f rotation) { + // Rotate ray inversely around block to represent visual block rotation. + final Quaternion quaternion = Quaternion.fromXYZDegrees(rotation); + quaternion.conj(); + + // Move ray in screen space to mouse position. + final float relMouseX = -mouseX / (float) BLOCK_RENDER_SIZE; + final float relMouseY = -mouseY / (float) BLOCK_RENDER_SIZE; + + final Vector3f source = new Vector3f(); + source.add(relMouseX, relMouseY, 1); + source.transform(quaternion); + + final Vector3f target = new Vector3f(); + target.add(relMouseX, relMouseY, -1); + target.transform(quaternion); + + // Intersect rotated ray with bounding box representing block. + final AABB aabb = new AABB(-0.5, -0.5, -0.5, 0.5, 0.5, 0.5); + return aabb.clip(new Vec3(source), new Vec3(target)) + .map(hit -> Direction.getNearest(hit.x, -hit.y(), hit.z())) + .filter(side -> side != Direction.SOUTH) + .orElse(null); + } + + public void render(final int x, final int y, final Vector3f rotation) { + RenderSystem.setShaderTexture(0, InventoryMenu.BLOCK_ATLAS); + RenderSystem.enableBlend(); + RenderSystem.blendFunc(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA); + RenderSystem.setShaderColor(1, 1, 1, 1); + + final Vector3f renderRotation = rotation.copy(); + renderRotation.add(0, 180, 0); + + final PoseStack stack = RenderSystem.getModelViewStack(); + stack.pushPose(); + stack.translate(x, y, 0); + stack.mulPose(Quaternion.fromXYZDegrees(renderRotation)); + stack.scale(BLOCK_RENDER_SIZE, -BLOCK_RENDER_SIZE, BLOCK_RENDER_SIZE); + RenderSystem.applyModelViewMatrix(); + + final MultiBufferSource.BufferSource bufferSource = Minecraft.getInstance().renderBuffers().bufferSource(); + renderBlock(bufferSource); + renderOverlays(stack, bufferSource); + bufferSource.endBatch(); + + stack.popPose(); + RenderSystem.applyModelViewMatrix(); + } + + private void renderBlock(final MultiBufferSource.BufferSource bufferSource) { + itemRenderer.render(computerItemStack, ItemTransforms.TransformType.NONE, false, new PoseStack(), bufferSource, 0xF000F0, OverlayTexture.NO_OVERLAY, model); + } + + private void renderOverlays(final PoseStack poseStack, final MultiBufferSource.BufferSource bufferSource) { + for (final Direction side : Direction.values()) { + // South face of computers is the front face (screen) and there's no connectivity allowed there. + if (side == Direction.SOUTH) { + continue; + } + + poseStack.pushPose(); + poseStack.setIdentity(); + + poseStack.translate(-side.getStepX() * 0.51, side.getStepY() * 0.51, -side.getStepZ() * 0.51); + + final Vector3f sideRotation = switch (side) { + case DOWN -> new Vector3f(-90, 0, 0); + case UP -> new Vector3f(90, 0, 0); + case NORTH -> new Vector3f(0, 180, 0); + case WEST -> new Vector3f(0, -90, 0); + case EAST -> new Vector3f(0, 90, 0); + default -> throw new IllegalStateException("Unexpected value: " + side); + }; + poseStack.mulPose(Quaternion.fromXYZDegrees(sideRotation)); + + poseStack.translate(-0.5, -0.5, 0); + + if (getConfiguration(side)) { + renderOverlay(poseStack, bufferSource, Textures.BLOCK_FACE_ENABLED_TEXTURE); + } else { + renderOverlay(poseStack, bufferSource, Textures.BLOCK_FACE_DISABLED_TEXTURE); + } + + if (side == focusedSide) { + renderOverlay(poseStack, bufferSource, Textures.BLOCK_FACE_FOCUSED_TEXTURE); + } + + poseStack.popPose(); + } + } + + private void renderOverlay(final PoseStack poseStack, final MultiBufferSource.BufferSource bufferSource, final Texture texture) { + final VertexConsumer buffer = bufferSource.getBuffer(ModRenderType.getOverlay(texture.location)); + + buffer.vertex(poseStack.last().pose(), 0, 0, 0).uv(0, 0).endVertex(); + buffer.vertex(poseStack.last().pose(), 0, 1, 0).uv(0, 1).endVertex(); + buffer.vertex(poseStack.last().pose(), 1, 1, 0).uv(1, 1).endVertex(); + buffer.vertex(poseStack.last().pose(), 1, 0, 0).uv(1, 0).endVertex(); + } + } +} 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 58c92a3c..771c9938 100644 --- a/src/main/java/li/cil/oc2/client/gui/Sprites.java +++ b/src/main/java/li/cil/oc2/client/gui/Sprites.java @@ -9,6 +9,7 @@ public final class Sprites { public static final Sprite ROBOT_CONTAINER = new Sprite(ROBOT_CONTAINER_TEXTURE); 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 TERMINAL_FOCUSED = new Sprite(TERMINAL_FOCUSED_TEXTURE); public static final Sprite SLOT_SELECTION = new Sprite(SLOT_SELECTION_TEXTURE, 18, 18, 0, 0); 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 042d5268..9786d594 100644 --- a/src/main/java/li/cil/oc2/client/gui/Textures.java +++ b/src/main/java/li/cil/oc2/client/gui/Textures.java @@ -7,11 +7,15 @@ public final class Textures { public static final Texture ROBOT_CONTAINER_TEXTURE = new Texture("textures/gui/widget/robot_container.png", 176, 197); 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 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); public static final Texture INFO_ICON_TEXTURE = new Texture("textures/gui/overlay/slot_info.png", 28, 28); public static final Texture WARN_ICON_TEXTURE = new Texture("textures/gui/overlay/slot_warn.png", 28, 28); + public static final Texture BLOCK_FACE_FOCUSED_TEXTURE = new Texture("textures/gui/overlay/block_face_focused.png", 16, 16); + public static final Texture BLOCK_FACE_ENABLED_TEXTURE = new Texture("textures/gui/overlay/block_face_enabled.png", 16, 16); + public static final Texture BLOCK_FACE_DISABLED_TEXTURE = new Texture("textures/gui/overlay/block_face_disabled.png", 16, 16); public static final Texture HOTBAR_TEXTURE = new Texture("textures/gui/widget/hotbar.png", 224, 26); public static final Texture SIDEBAR_2_TEXTURE = new Texture("textures/gui/widget/sidebar_2.png", 19, 34); diff --git a/src/main/java/li/cil/oc2/client/renderer/ModRenderType.java b/src/main/java/li/cil/oc2/client/renderer/ModRenderType.java index b222ff23..8eb819b8 100644 --- a/src/main/java/li/cil/oc2/client/renderer/ModRenderType.java +++ b/src/main/java/li/cil/oc2/client/renderer/ModRenderType.java @@ -47,6 +47,24 @@ public abstract class ModRenderType extends RenderType { state); } + public static RenderType getOverlay(final ResourceLocation location) { + final TextureStateShard texture = new TextureStateShard(location, false, true); + final RenderType.CompositeState state = RenderType.CompositeState.builder() + .setShaderState(RenderStateShard.POSITION_TEX_SHADER) + .setTextureState(texture) + .setOutputState(TRANSLUCENT_TARGET) + .setTransparencyState(ADDITIVE_TRANSPARENCY) + .createCompositeState(false); + return create( + API.MOD_ID + "/overlay", + DefaultVertexFormat.POSITION_TEX, + VertexFormat.Mode.QUADS, + 256, + false, + true, + state); + } + /////////////////////////////////////////////////////////////////// private ModRenderType(final String name, final VertexFormat format, final VertexFormat.Mode 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/bus/device/item/NetworkInterfaceCardItemDevice.java b/src/main/java/li/cil/oc2/common/bus/device/item/NetworkInterfaceCardItemDevice.java index d4798f58..3d3e4885 100644 --- 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 @@ -12,6 +12,7 @@ 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.item.NetworkInterfaceCardItem; import li.cil.oc2.common.serialization.NBTSerialization; import li.cil.oc2.common.util.NBTTagIds; import li.cil.sedna.device.virtio.VirtIONetworkDevice; @@ -51,7 +52,7 @@ public final class NetworkInterfaceCardItemDevice extends IdentityProxy LazyOptional getCapability(final Capability cap, @Nullable final Direction side) { - if (cap == Capabilities.NETWORK_INTERFACE && side != null) { + if (cap == Capabilities.NETWORK_INTERFACE && NetworkInterfaceCardItem.getSideConfiguration(identity, side)) { return LazyOptional.of(() -> networkInterface).cast(); } 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 3051e4db..5506705a 100644 --- a/src/main/java/li/cil/oc2/common/item/Items.java +++ b/src/main/java/li/cil/oc2/common/item/Items.java @@ -63,7 +63,7 @@ public final class Items { new FloppyItem(512 * Constants.KILOBYTE)); public static final RegistryObject REDSTONE_INTERFACE_CARD = register("redstone_interface_card"); - public static final RegistryObject NETWORK_INTERFACE_CARD = register("network_interface_card"); + public static final RegistryObject NETWORK_INTERFACE_CARD = register("network_interface_card", NetworkInterfaceCardItem::new); public static final RegistryObject FILE_IMPORT_EXPORT_CARD = register("file_import_export_card"); public static final RegistryObject SOUND_CARD = register("sound_card"); diff --git a/src/main/java/li/cil/oc2/common/item/NetworkInterfaceCardItem.java b/src/main/java/li/cil/oc2/common/item/NetworkInterfaceCardItem.java new file mode 100644 index 00000000..edbcbd56 --- /dev/null +++ b/src/main/java/li/cil/oc2/common/item/NetworkInterfaceCardItem.java @@ -0,0 +1,111 @@ +package li.cil.oc2.common.item; + +import li.cil.oc2.client.gui.NetworkInterfaceCardScreen; +import li.cil.oc2.common.Constants; +import li.cil.oc2.common.util.ItemStackUtils; +import li.cil.oc2.common.util.NBTTagIds; +import net.minecraft.ChatFormatting; +import net.minecraft.client.Minecraft; +import net.minecraft.core.Direction; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.chat.Component; +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 net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; + +import javax.annotation.Nullable; +import java.util.Arrays; +import java.util.List; + +import static li.cil.oc2.common.util.TooltipUtils.withColor; +import static li.cil.oc2.common.util.TranslationUtils.text; + +public final class NetworkInterfaceCardItem extends ModItem { + private static final String SIDE_CONFIGURATION_TAG_NAME = "sides"; + private static final Component IS_CONFIGURED_TEXT = withColor(text("item.{mod}.network_interface_card.is_configured"), ChatFormatting.GREEN); + + /////////////////////////////////////////////////////////////////// + + public static void setSideConfiguration(final ItemStack stack, final Direction side, final boolean enabled) { + final int index = side.get3DDataValue(); + + final CompoundTag tag = ItemStackUtils.getOrCreateModDataTag(stack); + final byte[] values; + if (tag.contains(SIDE_CONFIGURATION_TAG_NAME, NBTTagIds.TAG_BYTE_ARRAY) && + tag.getByteArray(SIDE_CONFIGURATION_TAG_NAME).length == Constants.BLOCK_FACE_COUNT) { + values = tag.getByteArray(SIDE_CONFIGURATION_TAG_NAME); + } else { + values = new byte[Constants.BLOCK_FACE_COUNT]; + Arrays.fill(values, (byte) 1); + } + + values[index] = (byte) (enabled ? 1 : 0); + + tag.putByteArray(SIDE_CONFIGURATION_TAG_NAME, values); + } + + public static boolean getSideConfiguration(final ItemStack stack, @Nullable final Direction side) { + if (side == null) { + return false; + } + + final int index = side.get3DDataValue(); + + final CompoundTag tag = ItemStackUtils.getModDataTag(stack); + if (tag.contains(SIDE_CONFIGURATION_TAG_NAME, NBTTagIds.TAG_BYTE_ARRAY)) { + final byte[] values = tag.getByteArray(SIDE_CONFIGURATION_TAG_NAME); + if (index < values.length) { + return values[index] != 0; + } + } + + return true; + } + + public static boolean hasConfiguration(final ItemStack stack) { + final byte[] values = ItemStackUtils.getModDataTag(stack).getByteArray(SIDE_CONFIGURATION_TAG_NAME); + for (final byte value : values) { + if (value == 0) { + return true; + } + } + + return false; + } + + /////////////////////////////////////////////////////////////////// + + + @Override + public void appendHoverText(final ItemStack stack, @Nullable final Level level, final List tooltip, final TooltipFlag flag) { + super.appendHoverText(stack, level, tooltip, flag); + if (NetworkInterfaceCardItem.hasConfiguration(stack)) { + tooltip.add(IS_CONFIGURED_TEXT); + } + } + + @Override + public InteractionResultHolder use(final Level level, final Player player, final InteractionHand hand) { + final ItemStack itemStack = player.getItemInHand(hand); + + if (player.getLevel().isClientSide()) { + if (itemStack.is(Items.NETWORK_INTERFACE_CARD.get())) { + openConfigurationScreen(player, hand); + } + } + + return InteractionResultHolder.sidedSuccess(itemStack, player.getLevel().isClientSide()); + } + + /////////////////////////////////////////////////////////////////// + + @OnlyIn(Dist.CLIENT) + private void openConfigurationScreen(final Player player, final InteractionHand hand) { + Minecraft.getInstance().setScreen(new NetworkInterfaceCardScreen(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 a7dc6fac..f797953a 100644 --- a/src/main/java/li/cil/oc2/common/network/Network.java +++ b/src/main/java/li/cil/oc2/common/network/Network.java @@ -66,6 +66,8 @@ public final class Network { registerMessage(ClientCanceledImportFileMessage.class, ClientCanceledImportFileMessage::new, NetworkDirection.PLAY_TO_SERVER); registerMessage(BusCableFacadeMessage.class, BusCableFacadeMessage::new, NetworkDirection.PLAY_TO_CLIENT); + + registerMessage(NetworkInterfaceCardConfigurationMessage.class, NetworkInterfaceCardConfigurationMessage::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/NetworkInterfaceCardConfigurationMessage.java b/src/main/java/li/cil/oc2/common/network/message/NetworkInterfaceCardConfigurationMessage.java new file mode 100644 index 00000000..682925e7 --- /dev/null +++ b/src/main/java/li/cil/oc2/common/network/message/NetworkInterfaceCardConfigurationMessage.java @@ -0,0 +1,61 @@ +package li.cil.oc2.common.network.message; + +import li.cil.oc2.common.item.Items; +import li.cil.oc2.common.item.NetworkInterfaceCardItem; +import net.minecraft.core.Direction; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.item.ItemStack; +import net.minecraftforge.network.NetworkEvent; + +public final class NetworkInterfaceCardConfigurationMessage extends AbstractMessage { + private InteractionHand hand; + private Direction side; + private boolean value; + + /////////////////////////////////////////////////////////////////// + + public NetworkInterfaceCardConfigurationMessage(final InteractionHand hand, final Direction side, final boolean value) { + this.hand = hand; + this.side = side; + this.value = value; + } + + public NetworkInterfaceCardConfigurationMessage(final FriendlyByteBuf buffer) { + super(buffer); + } + + /////////////////////////////////////////////////////////////////// + + @Override + public void fromBytes(final FriendlyByteBuf buffer) { + hand = buffer.readEnum(InteractionHand.class); + side = buffer.readEnum(Direction.class); + value = buffer.readBoolean(); + } + + @Override + public void toBytes(final FriendlyByteBuf buffer) { + buffer.writeEnum(hand); + buffer.writeEnum(side); + buffer.writeBoolean(value); + } + + /////////////////////////////////////////////////////////////////// + + @Override + protected void handleMessage(final NetworkEvent.Context context) { + final ServerPlayer player = context.getSender(); + if (player == null) { + return; + } + + final ItemStack itemStack = player.getItemInHand(hand); + if (!itemStack.is(Items.NETWORK_INTERFACE_CARD.get())) { + return; + } + + NetworkInterfaceCardItem.setSideConfiguration(itemStack, side, value); + } +} diff --git a/src/main/resources/assets/oc2/doc/en_us/item/network_interface_card.md b/src/main/resources/assets/oc2/doc/en_us/item/network_interface_card.md index 6be9fe3f..256c218a 100644 --- a/src/main/resources/assets/oc2/doc/en_us/item/network_interface_card.md +++ b/src/main/resources/assets/oc2/doc/en_us/item/network_interface_card.md @@ -5,6 +5,8 @@ The network interface card (NIC) allows [computers](../block/computer.md) to sen 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. +These cards can be configured to only connect to selected sides (use while holding). This allows using multiple cards to build a custom router, for example. + 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. \ No newline at end of file +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 d1a0ab6d..9a8f1dcd 100644 --- a/src/main/resources/assets/oc2/lang/en_us.json +++ b/src/main/resources/assets/oc2/lang/en_us.json @@ -27,6 +27,7 @@ "item.oc2.flash_memory": "Flash Memory", "item.oc2.redstone_interface_card": "Redstone Interface Card", "item.oc2.network_interface_card": "Network Interface Card", + "item.oc2.network_interface_card.is_configured": "Has connectivity configuration.", "item.oc2.file_import_export_card": "File Import/Export Card", "item.oc2.file_import_export_card.desc": "Provides an API to import and export files into and out of a virtual computer from and into your real file system.", "item.oc2.robot": "Robot", @@ -72,6 +73,11 @@ "gui.oc2.file_chooser.confirm_button.overwrite": "Overwrite", "gui.oc2.file_chooser.cancel_button": "Cancel", + "gui.oc2.network_interface_card.side_state": "Connectivity: %s", + "gui.oc2.network_interface_card.connectivity.enabled": "Enabled", + "gui.oc2.network_interface_card.connectivity.disabled": "Disabled", + "gui.oc2.network_interface_card.info": "Drag to rotate. Click faces to toggle connectivity.", + "manual.oc2.home": "Home", "manual.oc2.blocks": "Blocks", "manual.oc2.items": "Items", @@ -96,4 +102,4 @@ "subtitles.oc2.floppy_eject": "Floppy ejected", "subtitles.oc2.floppy_insert": "Floppy inserted", "subtitles.oc2.hdd": "Hard drive access" -} \ No newline at end of file +} diff --git a/src/main/resources/assets/oc2/textures/gui/overlay/block_face_disabled.png b/src/main/resources/assets/oc2/textures/gui/overlay/block_face_disabled.png new file mode 100644 index 00000000..bbdb9281 Binary files /dev/null and b/src/main/resources/assets/oc2/textures/gui/overlay/block_face_disabled.png differ diff --git a/src/main/resources/assets/oc2/textures/gui/overlay/block_face_enabled.png b/src/main/resources/assets/oc2/textures/gui/overlay/block_face_enabled.png new file mode 100644 index 00000000..8420a5e0 Binary files /dev/null and b/src/main/resources/assets/oc2/textures/gui/overlay/block_face_enabled.png differ diff --git a/src/main/resources/assets/oc2/textures/gui/overlay/block_face_focused.png b/src/main/resources/assets/oc2/textures/gui/overlay/block_face_focused.png new file mode 100644 index 00000000..946c9d5d Binary files /dev/null and b/src/main/resources/assets/oc2/textures/gui/overlay/block_face_focused.png differ diff --git a/src/main/resources/assets/oc2/textures/gui/widget/network_interface_card_screen.png b/src/main/resources/assets/oc2/textures/gui/widget/network_interface_card_screen.png new file mode 100644 index 00000000..57773128 Binary files /dev/null and b/src/main/resources/assets/oc2/textures/gui/widget/network_interface_card_screen.png differ