diff --git a/src/main/java/li/cil/oc2/client/gui/KeyboardScreen.java b/src/main/java/li/cil/oc2/client/gui/KeyboardScreen.java index 92995268..b146e2eb 100644 --- a/src/main/java/li/cil/oc2/client/gui/KeyboardScreen.java +++ b/src/main/java/li/cil/oc2/client/gui/KeyboardScreen.java @@ -3,6 +3,8 @@ package li.cil.oc2.client.gui; import com.mojang.blaze3d.platform.InputConstants; +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.vertex.*; import li.cil.oc2.common.blockentity.KeyboardBlockEntity; import li.cil.oc2.common.item.Items; import li.cil.oc2.common.network.Network; @@ -10,10 +12,29 @@ import li.cil.oc2.common.network.message.KeyboardInputMessage; import net.minecraft.client.Minecraft; import net.minecraft.client.MouseHandler; import net.minecraft.client.gui.screens.Screen; +import net.minecraft.client.player.LocalPlayer; +import net.minecraft.client.renderer.GameRenderer; +import net.minecraft.network.chat.TranslatableComponent; +import net.minecraft.world.InteractionHand; import net.minecraft.world.phys.Vec3; +import net.minecraftforge.client.gui.ForgeIngameGui; +import net.minecraftforge.client.gui.OverlayRegistry; +import org.lwjgl.glfw.GLFW; + +import java.util.Random; public final class KeyboardScreen extends Screen { + private static final int BORDER_SIZE = 4; + private static final float ARM_SWING_RATE = 0.8f; + private static final int BORDER_COLOR = 0xFFFFFFFF; + private static final int ESCAPE_DOUBLE_TAP_WINDOW = 300; + + private static final TranslatableComponent CLOSE_INFO = new TranslatableComponent("gui.oc2.keyboard.close_info"); + + /////////////////////////////////////////////////////////////////// + private final KeyboardBlockEntity keyboard; + private long lastEscapePress; /////////////////////////////////////////////////////////////////// @@ -31,6 +52,11 @@ public final class KeyboardScreen extends Screen { // Grabbing the mouse allows us to let the player keep turning the camera (to get a better // look at the projection of a projector, e.g.), while still grabbing all keyboard input. grabMouse(); + + lastEscapePress = 0; + + // Disable hotbar since we don't need it here, and it just blocks screen space. + OverlayRegistry.enableOverlay(ForgeIngameGui.HOTBAR_ELEMENT, false); } @Override @@ -47,19 +73,49 @@ public final class KeyboardScreen extends Screen { @Override public boolean keyPressed(final int keycode, final int scancode, final int modifiers) { - sendInputMessage(keycode, true); - return super.keyPressed(keycode, scancode, modifiers); + if (keycode == GLFW.GLFW_KEY_ESCAPE) { + if (lastEscapePress != 0 && System.currentTimeMillis() - lastEscapePress <= ESCAPE_DOUBLE_TAP_WINDOW) { + lastEscapePress = 0; + onClose(); + } else { + lastEscapePress = System.currentTimeMillis(); + sendInputMessage(keycode, true); + } + } else if (!super.keyPressed(keycode, scancode, modifiers)) { + sendInputMessage(keycode, true); + } + return true; } @Override public boolean keyReleased(final int keycode, final int scancode, final int modifiers) { - sendInputMessage(keycode, false); - return super.keyReleased(keycode, scancode, modifiers); + if (keycode == GLFW.GLFW_KEY_ESCAPE) { + sendInputMessage(keycode, false); + } else if (!super.keyReleased(keycode, scancode, modifiers)) { + sendInputMessage(keycode, false); + } + return true; } @Override - public void mouseMoved(final double p_94758_, final double p_94759_) { - super.mouseMoved(p_94758_, p_94759_); + public boolean mouseClicked(final double mouseX, final double mouseY, final int button) { + if (button == GLFW.GLFW_MOUSE_BUTTON_2) { + onClose(); + return true; + } else { + return super.mouseClicked(mouseX, mouseY, button); + } + } + + @Override + public void render(final PoseStack stack, final int mouseX, final int mouseY, final float partialTicks) { + super.render(stack, mouseX, mouseY, partialTicks); + + renderBorderOverlay(stack); + + font.drawWordWrap(CLOSE_INFO, + BORDER_SIZE * 3, height - BORDER_SIZE * 3 - font.lineHeight, + width - BORDER_SIZE * 6, 0x88FFFFFF); } @Override @@ -67,8 +123,34 @@ public final class KeyboardScreen extends Screen { return false; } + @Override + public void removed() { + super.removed(); + + OverlayRegistry.enableOverlay(ForgeIngameGui.HOTBAR_ELEMENT, true); + } + /////////////////////////////////////////////////////////////////// + private void renderBorderOverlay(final PoseStack stack) { + blitQuad(stack, BORDER_SIZE, BORDER_SIZE, width - BORDER_SIZE, BORDER_SIZE * 2, BORDER_COLOR); + blitQuad(stack, BORDER_SIZE, BORDER_SIZE, BORDER_SIZE * 2, height - BORDER_SIZE, BORDER_COLOR); + blitQuad(stack, BORDER_SIZE, height - BORDER_SIZE * 2, width - BORDER_SIZE, height - BORDER_SIZE, BORDER_COLOR); + blitQuad(stack, width - BORDER_SIZE * 2, BORDER_SIZE, width - BORDER_SIZE, height - BORDER_SIZE, BORDER_COLOR); + } + + private void blitQuad(final PoseStack stack, final int x0, final int y0, final int x1, final int y1, final int color) { + RenderSystem.setShader(GameRenderer::getPositionColorShader); + final Tesselator tesselator = Tesselator.getInstance(); + final BufferBuilder builder = tesselator.getBuilder(); + builder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_COLOR); + builder.vertex(stack.last().pose(), x0, y1, getBlitOffset()).color(color).endVertex(); + builder.vertex(stack.last().pose(), x1, y1, getBlitOffset()).color(color).endVertex(); + builder.vertex(stack.last().pose(), x1, y0, getBlitOffset()).color(color).endVertex(); + builder.vertex(stack.last().pose(), x0, y0, getBlitOffset()).color(color).endVertex(); + tesselator.end(); + } + private void grabMouse() { final Minecraft minecraft = getMinecraft(); final MouseHandler mouseHandler = minecraft.mouseHandler; @@ -78,8 +160,31 @@ public final class KeyboardScreen extends Screen { private void sendInputMessage(final int keycode, final boolean isDown) { if (KeyCodeMapping.MAPPING.containsKey(keycode)) { + swingArm(); final int evdevCode = KeyCodeMapping.MAPPING.get(keycode); Network.sendToServer(new KeyboardInputMessage(keyboard, evdevCode, isDown)); } } + + private void swingArm() { + final Minecraft minecraft = getMinecraft(); + final LocalPlayer player = minecraft.player; + if (player == null) { + return; + } + + final Random random = player.getRandom(); + if (random.nextFloat() < ARM_SWING_RATE) { + return; + } + + final InteractionHand handToSwing; + if (minecraft.options.getCameraType().isFirstPerson()) { + handToSwing = InteractionHand.MAIN_HAND; + } else { + handToSwing = random.nextBoolean() ? InteractionHand.MAIN_HAND : InteractionHand.OFF_HAND; + } + + player.swing(handToSwing); + } } diff --git a/src/main/resources/assets/oc2/lang/en_us.json b/src/main/resources/assets/oc2/lang/en_us.json index 6b86734c..ba2a112f 100644 --- a/src/main/resources/assets/oc2/lang/en_us.json +++ b/src/main/resources/assets/oc2/lang/en_us.json @@ -20,6 +20,8 @@ "block.oc2.creative_energy.desc": "Provides unlimited energy to adjacent blocks. Intended for testing.", "block.oc2.projector": "Projector", "block.oc2.projector.desc": "Projects images onto surfaces in front of it.", + "block.oc2.keyboard": "Keyboard", + "block.oc2.keyboard.desc": "Allows keyboard input when using a projector.", "item.oc2.wrench": "Scrench", "item.oc2.wrench.desc": "Configures devices and dismantles them (while sneaking).", @@ -98,6 +100,8 @@ "gui.oc2.network_interface_card.connectivity.disabled": "Disabled", "gui.oc2.network_interface_card.info": "Drag to rotate. Click faces to toggle connectivity.", + "gui.oc2.keyboard.close_info": "Right click or press [Escape] twice to close.", + "gui.oc2.network_tunnel.link": "Link", "manual.oc2.home": "Home",