Added chat, improved player input responses, and rebalanced weapons.

This commit is contained in:
Andrew Lalis 2022-07-28 11:02:09 +02:00
parent bf0982fbd9
commit e909b90457
18 changed files with 241 additions and 104 deletions

6
.gitignore vendored
View File

@ -1,7 +1,13 @@
.idea/ .idea/
target/ target/
client-builds/ client-builds/
client.yaml
server.yaml
# Ignore the ./config directory so that developers can put their config files # Ignore the ./config directory so that developers can put their config files
# there for server and client apps. # there for server and client apps.
config config
# Ignore compiled tool executables
build-clients
setversion

View File

@ -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). 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. 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 <jarfile>` in a terminal. This should generate a `config.yaml` file. 3. Run the game by double-clicking the JAR file, or entering `java -jar <jarfile>` 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. 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! 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
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. 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.
```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
```
## Configuration ## 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: 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:

View File

@ -83,14 +83,7 @@ public class Client implements Runnable {
return; return;
} }
gameRenderer = new GameRenderer(this); gameRenderer = new GameRenderer(this, inputHandler);
gameRenderer.setupWindow(
inputHandler,
new PlayerViewCursorCallback(config.input, this, gameRenderer.getCamera(), communicationHandler),
new PlayerInputKeyCallback(inputHandler),
new PlayerInputMouseClickCallback(inputHandler),
new PlayerInputMouseScrollCallback(this, communicationHandler)
);
soundManager = new SoundManager(); soundManager = new SoundManager();
log.debug("Sound system initialized."); log.debug("Sound system initialized.");
@ -207,6 +200,13 @@ public class Client implements Runnable {
if (soundManager != null) { if (soundManager != null) {
soundManager.play("chat", 1, myPlayer.getEyePosition(), myPlayer.getVelocity()); 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; return inputHandler;
} }
public CommunicationHandler getCommunicationHandler() {
return communicationHandler;
}
public Map<Integer, Team> getTeams() { public Map<Integer, Team> getTeams() {
return teams; return teams;
} }

View File

@ -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.item.BlockItemStack;
import nl.andrewl.aos_core.model.world.Hit; import nl.andrewl.aos_core.model.world.Hit;
import nl.andrewl.aos_core.net.client.BlockColorMessage; 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 nl.andrewl.aos_core.net.client.ClientInputState;
import static org.lwjgl.glfw.GLFW.*; import static org.lwjgl.glfw.GLFW.*;
@ -35,6 +36,9 @@ public class InputHandler {
private boolean debugEnabled; private boolean debugEnabled;
private boolean chatting;
private StringBuffer chatText = new StringBuffer();
public InputHandler(Client client, CommunicationHandler comm) { public InputHandler(Client client, CommunicationHandler comm) {
this.client = client; this.client = client;
@ -66,6 +70,7 @@ public class InputHandler {
} }
public void setForward(boolean forward) { public void setForward(boolean forward) {
if (chatting) return;
this.forward = forward; this.forward = forward;
updateInputState(); updateInputState();
} }
@ -75,6 +80,7 @@ public class InputHandler {
} }
public void setBackward(boolean backward) { public void setBackward(boolean backward) {
if (chatting) return;
this.backward = backward; this.backward = backward;
updateInputState(); updateInputState();
} }
@ -84,6 +90,7 @@ public class InputHandler {
} }
public void setLeft(boolean left) { public void setLeft(boolean left) {
if (chatting) return;
this.left = left; this.left = left;
updateInputState(); updateInputState();
} }
@ -93,6 +100,7 @@ public class InputHandler {
} }
public void setRight(boolean right) { public void setRight(boolean right) {
if (chatting) return;
this.right = right; this.right = right;
updateInputState(); updateInputState();
} }
@ -102,6 +110,7 @@ public class InputHandler {
} }
public void setJumping(boolean jumping) { public void setJumping(boolean jumping) {
if (chatting) return;
this.jumping = jumping; this.jumping = jumping;
updateInputState(); updateInputState();
} }
@ -111,6 +120,7 @@ public class InputHandler {
} }
public void setCrouching(boolean crouching) { public void setCrouching(boolean crouching) {
if (chatting) return;
this.crouching = crouching; this.crouching = crouching;
updateInputState(); updateInputState();
} }
@ -120,6 +130,7 @@ public class InputHandler {
} }
public void setSprinting(boolean sprinting) { public void setSprinting(boolean sprinting) {
if (chatting) return;
this.sprinting = sprinting; this.sprinting = sprinting;
updateInputState(); updateInputState();
} }
@ -129,6 +140,7 @@ public class InputHandler {
} }
public void setHitting(boolean hitting) { public void setHitting(boolean hitting) {
if (chatting) return;
this.hitting = hitting; this.hitting = hitting;
updateInputState(); updateInputState();
} }
@ -138,6 +150,7 @@ public class InputHandler {
} }
public void setInteracting(boolean interacting) { public void setInteracting(boolean interacting) {
if (chatting) return;
this.interacting = interacting; this.interacting = interacting;
updateInputState(); updateInputState();
} }
@ -147,6 +160,7 @@ public class InputHandler {
} }
public void setReloading(boolean reloading) { public void setReloading(boolean reloading) {
if (chatting) return;
this.reloading = reloading; this.reloading = reloading;
updateInputState(); updateInputState();
} }
@ -156,6 +170,7 @@ public class InputHandler {
} }
public void setSelectedInventoryIndex(int selectedInventoryIndex) { public void setSelectedInventoryIndex(int selectedInventoryIndex) {
if (chatting) return;
this.selectedInventoryIndex = selectedInventoryIndex; this.selectedInventoryIndex = selectedInventoryIndex;
updateInputState(); updateInputState();
} }
@ -168,6 +183,57 @@ public class InputHandler {
this.debugEnabled = !debugEnabled; 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() { public void pickBlock() {
var player = client.getMyPlayer(); var player = client.getMyPlayer();
if (player.getInventory().getSelectedItemStack() instanceof BlockItemStack stack) { if (player.getInventory().getSelectedItemStack() instanceof BlockItemStack stack) {

View File

@ -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);
}
}
}

View File

@ -24,12 +24,23 @@ public class PlayerInputKeyCallback implements GLFWKeyCallbackI {
case GLFW_KEY_LEFT_SHIFT -> inputHandler.setSprinting(true); case GLFW_KEY_LEFT_SHIFT -> inputHandler.setSprinting(true);
case GLFW_KEY_R -> inputHandler.setReloading(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_1 -> inputHandler.setSelectedInventoryIndex(0);
case GLFW_KEY_2 -> inputHandler.setSelectedInventoryIndex(1); case GLFW_KEY_2 -> inputHandler.setSelectedInventoryIndex(1);
case GLFW_KEY_3 -> inputHandler.setSelectedInventoryIndex(2); case GLFW_KEY_3 -> inputHandler.setSelectedInventoryIndex(2);
case GLFW_KEY_4 -> inputHandler.setSelectedInventoryIndex(3); case GLFW_KEY_4 -> inputHandler.setSelectedInventoryIndex(3);
case GLFW_KEY_F3 -> inputHandler.toggleDebugEnabled(); case GLFW_KEY_F3 -> inputHandler.toggleDebugEnabled();
case GLFW_KEY_ESCAPE -> {
if (inputHandler.isChatting()) {
inputHandler.cancelChatting();
} else {
glfwSetWindowShouldClose(window, true);
}
}
} }
} else if (action == GLFW_RELEASE) { } else if (action == GLFW_RELEASE) {
switch (key) { switch (key) {
@ -42,7 +53,15 @@ public class PlayerInputKeyCallback implements GLFWKeyCallbackI {
case GLFW_KEY_LEFT_SHIFT -> inputHandler.setSprinting(false); case GLFW_KEY_LEFT_SHIFT -> inputHandler.setSprinting(false);
case GLFW_KEY_R -> inputHandler.setReloading(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();
} }
} }
} }

View File

@ -10,9 +10,9 @@ public class PlayerInputMouseScrollCallback implements GLFWScrollCallbackI {
private final Client client; private final Client client;
private final CommunicationHandler comm; private final CommunicationHandler comm;
public PlayerInputMouseScrollCallback(Client client, CommunicationHandler comm) { public PlayerInputMouseScrollCallback(Client client) {
this.client = client; this.client = client;
this.comm = comm; this.comm = client.getCommunicationHandler();
} }
@Override @Override

View File

@ -30,11 +30,11 @@ public class PlayerViewCursorCallback implements GLFWCursorPosCallbackI {
private float lastMouseCursorY; private float lastMouseCursorY;
private long lastOrientationUpdateSentAt = 0L; private long lastOrientationUpdateSentAt = 0L;
public PlayerViewCursorCallback(ClientConfig.InputConfig config, Client client, Camera cam, CommunicationHandler comm) { public PlayerViewCursorCallback(Client client, Camera cam) {
this.config = config; this.config = client.getConfig().input;
this.client = client; this.client = client;
this.camera = cam; this.camera = cam;
this.comm = comm; this.comm = client.getCommunicationHandler();
} }
@Override @Override

View File

@ -3,7 +3,7 @@ package nl.andrewl.aos2_client.render;
import nl.andrewl.aos2_client.Camera; import nl.andrewl.aos2_client.Camera;
import nl.andrewl.aos2_client.Client; import nl.andrewl.aos2_client.Client;
import nl.andrewl.aos2_client.config.ClientConfig; 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.model.ClientPlayer;
import nl.andrewl.aos2_client.render.chunk.ChunkRenderer; import nl.andrewl.aos2_client.render.chunk.ChunkRenderer;
import nl.andrewl.aos2_client.render.gui.GuiRenderer; import nl.andrewl.aos2_client.render.gui.GuiRenderer;
@ -36,42 +36,36 @@ public class GameRenderer {
private static final float Z_FAR = 500f; private static final float Z_FAR = 500f;
private final ClientConfig.DisplayConfig config; private final ClientConfig.DisplayConfig config;
private ChunkRenderer chunkRenderer; private final ChunkRenderer chunkRenderer;
private GuiRenderer guiRenderer; private final GuiRenderer guiRenderer;
private ModelRenderer modelRenderer; private final ModelRenderer modelRenderer;
private final Camera camera; private final Camera camera;
private final Client client; private final Client client;
// Standard models for various game components. // Standard models for various game components.
private Model playerModel; private final Model playerModel;
private Model rifleModel; private final Model rifleModel;
private Model blockModel; private final Model blockModel;
private Model bulletModel; private final Model bulletModel;
private Model smgModel; private final Model smgModel;
private Model shotgunModel; private final Model shotgunModel;
private Model flagModel; private final Model flagModel;
private long windowHandle; private final long windowHandle;
private int screenWidth = 800; private final int screenWidth;
private int screenHeight = 600; private final int screenHeight;
private final Matrix4f perspectiveTransform; private final Matrix4f perspectiveTransform;
public GameRenderer(Client client) { public GameRenderer(Client client, InputHandler inputHandler) {
this.config = client.getConfig().display; this.config = client.getConfig().display;
this.client = client; this.client = client;
this.camera = new Camera(); this.camera = new Camera();
camera.setToPlayer(client.getMyPlayer()); camera.setToPlayer(client.getMyPlayer());
this.perspectiveTransform = new Matrix4f(); this.perspectiveTransform = new Matrix4f();
}
public void setupWindow( // Initialize window!
InputHandler inputHandler,
GLFWCursorPosCallbackI viewCursorCallback,
GLFWKeyCallbackI inputKeyCallback,
GLFWMouseButtonCallbackI mouseButtonCallback,
GLFWScrollCallbackI scrollCallback
) {
GLFWErrorCallback.createPrint(System.err).set(); GLFWErrorCallback.createPrint(System.err).set();
if (!glfwInit()) throw new IllegalStateException("Could not initialize GLFW."); if (!glfwInit()) throw new IllegalStateException("Could not initialize GLFW.");
glfwDefaultWindowHints(); glfwDefaultWindowHints();
@ -96,10 +90,11 @@ public class GameRenderer {
log.debug("Initialized GLFW window."); log.debug("Initialized GLFW window.");
// Setup callbacks. // Setup callbacks.
glfwSetKeyCallback(windowHandle, inputKeyCallback); glfwSetKeyCallback(windowHandle, new PlayerInputKeyCallback(inputHandler));
glfwSetCursorPosCallback(windowHandle, viewCursorCallback); glfwSetCursorPosCallback(windowHandle, new PlayerViewCursorCallback(client, camera));
glfwSetMouseButtonCallback(windowHandle, mouseButtonCallback); glfwSetMouseButtonCallback(windowHandle, new PlayerInputMouseClickCallback(inputHandler));
glfwSetScrollCallback(windowHandle, scrollCallback); glfwSetScrollCallback(windowHandle, new PlayerInputMouseScrollCallback(client));
glfwSetCharCallback(windowHandle, new PlayerCharacterInputCallback(inputHandler));
if (config.captureCursor) { if (config.captureCursor) {
glfwSetInputMode(windowHandle, GLFW_CURSOR, GLFW_CURSOR_DISABLED); glfwSetInputMode(windowHandle, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
} }
@ -282,15 +277,15 @@ public class GameRenderer {
} }
public void freeWindow() { public void freeWindow() {
if (rifleModel != null) rifleModel.free(); rifleModel.free();
if (smgModel != null) smgModel.free(); smgModel.free();
if (flagModel != null) flagModel.free(); flagModel.free();
if (bulletModel != null) bulletModel.free(); bulletModel.free();
if (playerModel != null) playerModel.free(); playerModel.free();
if (blockModel != null) blockModel.free(); blockModel.free();
if (modelRenderer != null) modelRenderer.free(); modelRenderer.free();
if (guiRenderer != null) guiRenderer.free(); guiRenderer.free();
if (chunkRenderer != null) chunkRenderer.free(); chunkRenderer.free();
GL.destroy(); GL.destroy();
Callbacks.glfwFreeCallbacks(windowHandle); Callbacks.glfwFreeCallbacks(windowHandle);
glfwSetErrorCallback(null); glfwSetErrorCallback(null);

View File

@ -223,7 +223,7 @@ public class GuiRenderer {
nvgSave(vgId); nvgSave(vgId);
drawCrosshair(width, height); drawCrosshair(width, height);
drawChat(width, height, client.getChat()); drawChat(width, height, client);
drawHealthBar(width, height, client.getMyPlayer()); drawHealthBar(width, height, client.getMyPlayer());
drawHeldItemStackInfo(width, height, client.getMyPlayer()); drawHeldItemStackInfo(width, height, client.getMyPlayer());
if (client.getInputHandler().isDebugEnabled()) { if (client.getInputHandler().isDebugEnabled()) {
@ -269,18 +269,18 @@ public class GuiRenderer {
private void drawHealthBar(float w, float h, ClientPlayer player) { private void drawHealthBar(float w, float h, ClientPlayer player) {
nvgFillColor(vgId, GuiUtils.rgba(1, 0, 0, 1, colorA)); nvgFillColor(vgId, GuiUtils.rgba(1, 0, 0, 1, colorA));
nvgBeginPath(vgId); nvgBeginPath(vgId);
nvgRect(vgId, 20, h - 60, 100, 20); nvgRect(vgId, w - 170, h - 110, 100, 20);
nvgFill(vgId); nvgFill(vgId);
nvgFillColor(vgId, GuiUtils.rgba(0, 1, 0, 1, colorA)); nvgFillColor(vgId, GuiUtils.rgba(0, 1, 0, 1, colorA));
nvgBeginPath(vgId); nvgBeginPath(vgId);
nvgRect(vgId, 20, h - 60, 100 * player.getHealth(), 20); nvgRect(vgId, w - 170, h - 110, 100 * player.getHealth(), 20);
nvgFill(vgId); nvgFill(vgId);
nvgFillColor(vgId, GuiUtils.rgba(1, 1, 1, 1, colorA)); nvgFillColor(vgId, GuiUtils.rgba(1, 1, 1, 1, colorA));
nvgFontSize(vgId, 12f); nvgFontSize(vgId, 12f);
nvgFontFaceId(vgId, jetbrainsMonoFont); nvgFontFaceId(vgId, jetbrainsMonoFont);
nvgTextAlign(vgId, NVG_ALIGN_LEFT | NVG_ALIGN_TOP); 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) { 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())); 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 chatWidth = w / 3;
float chatHeight = h / 4; float chatHeight = h / 4;
nvgFillColor(vgId, GuiUtils.rgba(0, 0, 0, 0.25f, colorA)); nvgFillColor(vgId, GuiUtils.rgba(0, 0, 0, 0.25f, colorA));
nvgBeginPath(vgId); nvgBeginPath(vgId);
nvgRect(vgId, 0, 0, chatWidth, chatHeight); nvgRect(vgId, 0, h - chatHeight - 16, chatWidth, chatHeight);
nvgFill(vgId); nvgFill(vgId);
var chat = client.getChat();
nvgFontSize(vgId, 12f); nvgFontSize(vgId, 12f);
nvgFontFaceId(vgId, jetbrainsMonoFont); nvgFontFaceId(vgId, jetbrainsMonoFont);
nvgTextAlign(vgId, NVG_ALIGN_LEFT | NVG_ALIGN_TOP); nvgTextAlign(vgId, NVG_ALIGN_LEFT | NVG_ALIGN_TOP);
float y = chatHeight - 12; float y = h - 16 - 12;
for (var msg : chat.getMessages()) { for (var msg : chat.getMessages()) {
if (msg.author().equals("_ANNOUNCE")) { if (msg.author().equals("_ANNOUNCE")) {
nvgFillColor(vgId, GuiUtils.rgba(0.7f, 0, 0, 1, colorA)); 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)); nvgFillColor(vgId, GuiUtils.rgba(1, 1, 1, 1, colorA));
nvgText(vgId, 5, y, msg.author() + ": " + msg.message()); nvgText(vgId, 5, y, msg.author() + ": " + msg.message());
} }
y -= 16; 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) { private void drawDebugInfo(float w, float h, Client client) {

View File

@ -48,6 +48,7 @@ public final class Net {
serializer.registerType(19, BlockColorMessage.class); serializer.registerType(19, BlockColorMessage.class);
serializer.registerType(20, ChatMessage.class); serializer.registerType(20, ChatMessage.class);
serializer.registerType(21, ChatWrittenMessage.class); serializer.registerType(21, ChatWrittenMessage.class);
serializer.registerType(22, ClientOrientationUpdateMessage.class);
} }
public static ExtendedDataInputStream getInputStream(InputStream in) { public static ExtendedDataInputStream getInputStream(InputStream in) {

View File

@ -14,7 +14,7 @@ public class Ak47 extends Gun {
0.1f, 0.1f,
1.2f, 1.2f,
0.4f, 0.4f,
30f, 0.1f,
true true
); );
} }

View File

@ -7,14 +7,14 @@ public class Rifle extends Gun {
super( super(
id, id,
"Rifle", "Rifle",
5, 6,
8, 8,
1, 1,
0.97f, 0.98f,
0.8f, 0.8f,
2.5f, 2.5f,
0.8f, 0.8f,
50f, 0.2f,
false false
); );
} }

View File

@ -7,14 +7,14 @@ public class Winchester extends Gun {
super( super(
id, id,
"Winchester", "Winchester",
10,
6, 6,
4, 4,
4,
0.85f, 0.85f,
0.75f, 0.75f,
2.5f, 2.5f,
0.3f, 0.3f,
60f, 0.33f,
false false
); );
} }

View File

@ -4,6 +4,6 @@ appender.console.name = STDOUT
appender.console.layout.type = PatternLayout appender.console.layout.type = PatternLayout
appender.console.layout.pattern = [%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c{1} - %msg%n 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.appenderRefs = stdout
rootLogger.appenderRef.stdout.ref = STDOUT rootLogger.appenderRef.stdout.ref = STDOUT

View File

@ -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.Chunk;
import nl.andrewl.aos_core.model.world.WorldIO; import nl.andrewl.aos_core.model.world.WorldIO;
import nl.andrewl.aos_core.net.TcpReceiver; 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.ConnectAcceptMessage;
import nl.andrewl.aos_core.net.connect.ConnectRejectMessage; import nl.andrewl.aos_core.net.connect.ConnectRejectMessage;
import nl.andrewl.aos_core.net.connect.ConnectRequestMessage; import nl.andrewl.aos_core.net.connect.ConnectRequestMessage;
@ -77,6 +79,25 @@ public class ClientCommunicationHandler {
if (chunk != null && hashMessage.hash() != chunk.blockHash()) { if (chunk != null && hashMessage.hash() != chunk.blockHash()) {
sendTcpMessage(new ChunkDataMessage(chunk)); 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."); log.debug("Sent connect accept message.");
sendInitialData(); sendInitialData();
log.debug("Sent initial data."); 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. // Initiate a TCP receiver thread to accept incoming messages from the client.
TcpReceiver tcpReceiver = new TcpReceiver(in, this::handleTcpMessage) TcpReceiver tcpReceiver = new TcpReceiver(in, this::handleTcpMessage)
.withShutdownHook(() -> server.getPlayerManager().deregister(this.player)); .withShutdownHook(() -> server.getPlayerManager().deregister(this.player));
@ -135,10 +160,12 @@ public class ClientCommunicationHandler {
public void sendTcpMessage(Message msg) { public void sendTcpMessage(Message msg) {
ForkJoinPool.commonPool().submit(() -> { ForkJoinPool.commonPool().submit(() -> {
try { synchronized (out) {
Net.write(msg, out); try {
} catch (IOException e) { Net.write(msg, out);
e.printStackTrace(); } catch (IOException e) {
e.printStackTrace();
}
} }
}); });
} }

View File

@ -111,6 +111,16 @@ public class PlayerActionManager {
gunNeedsReCock = true; gunNeedsReCock = true;
} }
server.getPlayerManager().getHandler(player.getId()).sendDatagramPacket(new ItemStackMessage(player.getInventory())); 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; String shotSound = null;
if (gun instanceof Rifle) { if (gun instanceof Rifle) {
shotSound = "shot_m1-garand_1"; shotSound = "shot_m1-garand_1";

View File

@ -2,6 +2,11 @@ package nl.andrewl.aos2_server.logic;
import nl.andrewl.aos_core.net.client.ClientInputState; 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 class PlayerImpulses {
public boolean forward; public boolean forward;
public boolean backward; public boolean backward;