From e909b90457203111158879ae7836613eaaca6793 Mon Sep 17 00:00:00 2001 From: Andrew Lalis Date: Thu, 28 Jul 2022 11:02:09 +0200 Subject: [PATCH] Added chat, improved player input responses, and rebalanced weapons. --- .gitignore | 6 ++ README.md | 47 ++++--------- .../java/nl/andrewl/aos2_client/Client.java | 20 +++--- .../aos2_client/control/InputHandler.java | 66 ++++++++++++++++++ .../control/PlayerCharacterInputCallback.java | 18 +++++ .../control/PlayerInputKeyCallback.java | 21 +++++- .../PlayerInputMouseScrollCallback.java | 4 +- .../control/PlayerViewCursorCallback.java | 6 +- .../aos2_client/render/GameRenderer.java | 67 +++++++++---------- .../aos2_client/render/gui/GuiRenderer.java | 25 ++++--- .../main/java/nl/andrewl/aos_core/Net.java | 1 + .../andrewl/aos_core/model/item/gun/Ak47.java | 2 +- .../aos_core/model/item/gun/Rifle.java | 6 +- .../aos_core/model/item/gun/Winchester.java | 4 +- core/src/main/resources/log4j2.properties | 2 +- .../ClientCommunicationHandler.java | 35 ++++++++-- .../logic/PlayerActionManager.java | 10 +++ .../aos2_server/logic/PlayerImpulses.java | 5 ++ 18 files changed, 241 insertions(+), 104 deletions(-) create mode 100644 client/src/main/java/nl/andrewl/aos2_client/control/PlayerCharacterInputCallback.java diff --git a/.gitignore b/.gitignore index b607676..0c2440e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,13 @@ .idea/ target/ client-builds/ +client.yaml +server.yaml # Ignore the ./config directory so that developers can put their config files # there for server and client apps. config + +# Ignore compiled tool executables +build-clients +setversion diff --git a/README.md b/README.md index 987b627..45516a2 100644 --- a/README.md +++ b/README.md @@ -8,45 +8,22 @@ _Read this guide to get started and join a server in just a minute or two!_ 1. Make sure you've got at least Java 17 installed. You can get it [here](https://adoptium.net/temurin/releases). 2. Download the `aos2-client` JAR file from the [releases page](https://github.com/andrewlalis/ace-of-shades-2/releases) that's compatible with your system. -3. Run the game by double-clicking the JAR file, or entering `java -jar ` in a terminal. This should generate a `config.yaml` file. +3. Run the game by double-clicking the JAR file, or entering `java -jar ` in a terminal. This should generate a `client.yaml` file. 4. Set the `serverHost`, `serverPort`, and `username` properties accordingly for the server you want to join. 5. Run the game again to join the server and start playing! +### Controls +- `WASD` to move, `SPACE` to jump, `LEFT-CONTROL` to crouch. +- `LEFT-CLICK` to use your held item (shoot, destroy blocks, etc.). +- `RIGHT-CLICK` to interact with things using your held item (place blocks, etc.). +- `R` to reload. +- `T` to chat. Press `ENTER` to send the chat. +- `ESCAPE` to exit the game. +- Numbers are used for selecting different items. +- `F3` toggles showing debug info. + ## Setting up a Server -Setting up a server is quite easy. Just go to the [releases page](https://github.com/andrewlalis/ace-of-shades-2/releases) and download the latest `aos2-server` JAR file. Similar to the client, it's best if you provide a `config.yaml` file to the server, in the same directory. The following snippet shows the structure and default values of a server's configuration. -```yaml -port: 25565 -connectionBacklog: 5 -ticksPerSecond: 20.0 -world: worlds.redfort -teams: - - name: Red - color: [0.8, 0, 0] - spawnPoint: A - - name: Blue - color: [0, 0, 0.8] - spawnPoint: B -physics: - gravity: 29.43 - walkingSpeed: 4 - crouchingSpeed: 1.5 - sprintingSpeed: 9 - movementAcceleration: 2 - movementDeceleration: 1 - jumpVerticalSpeed: 8 -actions: - blockBreakCooldown: 0.25 - blockPlaceCooldown: 0.1 - blockBreakReach: 5 - blockPlaceReach: 5 - blockBulletDamageResistance: 3 - blockBulletDamageCooldown: 10 - resupplyCooldown: 30 - resupplyRadius: 3 - teamSpawnProtection: 10 - movementAccuracyDecreaseFactor: 0.01 - friendlyFire: false -``` +Setting up a server is quite easy. Just go to the [releases page](https://github.com/andrewlalis/ace-of-shades-2/releases) and download the latest `aos2-server` JAR file, and run it. It'll create a `server.yaml` configuration file if you don't provide one. ## Configuration Both the client and server use a similar style of YAML-based configuration, where upon booting up, the program will look for a configuration file in the current working directory with one of the following names: `configuration`, `config`, `cfg`, ending in either `.yaml` or `.yml`. Alternatively, you can provide the path to a configuration file at a different location via a single command-line argument. For example: diff --git a/client/src/main/java/nl/andrewl/aos2_client/Client.java b/client/src/main/java/nl/andrewl/aos2_client/Client.java index b227eca..a756b2d 100644 --- a/client/src/main/java/nl/andrewl/aos2_client/Client.java +++ b/client/src/main/java/nl/andrewl/aos2_client/Client.java @@ -83,14 +83,7 @@ public class Client implements Runnable { return; } - gameRenderer = new GameRenderer(this); - gameRenderer.setupWindow( - inputHandler, - new PlayerViewCursorCallback(config.input, this, gameRenderer.getCamera(), communicationHandler), - new PlayerInputKeyCallback(inputHandler), - new PlayerInputMouseClickCallback(inputHandler), - new PlayerInputMouseScrollCallback(this, communicationHandler) - ); + gameRenderer = new GameRenderer(this, inputHandler); soundManager = new SoundManager(); log.debug("Sound system initialized."); @@ -207,6 +200,13 @@ public class Client implements Runnable { if (soundManager != null) { soundManager.play("chat", 1, myPlayer.getEyePosition(), myPlayer.getVelocity()); } + } else if (msg instanceof ClientOrientationUpdateMessage orientationUpdateMessage) { + runLater(() -> { + myPlayer.setOrientation(orientationUpdateMessage.x(), orientationUpdateMessage.y()); + if (gameRenderer != null) { + gameRenderer.getCamera().setOrientationToPlayer(myPlayer); + } + }); } } @@ -222,6 +222,10 @@ public class Client implements Runnable { return inputHandler; } + public CommunicationHandler getCommunicationHandler() { + return communicationHandler; + } + public Map getTeams() { return teams; } diff --git a/client/src/main/java/nl/andrewl/aos2_client/control/InputHandler.java b/client/src/main/java/nl/andrewl/aos2_client/control/InputHandler.java index b136c86..97758b2 100644 --- a/client/src/main/java/nl/andrewl/aos2_client/control/InputHandler.java +++ b/client/src/main/java/nl/andrewl/aos2_client/control/InputHandler.java @@ -6,6 +6,7 @@ import nl.andrewl.aos2_client.model.ClientPlayer; import nl.andrewl.aos_core.model.item.BlockItemStack; import nl.andrewl.aos_core.model.world.Hit; import nl.andrewl.aos_core.net.client.BlockColorMessage; +import nl.andrewl.aos_core.net.client.ChatWrittenMessage; import nl.andrewl.aos_core.net.client.ClientInputState; import static org.lwjgl.glfw.GLFW.*; @@ -35,6 +36,9 @@ public class InputHandler { private boolean debugEnabled; + private boolean chatting; + private StringBuffer chatText = new StringBuffer(); + public InputHandler(Client client, CommunicationHandler comm) { this.client = client; @@ -66,6 +70,7 @@ public class InputHandler { } public void setForward(boolean forward) { + if (chatting) return; this.forward = forward; updateInputState(); } @@ -75,6 +80,7 @@ public class InputHandler { } public void setBackward(boolean backward) { + if (chatting) return; this.backward = backward; updateInputState(); } @@ -84,6 +90,7 @@ public class InputHandler { } public void setLeft(boolean left) { + if (chatting) return; this.left = left; updateInputState(); } @@ -93,6 +100,7 @@ public class InputHandler { } public void setRight(boolean right) { + if (chatting) return; this.right = right; updateInputState(); } @@ -102,6 +110,7 @@ public class InputHandler { } public void setJumping(boolean jumping) { + if (chatting) return; this.jumping = jumping; updateInputState(); } @@ -111,6 +120,7 @@ public class InputHandler { } public void setCrouching(boolean crouching) { + if (chatting) return; this.crouching = crouching; updateInputState(); } @@ -120,6 +130,7 @@ public class InputHandler { } public void setSprinting(boolean sprinting) { + if (chatting) return; this.sprinting = sprinting; updateInputState(); } @@ -129,6 +140,7 @@ public class InputHandler { } public void setHitting(boolean hitting) { + if (chatting) return; this.hitting = hitting; updateInputState(); } @@ -138,6 +150,7 @@ public class InputHandler { } public void setInteracting(boolean interacting) { + if (chatting) return; this.interacting = interacting; updateInputState(); } @@ -147,6 +160,7 @@ public class InputHandler { } public void setReloading(boolean reloading) { + if (chatting) return; this.reloading = reloading; updateInputState(); } @@ -156,6 +170,7 @@ public class InputHandler { } public void setSelectedInventoryIndex(int selectedInventoryIndex) { + if (chatting) return; this.selectedInventoryIndex = selectedInventoryIndex; updateInputState(); } @@ -168,6 +183,57 @@ public class InputHandler { this.debugEnabled = !debugEnabled; } + public void enableChatting() { + if (chatting) return; + setForward(false); + setBackward(false); + setLeft(false); + setRight(false); + setJumping(false); + setCrouching(false); + setSprinting(false); + setReloading(false); + chatting = true; + chatText = new StringBuffer(); + } + + public boolean isChatting() { + return chatting; + } + + public void cancelChatting() { + chatting = false; + chatText.delete(0, chatText.length()); + } + + public void appendToChat(int codePoint) { + if (!chatting || chatText.length() + 1 > 120) return; + chatText.appendCodePoint(codePoint); + } + + public void appendToChat(String s) { + if (!chatting || chatText.length() + s.length() > 120) return; + chatText.append(s); + } + + public void deleteFromChat() { + if (!chatting || chatText.length() == 0) return; + chatText.deleteCharAt(chatText.length() - 1); + } + + public String getChatText() { + return chatText.toString(); + } + + public void sendChat() { + if (!chatting) return; + String text = chatText.toString().trim(); + cancelChatting(); + if (!text.isBlank()) { + client.getCommunicationHandler().sendMessage(new ChatWrittenMessage(text)); + } + } + public void pickBlock() { var player = client.getMyPlayer(); if (player.getInventory().getSelectedItemStack() instanceof BlockItemStack stack) { diff --git a/client/src/main/java/nl/andrewl/aos2_client/control/PlayerCharacterInputCallback.java b/client/src/main/java/nl/andrewl/aos2_client/control/PlayerCharacterInputCallback.java new file mode 100644 index 0000000..e0ff8ec --- /dev/null +++ b/client/src/main/java/nl/andrewl/aos2_client/control/PlayerCharacterInputCallback.java @@ -0,0 +1,18 @@ +package nl.andrewl.aos2_client.control; + +import org.lwjgl.glfw.GLFWCharCallbackI; + +public class PlayerCharacterInputCallback implements GLFWCharCallbackI { + private final InputHandler inputHandler; + + public PlayerCharacterInputCallback(InputHandler inputHandler) { + this.inputHandler = inputHandler; + } + + @Override + public void invoke(long window, int codepoint) { + if (inputHandler.isChatting()) { + inputHandler.appendToChat(codepoint); + } + } +} diff --git a/client/src/main/java/nl/andrewl/aos2_client/control/PlayerInputKeyCallback.java b/client/src/main/java/nl/andrewl/aos2_client/control/PlayerInputKeyCallback.java index ed4ddda..adccdbf 100644 --- a/client/src/main/java/nl/andrewl/aos2_client/control/PlayerInputKeyCallback.java +++ b/client/src/main/java/nl/andrewl/aos2_client/control/PlayerInputKeyCallback.java @@ -24,12 +24,23 @@ public class PlayerInputKeyCallback implements GLFWKeyCallbackI { case GLFW_KEY_LEFT_SHIFT -> inputHandler.setSprinting(true); case GLFW_KEY_R -> inputHandler.setReloading(true); + case GLFW_KEY_BACKSPACE -> inputHandler.deleteFromChat(); + case GLFW_KEY_ENTER -> inputHandler.sendChat(); + case GLFW_KEY_1 -> inputHandler.setSelectedInventoryIndex(0); case GLFW_KEY_2 -> inputHandler.setSelectedInventoryIndex(1); case GLFW_KEY_3 -> inputHandler.setSelectedInventoryIndex(2); case GLFW_KEY_4 -> inputHandler.setSelectedInventoryIndex(3); case GLFW_KEY_F3 -> inputHandler.toggleDebugEnabled(); + + case GLFW_KEY_ESCAPE -> { + if (inputHandler.isChatting()) { + inputHandler.cancelChatting(); + } else { + glfwSetWindowShouldClose(window, true); + } + } } } else if (action == GLFW_RELEASE) { switch (key) { @@ -42,7 +53,15 @@ public class PlayerInputKeyCallback implements GLFWKeyCallbackI { case GLFW_KEY_LEFT_SHIFT -> inputHandler.setSprinting(false); case GLFW_KEY_R -> inputHandler.setReloading(false); - case GLFW_KEY_ESCAPE -> glfwSetWindowShouldClose(window, true); + case GLFW_KEY_T -> inputHandler.enableChatting(); + case GLFW_KEY_SLASH -> { + inputHandler.enableChatting(); + inputHandler.appendToChat("/"); + } + } + } else if (action == GLFW_REPEAT) { + switch (key) { + case GLFW_KEY_BACKSPACE -> inputHandler.deleteFromChat(); } } } diff --git a/client/src/main/java/nl/andrewl/aos2_client/control/PlayerInputMouseScrollCallback.java b/client/src/main/java/nl/andrewl/aos2_client/control/PlayerInputMouseScrollCallback.java index 4bd6cd0..5021391 100644 --- a/client/src/main/java/nl/andrewl/aos2_client/control/PlayerInputMouseScrollCallback.java +++ b/client/src/main/java/nl/andrewl/aos2_client/control/PlayerInputMouseScrollCallback.java @@ -10,9 +10,9 @@ public class PlayerInputMouseScrollCallback implements GLFWScrollCallbackI { private final Client client; private final CommunicationHandler comm; - public PlayerInputMouseScrollCallback(Client client, CommunicationHandler comm) { + public PlayerInputMouseScrollCallback(Client client) { this.client = client; - this.comm = comm; + this.comm = client.getCommunicationHandler(); } @Override diff --git a/client/src/main/java/nl/andrewl/aos2_client/control/PlayerViewCursorCallback.java b/client/src/main/java/nl/andrewl/aos2_client/control/PlayerViewCursorCallback.java index 67dedf3..e18e833 100644 --- a/client/src/main/java/nl/andrewl/aos2_client/control/PlayerViewCursorCallback.java +++ b/client/src/main/java/nl/andrewl/aos2_client/control/PlayerViewCursorCallback.java @@ -30,11 +30,11 @@ public class PlayerViewCursorCallback implements GLFWCursorPosCallbackI { private float lastMouseCursorY; private long lastOrientationUpdateSentAt = 0L; - public PlayerViewCursorCallback(ClientConfig.InputConfig config, Client client, Camera cam, CommunicationHandler comm) { - this.config = config; + public PlayerViewCursorCallback(Client client, Camera cam) { + this.config = client.getConfig().input; this.client = client; this.camera = cam; - this.comm = comm; + this.comm = client.getCommunicationHandler(); } @Override diff --git a/client/src/main/java/nl/andrewl/aos2_client/render/GameRenderer.java b/client/src/main/java/nl/andrewl/aos2_client/render/GameRenderer.java index 6fe81c3..7477c1c 100644 --- a/client/src/main/java/nl/andrewl/aos2_client/render/GameRenderer.java +++ b/client/src/main/java/nl/andrewl/aos2_client/render/GameRenderer.java @@ -3,7 +3,7 @@ package nl.andrewl.aos2_client.render; import nl.andrewl.aos2_client.Camera; import nl.andrewl.aos2_client.Client; import nl.andrewl.aos2_client.config.ClientConfig; -import nl.andrewl.aos2_client.control.InputHandler; +import nl.andrewl.aos2_client.control.*; import nl.andrewl.aos2_client.model.ClientPlayer; import nl.andrewl.aos2_client.render.chunk.ChunkRenderer; import nl.andrewl.aos2_client.render.gui.GuiRenderer; @@ -36,42 +36,36 @@ public class GameRenderer { private static final float Z_FAR = 500f; private final ClientConfig.DisplayConfig config; - private ChunkRenderer chunkRenderer; - private GuiRenderer guiRenderer; - private ModelRenderer modelRenderer; + private final ChunkRenderer chunkRenderer; + private final GuiRenderer guiRenderer; + private final ModelRenderer modelRenderer; private final Camera camera; private final Client client; // Standard models for various game components. - private Model playerModel; - private Model rifleModel; - private Model blockModel; - private Model bulletModel; - private Model smgModel; - private Model shotgunModel; - private Model flagModel; + private final Model playerModel; + private final Model rifleModel; + private final Model blockModel; + private final Model bulletModel; + private final Model smgModel; + private final Model shotgunModel; + private final Model flagModel; - private long windowHandle; - private int screenWidth = 800; - private int screenHeight = 600; + private final long windowHandle; + private final int screenWidth; + private final int screenHeight; private final Matrix4f perspectiveTransform; - public GameRenderer(Client client) { + public GameRenderer(Client client, InputHandler inputHandler) { this.config = client.getConfig().display; this.client = client; this.camera = new Camera(); camera.setToPlayer(client.getMyPlayer()); this.perspectiveTransform = new Matrix4f(); - } - public void setupWindow( - InputHandler inputHandler, - GLFWCursorPosCallbackI viewCursorCallback, - GLFWKeyCallbackI inputKeyCallback, - GLFWMouseButtonCallbackI mouseButtonCallback, - GLFWScrollCallbackI scrollCallback - ) { + // Initialize window! + GLFWErrorCallback.createPrint(System.err).set(); if (!glfwInit()) throw new IllegalStateException("Could not initialize GLFW."); glfwDefaultWindowHints(); @@ -96,10 +90,11 @@ public class GameRenderer { log.debug("Initialized GLFW window."); // Setup callbacks. - glfwSetKeyCallback(windowHandle, inputKeyCallback); - glfwSetCursorPosCallback(windowHandle, viewCursorCallback); - glfwSetMouseButtonCallback(windowHandle, mouseButtonCallback); - glfwSetScrollCallback(windowHandle, scrollCallback); + glfwSetKeyCallback(windowHandle, new PlayerInputKeyCallback(inputHandler)); + glfwSetCursorPosCallback(windowHandle, new PlayerViewCursorCallback(client, camera)); + glfwSetMouseButtonCallback(windowHandle, new PlayerInputMouseClickCallback(inputHandler)); + glfwSetScrollCallback(windowHandle, new PlayerInputMouseScrollCallback(client)); + glfwSetCharCallback(windowHandle, new PlayerCharacterInputCallback(inputHandler)); if (config.captureCursor) { glfwSetInputMode(windowHandle, GLFW_CURSOR, GLFW_CURSOR_DISABLED); } @@ -282,15 +277,15 @@ public class GameRenderer { } public void freeWindow() { - if (rifleModel != null) rifleModel.free(); - if (smgModel != null) smgModel.free(); - if (flagModel != null) flagModel.free(); - if (bulletModel != null) bulletModel.free(); - if (playerModel != null) playerModel.free(); - if (blockModel != null) blockModel.free(); - if (modelRenderer != null) modelRenderer.free(); - if (guiRenderer != null) guiRenderer.free(); - if (chunkRenderer != null) chunkRenderer.free(); + rifleModel.free(); + smgModel.free(); + flagModel.free(); + bulletModel.free(); + playerModel.free(); + blockModel.free(); + modelRenderer.free(); + guiRenderer.free(); + chunkRenderer.free(); GL.destroy(); Callbacks.glfwFreeCallbacks(windowHandle); glfwSetErrorCallback(null); diff --git a/client/src/main/java/nl/andrewl/aos2_client/render/gui/GuiRenderer.java b/client/src/main/java/nl/andrewl/aos2_client/render/gui/GuiRenderer.java index db4bc4a..a9b1811 100644 --- a/client/src/main/java/nl/andrewl/aos2_client/render/gui/GuiRenderer.java +++ b/client/src/main/java/nl/andrewl/aos2_client/render/gui/GuiRenderer.java @@ -223,7 +223,7 @@ public class GuiRenderer { nvgSave(vgId); drawCrosshair(width, height); - drawChat(width, height, client.getChat()); + drawChat(width, height, client); drawHealthBar(width, height, client.getMyPlayer()); drawHeldItemStackInfo(width, height, client.getMyPlayer()); if (client.getInputHandler().isDebugEnabled()) { @@ -269,18 +269,18 @@ public class GuiRenderer { private void drawHealthBar(float w, float h, ClientPlayer player) { nvgFillColor(vgId, GuiUtils.rgba(1, 0, 0, 1, colorA)); nvgBeginPath(vgId); - nvgRect(vgId, 20, h - 60, 100, 20); + nvgRect(vgId, w - 170, h - 110, 100, 20); nvgFill(vgId); nvgFillColor(vgId, GuiUtils.rgba(0, 1, 0, 1, colorA)); nvgBeginPath(vgId); - nvgRect(vgId, 20, h - 60, 100 * player.getHealth(), 20); + nvgRect(vgId, w - 170, h - 110, 100 * player.getHealth(), 20); nvgFill(vgId); nvgFillColor(vgId, GuiUtils.rgba(1, 1, 1, 1, colorA)); nvgFontSize(vgId, 12f); nvgFontFaceId(vgId, jetbrainsMonoFont); nvgTextAlign(vgId, NVG_ALIGN_LEFT | NVG_ALIGN_TOP); - nvgText(vgId, 20, h - 30, String.format("%.2f / 1.00 HP", player.getHealth())); + nvgText(vgId, w - 170, h - 80, String.format("%.2f / 1.00 HP", player.getHealth())); } private void drawHeldItemStackInfo(float w, float h, ClientPlayer player) { @@ -324,19 +324,20 @@ public class GuiRenderer { nvgText(vgId, w - 140, h - 14, String.format("Selected value: %d", stack.getSelectedValue())); } - private void drawChat(float w, float h, Chat chat) { + private void drawChat(float w, float h, Client client) { float chatWidth = w / 3; float chatHeight = h / 4; nvgFillColor(vgId, GuiUtils.rgba(0, 0, 0, 0.25f, colorA)); nvgBeginPath(vgId); - nvgRect(vgId, 0, 0, chatWidth, chatHeight); + nvgRect(vgId, 0, h - chatHeight - 16, chatWidth, chatHeight); nvgFill(vgId); + var chat = client.getChat(); nvgFontSize(vgId, 12f); nvgFontFaceId(vgId, jetbrainsMonoFont); nvgTextAlign(vgId, NVG_ALIGN_LEFT | NVG_ALIGN_TOP); - float y = chatHeight - 12; + float y = h - 16 - 12; for (var msg : chat.getMessages()) { if (msg.author().equals("_ANNOUNCE")) { nvgFillColor(vgId, GuiUtils.rgba(0.7f, 0, 0, 1, colorA)); @@ -348,9 +349,17 @@ public class GuiRenderer { nvgFillColor(vgId, GuiUtils.rgba(1, 1, 1, 1, colorA)); nvgText(vgId, 5, y, msg.author() + ": " + msg.message()); } - y -= 16; } + var input = client.getInputHandler(); + if (input.isChatting()) { + nvgFillColor(vgId, GuiUtils.rgba(0, 0, 0, 0.5f, colorA)); + nvgBeginPath(vgId); + nvgRect(vgId, 0, h - 16, w, 16); + nvgFill(vgId); + nvgFillColor(vgId, GuiUtils.rgba(1, 1, 1, 1, colorA)); + nvgText(vgId, 5, h - 14, "> " + input.getChatText() + "_"); + } } private void drawDebugInfo(float w, float h, Client client) { diff --git a/core/src/main/java/nl/andrewl/aos_core/Net.java b/core/src/main/java/nl/andrewl/aos_core/Net.java index 1be6b3c..2a9f836 100644 --- a/core/src/main/java/nl/andrewl/aos_core/Net.java +++ b/core/src/main/java/nl/andrewl/aos_core/Net.java @@ -48,6 +48,7 @@ public final class Net { serializer.registerType(19, BlockColorMessage.class); serializer.registerType(20, ChatMessage.class); serializer.registerType(21, ChatWrittenMessage.class); + serializer.registerType(22, ClientOrientationUpdateMessage.class); } public static ExtendedDataInputStream getInputStream(InputStream in) { diff --git a/core/src/main/java/nl/andrewl/aos_core/model/item/gun/Ak47.java b/core/src/main/java/nl/andrewl/aos_core/model/item/gun/Ak47.java index 0fdd2ed..bcf96ff 100644 --- a/core/src/main/java/nl/andrewl/aos_core/model/item/gun/Ak47.java +++ b/core/src/main/java/nl/andrewl/aos_core/model/item/gun/Ak47.java @@ -14,7 +14,7 @@ public class Ak47 extends Gun { 0.1f, 1.2f, 0.4f, - 30f, + 0.1f, true ); } diff --git a/core/src/main/java/nl/andrewl/aos_core/model/item/gun/Rifle.java b/core/src/main/java/nl/andrewl/aos_core/model/item/gun/Rifle.java index a5ed149..aaf10d1 100644 --- a/core/src/main/java/nl/andrewl/aos_core/model/item/gun/Rifle.java +++ b/core/src/main/java/nl/andrewl/aos_core/model/item/gun/Rifle.java @@ -7,14 +7,14 @@ public class Rifle extends Gun { super( id, "Rifle", - 5, + 6, 8, 1, - 0.97f, + 0.98f, 0.8f, 2.5f, 0.8f, - 50f, + 0.2f, false ); } diff --git a/core/src/main/java/nl/andrewl/aos_core/model/item/gun/Winchester.java b/core/src/main/java/nl/andrewl/aos_core/model/item/gun/Winchester.java index 7ea306b..240ffb8 100644 --- a/core/src/main/java/nl/andrewl/aos_core/model/item/gun/Winchester.java +++ b/core/src/main/java/nl/andrewl/aos_core/model/item/gun/Winchester.java @@ -7,14 +7,14 @@ public class Winchester extends Gun { super( id, "Winchester", - 10, 6, 4, + 4, 0.85f, 0.75f, 2.5f, 0.3f, - 60f, + 0.33f, false ); } diff --git a/core/src/main/resources/log4j2.properties b/core/src/main/resources/log4j2.properties index 0f8dfbe..5799639 100644 --- a/core/src/main/resources/log4j2.properties +++ b/core/src/main/resources/log4j2.properties @@ -4,6 +4,6 @@ appender.console.name = STDOUT appender.console.layout.type = PatternLayout appender.console.layout.pattern = [%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c{1} - %msg%n -rootLogger.level = debug +rootLogger.level = info rootLogger.appenderRefs = stdout rootLogger.appenderRef.stdout.ref = STDOUT \ No newline at end of file diff --git a/server/src/main/java/nl/andrewl/aos2_server/ClientCommunicationHandler.java b/server/src/main/java/nl/andrewl/aos2_server/ClientCommunicationHandler.java index c5bc0ef..f554afa 100644 --- a/server/src/main/java/nl/andrewl/aos2_server/ClientCommunicationHandler.java +++ b/server/src/main/java/nl/andrewl/aos2_server/ClientCommunicationHandler.java @@ -7,6 +7,8 @@ import nl.andrewl.aos_core.model.item.ItemStack; import nl.andrewl.aos_core.model.world.Chunk; import nl.andrewl.aos_core.model.world.WorldIO; import nl.andrewl.aos_core.net.TcpReceiver; +import nl.andrewl.aos_core.net.client.ChatMessage; +import nl.andrewl.aos_core.net.client.ChatWrittenMessage; import nl.andrewl.aos_core.net.connect.ConnectAcceptMessage; import nl.andrewl.aos_core.net.connect.ConnectRejectMessage; import nl.andrewl.aos_core.net.connect.ConnectRequestMessage; @@ -77,6 +79,25 @@ public class ClientCommunicationHandler { if (chunk != null && hashMessage.hash() != chunk.blockHash()) { sendTcpMessage(new ChunkDataMessage(chunk)); } + } else if (msg instanceof ChatWrittenMessage chatWrittenMessage) { + if (chatWrittenMessage.message().startsWith("/t ")) { + if (player.getTeam() != null) { + var chat = new ChatMessage( + System.currentTimeMillis(), + player.getUsername(), + chatWrittenMessage.message().substring(3) + ); + for (var teamPlayer : server.getTeamManager().getPlayers(player.getTeam())) { + server.getPlayerManager().getHandler(teamPlayer).sendTcpMessage(chat); + } + } + } else { + server.getPlayerManager().broadcastTcpMessage(new ChatMessage( + System.currentTimeMillis(), + player.getUsername(), + chatWrittenMessage.message() + )); + } } } @@ -110,6 +131,10 @@ public class ClientCommunicationHandler { log.debug("Sent connect accept message."); sendInitialData(); log.debug("Sent initial data."); + sendTcpMessage(ChatMessage.privateMessage("Welcome to the server, " + player.getUsername() + ".")); + if (player.getTeam() != null) { + sendTcpMessage(ChatMessage.privateMessage("You've joined the " + player.getTeam().getName() + " team.")); + } // Initiate a TCP receiver thread to accept incoming messages from the client. TcpReceiver tcpReceiver = new TcpReceiver(in, this::handleTcpMessage) .withShutdownHook(() -> server.getPlayerManager().deregister(this.player)); @@ -135,10 +160,12 @@ public class ClientCommunicationHandler { public void sendTcpMessage(Message msg) { ForkJoinPool.commonPool().submit(() -> { - try { - Net.write(msg, out); - } catch (IOException e) { - e.printStackTrace(); + synchronized (out) { + try { + Net.write(msg, out); + } catch (IOException e) { + e.printStackTrace(); + } } }); } diff --git a/server/src/main/java/nl/andrewl/aos2_server/logic/PlayerActionManager.java b/server/src/main/java/nl/andrewl/aos2_server/logic/PlayerActionManager.java index 065dc5d..8dbcb3f 100644 --- a/server/src/main/java/nl/andrewl/aos2_server/logic/PlayerActionManager.java +++ b/server/src/main/java/nl/andrewl/aos2_server/logic/PlayerActionManager.java @@ -111,6 +111,16 @@ public class PlayerActionManager { gunNeedsReCock = true; } server.getPlayerManager().getHandler(player.getId()).sendDatagramPacket(new ItemStackMessage(player.getInventory())); + // Apply recoil! + float recoilFactor = 10f; // Maximum number of degrees to recoil. + float recoil = recoilFactor * gun.getRecoil() + (float) ThreadLocalRandom.current().nextGaussian(0, 0.01); + player.getOrientation().y += Math.toRadians(recoil); + server.getPlayerManager().getHandler(player.getId()).sendDatagramPacket(new ClientOrientationUpdateMessage( + player.getOrientation().x(), + player.getOrientation().y() + )); + server.getPlayerManager().broadcastUdpMessageToAllBut(player.getUpdateMessage(now), player); + // Play sound! String shotSound = null; if (gun instanceof Rifle) { shotSound = "shot_m1-garand_1"; diff --git a/server/src/main/java/nl/andrewl/aos2_server/logic/PlayerImpulses.java b/server/src/main/java/nl/andrewl/aos2_server/logic/PlayerImpulses.java index 5247c75..772d0ef 100644 --- a/server/src/main/java/nl/andrewl/aos2_server/logic/PlayerImpulses.java +++ b/server/src/main/java/nl/andrewl/aos2_server/logic/PlayerImpulses.java @@ -2,6 +2,11 @@ package nl.andrewl.aos2_server.logic; import nl.andrewl.aos_core.net.client.ClientInputState; +/** + * Component that keeps track of any impulses the player has made since the + * last tick. This way, if a player activates an input for even a fraction of + * a tick, it'll register. + */ public class PlayerImpulses { public boolean forward; public boolean backward;