From b88150fd3ede6753ac6e5776ec40db812992c9f4 Mon Sep 17 00:00:00 2001 From: andrewlalis Date: Wed, 20 Jul 2022 16:36:44 +0200 Subject: [PATCH] Added improved init network flow, team colors, and teams. --- .gitignore | 6 +- .../java/nl/andrewl/aos2_client/Client.java | 101 +++++++++++++----- .../nl/andrewl/aos2_client/ClientWorld.java | 44 -------- .../aos2_client/CommunicationHandler.java | 67 +++++++++++- .../aos2_client/control/InputHandler.java | 2 +- .../control/PlayerViewCursorCallback.java | 14 +-- .../aos2_client/model/ClientPlayer.java | 2 +- .../aos2_client/render/GameRenderer.java | 56 +++++----- .../main/resources/model/simple_player.png | Bin 65324 -> 45923 bytes .../main/java/nl/andrewl/aos_core/Net.java | 8 +- .../nl/andrewl/aos_core/model/Player.java | 20 +++- .../java/nl/andrewl/aos_core/model/Squad.java | 13 +++ .../java/nl/andrewl/aos_core/model/Team.java | 19 +++- .../andrewl/aos_core/model/world/World.java | 25 +++++ .../andrewl/aos_core/model/world/WorldIO.java | 101 +++++++++++------- .../andrewl/aos_core/model/world/Worlds.java | 3 + .../nl/andrewl/aos_core/net/TcpReceiver.java | 4 + .../net/client/PlayerJoinMessage.java | 4 +- .../net/client/PlayerUpdateMessage.java | 5 +- .../aos_core/net/world/WorldInfoMessage.java | 16 --- design/net.md | 9 ++ .../ClientCommunicationHandler.java | 95 ++++++++++++---- .../nl/andrewl/aos2_server/PlayerManager.java | 52 ++++++++- .../java/nl/andrewl/aos2_server/Server.java | 30 +++++- .../nl/andrewl/aos2_server/ServerPlayer.java | 4 +- .../aos2_server/logic/WorldUpdater.java | 8 +- 26 files changed, 506 insertions(+), 202 deletions(-) create mode 100644 core/src/main/java/nl/andrewl/aos_core/model/Squad.java delete mode 100644 core/src/main/java/nl/andrewl/aos_core/net/world/WorldInfoMessage.java diff --git a/.gitignore b/.gitignore index e673575..d0bb1d9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,6 @@ .idea/ -target/ \ No newline at end of file +target/ + +# Ignore the ./config directory so that developers can put their config files +# there for server and client apps. +config 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 fa0f216..4f3a4d4 100644 --- a/client/src/main/java/nl/andrewl/aos2_client/Client.java +++ b/client/src/main/java/nl/andrewl/aos2_client/Client.java @@ -6,21 +6,25 @@ import nl.andrewl.aos2_client.control.PlayerInputKeyCallback; import nl.andrewl.aos2_client.control.PlayerInputMouseClickCallback; import nl.andrewl.aos2_client.control.PlayerViewCursorCallback; import nl.andrewl.aos2_client.model.ClientPlayer; +import nl.andrewl.aos2_client.model.OtherPlayer; import nl.andrewl.aos2_client.render.GameRenderer; import nl.andrewl.aos_core.config.Config; -import nl.andrewl.aos_core.model.world.ColorPalette; +import nl.andrewl.aos_core.model.Player; +import nl.andrewl.aos_core.model.Team; import nl.andrewl.aos_core.net.client.*; import nl.andrewl.aos_core.net.world.ChunkDataMessage; import nl.andrewl.aos_core.net.world.ChunkHashMessage; import nl.andrewl.aos_core.net.world.ChunkUpdateMessage; -import nl.andrewl.aos_core.net.world.WorldInfoMessage; import nl.andrewl.record_net.Message; +import org.joml.Vector3f; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.nio.file.Path; import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; public class Client implements Runnable { private static final Logger log = LoggerFactory.getLogger(Client.class); @@ -30,32 +34,36 @@ public class Client implements Runnable { private final CommunicationHandler communicationHandler; private final InputHandler inputHandler; private GameRenderer gameRenderer; + private long lastPlayerUpdate = 0; - private final ClientWorld world; - private ClientPlayer player; + private ClientWorld world; + private ClientPlayer myPlayer; + private final Map players; + private Map teams; public Client(ClientConfig config) { this.config = config; + this.players = new ConcurrentHashMap<>(); + this.teams = new ConcurrentHashMap<>(); this.communicationHandler = new CommunicationHandler(this); this.inputHandler = new InputHandler(this, communicationHandler); - this.world = new ClientWorld(); } public ClientConfig getConfig() { return config; } - public ClientPlayer getPlayer() { - return player; + public ClientPlayer getMyPlayer() { + return myPlayer; } /** * Called by the {@link CommunicationHandler} when a connection is * established, and we need to begin tracking the player's state. - * @param player The player. + * @param myPlayer The player. */ - public void setPlayer(ClientPlayer player) { - this.player = player; + public void setMyPlayer(ClientPlayer myPlayer) { + this.myPlayer = myPlayer; } @Override @@ -67,7 +75,7 @@ public class Client implements Runnable { return; } - gameRenderer = new GameRenderer(config.display, player, world); + gameRenderer = new GameRenderer(config.display, this); gameRenderer.setupWindow( new PlayerViewCursorCallback(config.input, this, gameRenderer.getCamera(), communicationHandler), new PlayerInputKeyCallback(inputHandler), @@ -80,7 +88,7 @@ public class Client implements Runnable { float dt = (now - lastFrameAt) / 1000f; world.processQueuedChunkUpdates(); gameRenderer.getCamera().interpolatePosition(dt); - world.interpolatePlayers(dt); + interpolatePlayers(dt); gameRenderer.draw(); lastFrameAt = now; } @@ -89,9 +97,7 @@ public class Client implements Runnable { } public void onMessageReceived(Message msg) { - if (msg instanceof WorldInfoMessage worldInfo) { - world.setPalette(ColorPalette.fromArray(worldInfo.palette())); - } else if (msg instanceof ChunkDataMessage chunkDataMessage) { + if (msg instanceof ChunkDataMessage chunkDataMessage) { world.addChunk(chunkDataMessage); } else if (msg instanceof ChunkUpdateMessage u) { world.updateChunk(u); @@ -100,29 +106,72 @@ public class Client implements Runnable { communicationHandler.sendMessage(new ChunkHashMessage(u.cx(), u.cy(), u.cz(), -1)); } } else if (msg instanceof PlayerUpdateMessage playerUpdate) { - if (playerUpdate.clientId() == player.getId()) { - player.getPosition().set(playerUpdate.px(), playerUpdate.py(), playerUpdate.pz()); - player.getVelocity().set(playerUpdate.vx(), playerUpdate.vy(), playerUpdate.vz()); - player.setCrouching(playerUpdate.crouching()); + if (playerUpdate.clientId() == myPlayer.getId() && playerUpdate.timestamp() > lastPlayerUpdate) { + myPlayer.getPosition().set(playerUpdate.px(), playerUpdate.py(), playerUpdate.pz()); + myPlayer.getVelocity().set(playerUpdate.vx(), playerUpdate.vy(), playerUpdate.vz()); + myPlayer.setCrouching(playerUpdate.crouching()); if (gameRenderer != null) { - gameRenderer.getCamera().setToPlayer(player); + gameRenderer.getCamera().setToPlayer(myPlayer); } + lastPlayerUpdate = playerUpdate.timestamp(); } else { - world.playerUpdated(playerUpdate); + OtherPlayer p = players.get(playerUpdate.clientId()); + if (p != null) { + playerUpdate.apply(p); + p.setHeldItemId(playerUpdate.selectedItemId()); + p.updateModelTransform(); + } } } else if (msg instanceof ClientInventoryMessage inventoryMessage) { - player.setInventory(inventoryMessage.inv()); + myPlayer.setInventory(inventoryMessage.inv()); } else if (msg instanceof InventorySelectedStackMessage selectedStackMessage) { - player.getInventory().setSelectedIndex(selectedStackMessage.index()); + myPlayer.getInventory().setSelectedIndex(selectedStackMessage.index()); } else if (msg instanceof ItemStackMessage itemStackMessage) { - player.getInventory().getItemStacks().set(itemStackMessage.index(), itemStackMessage.stack()); + myPlayer.getInventory().getItemStacks().set(itemStackMessage.index(), itemStackMessage.stack()); } else if (msg instanceof PlayerJoinMessage joinMessage) { - world.playerJoined(joinMessage); + Player p = joinMessage.toPlayer(); + OtherPlayer op = new OtherPlayer(p.getId(), p.getUsername()); + if (joinMessage.teamId() != -1) { + op.setTeam(teams.get(joinMessage.teamId())); + } + op.getPosition().set(p.getPosition()); + op.getVelocity().set(p.getVelocity()); + op.getOrientation().set(p.getOrientation()); + op.setHeldItemId(joinMessage.selectedItemId()); + players.put(op.getId(), op); } else if (msg instanceof PlayerLeaveMessage leaveMessage) { - world.playerLeft(leaveMessage); + players.remove(leaveMessage.id()); } } + public void setWorld(ClientWorld world) { + this.world = world; + } + + public ClientWorld getWorld() { + return world; + } + + public Map getTeams() { + return teams; + } + + public void setTeams(Map teams) { + this.teams = teams; + } + + public Map getPlayers() { + return players; + } + + public void interpolatePlayers(float dt) { + Vector3f movement = new Vector3f(); + for (var player : players.values()) { + movement.set(player.getVelocity()).mul(dt); + player.getPosition().add(movement); + player.updateModelTransform(); + } + } public static void main(String[] args) throws IOException { List configPaths = Config.getCommonConfigPaths(); diff --git a/client/src/main/java/nl/andrewl/aos2_client/ClientWorld.java b/client/src/main/java/nl/andrewl/aos2_client/ClientWorld.java index 908bada..d7b8bc4 100644 --- a/client/src/main/java/nl/andrewl/aos2_client/ClientWorld.java +++ b/client/src/main/java/nl/andrewl/aos2_client/ClientWorld.java @@ -1,17 +1,11 @@ package nl.andrewl.aos2_client; -import nl.andrewl.aos2_client.model.OtherPlayer; import nl.andrewl.aos2_client.render.chunk.ChunkMesh; import nl.andrewl.aos2_client.render.chunk.ChunkMeshGenerator; -import nl.andrewl.aos_core.model.Player; import nl.andrewl.aos_core.model.world.Chunk; import nl.andrewl.aos_core.model.world.World; -import nl.andrewl.aos_core.net.client.PlayerJoinMessage; -import nl.andrewl.aos_core.net.client.PlayerLeaveMessage; -import nl.andrewl.aos_core.net.client.PlayerUpdateMessage; import nl.andrewl.aos_core.net.world.ChunkDataMessage; import nl.andrewl.aos_core.net.world.ChunkUpdateMessage; -import org.joml.Vector3f; import org.joml.Vector3i; import java.util.*; @@ -29,44 +23,6 @@ public class ClientWorld extends World { private final ChunkMeshGenerator chunkMeshGenerator = new ChunkMeshGenerator(); private final Map chunkMeshes = new ConcurrentHashMap<>(); - private final Map players = new HashMap<>(); - - public void playerJoined(PlayerJoinMessage joinMessage) { - Player p = joinMessage.toPlayer(); - OtherPlayer op = new OtherPlayer(p.getId(), p.getUsername()); - op.getPosition().set(p.getPosition()); - op.getVelocity().set(p.getVelocity()); - op.getOrientation().set(p.getOrientation()); - op.setHeldItemId(joinMessage.selectedItemId()); - players.put(op.getId(), op); - } - - public void playerLeft(PlayerLeaveMessage leaveMessage) { - players.remove(leaveMessage.id()); - } - - public void playerUpdated(PlayerUpdateMessage playerUpdate) { - OtherPlayer p = players.get(playerUpdate.clientId()); - if (p != null) { - playerUpdate.apply(p); - p.setHeldItemId(playerUpdate.selectedItemId()); - p.updateModelTransform(); - } - } - - public Collection getPlayers() { - return players.values(); - } - - public void interpolatePlayers(float dt) { - Vector3f movement = new Vector3f(); - for (var player : getPlayers()) { - movement.set(player.getVelocity()).mul(dt); - player.getPosition().add(movement); - player.updateModelTransform(); - } - } - @Override public void addChunk(Chunk chunk) { super.addChunk(chunk); diff --git a/client/src/main/java/nl/andrewl/aos2_client/CommunicationHandler.java b/client/src/main/java/nl/andrewl/aos2_client/CommunicationHandler.java index bbc03f8..5f45e7a 100644 --- a/client/src/main/java/nl/andrewl/aos2_client/CommunicationHandler.java +++ b/client/src/main/java/nl/andrewl/aos2_client/CommunicationHandler.java @@ -1,7 +1,12 @@ package nl.andrewl.aos2_client; import nl.andrewl.aos2_client.model.ClientPlayer; +import nl.andrewl.aos2_client.model.OtherPlayer; import nl.andrewl.aos_core.Net; +import nl.andrewl.aos_core.model.Team; +import nl.andrewl.aos_core.model.item.ItemStack; +import nl.andrewl.aos_core.model.world.World; +import nl.andrewl.aos_core.model.world.WorldIO; import nl.andrewl.aos_core.net.*; import nl.andrewl.aos_core.net.connect.ConnectAcceptMessage; import nl.andrewl.aos_core.net.connect.ConnectRejectMessage; @@ -10,6 +15,7 @@ import nl.andrewl.aos_core.net.connect.DatagramInit; import nl.andrewl.record_net.Message; import nl.andrewl.record_net.util.ExtendedDataInputStream; import nl.andrewl.record_net.util.ExtendedDataOutputStream; +import org.joml.Vector3f; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -31,6 +37,7 @@ public class CommunicationHandler { private Socket socket; private DatagramSocket datagramSocket; private ExtendedDataOutputStream out; + private ExtendedDataInputStream in; private int clientId; public CommunicationHandler(Client client) { @@ -48,7 +55,7 @@ public class CommunicationHandler { socket = new Socket(address, port); socket.setSoTimeout(1000); - ExtendedDataInputStream in = Net.getInputStream(socket.getInputStream()); + in = Net.getInputStream(socket.getInputStream()); out = Net.getOutputStream(socket.getOutputStream()); Net.write(new ConnectRequestMessage(username), out); Message response = Net.read(in); @@ -58,7 +65,10 @@ public class CommunicationHandler { } if (response instanceof ConnectAcceptMessage acceptMessage) { this.clientId = acceptMessage.clientId(); - client.setPlayer(new ClientPlayer(clientId, username)); + log.debug("Connection accepted. My client id is {}.", clientId); + client.setMyPlayer(new ClientPlayer(clientId, username)); + receiveInitialData(); + log.debug("Initial data received."); establishDatagramConnection(); log.info("Connection to server established. My client id is {}.", clientId); new Thread(new TcpReceiver(in, client::onMessageReceived)).start(); @@ -127,4 +137,57 @@ public class CommunicationHandler { public int getClientId() { return clientId; } + + private void receiveInitialData() throws IOException { + // Read the world data. + World world = WorldIO.read(in); + ClientWorld clientWorld = new ClientWorld(); + clientWorld.setPalette(world.getPalette()); + for (var chunk : world.getChunkMap().values()) { + clientWorld.addChunk(chunk); + } + for (var spawnPoint : world.getSpawnPoints().entrySet()) { + clientWorld.setSpawnPoint(spawnPoint.getKey(), spawnPoint.getValue()); + } + client.setWorld(clientWorld); + + // Read the team data. + int teamCount = in.readInt(); + for (int i = 0; i < teamCount; i++) { + int id = in.readInt(); + client.getTeams().put(id, new Team( + id, in.readString(), + new Vector3f(in.readFloat(), in.readFloat(), in.readFloat()), + new Vector3f(in.readFloat(), in.readFloat(), in.readFloat()) + )); + } + + // Read player data. + int playerCount = in.readInt(); + for (int i = 0; i < playerCount; i++) { + OtherPlayer player = new OtherPlayer(in.readInt(), in.readString()); + int teamId = in.readInt(); + if (teamId != -1) player.setTeam(client.getTeams().get(teamId)); + System.out.println(teamId); + player.getPosition().set(in.readFloat(), in.readFloat(), in.readFloat()); + player.getVelocity().set(in.readFloat(), in.readFloat(), in.readFloat()); + player.getOrientation().set(in.readFloat(), in.readFloat()); + player.setCrouching(in.readBoolean()); + player.setHeldItemId(in.readInt()); + client.getPlayers().put(player.getId(), player); + } + + // Read inventory data. + int itemStackCount = in.readInt(); + var inv = client.getMyPlayer().getInventory(); + for (int i = 0; i < itemStackCount; i++) { + inv.getItemStacks().add(ItemStack.read(in)); + } + inv.setSelectedIndex(in.readInt()); + + // Read our own player data. + int teamId = in.readInt(); + if (teamId != -1) client.getMyPlayer().setTeam(client.getTeams().get(teamId)); + client.getMyPlayer().getPosition().set(in.readFloat(), in.readFloat(), in.readFloat()); + } } 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 df00d6a..8887fd0 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 @@ -23,7 +23,7 @@ public class InputHandler { public void updateInputState(long window) { // TODO: Allow customized keybindings. int selectedInventoryIndex; - selectedInventoryIndex = client.getPlayer().getInventory().getSelectedIndex(); + selectedInventoryIndex = client.getMyPlayer().getInventory().getSelectedIndex(); if (glfwGetKey(window, GLFW_KEY_1) == GLFW_PRESS) selectedInventoryIndex = 0; if (glfwGetKey(window, GLFW_KEY_2) == GLFW_PRESS) selectedInventoryIndex = 1; if (glfwGetKey(window, GLFW_KEY_3) == GLFW_PRESS) selectedInventoryIndex = 2; 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 e8c4f43..67dedf3 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 @@ -48,17 +48,17 @@ public class PlayerViewCursorCallback implements GLFWCursorPosCallbackI { float dy = y - lastMouseCursorY; lastMouseCursorX = x; lastMouseCursorY = y; - client.getPlayer().setOrientation( - client.getPlayer().getOrientation().x - dx * config.mouseSensitivity, - client.getPlayer().getOrientation().y - dy * config.mouseSensitivity + client.getMyPlayer().setOrientation( + client.getMyPlayer().getOrientation().x - dx * config.mouseSensitivity, + client.getMyPlayer().getOrientation().y - dy * config.mouseSensitivity ); - camera.setOrientationToPlayer(client.getPlayer()); + camera.setOrientationToPlayer(client.getMyPlayer()); long now = System.currentTimeMillis(); if (lastOrientationUpdateSentAt + ORIENTATION_UPDATE_LIMIT < now) { ForkJoinPool.commonPool().submit(() -> comm.sendDatagramPacket(new ClientOrientationState( - client.getPlayer().getId(), - client.getPlayer().getOrientation().x, - client.getPlayer().getOrientation().y + client.getMyPlayer().getId(), + client.getMyPlayer().getOrientation().x, + client.getMyPlayer().getOrientation().y ))); lastOrientationUpdateSentAt = now; } diff --git a/client/src/main/java/nl/andrewl/aos2_client/model/ClientPlayer.java b/client/src/main/java/nl/andrewl/aos2_client/model/ClientPlayer.java index 6850aea..a2c9dbf 100644 --- a/client/src/main/java/nl/andrewl/aos2_client/model/ClientPlayer.java +++ b/client/src/main/java/nl/andrewl/aos2_client/model/ClientPlayer.java @@ -33,7 +33,7 @@ public class ClientPlayer extends Player { .translate(cam.getPosition()) .rotate((float) (cam.getOrientation().x + Math.PI), Camera.UP) .rotate(-cam.getOrientation().y + (float) Math.PI / 2, Camera.RIGHT) - .translate(-0.35f, -0.4f, 1f); + .translate(-0.35f, -0.4f, 0.5f); heldItemTransform.get(heldItemTransformData); } 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 e660902..fb3ac2e 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 @@ -1,9 +1,8 @@ package nl.andrewl.aos2_client.render; import nl.andrewl.aos2_client.Camera; -import nl.andrewl.aos2_client.ClientWorld; +import nl.andrewl.aos2_client.Client; import nl.andrewl.aos2_client.config.ClientConfig; -import nl.andrewl.aos2_client.model.ClientPlayer; import nl.andrewl.aos2_client.render.chunk.ChunkRenderer; import nl.andrewl.aos2_client.render.gui.GUIRenderer; import nl.andrewl.aos2_client.render.gui.GUITexture; @@ -14,7 +13,6 @@ import org.joml.Matrix4f; import org.joml.Vector3f; import org.lwjgl.glfw.*; import org.lwjgl.opengl.GL; -import org.lwjgl.opengl.GLUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -39,8 +37,7 @@ public class GameRenderer { private GUIRenderer guiRenderer; private ModelRenderer modelRenderer; private final Camera camera; - private final ClientPlayer clientPlayer; - private final ClientWorld world; + private final Client client; // Standard models for various game components. private Model playerModel; @@ -53,12 +50,11 @@ public class GameRenderer { private final Matrix4f perspectiveTransform; - public GameRenderer(ClientConfig.DisplayConfig config, ClientPlayer clientPlayer, ClientWorld world) { + public GameRenderer(ClientConfig.DisplayConfig config, Client client) { this.config = config; - this.clientPlayer = clientPlayer; - this.world = world; + this.client = client; this.camera = new Camera(); - camera.setToPlayer(clientPlayer); + camera.setToPlayer(client.getMyPlayer()); this.perspectiveTransform = new Matrix4f(); } @@ -76,7 +72,7 @@ public class GameRenderer { long monitorId = glfwGetPrimaryMonitor(); GLFWVidMode primaryMonitorSettings = glfwGetVideoMode(monitorId); if (primaryMonitorSettings == null) throw new IllegalStateException("Could not get information about the primary monitory."); - log.debug("Primary monitor settings: Width: {}, Height: {}", primaryMonitorSettings.width(), primaryMonitorSettings.height()); + log.debug("Primary monitor settings: Width: {}, Height: {}, FOV: {}", primaryMonitorSettings.width(), primaryMonitorSettings.height(), config.fov); if (config.fullscreen) { screenWidth = primaryMonitorSettings.width(); screenHeight = primaryMonitorSettings.height(); @@ -145,7 +141,13 @@ public class GameRenderer { * Updates the rendering perspective used to render the game. */ private void updatePerspective() { - perspectiveTransform.setPerspective(config.fov, getAspectRatio(), Z_NEAR, Z_FAR); + float fovRad = (float) Math.toRadians(config.fov); + if (fovRad >= Math.PI) { + fovRad = (float) (Math.PI - 0.01f); + } else if (fovRad <= 0) { + fovRad = 0.01f; + } + perspectiveTransform.setPerspective(fovRad, getAspectRatio(), Z_NEAR, Z_FAR); float[] data = new float[16]; perspectiveTransform.get(data); if (chunkRenderer != null) chunkRenderer.setPerspective(data); @@ -166,36 +168,42 @@ public class GameRenderer { public void draw() { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - chunkRenderer.draw(camera, world.getChunkMeshesToDraw()); + chunkRenderer.draw(camera, client.getWorld().getChunkMeshesToDraw()); // Draw models. Use one texture at a time for efficiency. modelRenderer.start(camera.getViewTransformData()); - clientPlayer.updateHeldItemTransform(camera); + client.getMyPlayer().updateHeldItemTransform(camera); + playerModel.bind(); - for (var player : world.getPlayers()) { - // TODO: set aspect color based on player team color - modelRenderer.setAspectColor(new Vector3f(0.8f, 0.4f, 0)); + for (var player : client.getPlayers().values()) { + if (player.getTeam() != null) { + modelRenderer.setAspectColor(player.getTeam().getColor()); + } else { + modelRenderer.setAspectColor(new Vector3f(0.3f, 0.3f, 0.3f)); + } modelRenderer.render(playerModel, player.getModelTransformData()); } playerModel.unbind(); + rifleModel.bind(); - if (clientPlayer.getInventory().getSelectedItemStack().getType().getId() == ItemTypes.RIFLE.getId()) { - modelRenderer.render(rifleModel, clientPlayer.getHeldItemTransformData()); + if (client.getMyPlayer().getInventory().getSelectedItemStack().getType().getId() == ItemTypes.RIFLE.getId()) { + modelRenderer.render(rifleModel, client.getMyPlayer().getHeldItemTransformData()); } - for (var player : world.getPlayers()) { + for (var player : client.getPlayers().values()) { if (player.getHeldItemId() == ItemTypes.RIFLE.getId()) { modelRenderer.render(rifleModel, player.getHeldItemTransformData()); } } rifleModel.unbind(); + blockModel.bind(); - if (clientPlayer.getInventory().getSelectedItemStack().getType().getId() == ItemTypes.BLOCK.getId()) { - BlockItemStack stack = (BlockItemStack) clientPlayer.getInventory().getSelectedItemStack(); - modelRenderer.setAspectColor(world.getPalette().getColor(stack.getSelectedValue())); - modelRenderer.render(blockModel, clientPlayer.getHeldItemTransformData()); + if (client.getMyPlayer().getInventory().getSelectedItemStack().getType().getId() == ItemTypes.BLOCK.getId()) { + BlockItemStack stack = (BlockItemStack) client.getMyPlayer().getInventory().getSelectedItemStack(); + modelRenderer.setAspectColor(client.getWorld().getPalette().getColor(stack.getSelectedValue())); + modelRenderer.render(blockModel, client.getMyPlayer().getHeldItemTransformData()); } modelRenderer.setAspectColor(new Vector3f(0.5f, 0.5f, 0.5f)); - for (var player : world.getPlayers()) { + for (var player : client.getPlayers().values()) { if (player.getHeldItemId() == ItemTypes.BLOCK.getId()) { modelRenderer.render(blockModel, player.getHeldItemTransformData()); } diff --git a/client/src/main/resources/model/simple_player.png b/client/src/main/resources/model/simple_player.png index e7b7d62d9519f3410b12562d94cfdf65cb02765e..808fa20d2e4fb79c62a60830e76de01109e02451 100644 GIT binary patch literal 45923 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 T2gNrV1+~ZE;I8L8sMr4osDoV` literal 65324 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 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 59881f2..3791028 100644 --- a/core/src/main/java/nl/andrewl/aos_core/Net.java +++ b/core/src/main/java/nl/andrewl/aos_core/Net.java @@ -8,7 +8,6 @@ import nl.andrewl.aos_core.net.connect.DatagramInit; import nl.andrewl.aos_core.net.world.ChunkDataMessage; import nl.andrewl.aos_core.net.world.ChunkHashMessage; import nl.andrewl.aos_core.net.world.ChunkUpdateMessage; -import nl.andrewl.aos_core.net.world.WorldInfoMessage; import nl.andrewl.record_net.Message; import nl.andrewl.record_net.Serializer; import nl.andrewl.record_net.util.ExtendedDataInputStream; @@ -39,11 +38,10 @@ public final class Net { serializer.registerType(10, PlayerUpdateMessage.class); serializer.registerType(11, PlayerJoinMessage.class); serializer.registerType(12, PlayerLeaveMessage.class); - serializer.registerType(13, WorldInfoMessage.class); // Separate serializers for client inventory messages. - serializer.registerTypeSerializer(14, new InventorySerializer()); - serializer.registerTypeSerializer(15, new ItemStackSerializer()); - serializer.registerType(16, InventorySelectedStackMessage.class); + serializer.registerTypeSerializer(13, new InventorySerializer()); + serializer.registerTypeSerializer(14, new ItemStackSerializer()); + serializer.registerType(15, InventorySelectedStackMessage.class); } public static ExtendedDataInputStream getInputStream(InputStream in) { diff --git a/core/src/main/java/nl/andrewl/aos_core/model/Player.java b/core/src/main/java/nl/andrewl/aos_core/model/Player.java index 826b3f3..54b1e1e 100644 --- a/core/src/main/java/nl/andrewl/aos_core/model/Player.java +++ b/core/src/main/java/nl/andrewl/aos_core/model/Player.java @@ -65,13 +65,23 @@ public class Player { */ protected final int id; - public Player(int id, String username) { + /** + * The team that this player belongs to. This might be null. + */ + protected Team team; + + public Player(int id, String username, Team team) { this.position = new Vector3f(); this.velocity = new Vector3f(); this.orientation = new Vector2f(); this.viewVector = new Vector3f(); this.id = id; this.username = username; + this.team = team; + } + + public Player(int id, String username) { + this(id, username, null); } public Vector3f getPosition() { @@ -116,6 +126,14 @@ public class Player { return id; } + public Team getTeam() { + return team; + } + + public void setTeam(Team team) { + this.team = team; + } + public Vector3f getViewVector() { return viewVector; } diff --git a/core/src/main/java/nl/andrewl/aos_core/model/Squad.java b/core/src/main/java/nl/andrewl/aos_core/model/Squad.java new file mode 100644 index 0000000..a74e6cd --- /dev/null +++ b/core/src/main/java/nl/andrewl/aos_core/model/Squad.java @@ -0,0 +1,13 @@ +package nl.andrewl.aos_core.model; + +/** + * Represents a group within a team that players can create and join to work + * together on some tasks. + */ +public class Squad { + private final int id; + + public Squad(int id) { + this.id = id; + } +} diff --git a/core/src/main/java/nl/andrewl/aos_core/model/Team.java b/core/src/main/java/nl/andrewl/aos_core/model/Team.java index 118ba50..029f01c 100644 --- a/core/src/main/java/nl/andrewl/aos_core/model/Team.java +++ b/core/src/main/java/nl/andrewl/aos_core/model/Team.java @@ -4,7 +4,8 @@ import org.joml.Vector3f; /** * A team is a group of players in a world that should work together to - * achieve some goal. + * achieve some goal. Teams belong to a server directly, and persist even if + * the world is changed. */ public class Team { /** @@ -23,10 +24,16 @@ public class Team { */ private final Vector3f color; - public Team(int id, String name, Vector3f color) { + /** + * The team's spawn point, in the current world. + */ + private final Vector3f spawnPoint; + + public Team(int id, String name, Vector3f color, Vector3f spawnPoint) { this.id = id; this.name = name; this.color = color; + this.spawnPoint = spawnPoint; } public int getId() { @@ -40,4 +47,12 @@ public class Team { public Vector3f getColor() { return color; } + + public Vector3f getSpawnPoint() { + return spawnPoint; + } + + public void setSpawnPoint(Vector3f p) { + spawnPoint.set(p); + } } diff --git a/core/src/main/java/nl/andrewl/aos_core/model/world/World.java b/core/src/main/java/nl/andrewl/aos_core/model/world/World.java index 1ead2bb..8a9fbd7 100644 --- a/core/src/main/java/nl/andrewl/aos_core/model/world/World.java +++ b/core/src/main/java/nl/andrewl/aos_core/model/world/World.java @@ -21,6 +21,7 @@ public class World { protected final Map chunkMap = new HashMap<>(); protected ColorPalette palette; + protected final Map spawnPoints = new HashMap<>(); public World(ColorPalette palette, Collection chunks) { this.palette = palette; @@ -91,6 +92,30 @@ public class World { return chunkMap.get(new Vector3i(x, y, z)); } + public Vector3f getSpawnPoint(String name) { + return spawnPoints.get(name); + } + + public void setSpawnPoint(String name, Vector3f location) { + spawnPoints.put(name, location); + } + + public void removeSpawnPoint(String name) { + spawnPoints.remove(name); + } + + public Map getSpawnPoints() { + return Collections.unmodifiableMap(spawnPoints); + } + + /** + * Clears all data from the world. + */ + public void clear() { + chunkMap.clear(); + spawnPoints.clear(); + } + /** * Gets the position that a system is looking at, within a distance limit. * Usually used to determine where a player has interacted/clicked in the diff --git a/core/src/main/java/nl/andrewl/aos_core/model/world/WorldIO.java b/core/src/main/java/nl/andrewl/aos_core/model/world/WorldIO.java index a28bb53..1b34dc3 100644 --- a/core/src/main/java/nl/andrewl/aos_core/model/world/WorldIO.java +++ b/core/src/main/java/nl/andrewl/aos_core/model/world/WorldIO.java @@ -1,51 +1,80 @@ package nl.andrewl.aos_core.model.world; -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; +import org.joml.Vector3f; + +import java.io.*; +import java.util.Map; /** * Utility class for reading and writing worlds to files. */ public final class WorldIO { - public static void write(World world, Path path) throws IOException { + /** + * Writes a world to an output stream. + * @param world The world to write. + * @param out The output stream to write to. + * @throws IOException If an exception occurs. + */ + public static void write(World world, OutputStream out) throws IOException { + var d = new DataOutputStream(out); + // Write color palette. + for (var v : world.getPalette().toArray()) { + d.writeFloat(v); + } + // Write spawn points. + var spawnPoints = world.getSpawnPoints(); + d.writeInt(spawnPoints.size()); + var sortedEntries = spawnPoints.entrySet().stream() + .sorted(Map.Entry.comparingByKey()).toList(); + for (var entry : sortedEntries) { + d.writeUTF(entry.getKey()); + d.writeFloat(entry.getValue().x()); + d.writeFloat(entry.getValue().y()); + d.writeFloat(entry.getValue().z()); + } + // Write chunks. var chunks = world.getChunkMap().values(); - try (var os = Files.newOutputStream(path)) { - var out = new DataOutputStream(os); - for (var v : world.getPalette().toArray()) { - out.writeFloat(v); - } - out.writeInt(chunks.size()); - for (var chunk : chunks) { - out.writeInt(chunk.getPosition().x); - out.writeInt(chunk.getPosition().y); - out.writeInt(chunk.getPosition().z); - out.write(chunk.getBlocks()); - } + d.writeInt(chunks.size()); + for (var chunk : chunks) { + d.writeInt(chunk.getPosition().x); + d.writeInt(chunk.getPosition().y); + d.writeInt(chunk.getPosition().z); + d.write(chunk.getBlocks()); } } - public static World read(Path path) throws IOException { + /** + * Reads a world from an input stream. + * @param in The input stream to read from. + * @return The world which was read. + * @throws IOException If an exception occurs. + */ + public static World read(InputStream in) throws IOException { World world = new World(); - try (var is = Files.newInputStream(path)) { - var in = new DataInputStream(is); - ColorPalette palette = new ColorPalette(); - for (int i = 0; i < ColorPalette.MAX_COLORS; i++) { - palette.setColor((byte) (i + 1), in.readFloat(), in.readFloat(), in.readFloat()); - } - world.setPalette(palette); - int chunkCount = in.readInt(); - for (int i = 0; i < chunkCount; i++) { - Chunk chunk = new Chunk( - in.readInt(), - in.readInt(), - in.readInt(), - in.readNBytes(Chunk.TOTAL_SIZE) - ); - world.addChunk(chunk); - } + var d = new DataInputStream(in); + // Read color palette. + ColorPalette palette = new ColorPalette(); + for (int i = 0; i < ColorPalette.MAX_COLORS; i++) { + palette.setColor((byte) (i + 1), d.readFloat(), d.readFloat(), d.readFloat()); + } + world.setPalette(palette); + // Read spawn points. + int spawnPointCount = d.readInt(); + for (int i = 0; i < spawnPointCount; i++) { + String name = d.readUTF(); + Vector3f location = new Vector3f(d.readFloat(), d.readFloat(), d.readFloat()); + world.setSpawnPoint(name, location); + } + // Read chunks. + int chunkCount = d.readInt(); + for (int i = 0; i < chunkCount; i++) { + Chunk chunk = new Chunk( + d.readInt(), + d.readInt(), + d.readInt(), + d.readNBytes(Chunk.TOTAL_SIZE) + ); + world.addChunk(chunk); } return world; } diff --git a/core/src/main/java/nl/andrewl/aos_core/model/world/Worlds.java b/core/src/main/java/nl/andrewl/aos_core/model/world/Worlds.java index 28cfe73..7bf35d8 100644 --- a/core/src/main/java/nl/andrewl/aos_core/model/world/Worlds.java +++ b/core/src/main/java/nl/andrewl/aos_core/model/world/Worlds.java @@ -1,5 +1,6 @@ package nl.andrewl.aos_core.model.world; +import org.joml.Vector3f; import org.joml.Vector3i; import java.util.Collections; @@ -128,6 +129,8 @@ public final class Worlds { } } + world.setSpawnPoint("first", new Vector3f(0.5f, 0f, 0.5f)); + return world; } } diff --git a/core/src/main/java/nl/andrewl/aos_core/net/TcpReceiver.java b/core/src/main/java/nl/andrewl/aos_core/net/TcpReceiver.java index e806d0f..bc9f4e2 100644 --- a/core/src/main/java/nl/andrewl/aos_core/net/TcpReceiver.java +++ b/core/src/main/java/nl/andrewl/aos_core/net/TcpReceiver.java @@ -9,6 +9,10 @@ import java.io.IOException; import java.net.SocketException; import java.util.function.Consumer; +/** + * A generic runnable that's meant to be run in its own thread, and handle + * receiving messages from a TCP socket. + */ public class TcpReceiver implements Runnable { private final ExtendedDataInputStream in; private final Consumer messageConsumer; diff --git a/core/src/main/java/nl/andrewl/aos_core/net/client/PlayerJoinMessage.java b/core/src/main/java/nl/andrewl/aos_core/net/client/PlayerJoinMessage.java index a6ba920..2d239cf 100644 --- a/core/src/main/java/nl/andrewl/aos_core/net/client/PlayerJoinMessage.java +++ b/core/src/main/java/nl/andrewl/aos_core/net/client/PlayerJoinMessage.java @@ -9,7 +9,7 @@ import nl.andrewl.record_net.Message; * joins, so that they can add that player to their world. */ public record PlayerJoinMessage( - int id, String username, + int id, String username, int teamId, float px, float py, float pz, float vx, float vy, float vz, float ox, float oy, @@ -18,7 +18,7 @@ public record PlayerJoinMessage( ) implements Message { public PlayerJoinMessage(Player player) { this( - player.getId(), player.getUsername(), + player.getId(), player.getUsername(), player.getTeam() == null ? -1 : player.getTeam().getId(), player.getPosition().x, player.getPosition().y, player.getPosition().z, player.getVelocity().x, player.getVelocity().y, player.getVelocity().z, player.getOrientation().x, player.getOrientation().y, diff --git a/core/src/main/java/nl/andrewl/aos_core/net/client/PlayerUpdateMessage.java b/core/src/main/java/nl/andrewl/aos_core/net/client/PlayerUpdateMessage.java index ac5c9f2..48189b5 100644 --- a/core/src/main/java/nl/andrewl/aos_core/net/client/PlayerUpdateMessage.java +++ b/core/src/main/java/nl/andrewl/aos_core/net/client/PlayerUpdateMessage.java @@ -5,10 +5,13 @@ import nl.andrewl.record_net.Message; /** * This message is sent by the server to clients whenever a player has updated - * in some way, like movement or orientation or held items. + * in some way, like movement or orientation or held items. This is generally + * the thing that clients receive rapidly on each game tick, when players are + * moving. */ public record PlayerUpdateMessage( int clientId, + long timestamp, float px, float py, float pz, float vx, float vy, float vz, float ox, float oy, diff --git a/core/src/main/java/nl/andrewl/aos_core/net/world/WorldInfoMessage.java b/core/src/main/java/nl/andrewl/aos_core/net/world/WorldInfoMessage.java deleted file mode 100644 index b7d1604..0000000 --- a/core/src/main/java/nl/andrewl/aos_core/net/world/WorldInfoMessage.java +++ /dev/null @@ -1,16 +0,0 @@ -package nl.andrewl.aos_core.net.world; - -import nl.andrewl.aos_core.model.world.World; -import nl.andrewl.record_net.Message; - -/** - * Message that the server sends to connecting clients with some metadata about - * the world. - */ -public record WorldInfoMessage( - float[] palette -) implements Message { - public WorldInfoMessage(World world) { - this(world.getPalette().toArray()); - } -} diff --git a/design/net.md b/design/net.md index a29c024..780e17d 100644 --- a/design/net.md +++ b/design/net.md @@ -12,6 +12,15 @@ This workflow is involved in the establishment of a connection between the clien 2. The server will respond with either a `ConnectRejectMessage` with a `reason` for the rejection, or a `ConnectAcceptMessage` containing the client's `clientId`. 3. If the player received an acceptance message, they will then send a `DatagramInit` to the server's UDP socket (on the same address/port) containing the `clientId` received in the `ConnectAcceptMessage`. The player should keep sending such an init message until they receive a `DatagramInit` message echoed back as a response. The player should then stop sending init messages, and expect to begin receiving normal communication data through the datagram socket. +If the client's connection is accepted, then the server will need to begin sending all the initial data that's needed for the client to be able to start rendering the game. This includes the following data: +- The world's color palette. +- The world's chunks. +- All teams on the server (including squads). +- Each team's spawn point. +- All players that are already connected to the server. +- The client's own player inventory. +- The client's own player position. + ### World Data A combination of TCP and UDP communication is used to ensure that all connected clients have the latest information about the state of the world. 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 b0d7900..32979d3 100644 --- a/server/src/main/java/nl/andrewl/aos2_server/ClientCommunicationHandler.java +++ b/server/src/main/java/nl/andrewl/aos2_server/ClientCommunicationHandler.java @@ -1,16 +1,15 @@ package nl.andrewl.aos2_server; import nl.andrewl.aos_core.Net; +import nl.andrewl.aos_core.model.item.ItemStack; import nl.andrewl.aos_core.model.world.Chunk; -import nl.andrewl.aos_core.net.*; -import nl.andrewl.aos_core.net.client.PlayerJoinMessage; +import nl.andrewl.aos_core.model.world.WorldIO; +import nl.andrewl.aos_core.net.TcpReceiver; import nl.andrewl.aos_core.net.connect.ConnectAcceptMessage; import nl.andrewl.aos_core.net.connect.ConnectRejectMessage; import nl.andrewl.aos_core.net.connect.ConnectRequestMessage; -import nl.andrewl.aos_core.net.client.ClientInventoryMessage; import nl.andrewl.aos_core.net.world.ChunkDataMessage; import nl.andrewl.aos_core.net.world.ChunkHashMessage; -import nl.andrewl.aos_core.net.world.WorldInfoMessage; import nl.andrewl.record_net.Message; import nl.andrewl.record_net.util.ExtendedDataInputStream; import nl.andrewl.record_net.util.ExtendedDataOutputStream; @@ -23,6 +22,7 @@ import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.Socket; +import java.util.LinkedList; /** * Component which manages the establishing and maintenance of a connection @@ -97,22 +97,8 @@ public class ClientCommunicationHandler { this.player = server.getPlayerManager().register(this, connectMsg.username()); Net.write(new ConnectAcceptMessage(player.getId()), out); log.debug("Sent connect accept message."); - - sendTcpMessage(new WorldInfoMessage(server.getWorld())); - // Send player's inventory information. - sendTcpMessage(new ClientInventoryMessage(player.getInventory())); - - // Send "join" info about all the players that are already connected, so the client is aware of them. - for (var player : server.getPlayerManager().getPlayers()) { - if (player.getId() != this.player.getId()) { - sendTcpMessage(new PlayerJoinMessage(player)); - } - } - // Send chunk data. - for (var chunk : server.getWorld().getChunkMap().values()) { - sendTcpMessage(new ChunkDataMessage(chunk)); - } - + sendInitialData(); + log.debug("Sent initial data."); // 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)); @@ -166,4 +152,73 @@ public class ClientCommunicationHandler { e.printStackTrace(); } } + + /** + * Dedicated method to send all initial data to the client when they first + * connect. We don't use record-net for this, but a custom stream writing + * operation to improve efficiency. + */ + private void sendInitialData() throws IOException { + // First world data. We send this in the same format that we'd use for files. + WorldIO.write(server.getWorld(), out); + + // Team data. + var teams = server.getTeams().values(); + out.writeInt(teams.size()); + for (var team : teams) { + out.writeInt(team.getId()); + out.writeString(team.getName()); + out.writeFloat(team.getColor().x()); + out.writeFloat(team.getColor().y()); + out.writeFloat(team.getColor().z()); + out.writeFloat(team.getSpawnPoint().x()); + out.writeFloat(team.getSpawnPoint().y()); + out.writeFloat(team.getSpawnPoint().z()); + } + + // Player data. + var otherPlayers = new LinkedList<>(server.getPlayerManager().getPlayers()); + otherPlayers.remove(player); + out.writeInt(otherPlayers.size()); + for (var player : otherPlayers) { + out.writeInt(player.getId()); + out.writeString(player.getUsername()); + if (player.getTeam() == null) { + out.writeInt(-1); + } else { + out.writeInt(player.getTeam().getId()); + } + + out.writeFloat(player.getPosition().x()); + out.writeFloat(player.getPosition().y()); + out.writeFloat(player.getPosition().z()); + + out.writeFloat(player.getVelocity().x()); + out.writeFloat(player.getVelocity().y()); + out.writeFloat(player.getVelocity().z()); + + out.writeFloat(player.getOrientation().x()); + out.writeFloat(player.getOrientation().y()); + + out.writeBoolean(player.isCrouching()); + out.writeInt(player.getInventory().getSelectedItemStack().getType().getId()); + } + + // Send the player's own inventory data. + out.writeInt(player.getInventory().getItemStacks().size()); + for (var stack : player.getInventory().getItemStacks()) { + ItemStack.write(stack, out); + } + out.writeInt(player.getInventory().getSelectedIndex()); + + // Send the player's own player data. + if (player.getTeam() == null) { + out.writeInt(-1); + } else { + out.writeInt(player.getTeam().getId()); + } + out.writeFloat(player.getPosition().x()); + out.writeFloat(player.getPosition().y()); + out.writeFloat(player.getPosition().z()); + } } diff --git a/server/src/main/java/nl/andrewl/aos2_server/PlayerManager.java b/server/src/main/java/nl/andrewl/aos2_server/PlayerManager.java index bdf6a05..7776f1a 100644 --- a/server/src/main/java/nl/andrewl/aos2_server/PlayerManager.java +++ b/server/src/main/java/nl/andrewl/aos2_server/PlayerManager.java @@ -1,6 +1,7 @@ package nl.andrewl.aos2_server; import nl.andrewl.aos_core.Net; +import nl.andrewl.aos_core.model.Team; import nl.andrewl.aos_core.net.client.PlayerJoinMessage; import nl.andrewl.aos_core.net.client.PlayerLeaveMessage; import nl.andrewl.aos_core.net.connect.DatagramInit; @@ -15,23 +16,33 @@ import java.util.*; /** * This component is responsible for managing the set of players connected to - * the server. + * the server, and components related to that. */ public class PlayerManager { private static final Logger log = LoggerFactory.getLogger(PlayerManager.class); + private final Server server; private final Map players = new HashMap<>(); private final Map clientHandlers = new HashMap<>(); private int nextClientId = 1; + public PlayerManager(Server server) { + this.server = server; + } + public synchronized ServerPlayer register(ClientCommunicationHandler handler, String username) { ServerPlayer player = new ServerPlayer(nextClientId++, username); + log.info("Registered player \"{}\" with id {}", player.getUsername(), player.getId()); players.put(player.getId(), player); clientHandlers.put(player.getId(), handler); - log.info("Registered player \"{}\" with id {}", player.getUsername(), player.getId()); - player.setPosition(new Vector3f(0, 64, 0)); + Team team = findBestTeamForNewPlayer(); + if (team != null) { + player.setTeam(team); + log.info("Player \"{}\" joined the \"{}\" team.", player.getUsername(), team.getName()); + } + player.setPosition(getBestSpawnPoint(player)); + // Tell all other players that this one has joined. broadcastTcpMessageToAllBut(new PlayerJoinMessage(player), player); - broadcastUdpMessage(player.getUpdateMessage()); return player; } @@ -67,6 +78,39 @@ public class PlayerManager { return Collections.unmodifiableCollection(clientHandlers.values()); } + /** + * Finds the team that's best suited for adding a new player. This is the + * team that has the minimum (or tied for minimum) number of players. + * @return The best team to add a player to. + */ + private Team findBestTeamForNewPlayer() { + Team minTeam = null; + int minCount = Integer.MAX_VALUE; + for (var team : server.getTeams().values()) { + int playerCount = (int) players.values().stream() + .filter(p -> Objects.equals(p.getTeam(), team)) + .count(); + if (playerCount < minCount) { + minCount = playerCount; + minTeam = team; + } + } + return minTeam; + } + + /** + * Determines the best location to spawn the given player at. This is + * usually the player's team spawn point, if they have a team. Otherwise, a + * spawn point is randomly chosen from the world. If no spawnpoint exists in + * the world, we resort to 0, 0, 0 as the last option. + * @param player The player to spawn. + * @return The best location to spawn the player at. + */ + private Vector3f getBestSpawnPoint(ServerPlayer player) { + if (player.getTeam() != null) return player.getTeam().getSpawnPoint(); + return server.getWorld().getSpawnPoints().values().stream().findAny().orElse(new Vector3f(0, 0, 0)); + } + public void handleUdpInit(DatagramInit init, DatagramPacket packet) { var handler = getHandler(init.clientId()); if (handler != null) { diff --git a/server/src/main/java/nl/andrewl/aos2_server/Server.java b/server/src/main/java/nl/andrewl/aos2_server/Server.java index a2e461a..27fd364 100644 --- a/server/src/main/java/nl/andrewl/aos2_server/Server.java +++ b/server/src/main/java/nl/andrewl/aos2_server/Server.java @@ -3,6 +3,7 @@ package nl.andrewl.aos2_server; import nl.andrewl.aos2_server.config.ServerConfig; import nl.andrewl.aos2_server.logic.WorldUpdater; import nl.andrewl.aos_core.config.Config; +import nl.andrewl.aos_core.model.Team; import nl.andrewl.aos_core.model.world.World; import nl.andrewl.aos_core.model.world.Worlds; import nl.andrewl.aos_core.net.UdpReceiver; @@ -10,13 +11,14 @@ import nl.andrewl.aos_core.net.client.ClientInputState; import nl.andrewl.aos_core.net.client.ClientOrientationState; import nl.andrewl.aos_core.net.connect.DatagramInit; import nl.andrewl.record_net.Message; +import org.joml.Vector3f; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.net.*; import java.nio.file.Path; -import java.util.List; +import java.util.*; import java.util.concurrent.ForkJoinPool; public class Server implements Runnable { @@ -27,6 +29,8 @@ public class Server implements Runnable { private volatile boolean running; private final ServerConfig config; private final PlayerManager playerManager; + private final Map teams; + private final Map teamSpawnPoints; private final World world; private final WorldUpdater worldUpdater; @@ -36,9 +40,14 @@ public class Server implements Runnable { this.serverSocket.setReuseAddress(true); this.datagramSocket = new DatagramSocket(config.port); this.datagramSocket.setReuseAddress(true); - this.playerManager = new PlayerManager(); + this.playerManager = new PlayerManager(this); this.worldUpdater = new WorldUpdater(this, config.ticksPerSecond); this.world = Worlds.testingWorld(); + this.teams = new HashMap<>(); + this.teamSpawnPoints = new HashMap<>(); + // TODO: Add some way to configure teams with config files. + teams.put(1, new Team(1, "Red", new Vector3f(0.8f, 0, 0), world.getSpawnPoint("first"))); + teams.put(2, new Team(2, "Blue", new Vector3f(0, 0, 0.8f), world.getSpawnPoint("first"))); } @Override @@ -61,20 +70,21 @@ public class Server implements Runnable { } public void handleUdpMessage(Message msg, DatagramPacket packet) { + long now = System.currentTimeMillis(); if (msg instanceof DatagramInit init) { playerManager.handleUdpInit(init, packet); } else if (msg instanceof ClientInputState inputState) { ServerPlayer player = playerManager.getPlayer(inputState.clientId()); if (player != null) { if (player.getActionManager().setLastInputState(inputState)) { - playerManager.broadcastUdpMessage(player.getUpdateMessage()); + playerManager.broadcastUdpMessage(player.getUpdateMessage(now)); } } } else if (msg instanceof ClientOrientationState orientationState) { ServerPlayer player = playerManager.getPlayer(orientationState.clientId()); if (player != null) { player.setOrientation(orientationState.x(), orientationState.y()); - playerManager.broadcastUdpMessageToAllBut(player.getUpdateMessage(), player); + playerManager.broadcastUdpMessageToAllBut(player.getUpdateMessage(now), player); } } } @@ -111,6 +121,18 @@ public class Server implements Runnable { return playerManager; } + public Map getTeams() { + return teams; + } + + public String getSpawnPoint(Team team) { + return teamSpawnPoints.get(team); + } + + public Collection getPlayersInTeam(Team team) { + return playerManager.getPlayers().stream().filter(p -> Objects.equals(p.getTeam(), team)).toList(); + } + public static void main(String[] args) throws IOException { List configPaths = Config.getCommonConfigPaths(); if (args.length > 0) { diff --git a/server/src/main/java/nl/andrewl/aos2_server/ServerPlayer.java b/server/src/main/java/nl/andrewl/aos2_server/ServerPlayer.java index 9741f66..ea5a52e 100644 --- a/server/src/main/java/nl/andrewl/aos2_server/ServerPlayer.java +++ b/server/src/main/java/nl/andrewl/aos2_server/ServerPlayer.java @@ -43,9 +43,9 @@ public class ServerPlayer extends Player { * various clients. * @return The update message. */ - public PlayerUpdateMessage getUpdateMessage() { + public PlayerUpdateMessage getUpdateMessage(long timestamp) { return new PlayerUpdateMessage( - id, + id, timestamp, position.x, position.y, position.z, velocity.x, velocity.y, velocity.z, orientation.x, orientation.y, diff --git a/server/src/main/java/nl/andrewl/aos2_server/logic/WorldUpdater.java b/server/src/main/java/nl/andrewl/aos2_server/logic/WorldUpdater.java index 095a58a..dc86d35 100644 --- a/server/src/main/java/nl/andrewl/aos2_server/logic/WorldUpdater.java +++ b/server/src/main/java/nl/andrewl/aos2_server/logic/WorldUpdater.java @@ -35,7 +35,7 @@ public class WorldUpdater implements Runnable { running = true; while (running) { long start = System.nanoTime(); - tick(); + tick(System.currentTimeMillis()); long elapsedNs = System.nanoTime() - start; if (elapsedNs > nsPerTick) { log.warn("Took {} ns to do one tick, which is more than the desired {} ns per tick.", elapsedNs, nsPerTick); @@ -52,10 +52,12 @@ public class WorldUpdater implements Runnable { } } - private void tick() { + private void tick(long currentTimeMillis) { for (var player : server.getPlayerManager().getPlayers()) { player.getActionManager().tick(secondsPerTick, server.getWorld(), server); - if (player.getActionManager().isUpdated()) server.getPlayerManager().broadcastUdpMessage(player.getUpdateMessage()); + if (player.getActionManager().isUpdated()) { + server.getPlayerManager().broadcastUdpMessage(player.getUpdateMessage(currentTimeMillis)); + } } } }