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 00000000..d08c6791 Binary files /dev/null and b/src/main/resources/assets/oc2/textures/gui/screen/terminal.png differ 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 00000000..d590f576 Binary files /dev/null and b/src/main/resources/assets/oc2/textures/gui/screen/terminal_focused.png differ