From 9275d9562aa01fd82217c033b3200a178d1312b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20N=C3=BCcke?= Date: Wed, 30 Sep 2020 13:27:32 +0200 Subject: [PATCH] Send terminal output to client and input back to server. --- src/main/java/li/cil/oc2/OpenComputers.java | 5 +- .../java/li/cil/oc2/client/ClientSetup.java | 4 +- ...reen.java => ComputerContainerScreen.java} | 13 +- .../cil/oc2/client/gui/RISCVTestScreen.java | 2 +- .../li/cil/oc2/client/gui/TerminalScreen.java | 123 ++++++++++++++++++ .../cil/oc2/client/gui/terminal/Terminal.java | 100 ++++++++++++-- .../java/li/cil/oc2/common/CommonSetup.java | 3 +- .../cil/oc2/common/block/ComputerBlock.java | 64 ++++++--- .../common/container/ComputerContainer.java | 6 +- .../AbstractComputerTerminalMessage.java | 31 +++++ .../network/ComputerTerminalInputMessage.java | 34 +++++ .../ComputerTerminalOutputMessage.java | 32 +++++ .../li/cil/oc2/common/network/Network.java | 33 +++++ .../oc2/common/tile/ComputerTileEntity.java | 45 ++++++- .../textures/gui/{ => container}/computer.png | Bin .../oc2/textures/gui/screen/terminal.png | Bin 0 -> 3514 bytes .../textures/gui/screen/terminal_focused.png | Bin 0 -> 6379 bytes 17 files changed, 448 insertions(+), 47 deletions(-) rename src/main/java/li/cil/oc2/client/gui/{ComputerScreen.java => ComputerContainerScreen.java} (70%) create mode 100644 src/main/java/li/cil/oc2/client/gui/TerminalScreen.java create mode 100644 src/main/java/li/cil/oc2/common/network/AbstractComputerTerminalMessage.java create mode 100644 src/main/java/li/cil/oc2/common/network/ComputerTerminalInputMessage.java create mode 100644 src/main/java/li/cil/oc2/common/network/ComputerTerminalOutputMessage.java rename src/main/resources/assets/oc2/textures/gui/{ => container}/computer.png (100%) create mode 100644 src/main/resources/assets/oc2/textures/gui/screen/terminal.png create mode 100644 src/main/resources/assets/oc2/textures/gui/screen/terminal_focused.png diff --git a/src/main/java/li/cil/oc2/OpenComputers.java b/src/main/java/li/cil/oc2/OpenComputers.java index 4e60d8db..11c35e87 100644 --- a/src/main/java/li/cil/oc2/OpenComputers.java +++ b/src/main/java/li/cil/oc2/OpenComputers.java @@ -46,7 +46,10 @@ public final class OpenComputers { public static final RegistryObject> COMPUTER_CONTAINER = CONTAINERS.register(Constants.COMPUTER_BLOCK_NAME, () -> IForgeContainerType.create((id, inventory, data) -> { final BlockPos pos = data.readBlockPos(); final TileEntity tileEntity = inventory.player.getEntityWorld().getTileEntity(pos); - return new ComputerContainer(id, tileEntity); + if (!(tileEntity instanceof ComputerTileEntity)) { + return null; + } + return new ComputerContainer(id, (ComputerTileEntity) tileEntity); })); public OpenComputers() { diff --git a/src/main/java/li/cil/oc2/client/ClientSetup.java b/src/main/java/li/cil/oc2/client/ClientSetup.java index 6d356e31..4dd7c14c 100644 --- a/src/main/java/li/cil/oc2/client/ClientSetup.java +++ b/src/main/java/li/cil/oc2/client/ClientSetup.java @@ -1,12 +1,12 @@ package li.cil.oc2.client; import li.cil.oc2.OpenComputers; -import li.cil.oc2.client.gui.ComputerScreen; +import li.cil.oc2.client.gui.ComputerContainerScreen; import net.minecraft.client.gui.ScreenManager; import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent; public final class ClientSetup { public static void run(final FMLClientSetupEvent event) { - ScreenManager.registerFactory(OpenComputers.COMPUTER_CONTAINER.get(), ComputerScreen::new); + ScreenManager.registerFactory(OpenComputers.COMPUTER_CONTAINER.get(), ComputerContainerScreen::new); } } diff --git a/src/main/java/li/cil/oc2/client/gui/ComputerScreen.java b/src/main/java/li/cil/oc2/client/gui/ComputerContainerScreen.java similarity index 70% rename from src/main/java/li/cil/oc2/client/gui/ComputerScreen.java rename to src/main/java/li/cil/oc2/client/gui/ComputerContainerScreen.java index 4a8c517c..00defffb 100644 --- a/src/main/java/li/cil/oc2/client/gui/ComputerScreen.java +++ b/src/main/java/li/cil/oc2/client/gui/ComputerContainerScreen.java @@ -10,10 +10,10 @@ import net.minecraft.util.text.ITextComponent; import java.util.Objects; -public final class ComputerScreen extends ContainerScreen { +public final class ComputerContainerScreen extends ContainerScreen { private static final ResourceLocation BACKGROUND = new ResourceLocation(API.MOD_ID, "textures/gui/container/computer.png"); - public ComputerScreen(final ComputerContainer container, final PlayerInventory inventory, final ITextComponent title) { + public ComputerContainerScreen(final ComputerContainer container, final PlayerInventory inventory, final ITextComponent title) { super(container, inventory, title); xSize = 196; ySize = 197; @@ -23,19 +23,12 @@ public final class ComputerScreen extends ContainerScreen { public void render(final int mouseX, final int mouseY, final float partialTicks) { renderBackground(); super.render(mouseX, mouseY, partialTicks); - RenderSystem.disableBlend(); - - // TODO Render terminal text. - - renderHoveredToolTip(mouseX, mouseY); } @Override protected void drawGuiContainerBackgroundLayer(final float partialTicks, final int mouseX, final int mouseY) { RenderSystem.color4f(1f, 1f, 1f, 1f); Objects.requireNonNull(minecraft).getTextureManager().bindTexture(BACKGROUND); - final int x = (width - xSize) / 2; - final int y = (height - ySize) / 2; - blit(x, y, 0, 0, xSize, ySize); + blit(guiLeft, guiTop, 0, 0, xSize, ySize); } } diff --git a/src/main/java/li/cil/oc2/client/gui/RISCVTestScreen.java b/src/main/java/li/cil/oc2/client/gui/RISCVTestScreen.java index 816f4555..13dc8fe0 100644 --- a/src/main/java/li/cil/oc2/client/gui/RISCVTestScreen.java +++ b/src/main/java/li/cil/oc2/client/gui/RISCVTestScreen.java @@ -130,7 +130,7 @@ public final class RISCVTestScreen extends Screen { return true; } } else { - if (keyCode == GLFW.GLFW_KEY_V && (modifiers & GLFW.GLFW_MOD_CONTROL) != 0) { + if ((modifiers & GLFW.GLFW_MOD_CONTROL) != 0 && keyCode == GLFW.GLFW_KEY_V) { final String value = Objects.requireNonNull(minecraft).keyboardListener.getClipboardString(); for (final char ch : value.toCharArray()) { terminal.putInput((byte) ch); diff --git a/src/main/java/li/cil/oc2/client/gui/TerminalScreen.java b/src/main/java/li/cil/oc2/client/gui/TerminalScreen.java new file mode 100644 index 00000000..12931f60 --- /dev/null +++ b/src/main/java/li/cil/oc2/client/gui/TerminalScreen.java @@ -0,0 +1,123 @@ +package li.cil.oc2.client.gui; + +import com.mojang.blaze3d.matrix.MatrixStack; +import com.mojang.blaze3d.systems.RenderSystem; +import li.cil.oc2.api.API; +import li.cil.oc2.client.gui.terminal.Terminal; +import li.cil.oc2.client.gui.terminal.TerminalInput; +import li.cil.oc2.common.network.ComputerTerminalInputMessage; +import li.cil.oc2.common.network.Network; +import li.cil.oc2.common.tile.ComputerTileEntity; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.text.ITextComponent; +import org.lwjgl.glfw.GLFW; + +import java.nio.ByteBuffer; +import java.util.Objects; + +public final class TerminalScreen extends Screen { + private static final ResourceLocation BACKGROUND = new ResourceLocation(API.MOD_ID, "textures/gui/screen/terminal.png"); + private static final ResourceLocation BACKGROUND_TERMINAL_FOCUSED = new ResourceLocation(API.MOD_ID, "textures/gui/screen/terminal_focused.png"); + private static final int TEXTURE_SIZE = 512; + private static final int SCREEN_WIDTH = 8 + 80 * 9 / 2 + 8; + private static final int SCREEN_HEIGHT = 8 + 24 * 16 / 2 + 8; + private static final int TERMINAL_AREA_X = 8; + private static final int TERMINAL_AREA_Y = 8; + private static final int TERMINAL_AREA_WIDTH = 80 * 9 / 2; + private static final int TERMINAL_AREA_HEIGHT = 24 * 16 / 2; + + private final ComputerTileEntity tileEntity; + private final Terminal terminal; + private final int windowWidth, windowHeight; + private int windowLeft, windowTop; + private boolean isMouseOverTerminal; + + public TerminalScreen(final ComputerTileEntity tileEntity, final ITextComponent title) { + super(title); + this.tileEntity = tileEntity; + terminal = tileEntity.getTerminal(); + windowWidth = SCREEN_WIDTH; + windowHeight = SCREEN_HEIGHT; + } + + @Override + public void render(final int mouseX, final int mouseY, final float partialTicks) { + renderBackground(); + + isMouseOverTerminal = isPointInRegion(TERMINAL_AREA_X, TERMINAL_AREA_Y, TERMINAL_AREA_WIDTH, TERMINAL_AREA_HEIGHT, mouseX, mouseY); + RenderSystem.color4f(1f, 1f, 1f, 1f); + Objects.requireNonNull(minecraft).getTextureManager().bindTexture(BACKGROUND); + blit(windowLeft, windowTop, 0, 0, windowWidth, windowHeight, TEXTURE_SIZE, TEXTURE_SIZE); + + if (isMouseOverTerminal) { + Objects.requireNonNull(minecraft).getTextureManager().bindTexture(BACKGROUND_TERMINAL_FOCUSED); + blit(windowLeft, windowTop, 0, 0, windowWidth, windowHeight, TEXTURE_SIZE, TEXTURE_SIZE); + } + + super.render(mouseX, mouseY, partialTicks); + + final MatrixStack stack = new MatrixStack(); + stack.translate(windowLeft + TERMINAL_AREA_X, windowTop + TERMINAL_AREA_Y, this.itemRenderer.zLevel); + stack.scale(TERMINAL_AREA_WIDTH / (float) terminal.getWidth(), TERMINAL_AREA_HEIGHT / (float) terminal.getHeight(), 1f); + terminal.render(stack); + } + + @Override + public void tick() { + super.tick(); + + final ByteBuffer input = terminal.getInput(); + if (input != null) { + Network.INSTANCE.sendToServer(new ComputerTerminalInputMessage(tileEntity, input)); + } + } + + @Override + public boolean isPauseScreen() { + return false; + } + + @Override + public boolean charTyped(final char ch, final int modifier) { + terminal.putInput((byte) ch); + return true; + } + + @Override + public boolean keyPressed(final int keyCode, final int scanCode, final int modifiers) { + if (!isMouseOverTerminal && keyCode == GLFW.GLFW_KEY_ESCAPE) { + return super.keyPressed(keyCode, scanCode, modifiers); + } + + if ((modifiers & GLFW.GLFW_MOD_CONTROL) != 0 && keyCode == GLFW.GLFW_KEY_V) { + final String value = Objects.requireNonNull(minecraft).keyboardListener.getClipboardString(); + for (final char ch : value.toCharArray()) { + terminal.putInput((byte) ch); + } + return true; + } + + if (TerminalInput.KEYCODE_SEQUENCES.containsKey(keyCode)) { + final byte[] sequence = TerminalInput.KEYCODE_SEQUENCES.get(keyCode); + for (int i = 0; i < sequence.length; i++) { + terminal.putInput(sequence[i]); + } + return true; + } + + return false; + } + + protected void init() { + super.init(); + this.windowLeft = (this.width - this.windowWidth) / 2; + this.windowTop = (this.height - this.windowHeight) / 2; + } + + private boolean isPointInRegion(int x, int y, int width, int height, double mouseX, double mouseY) { + mouseX = mouseX - this.windowLeft; + mouseY = mouseY - this.windowTop; + return mouseX >= x && mouseX < x + width && mouseY >= y && mouseY < y + height; + } +} diff --git a/src/main/java/li/cil/oc2/client/gui/terminal/Terminal.java b/src/main/java/li/cil/oc2/client/gui/terminal/Terminal.java index 97505eb1..31450fa0 100644 --- a/src/main/java/li/cil/oc2/client/gui/terminal/Terminal.java +++ b/src/main/java/li/cil/oc2/client/gui/terminal/Terminal.java @@ -13,16 +13,21 @@ import net.minecraft.client.renderer.Matrix4f; import net.minecraft.client.renderer.Tessellator; import net.minecraft.client.renderer.WorldVertexBufferUploader; import net.minecraft.client.renderer.vertex.DefaultVertexFormats; +import net.minecraft.nbt.CompoundNBT; import net.minecraft.state.properties.NoteBlockInstrument; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; import org.lwjgl.opengl.GL11; +import javax.annotation.Nullable; +import java.nio.ByteBuffer; import java.util.Arrays; import java.util.concurrent.atomic.AtomicInteger; // Implements a couple of control sequences from here: https://en.wikipedia.org/wiki/ANSI_escape_code#CSI_sequences public final class Terminal { private static final int TAB_WIDTH = 4; - private static final int WIDTH = 80, HEIGHT = 25; + private static final int WIDTH = 80, HEIGHT = 24; private enum State { NORMAL, // Currently reading characters normally. @@ -31,7 +36,7 @@ public final class Terminal { } private final ByteArrayFIFOQueue input = new ByteArrayFIFOQueue(32); - private final char[] buffer = new char[WIDTH * HEIGHT]; + private final byte[] buffer = new byte[WIDTH * HEIGHT]; private State state = State.NORMAL; private final int[] args = new int[4]; private int argCount = 0; @@ -42,7 +47,7 @@ public final class Terminal { private final AtomicInteger dirty = new AtomicInteger(-1); public Terminal() { - Arrays.fill(buffer, ' '); + clear(); } public int getWidth() { @@ -53,6 +58,7 @@ public final class Terminal { return HEIGHT * MonospaceFontRenderer.INSTANCE.getCharHeight(); } + @OnlyIn(Dist.CLIENT) public void render(final MatrixStack stack) { final FontRenderer fontRenderer = MonospaceFontRenderer.INSTANCE; @@ -70,6 +76,53 @@ public final class Terminal { } } + public CompoundNBT serialize(final boolean forClient) { + final CompoundNBT nbt = new CompoundNBT(); + + if (!forClient) { + // todo serialize input + } + + nbt.putByteArray("buffer", buffer); + nbt.putByte("state", (byte) state.ordinal()); + nbt.putIntArray("args", args); + nbt.putInt("argCount", argCount); + nbt.putInt("x", x); + nbt.putInt("y", y); + nbt.putInt("savedX", savedX); + nbt.putInt("savedY", savedY); + + return nbt; + } + + public void deserialize(final CompoundNBT nbt) { + if (nbt.contains("input")) { + // todo deserialize input + } + + final byte[] buffer = nbt.getByteArray("buffer"); + if (buffer.length == this.buffer.length) { + System.arraycopy(buffer, 0, this.buffer, 0, buffer.length); + } + + final byte state = nbt.getByte("state"); + final State[] states = State.values(); + if (state >= 0 && state < states.length) { + this.state = states[state]; + } + + final int[] args = nbt.getIntArray("args"); + if (args.length == this.args.length) { + System.arraycopy(args, 0, this.args, 0, args.length); + } + + argCount = nbt.getInt("argCount"); + x = nbt.getInt("x"); + y = nbt.getInt("y"); + savedX = nbt.getInt("savedX"); + savedY = nbt.getInt("savedY"); + } + public synchronized int readInput() { if (input.isEmpty()) { return -1; @@ -78,6 +131,32 @@ public final class Terminal { } } + @Nullable + public synchronized ByteBuffer getInput() { + if (input.isEmpty()) { + return null; + } else { + final ByteBuffer buffer = ByteBuffer.allocate(input.size()); + while (!input.isEmpty()) { + buffer.put(input.dequeueByte()); + } + buffer.flip(); + return buffer; + } + } + + public synchronized void putInput(final ByteBuffer values) { + while (values.hasRemaining()) { + input.enqueue(values.get()); + } + } + + public synchronized void putOutput(final ByteBuffer values) { + while (values.hasRemaining()) { + putOutput(values.get()); + } + } + public synchronized void putInput(final byte value) { input.enqueue(value); } @@ -243,7 +322,12 @@ public final class Terminal { } } + @OnlyIn(Dist.CLIENT) private void renderCursor(final MatrixStack stack) { + if (x < 0 || x >= WIDTH || y < 0 || y >= HEIGHT) { + return; + } + final FontRenderer fontRenderer = MonospaceFontRenderer.INSTANCE; GlStateManager.depthMask(false); @@ -282,7 +366,7 @@ public final class Terminal { } setChar(x, y, ch); - setCursorPos(x + 1, y); + x++; } private void setChar(final int x, final int y, final char ch) { @@ -290,12 +374,12 @@ public final class Terminal { return; } - buffer[x + y * WIDTH] = ch; + buffer[x + y * WIDTH] = (byte) ch; dirty.accumulateAndGet(1 << y, (prev, next) -> prev | next); } private void clear() { - Arrays.fill(buffer, ' '); + Arrays.fill(buffer, (byte) ' '); dirty.set((1 << HEIGHT) - 1); } @@ -304,7 +388,7 @@ public final class Terminal { } private void clearLine(final int y, final int fromIndex, final int toIndex) { - Arrays.fill(buffer, y * WIDTH + fromIndex, y * WIDTH + toIndex, ' '); + Arrays.fill(buffer, y * WIDTH + fromIndex, y * WIDTH + toIndex, (byte) ' '); dirty.accumulateAndGet(1 << y, (prev, next) -> prev | next); } @@ -319,7 +403,7 @@ public final class Terminal { private void shiftUpOne() { System.arraycopy(buffer, WIDTH, buffer, 0, buffer.length - WIDTH); System.arraycopy(lines, 1, lines, 0, lines.length - 1); - Arrays.fill(buffer, WIDTH * HEIGHT - WIDTH, WIDTH * HEIGHT, ' '); + Arrays.fill(buffer, WIDTH * HEIGHT - WIDTH, WIDTH * HEIGHT, (byte) ' '); // Shift all dirty down one because we moved rows up one (up = lower indices). // Mark bottom-most line (highest index) as dirty. diff --git a/src/main/java/li/cil/oc2/common/CommonSetup.java b/src/main/java/li/cil/oc2/common/CommonSetup.java index baab3568..7e0a5538 100644 --- a/src/main/java/li/cil/oc2/common/CommonSetup.java +++ b/src/main/java/li/cil/oc2/common/CommonSetup.java @@ -1,9 +1,10 @@ package li.cil.oc2.common; +import li.cil.oc2.common.network.Network; import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent; public final class CommonSetup { public static void run(final FMLCommonSetupEvent event) { - + Network.setup(); } } diff --git a/src/main/java/li/cil/oc2/common/block/ComputerBlock.java b/src/main/java/li/cil/oc2/common/block/ComputerBlock.java index 8d354ed0..6c63d50a 100644 --- a/src/main/java/li/cil/oc2/common/block/ComputerBlock.java +++ b/src/main/java/li/cil/oc2/common/block/ComputerBlock.java @@ -1,12 +1,14 @@ package li.cil.oc2.common.block; import li.cil.oc2.OpenComputers; +import li.cil.oc2.client.gui.TerminalScreen; import li.cil.oc2.common.container.ComputerContainer; import li.cil.oc2.common.tile.ComputerTileEntity; import net.minecraft.block.Block; import net.minecraft.block.BlockState; import net.minecraft.block.SoundType; import net.minecraft.block.material.Material; +import net.minecraft.client.Minecraft; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.player.PlayerInventory; import net.minecraft.entity.player.ServerPlayerEntity; @@ -18,7 +20,6 @@ import net.minecraft.util.Hand; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockRayTraceResult; import net.minecraft.util.text.ITextComponent; -import net.minecraft.util.text.TranslationTextComponent; import net.minecraft.world.IBlockReader; import net.minecraft.world.World; import net.minecraftforge.fml.network.NetworkHooks; @@ -44,28 +45,53 @@ public final class ComputerBlock extends Block { @SuppressWarnings("deprecation") @Override public ActionResultType onBlockActivated(final BlockState state, final World world, final BlockPos pos, final PlayerEntity player, final Hand hand, final BlockRayTraceResult hit) { - if (!world.isRemote()) { - if (!(player instanceof ServerPlayerEntity)) { - throw new IllegalArgumentException(); - } + final TileEntity tileEntity = world.getTileEntity(pos); + if (!(tileEntity instanceof ComputerTileEntity)) { + throw new IllegalStateException(); + } - final TileEntity tileEntity = world.getTileEntity(pos); - if (!(tileEntity instanceof ComputerTileEntity)) { - throw new IllegalStateException(); - } - - NetworkHooks.openGui((ServerPlayerEntity) player, new INamedContainerProvider() { - @Override - public ITextComponent getDisplayName() { - return new TranslationTextComponent("blah"); + final ComputerTileEntity computer = (ComputerTileEntity) tileEntity; + if (player.isSneaking()) { + if (!world.isRemote()) { + if (computer.isRunning()) { + return ActionResultType.CONSUME; + } else { + computer.start(); } - - @Override - public Container createMenu(final int id, final PlayerInventory inventory, final PlayerEntity player) { - return new ComputerContainer(id, tileEntity); + } + } else { + final boolean openContainer = false; // TODO + if (openContainer) { + if (!world.isRemote()) { + if (!(player instanceof ServerPlayerEntity)) { + throw new IllegalArgumentException(); + } + openContainerScreen(tileEntity, (ServerPlayerEntity) player); } - }, tileEntity.getPos()); + } else { + if (world.isRemote()) { + openTerminalScreen(computer); + } + } } return ActionResultType.SUCCESS; } + + private void openContainerScreen(final TileEntity tileEntity, final ServerPlayerEntity player) { + NetworkHooks.openGui(player, new INamedContainerProvider() { + @Override + public ITextComponent getDisplayName() { + return getNameTextComponent(); + } + + @Override + public Container createMenu(final int id, final PlayerInventory inventory, final PlayerEntity player) { + return new ComputerContainer(id, (ComputerTileEntity) tileEntity); + } + }, tileEntity.getPos()); + } + + private void openTerminalScreen(final ComputerTileEntity computer) { + Minecraft.getInstance().displayGuiScreen(new TerminalScreen(computer, getNameTextComponent())); + } } diff --git a/src/main/java/li/cil/oc2/common/container/ComputerContainer.java b/src/main/java/li/cil/oc2/common/container/ComputerContainer.java index 3ad346f1..2c4e0478 100644 --- a/src/main/java/li/cil/oc2/common/container/ComputerContainer.java +++ b/src/main/java/li/cil/oc2/common/container/ComputerContainer.java @@ -1,18 +1,18 @@ package li.cil.oc2.common.container; import li.cil.oc2.OpenComputers; +import li.cil.oc2.common.tile.ComputerTileEntity; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.inventory.container.Container; -import net.minecraft.tileentity.TileEntity; import net.minecraft.util.IWorldPosCallable; import net.minecraft.world.World; import javax.annotation.Nullable; public final class ComputerContainer extends Container { - private final TileEntity tileEntity; + private final ComputerTileEntity tileEntity; - public ComputerContainer(final int id, @Nullable final TileEntity tileEntity) { + public ComputerContainer(final int id, @Nullable final ComputerTileEntity tileEntity) { super(OpenComputers.COMPUTER_CONTAINER.get(), id); this.tileEntity = tileEntity; } diff --git a/src/main/java/li/cil/oc2/common/network/AbstractComputerTerminalMessage.java b/src/main/java/li/cil/oc2/common/network/AbstractComputerTerminalMessage.java new file mode 100644 index 00000000..dd61ccee --- /dev/null +++ b/src/main/java/li/cil/oc2/common/network/AbstractComputerTerminalMessage.java @@ -0,0 +1,31 @@ +package li.cil.oc2.common.network; + +import li.cil.oc2.common.tile.ComputerTileEntity; +import net.minecraft.network.PacketBuffer; +import net.minecraft.util.math.BlockPos; + +import java.nio.ByteBuffer; + +public abstract class AbstractComputerTerminalMessage { + protected BlockPos pos; + protected byte[] data; + + public AbstractComputerTerminalMessage(final ComputerTileEntity tileEntity, final ByteBuffer data) { + this.pos = tileEntity.getPos(); + this.data = data.array(); + } + + public AbstractComputerTerminalMessage(final PacketBuffer buffer) { + fromBytes(buffer); + } + + public void fromBytes(final PacketBuffer buffer) { + pos = buffer.readBlockPos(); + data = buffer.readByteArray(); + } + + public static void toBytes(final AbstractComputerTerminalMessage message, final PacketBuffer buffer) { + buffer.writeBlockPos(message.pos); + buffer.writeByteArray(message.data); + } +} diff --git a/src/main/java/li/cil/oc2/common/network/ComputerTerminalInputMessage.java b/src/main/java/li/cil/oc2/common/network/ComputerTerminalInputMessage.java new file mode 100644 index 00000000..21de48da --- /dev/null +++ b/src/main/java/li/cil/oc2/common/network/ComputerTerminalInputMessage.java @@ -0,0 +1,34 @@ +package li.cil.oc2.common.network; + +import li.cil.oc2.common.tile.ComputerTileEntity; +import net.minecraft.entity.player.ServerPlayerEntity; +import net.minecraft.network.PacketBuffer; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.world.server.ServerWorld; +import net.minecraftforge.fml.network.NetworkEvent; + +import java.nio.ByteBuffer; +import java.util.function.Supplier; + +public final class ComputerTerminalInputMessage extends AbstractComputerTerminalMessage { + public ComputerTerminalInputMessage(final ComputerTileEntity tileEntity, final ByteBuffer data) { + super(tileEntity, data); + } + + public ComputerTerminalInputMessage(final PacketBuffer buffer) { + super(buffer); + } + + public static boolean handleInput(final AbstractComputerTerminalMessage message, final Supplier context) { + context.get().enqueueWork(() -> { + final ServerPlayerEntity player = context.get().getSender(); + if (player == null) return; + final ServerWorld world = player.getServerWorld(); + // TODO Check if chunk is loaded first. + final TileEntity tileEntity = world.getTileEntity(message.pos); + if (!(tileEntity instanceof ComputerTileEntity)) return; + ((ComputerTileEntity) tileEntity).getTerminal().putInput(ByteBuffer.wrap(message.data)); + }); + return true; + } +} diff --git a/src/main/java/li/cil/oc2/common/network/ComputerTerminalOutputMessage.java b/src/main/java/li/cil/oc2/common/network/ComputerTerminalOutputMessage.java new file mode 100644 index 00000000..ae08957f --- /dev/null +++ b/src/main/java/li/cil/oc2/common/network/ComputerTerminalOutputMessage.java @@ -0,0 +1,32 @@ +package li.cil.oc2.common.network; + +import li.cil.oc2.common.tile.ComputerTileEntity; +import net.minecraft.client.Minecraft; +import net.minecraft.client.world.ClientWorld; +import net.minecraft.network.PacketBuffer; +import net.minecraft.tileentity.TileEntity; +import net.minecraftforge.fml.network.NetworkEvent; + +import java.nio.ByteBuffer; +import java.util.function.Supplier; + +public final class ComputerTerminalOutputMessage extends AbstractComputerTerminalMessage { + public ComputerTerminalOutputMessage(final ComputerTileEntity tileEntity, final ByteBuffer data) { + super(tileEntity, data); + } + + public ComputerTerminalOutputMessage(final PacketBuffer buffer) { + super(buffer); + } + + public static boolean handleOutput(final AbstractComputerTerminalMessage message, final Supplier context) { + context.get().enqueueWork(() -> { + final ClientWorld world = Minecraft.getInstance().world; + if (world == null) return; + final TileEntity tileEntity = world.getTileEntity(message.pos); + if (!(tileEntity instanceof ComputerTileEntity)) return; + ((ComputerTileEntity) tileEntity).getTerminal().putOutput(ByteBuffer.wrap(message.data)); + }); + return true; + } +} 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 64c49be6..a63e1b6d 100644 --- a/src/main/java/li/cil/oc2/common/network/Network.java +++ b/src/main/java/li/cil/oc2/common/network/Network.java @@ -1,4 +1,37 @@ package li.cil.oc2.common.network; +import li.cil.oc2.api.API; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.fml.network.NetworkDirection; +import net.minecraftforge.fml.network.NetworkRegistry; +import net.minecraftforge.fml.network.simple.SimpleChannel; + public final class Network { + private static final String PROTOCOL_VERSION = "1"; + private static int nextPacketId = 1; + + public static final SimpleChannel INSTANCE = NetworkRegistry.newSimpleChannel( + new ResourceLocation(API.MOD_ID, "main"), + () -> PROTOCOL_VERSION, + PROTOCOL_VERSION::equals, + PROTOCOL_VERSION::equals + ); + + public static void setup() { + INSTANCE.messageBuilder(ComputerTerminalOutputMessage.class, getNextPacketId(), NetworkDirection.PLAY_TO_CLIENT) + .encoder(ComputerTerminalOutputMessage::toBytes) + .decoder(ComputerTerminalOutputMessage::new) + .consumer(ComputerTerminalOutputMessage::handleOutput) + .add(); + + INSTANCE.messageBuilder(ComputerTerminalInputMessage.class, getNextPacketId(), NetworkDirection.PLAY_TO_SERVER) + .encoder(ComputerTerminalInputMessage::toBytes) + .decoder(ComputerTerminalInputMessage::new) + .consumer(ComputerTerminalInputMessage::handleInput) + .add(); + } + + private static int getNextPacketId() { + return nextPacketId++; + } } diff --git a/src/main/java/li/cil/oc2/common/tile/ComputerTileEntity.java b/src/main/java/li/cil/oc2/common/tile/ComputerTileEntity.java index ed7d0cec..2f910cc8 100644 --- a/src/main/java/li/cil/oc2/common/tile/ComputerTileEntity.java +++ b/src/main/java/li/cil/oc2/common/tile/ComputerTileEntity.java @@ -3,6 +3,8 @@ package li.cil.oc2.common.tile; import it.unimi.dsi.fastutil.bytes.ByteArrayFIFOQueue; import li.cil.oc2.OpenComputers; import li.cil.oc2.client.gui.terminal.Terminal; +import li.cil.oc2.common.network.ComputerTerminalOutputMessage; +import li.cil.oc2.common.network.Network; import li.cil.oc2.common.vm.VirtualMachineRunner; import li.cil.sedna.api.Sizes; import li.cil.sedna.api.device.PhysicalMemory; @@ -15,16 +17,21 @@ import li.cil.sedna.riscv.R5Board; import net.minecraft.nbt.CompoundNBT; import net.minecraft.tileentity.ITickableTileEntity; import net.minecraft.tileentity.TileEntity; +import net.minecraft.world.chunk.Chunk; +import net.minecraftforge.fml.network.PacketDistributor; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import java.io.BufferedInputStream; import java.io.InputStream; +import java.nio.ByteBuffer; +import java.util.Objects; public final class ComputerTileEntity extends TileEntity implements ITickableTileEntity { private static final Logger LOGGER = LogManager.getLogger(); private final Terminal terminal = new Terminal(); + private Chunk chunk; private VirtualMachineRunner runner; private R5Board board; @@ -37,6 +44,10 @@ public final class ComputerTileEntity extends TileEntity implements ITickableTil super(OpenComputers.COMPUTER_TILE_ENTITY.get()); } + public Terminal getTerminal() { + return terminal; + } + public void start() { startVirtualMachine(); } @@ -45,12 +56,20 @@ public final class ComputerTileEntity extends TileEntity implements ITickableTil stopVirtualMachine(); } + public boolean isRunning() { + return runner != null; + } + @Override public void tick() { if (world == null || world.isRemote()) { return; } + if (chunk == null) { + chunk = Objects.requireNonNull(getWorld()).getChunkAt(getPos()); + } + if (runner != null) { runner.tick(); } @@ -64,9 +83,23 @@ public final class ComputerTileEntity extends TileEntity implements ITickableTil @Override public void onChunkUnloaded() { + super.onChunkUnloaded(); stopVirtualMachine(); } + @Override + public CompoundNBT getUpdateTag() { + final CompoundNBT result = super.getUpdateTag(); + result.put("terminal", terminal.serialize(true)); + return result; + } + + @Override + public void handleUpdateTag(final CompoundNBT tag) { + super.handleUpdateTag(tag); + terminal.deserialize(tag.getCompound("terminal")); + } + @Override public void read(final CompoundNBT compound) { super.read(compound); @@ -78,7 +111,7 @@ public final class ComputerTileEntity extends TileEntity implements ITickableTil public CompoundNBT write(final CompoundNBT compound) { final CompoundNBT result = super.write(compound); joinVirtualMachine(); - // todo serialize VM + // TODO serialize VM return result; } @@ -172,9 +205,17 @@ public final class ComputerTileEntity extends TileEntity implements ITickableTil @Override protected void handleAfterRun() { + final ByteBuffer output = ByteBuffer.allocate(outputBuffer.size()); while (!outputBuffer.isEmpty()) { - terminal.putOutput(outputBuffer.dequeueByte()); + output.put(outputBuffer.dequeueByte()); } + + output.flip(); + terminal.putOutput(output); + + output.flip(); + final ComputerTerminalOutputMessage message = new ComputerTerminalOutputMessage(ComputerTileEntity.this, output); + Network.INSTANCE.send(PacketDistributor.TRACKING_CHUNK.with(() -> chunk), message); } } } diff --git a/src/main/resources/assets/oc2/textures/gui/computer.png b/src/main/resources/assets/oc2/textures/gui/container/computer.png similarity index 100% rename from src/main/resources/assets/oc2/textures/gui/computer.png rename to src/main/resources/assets/oc2/textures/gui/container/computer.png diff --git a/src/main/resources/assets/oc2/textures/gui/screen/terminal.png b/src/main/resources/assets/oc2/textures/gui/screen/terminal.png new file mode 100644 index 0000000000000000000000000000000000000000..d08c6791e5f825b0b31d72de2b232592663d641f GIT binary patch literal 3514 zcmeHJYfuwc6uvymqbNeHOtrW!jAO-Q^AI%K1jG<|Xpol!K~zGrAuF3~$Zp66$D)jH zTc0yppEzo#Eq26eD=J!1TU2mFN2Q8IExu}7L`SD0p!RNpJVe_+{a1f%cJF@YeCOQn z+;cWdQ4l}Fe1ZF0Tr*Lt^5sc1CT3C`c zBRr=zkG8Q26ooj_Mb46j9(FU+wS6cA#v_vi3!SZr3xnTTG85fot}%q#=# z#eM7vj(h_>kMyIr7-^PfjI^aYN}rUv;eL)#?$^6;TFdK<)#`sXH<|iugJD%gP`Iul z^vUUE04Bp>A+R)pp>0+jp(=uMggKStb;J`*+I(UPVa{ib3N%tIc?HQN_5A>zin8&w zn`S0q@LoPyJ(vO=CxNGiuc$MS=Cu$nHl9Lq1eFy>!b&lawI`1rRP!cONmH~n9kv3Y zK*i2z-L=4-n3~o@Ncswts8ps{X#+_?j7f{1h$O0%QDRwClt3bM(ZIst(3dng;|$`PUQ)=hKKyc+pIVJ4kPJ&( z9lb)P6NP<4Iy(?SoaYMH>i8-vP>Yq;+jNB9DI!eXg)+2(wQH?JY(6|v3N+SWAmMp% zASryWAY$Qop-c5{2fLAgWN#J{>%EhgnaQz!xw|&rYlc8S*E=|y;E=<*ryN{#&q=}z zhocov!F4qUq5yE4Pr@_Mul4|pT5Vf>aI!KsaB<9zpsA=)J0d82_S%F`k;bfWZhm(7 zpu-z)-43$KuPtA4*>As_*ZG=V=Z;R8ek?oiJyl?Ye}QUfK)oXoi9I8fK5C)ui<&fh zJl7^ARWQoYTP}63o!N1`c}-J$Qdm>oGj8It-DkZ=mS%i7Yv9GtAD$%|%0Ib({rs?{ zzx!Pr>$CEs@^yo^25r^Umyc}qvyWPu64#b<`RmhO3tQESoLl48-c&soYy8{i=90nt zPezQ-IhHhWvTt2o;m$P$N%x}fOwQhZ@9DfBL%$WWol}~BIy?4eTZmy*>cbNU9*B^L z@>^B@PZ@o9=Aeqb^J_*I2I&iz9U6IQN04rNJTdZ$sN+RtMKN_M{hMpe3)_dBX#36U zj{&!sMGxmvAy0$9n!9KhD~n+~!^*bbt=)JtkeY5OoY8Q6j^$v$mZ`H7{0HyM7Ed1I z-I{;ykWWkHx$koR)R1Kt;(g4unN8Kt-5#HIT&=~9ylV^?QJ*S-pEF-ef`$RWKbXJV zz{brX0C<-s#>HlEn_C)>nVNG$ROjnc%S)q-o=c-gEr{Oc(Kuq4ags)Kddh;uzc59o zW*%Pn#fw_QhFNBh>V4B2LaQ0BB*k30>Cx~TR|IvEF!#0v>kHdFIz#S0a?}i+5v|=Y z(9Lo%r(wEBb&M=@rBuq`TMUPNZ>(5td4&O^l$;r*g2*{}gC8{L8YR1>&tu4E48gnsV@xWKZWCTSb7;^fW7 zU=FR2?i*8Q{kW_?G!!i1N<8*ZQJbbM1`ia;UtTzKX767Ep3}pJuev%q0<&gZui98(!T-6p*7|J literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/oc2/textures/gui/screen/terminal_focused.png b/src/main/resources/assets/oc2/textures/gui/screen/terminal_focused.png new file mode 100644 index 0000000000000000000000000000000000000000..d590f576b078255b6c5adc6658c4e9b887b3c99e GIT binary patch literal 6379 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