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 7e3d748..de35b1e 100644 --- a/client/src/main/java/nl/andrewl/aos2_client/Client.java +++ b/client/src/main/java/nl/andrewl/aos2_client/Client.java @@ -12,6 +12,7 @@ import nl.andrewl.aos2_client.sound.SoundManager; import nl.andrewl.aos2_client.sound.SoundSource; import nl.andrewl.aos_core.config.Config; import nl.andrewl.aos_core.model.Player; +import nl.andrewl.aos_core.model.Projectile; import nl.andrewl.aos_core.model.Team; import nl.andrewl.aos_core.net.client.*; import nl.andrewl.aos_core.net.world.ChunkDataMessage; @@ -43,12 +44,14 @@ public class Client implements Runnable { private ClientWorld world; private ClientPlayer myPlayer; private final Map players; + private final Map projectiles; private Map teams; public Client(ClientConfig config) { this.config = config; this.players = new ConcurrentHashMap<>(); this.teams = new ConcurrentHashMap<>(); + this.projectiles = new ConcurrentHashMap<>(); this.communicationHandler = new CommunicationHandler(this); this.inputHandler = new InputHandler(this, communicationHandler); } @@ -96,6 +99,7 @@ public class Client implements Runnable { world.processQueuedChunkUpdates(); gameRenderer.getCamera().interpolatePosition(dt); interpolatePlayers(dt); + interpolateProjectiles(dt); gameRenderer.draw(); lastFrameAt = now; } @@ -120,7 +124,9 @@ public class Client implements Runnable { if (gameRenderer != null) { gameRenderer.getCamera().setToPlayer(myPlayer); } - soundManager.updateListener(myPlayer.getPosition(), myPlayer.getVelocity()); + if (soundManager != null) { + soundManager.updateListener(myPlayer.getPosition(), myPlayer.getVelocity()); + } lastPlayerUpdate = playerUpdate.timestamp(); } else { OtherPlayer p = players.get(playerUpdate.clientId()); @@ -151,6 +157,18 @@ public class Client implements Runnable { players.remove(leaveMessage.id()); } else if (msg instanceof SoundMessage soundMessage) { playerSource.play(soundManager.getSoundBuffer(soundMessage.name())); + } else if (msg instanceof ProjectileMessage pm) { + Projectile p = projectiles.get(pm.id()); + if (p == null && !pm.destroyed()) { + p = new Projectile(pm.id(), new Vector3f(pm.px(), pm.py(), pm.pz()), new Vector3f(pm.vx(), pm.vy(), pm.vz()), pm.type()); + projectiles.put(p.getId(), p); + } else if (p != null) { + p.getPosition().set(pm.px(), pm.py(), pm.pz()); // Don't update position, it's too short of a timeframe to matter. + p.getVelocity().set(pm.vx(), pm.vy(), pm.vz()); + if (pm.destroyed()) { + projectiles.remove(p.getId()); + } + } } } @@ -174,6 +192,10 @@ public class Client implements Runnable { return players; } + public Map getProjectiles() { + return projectiles; + } + public void interpolatePlayers(float dt) { Vector3f movement = new Vector3f(); for (var player : players.values()) { @@ -183,6 +205,14 @@ public class Client implements Runnable { } } + public void interpolateProjectiles(float dt) { + Vector3f movement = new Vector3f(); + for (var proj : projectiles.values()) { + movement.set(proj.getVelocity()).mul(dt); + proj.getPosition().add(movement); + } + } + public static void main(String[] args) throws IOException { List configPaths = Config.getCommonConfigPaths(); if (args.length > 0) { diff --git a/client/src/main/java/nl/andrewl/aos2_client/config/ClientConfig.java b/client/src/main/java/nl/andrewl/aos2_client/config/ClientConfig.java index 16b1b0b..b4021ee 100644 --- a/client/src/main/java/nl/andrewl/aos2_client/config/ClientConfig.java +++ b/client/src/main/java/nl/andrewl/aos2_client/config/ClientConfig.java @@ -12,8 +12,8 @@ public class ClientConfig { } public static class DisplayConfig { - public boolean fullscreen = true; - public boolean captureCursor = true; + public boolean fullscreen = false; + public boolean captureCursor = false; public float fov = 70; } } 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 72e80b3..2e5b3f9 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 @@ -45,6 +45,7 @@ public class GameRenderer { private Model playerModel; private Model rifleModel; private Model blockModel; + private Model bulletModel; // Standard GUI textures. private GUITexture crosshairTexture; @@ -132,6 +133,7 @@ public class GameRenderer { playerModel = new Model("model/player_simple.obj", "model/simple_player.png"); rifleModel = new Model("model/rifle.obj", "model/rifle.png"); blockModel = new Model("model/block.obj", "model/block.png"); + bulletModel = new Model("model/bullet.obj", "model/bullet.png"); } catch (IOException e) { throw new RuntimeException(e); } @@ -216,6 +218,16 @@ public class GameRenderer { } blockModel.unbind(); + bulletModel.bind(); + Matrix4f projectileTransform = new Matrix4f(); + for (var projectile : client.getProjectiles().values()) { + projectileTransform.identity() + .translate(projectile.getPosition()) + .rotateTowards(projectile.getVelocity(), Camera.UP); + modelRenderer.render(bulletModel, projectileTransform); + } + bulletModel.unbind(); + modelRenderer.end(); // GUI rendering diff --git a/client/src/main/resources/model/bullet.mtl b/client/src/main/resources/model/bullet.mtl new file mode 100644 index 0000000..25f313e --- /dev/null +++ b/client/src/main/resources/model/bullet.mtl @@ -0,0 +1,13 @@ +# Blender MTL File: 'bullet.blend' +# Material Count: 1 + +newmtl Material.001 +Ns 225.000000 +Ka 1.000000 1.000000 1.000000 +Kd 0.800000 0.800000 0.800000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.450000 +d 1.000000 +illum 2 +map_Kd /home/andrew/Programming/github-andrewlalis/ace-of-shades-2/client/src/main/resources/model/bullet.png diff --git a/client/src/main/resources/model/bullet.obj b/client/src/main/resources/model/bullet.obj new file mode 100644 index 0000000..00c81e5 --- /dev/null +++ b/client/src/main/resources/model/bullet.obj @@ -0,0 +1,140 @@ +# Blender v2.82 (sub 7) OBJ File: 'bullet.blend' +# www.blender.org +mtllib bullet.mtl +o Cylinder +v 0.000000 0.020000 -0.033933 +v 0.000000 0.020000 0.029329 +v 0.017321 0.010000 -0.033933 +v 0.017321 0.010000 0.029329 +v 0.017321 -0.010000 -0.033933 +v 0.017321 -0.010000 0.029329 +v -0.000000 -0.020000 -0.033933 +v -0.000000 -0.020000 0.029329 +v -0.017321 -0.010000 -0.033933 +v -0.017321 -0.010000 0.029329 +v -0.017321 0.010000 -0.033933 +v -0.017321 0.010000 0.029329 +v -0.000000 0.015593 0.042620 +v 0.013504 0.007796 0.042620 +v 0.013504 -0.007796 0.042620 +v -0.000000 -0.015593 0.042620 +v -0.013504 -0.007796 0.042620 +v -0.013504 0.007796 0.042620 +v -0.000000 0.004471 0.055747 +v 0.003872 0.002236 0.055747 +v 0.003872 -0.002236 0.055747 +v -0.000000 -0.004471 0.055747 +v -0.003872 -0.002236 0.055747 +v -0.003872 0.002236 0.055747 +vt 0.436144 0.309159 +vt 0.610602 1.000000 +vt 0.436144 1.000000 +vt 0.610602 0.309159 +vt 0.697830 0.973380 +vt 0.348915 0.717461 +vt 0.261686 0.000000 +vt 0.348915 0.026620 +vt 0.261686 0.690841 +vt 0.087229 0.000000 +vt 0.242465 0.841851 +vt 0.087229 0.690841 +vt 0.000000 0.026620 +vt 0.348915 0.282539 +vt 0.348915 0.973380 +vt 1.000000 0.110280 +vt 0.848915 0.441121 +vt 0.697831 0.110280 +vt 0.815623 0.441121 +vt 0.849399 0.588424 +vt 0.815623 0.563770 +vt 0.455366 0.158149 +vt 0.106450 0.841851 +vt 0.659387 0.137395 +vt 0.697830 0.282539 +vt 0.038443 0.862605 +vt 0.000000 0.717461 +vt 0.591380 0.158149 +vt 0.781846 0.637733 +vt 0.781846 0.588424 +vt 0.697831 0.699058 +vt 0.815623 0.662388 +vt 0.815623 0.785037 +vt 0.697831 0.527100 +vt 0.503872 0.000000 +vt 0.542874 0.000000 +vt 0.849399 0.637733 +vt 0.933415 0.699058 +vt 0.154956 1.000000 +vt 0.310472 0.862605 +vt 0.848915 0.000000 +vt 1.000000 0.330840 +vt 0.697831 0.330840 +vt 0.933415 0.527100 +vt 0.387358 0.137395 +vt 0.193959 1.000000 +vn 0.5000 0.8660 -0.0000 +vn 1.0000 0.0000 0.0000 +vn 0.5000 -0.8660 0.0000 +vn -0.5000 -0.8660 0.0000 +vn 0.4806 -0.8324 0.2760 +vn -1.0000 0.0000 0.0000 +vn -0.5000 0.8660 -0.0000 +vn -0.0000 0.0000 -1.0000 +vn 0.8062 0.0000 0.5916 +vn -0.4806 0.8324 0.2760 +vn -0.4806 -0.8324 0.2760 +vn 0.9612 0.0000 0.2760 +vn -0.9612 0.0000 0.2760 +vn 0.4806 0.8324 0.2760 +vn 0.0000 0.0000 1.0000 +vn -0.8062 0.0000 0.5916 +vn 0.4031 -0.6982 0.5916 +vn 0.4031 0.6982 0.5916 +vn -0.4031 0.6982 0.5916 +vn -0.4031 -0.6982 0.5916 +usemtl Material.001 +s off +f 2/1/1 3/2/1 1/3/1 +f 4/4/2 5/5/2 3/2/2 +f 6/6/3 7/7/3 5/8/3 +f 8/9/4 9/10/4 7/7/4 +f 6/6/5 16/11/5 8/9/5 +f 10/12/6 11/13/6 9/10/6 +f 12/14/7 1/3/7 11/15/7 +f 3/16/8 7/17/8 11/18/8 +f 15/19/9 20/20/9 21/21/9 +f 12/14/10 13/22/10 2/1/10 +f 8/9/11 17/23/11 10/12/11 +f 4/4/12 15/24/12 6/25/12 +f 10/12/13 18/26/13 12/27/13 +f 4/4/14 13/22/14 14/28/14 +f 23/29/15 22/30/15 21/21/15 +f 17/31/16 24/32/16 18/33/16 +f 16/34/17 21/21/17 22/30/17 +f 14/28/18 19/35/18 20/36/18 +f 18/33/19 19/37/19 13/38/19 +f 16/11/20 23/39/20 17/23/20 +f 2/1/1 4/4/1 3/2/1 +f 4/4/2 6/25/2 5/5/2 +f 6/6/3 8/9/3 7/7/3 +f 8/9/4 10/12/4 9/10/4 +f 6/6/5 15/40/5 16/11/5 +f 10/12/6 12/27/6 11/13/6 +f 12/14/7 2/1/7 1/3/7 +f 11/18/8 1/41/8 3/16/8 +f 3/16/8 5/42/8 7/17/8 +f 7/17/8 9/43/8 11/18/8 +f 15/19/9 14/44/9 20/20/9 +f 12/14/10 18/45/10 13/22/10 +f 8/9/11 16/11/11 17/23/11 +f 4/4/12 14/28/12 15/24/12 +f 10/12/13 17/23/13 18/26/13 +f 4/4/14 2/1/14 13/22/14 +f 21/21/15 20/20/15 23/29/15 +f 20/20/15 19/37/15 23/29/15 +f 19/37/15 24/32/15 23/29/15 +f 17/31/16 23/29/16 24/32/16 +f 16/34/17 15/19/17 21/21/17 +f 14/28/18 13/22/18 19/35/18 +f 18/33/19 24/32/19 19/37/19 +f 16/11/20 22/46/20 23/39/20 diff --git a/client/src/main/resources/model/bullet.png b/client/src/main/resources/model/bullet.png new file mode 100644 index 0000000..56fb7bb Binary files /dev/null and b/client/src/main/resources/model/bullet.png differ 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 779f983..b49d007 100644 --- a/core/src/main/java/nl/andrewl/aos_core/Net.java +++ b/core/src/main/java/nl/andrewl/aos_core/Net.java @@ -43,6 +43,7 @@ public final class Net { serializer.registerTypeSerializer(14, new ItemStackSerializer()); serializer.registerType(15, InventorySelectedStackMessage.class); serializer.registerType(16, SoundMessage.class); + serializer.registerType(17, ProjectileMessage.class); } public static ExtendedDataInputStream getInputStream(InputStream in) { diff --git a/core/src/main/java/nl/andrewl/aos_core/model/Projectile.java b/core/src/main/java/nl/andrewl/aos_core/model/Projectile.java new file mode 100644 index 0000000..7e758b7 --- /dev/null +++ b/core/src/main/java/nl/andrewl/aos_core/model/Projectile.java @@ -0,0 +1,35 @@ +package nl.andrewl.aos_core.model; + +import org.joml.Vector3f; + +/** + * Represents a flying projectile object in the world (usually a bullet). + * All projectiles have their orientation dependent on their velocity vector. + */ +public class Projectile { + protected final int id; + protected final Vector3f position; + protected final Vector3f velocity; + + public enum Type {BULLET} + protected final Type type; + + public Projectile(int id, Vector3f position, Vector3f velocity, Type type) { + this.id = id; + this.position = position; + this.velocity = velocity; + this.type = type; + } + + public int getId() { + return id; + } + + public Vector3f getPosition() { + return position; + } + + public Vector3f getVelocity() { + return velocity; + } +} diff --git a/core/src/main/java/nl/andrewl/aos_core/model/item/ItemTypes.java b/core/src/main/java/nl/andrewl/aos_core/model/item/ItemTypes.java index 639ecf4..b8e2080 100644 --- a/core/src/main/java/nl/andrewl/aos_core/model/item/ItemTypes.java +++ b/core/src/main/java/nl/andrewl/aos_core/model/item/ItemTypes.java @@ -1,5 +1,6 @@ package nl.andrewl.aos_core.model.item; +import nl.andrewl.aos_core.model.item.gun.Ak47; import nl.andrewl.aos_core.model.item.gun.Rifle; import java.util.HashMap; @@ -14,10 +15,12 @@ public final class ItemTypes { public static final BlockItem BLOCK = new BlockItem(1); public static final Rifle RIFLE = new Rifle(2); + public static final Ak47 AK_47 = new Ak47(3); static { registerType(BLOCK); registerType(RIFLE); + registerType(AK_47); } public static void registerType(Item type) { 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 new file mode 100644 index 0000000..37c6517 --- /dev/null +++ b/core/src/main/java/nl/andrewl/aos_core/model/item/gun/Ak47.java @@ -0,0 +1,21 @@ +package nl.andrewl.aos_core.model.item.gun; + +import nl.andrewl.aos_core.model.item.Gun; + +public class Ak47 extends Gun { + public Ak47(int id) { + super( + id, + "AK-47", + 4, + 30, + 1, + 0.95f, + 0.1f, + 1.2f, + 40f, + 30f, + true + ); + } +} 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 7bf35d8..d68b33a 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 @@ -129,7 +129,8 @@ public final class Worlds { } } - world.setSpawnPoint("first", new Vector3f(0.5f, 0f, 0.5f)); + world.setSpawnPoint("A", new Vector3f(0.5f, 0f, 0.5f)); + world.setSpawnPoint("B", new Vector3f(20.5f, 0f, 20.5f)); return world; } diff --git a/core/src/main/java/nl/andrewl/aos_core/net/client/ProjectileMessage.java b/core/src/main/java/nl/andrewl/aos_core/net/client/ProjectileMessage.java new file mode 100644 index 0000000..26f9b38 --- /dev/null +++ b/core/src/main/java/nl/andrewl/aos_core/net/client/ProjectileMessage.java @@ -0,0 +1,16 @@ +package nl.andrewl.aos_core.net.client; + +import nl.andrewl.aos_core.model.Projectile; +import nl.andrewl.record_net.Message; + +/** + * A simple message with an update about a bullet. Instead of having a separate + * message to indicate that a bullet has been destroyed, we add it to the + * normal bullet message. + */ +public record ProjectileMessage( + int id, Projectile.Type type, + float px, float py, float pz, + float vx, float vy, float vz, + boolean destroyed +) implements Message {} diff --git a/design/bullet.svg b/design/gui/bullet.svg similarity index 100% rename from design/bullet.svg rename to design/gui/bullet.svg diff --git a/design/clip.svg b/design/gui/clip.svg similarity index 100% rename from design/clip.svg rename to design/gui/clip.svg diff --git a/design/crosshair.svg b/design/gui/crosshair.svg similarity index 100% rename from design/crosshair.svg rename to design/gui/crosshair.svg diff --git a/design/block.blend b/design/models/block.blend similarity index 100% rename from design/block.blend rename to design/models/block.blend diff --git a/design/block.blend1 b/design/models/block.blend1 similarity index 100% rename from design/block.blend1 rename to design/models/block.blend1 diff --git a/design/models/bullet.blend b/design/models/bullet.blend new file mode 100644 index 0000000..2deb627 Binary files /dev/null and b/design/models/bullet.blend differ diff --git a/design/models/bullet.blend1 b/design/models/bullet.blend1 new file mode 100644 index 0000000..56328be Binary files /dev/null and b/design/models/bullet.blend1 differ diff --git a/design/player.blend b/design/models/player.blend similarity index 100% rename from design/player.blend rename to design/models/player.blend diff --git a/design/player.blend1 b/design/models/player.blend1 similarity index 100% rename from design/player.blend1 rename to design/models/player.blend1 diff --git a/design/player_simple.blend b/design/models/player_simple.blend similarity index 100% rename from design/player_simple.blend rename to design/models/player_simple.blend diff --git a/design/player_simple.blend1 b/design/models/player_simple.blend1 similarity index 100% rename from design/player_simple.blend1 rename to design/models/player_simple.blend1 diff --git a/design/rifle.blend b/design/models/rifle.blend similarity index 100% rename from design/rifle.blend rename to design/models/rifle.blend diff --git a/design/rifle.blend1 b/design/models/rifle.blend1 similarity index 100% rename from design/rifle.blend1 rename to design/models/rifle.blend1 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 32979d3..6953db2 100644 --- a/server/src/main/java/nl/andrewl/aos2_server/ClientCommunicationHandler.java +++ b/server/src/main/java/nl/andrewl/aos2_server/ClientCommunicationHandler.java @@ -1,5 +1,6 @@ package nl.andrewl.aos2_server; +import nl.andrewl.aos2_server.model.ServerPlayer; import nl.andrewl.aos_core.Net; import nl.andrewl.aos_core.model.item.ItemStack; import nl.andrewl.aos_core.model.world.Chunk; @@ -163,7 +164,7 @@ public class ClientCommunicationHandler { WorldIO.write(server.getWorld(), out); // Team data. - var teams = server.getTeams().values(); + var teams = server.getTeamManager().getTeams(); out.writeInt(teams.size()); for (var team : teams) { out.writeInt(team.getId()); 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 7776f1a..c0408bb 100644 --- a/server/src/main/java/nl/andrewl/aos2_server/PlayerManager.java +++ b/server/src/main/java/nl/andrewl/aos2_server/PlayerManager.java @@ -1,5 +1,6 @@ package nl.andrewl.aos2_server; +import nl.andrewl.aos2_server.model.ServerPlayer; import nl.andrewl.aos_core.Net; import nl.andrewl.aos_core.model.Team; import nl.andrewl.aos_core.net.client.PlayerJoinMessage; @@ -78,6 +79,15 @@ public class PlayerManager { return Collections.unmodifiableCollection(clientHandlers.values()); } + public void tick(long currentTimeMillis, float dt) { + for (var player : players.values()) { + player.getActionManager().tick(currentTimeMillis, dt, server.getWorld(), server); + if (player.getActionManager().isUpdated()) { + broadcastUdpMessage(player.getUpdateMessage(currentTimeMillis)); + } + } + } + /** * 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. @@ -86,7 +96,7 @@ public class PlayerManager { private Team findBestTeamForNewPlayer() { Team minTeam = null; int minCount = Integer.MAX_VALUE; - for (var team : server.getTeams().values()) { + for (var team : server.getTeamManager().getTeams()) { int playerCount = (int) players.values().stream() .filter(p -> Objects.equals(p.getTeam(), team)) .count(); diff --git a/server/src/main/java/nl/andrewl/aos2_server/ProjectileManager.java b/server/src/main/java/nl/andrewl/aos2_server/ProjectileManager.java new file mode 100644 index 0000000..e149465 --- /dev/null +++ b/server/src/main/java/nl/andrewl/aos2_server/ProjectileManager.java @@ -0,0 +1,58 @@ +package nl.andrewl.aos2_server; + +import nl.andrewl.aos2_server.model.ServerPlayer; +import nl.andrewl.aos2_server.model.ServerProjectile; +import nl.andrewl.aos_core.model.Projectile; +import org.joml.Vector3f; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.Map; +import java.util.Queue; + +public class ProjectileManager { + private final Server server; + private int nextProjectileId = 1; + private final Map projectiles; + private final Queue removalQueue; + + public ProjectileManager(Server server) { + this.server = server; + this.projectiles = new HashMap<>(); + this.removalQueue = new LinkedList<>(); + } + + public void spawnBullet(ServerPlayer player) { + int id = nextProjectileId++; + if (nextProjectileId == Integer.MAX_VALUE) nextProjectileId = 1; + Vector3f pos = new Vector3f(player.getEyePosition()); + Vector3f vel = new Vector3f(player.getViewVector()).normalize().mul(300); + ServerProjectile bullet = new ServerProjectile(id, pos, vel, Projectile.Type.BULLET, player); + projectiles.put(bullet.getId(), bullet); + server.getPlayerManager().broadcastUdpMessage(bullet.toMessage(false)); + } + + public void tick(float dt) { + for (var projectile : projectiles.values()) { + tickProjectile(projectile, dt); + } + while (!removalQueue.isEmpty()) { + ServerProjectile projectile = removalQueue.remove(); + projectiles.remove(projectile.getId()); + } + } + + private void tickProjectile(ServerProjectile projectile, float dt) { + projectile.getVelocity().y -= server.getConfig().physics.gravity * dt; + // TODO: Check if bullet will hit anything, like blocks or players, if it follows current velocity. + Vector3f movement = new Vector3f(projectile.getVelocity()).mul(dt); + + projectile.getPosition().add(movement); + if (projectile.getDistanceTravelled() > 500) { + removalQueue.add(projectile); + server.getPlayerManager().broadcastUdpMessage(projectile.toMessage(true)); + } else { + server.getPlayerManager().broadcastUdpMessage(projectile.toMessage(false)); + } + } +} 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 27fd364..ae07ccc 100644 --- a/server/src/main/java/nl/andrewl/aos2_server/Server.java +++ b/server/src/main/java/nl/andrewl/aos2_server/Server.java @@ -2,8 +2,8 @@ package nl.andrewl.aos2_server; import nl.andrewl.aos2_server.config.ServerConfig; import nl.andrewl.aos2_server.logic.WorldUpdater; +import nl.andrewl.aos2_server.model.ServerPlayer; 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; @@ -18,7 +18,7 @@ import org.slf4j.LoggerFactory; import java.io.IOException; import java.net.*; import java.nio.file.Path; -import java.util.*; +import java.util.List; import java.util.concurrent.ForkJoinPool; public class Server implements Runnable { @@ -29,8 +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 TeamManager teamManager; + private final ProjectileManager projectileManager; private final World world; private final WorldUpdater worldUpdater; @@ -41,13 +41,14 @@ public class Server implements Runnable { this.datagramSocket = new DatagramSocket(config.port); this.datagramSocket.setReuseAddress(true); this.playerManager = new PlayerManager(this); + this.teamManager = new TeamManager(this); + this.projectileManager = new ProjectileManager(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"))); + + // TODO: Add some way to configure teams with config files or commands. + teamManager.addTeam("Red", new Vector3f(0.8f, 0, 0), "A"); + teamManager.addTeam("Blue", new Vector3f(0, 0, 0.8f), "B"); } @Override @@ -121,16 +122,12 @@ public class Server implements Runnable { return playerManager; } - public Map getTeams() { - return teams; + public TeamManager getTeamManager() { + return teamManager; } - 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 ProjectileManager getProjectileManager() { + return projectileManager; } public static void main(String[] args) throws IOException { diff --git a/server/src/main/java/nl/andrewl/aos2_server/TeamManager.java b/server/src/main/java/nl/andrewl/aos2_server/TeamManager.java new file mode 100644 index 0000000..ff39c84 --- /dev/null +++ b/server/src/main/java/nl/andrewl/aos2_server/TeamManager.java @@ -0,0 +1,49 @@ +package nl.andrewl.aos2_server; + +import nl.andrewl.aos2_server.model.ServerPlayer; +import nl.andrewl.aos_core.model.Team; +import org.joml.Vector3f; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * Component that manages the teams on a server. + */ +public class TeamManager { + private int nextTeamId = 1; + private final Server server; + private final Map teams; + + public TeamManager(Server server) { + this.server = server; + this.teams = new HashMap<>(); + } + + public synchronized void addTeam(String name, Vector3f color, String spawnPoint) { + int id = nextTeamId++; + teams.put(id, new Team(id, name, color, server.getWorld().getSpawnPoint(spawnPoint))); + } + + public Team getTeam(int id) { + return teams.get(id); + } + + public Collection getTeams() { + return Collections.unmodifiableCollection(teams.values()); + } + + public Collection getPlayers(Team team) { + return server.getPlayerManager().getPlayers().stream() + .filter(p -> p.getTeam().equals(team)) + .toList(); + } + + public Collection getPlayers(int id) { + Team team = getTeam(id); + if (team == null) return Collections.emptyList(); + return getPlayers(team); + } +} 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 b3f484d..b2206b4 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 @@ -1,7 +1,7 @@ package nl.andrewl.aos2_server.logic; import nl.andrewl.aos2_server.Server; -import nl.andrewl.aos2_server.ServerPlayer; +import nl.andrewl.aos2_server.model.ServerPlayer; import nl.andrewl.aos2_server.config.ServerConfig; import nl.andrewl.aos_core.model.item.*; import nl.andrewl.aos_core.model.world.World; @@ -18,7 +18,7 @@ import org.joml.Vector3i; import java.util.ArrayList; import java.util.List; -import static nl.andrewl.aos2_server.ServerPlayer.*; +import static nl.andrewl.aos2_server.model.ServerPlayer.*; /** * Component that manages a server player's current actions and movement. @@ -62,9 +62,8 @@ public class PlayerActionManager { return updated; } - public void tick(float dt, World world, Server server) { + public void tick(long now, float dt, World world, Server server) { updated = false; // Reset the updated flag. This will be set to true if the player was updated in this tick. - long now = System.currentTimeMillis(); if (player.getInventory().getSelectedIndex() != lastInputState.selectedInventoryIndex()) { player.getInventory().setSelectedIndex(lastInputState.selectedInventoryIndex()); // Tell the client that their inventory slot has been updated properly. @@ -100,12 +99,7 @@ public class PlayerActionManager { now - gunLastShotAt > gun.getShotCooldownTime() * 1000 && (gun.isAutomatic() || !gunNeedsReCock) ) { - // TODO: trace a ray from gun to see if players intersect with it. - var hit = world.getLookingAtPos(player.getEyePosition(), player.getViewVector(), 100); - if (hit != null) { - world.setBlockAt(hit.pos().x, hit.pos().y, hit.pos().z, (byte) 0); - server.getPlayerManager().broadcastUdpMessage(ChunkUpdateMessage.fromWorld(hit.pos(), world)); - } + server.getProjectileManager().spawnBullet(player); g.setBulletCount(g.getBulletCount() - 1); gunLastShotAt = now; if (!gun.isAutomatic()) { 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 dc86d35..22470e4 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 @@ -52,12 +52,14 @@ public class WorldUpdater implements Runnable { } } + /** + * The main server update method, that runs once on each game tick, and + * performs all game state updates. + * @param currentTimeMillis The current timestamp for the tick. This may + * be needed for certain functions in logic. + */ 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(currentTimeMillis)); - } - } + server.getPlayerManager().tick(currentTimeMillis, secondsPerTick); + server.getProjectileManager().tick(secondsPerTick); } } diff --git a/server/src/main/java/nl/andrewl/aos2_server/ServerPlayer.java b/server/src/main/java/nl/andrewl/aos2_server/model/ServerPlayer.java similarity index 85% rename from server/src/main/java/nl/andrewl/aos2_server/ServerPlayer.java rename to server/src/main/java/nl/andrewl/aos2_server/model/ServerPlayer.java index ea5a52e..f7a9f4b 100644 --- a/server/src/main/java/nl/andrewl/aos2_server/ServerPlayer.java +++ b/server/src/main/java/nl/andrewl/aos2_server/model/ServerPlayer.java @@ -1,4 +1,4 @@ -package nl.andrewl.aos2_server; +package nl.andrewl.aos2_server.model; import nl.andrewl.aos2_server.logic.PlayerActionManager; import nl.andrewl.aos_core.model.Player; @@ -26,8 +26,9 @@ public class ServerPlayer extends Player { super(id, username); this.inventory = new Inventory(new ArrayList<>(), 0); this.actionManager = new PlayerActionManager(this); - inventory.getItemStacks().add(new GunItemStack(ItemTypes.get("Rifle"))); - inventory.getItemStacks().add(new BlockItemStack(ItemTypes.get("Block"), 50, (byte) 1)); + inventory.getItemStacks().add(new GunItemStack(ItemTypes.RIFLE)); + inventory.getItemStacks().add(new BlockItemStack(ItemTypes.BLOCK, 50, (byte) 1)); + inventory.getItemStacks().add(new GunItemStack(ItemTypes.AK_47)); } public PlayerActionManager getActionManager() { diff --git a/server/src/main/java/nl/andrewl/aos2_server/model/ServerProjectile.java b/server/src/main/java/nl/andrewl/aos2_server/model/ServerProjectile.java new file mode 100644 index 0000000..a244c8e --- /dev/null +++ b/server/src/main/java/nl/andrewl/aos2_server/model/ServerProjectile.java @@ -0,0 +1,42 @@ +package nl.andrewl.aos2_server.model; + +import nl.andrewl.aos_core.model.Projectile; +import nl.andrewl.aos_core.net.client.ProjectileMessage; +import org.joml.Vector3f; + +/** + * A bullet with extra information about who shot it, and from where it was + * shot, so we can properly delete the bullet and know who is responsible + * for damage it causes. + */ +public class ServerProjectile extends Projectile { + private final ServerPlayer player; + private final Vector3f origin; + + public ServerProjectile(int id, Vector3f position, Vector3f velocity, Type type, ServerPlayer player) { + super(id, position, velocity, type); + this.player = player; + this.origin = new Vector3f(position); + } + + public ServerPlayer getPlayer() { + return player; + } + + public Vector3f getOrigin() { + return origin; + } + + public float getDistanceTravelled() { + return origin.distance(position); + } + + public ProjectileMessage toMessage(boolean destroyed) { + return new ProjectileMessage( + id, type, + position.x, position.y, position.z, + velocity.x, velocity.y, velocity.z, + destroyed + ); + } +}