From f50a2d72a38f15e7f78460efc96e12fa6cbde54f Mon Sep 17 00:00:00 2001 From: Andrew Lalis Date: Tue, 29 Jun 2021 09:56:41 +0200 Subject: [PATCH] Added basic settings support, smoother player movement, customizable guns, etc. --- .../nl/andrewlalis/aos_client/Client.java | 11 +- .../aos_client/MessageTransceiver.java | 2 + .../nl/andrewlalis/aos_client/Tester.java | 2 +- .../aos_client/control/PlayerKeyListener.java | 8 ++ .../aos_client/view/GamePanel.java | 12 +- core/pom.xml | 15 +++ .../nl/andrewlalis/aos_core/geom/Vec2.java | 3 + .../nl/andrewlalis/aos_core/model/Bullet.java | 21 ++- .../nl/andrewlalis/aos_core/model/Player.java | 30 ++++- .../aos_core/model/PlayerControlState.java | 35 +++-- .../nl/andrewlalis/aos_core/model/Team.java | 1 + .../nl/andrewlalis/aos_core/model/World.java | 15 +-- .../andrewlalis/aos_core/model/tools/Gun.java | 125 ++---------------- .../aos_core/model/tools/GunCategory.java | 24 ++++ .../aos_core/model/tools/GunType.java | 117 ++++++++++++++-- .../net/ConnectionRejectedMessage.java | 14 ++ .../nl/andrewlalis/aos_core/net/Type.java | 3 +- .../aos_core/net/data/PlayerDetailUpdate.java | 6 +- .../aos_core/net/data/PlayerUpdate.java | 34 ++--- server/src/main/java/module-info.java | 4 + .../andrewlalis/aos_server/ChatManager.java | 2 + .../andrewlalis/aos_server/ClientHandler.java | 15 ++- .../nl/andrewlalis/aos_server/Server.java | 62 ++++++--- .../nl/andrewlalis/aos_server/ServerCli.java | 1 + .../andrewlalis/aos_server/WorldUpdater.java | 90 ++++++++++--- .../aos_server/command/GunsCommand.java | 31 +++++ .../command/ListPlayersCommand.java | 2 +- .../aos_server/command/chat/GunCommand.java | 25 ++-- .../aos_server/settings/GunSettings.java | 59 +++++++++ .../aos_server/settings/PlayerSettings.java | 64 +++++++++ .../aos_server/settings/ServerSettings.java | 36 +++++ .../aos_server/settings/TeamSettings.java | 22 +++ .../src/main/resources/default_settings.yaml | 63 +++++++++ 33 files changed, 704 insertions(+), 250 deletions(-) create mode 100644 core/src/main/java/nl/andrewlalis/aos_core/model/tools/GunCategory.java create mode 100644 core/src/main/java/nl/andrewlalis/aos_core/net/ConnectionRejectedMessage.java create mode 100644 server/src/main/java/nl/andrewlalis/aos_server/command/GunsCommand.java create mode 100644 server/src/main/java/nl/andrewlalis/aos_server/settings/GunSettings.java create mode 100644 server/src/main/java/nl/andrewlalis/aos_server/settings/PlayerSettings.java create mode 100644 server/src/main/java/nl/andrewlalis/aos_server/settings/ServerSettings.java create mode 100644 server/src/main/java/nl/andrewlalis/aos_server/settings/TeamSettings.java create mode 100644 server/src/main/resources/default_settings.yaml diff --git a/client/src/main/java/nl/andrewlalis/aos_client/Client.java b/client/src/main/java/nl/andrewlalis/aos_client/Client.java index 8215151..6dc82b1 100644 --- a/client/src/main/java/nl/andrewlalis/aos_client/Client.java +++ b/client/src/main/java/nl/andrewlalis/aos_client/Client.java @@ -70,7 +70,7 @@ public class Client { player.setPosition(p.getPosition()); player.setOrientation(p.getOrientation()); player.setVelocity(p.getVelocity()); - player.setGun(Gun.forType(p.getGunType())); + player.setGun(new Gun(this.world.getGunTypes().get(p.getGunTypeName()))); } } this.soundManager.play(update.getSoundsToPlay()); @@ -92,14 +92,7 @@ public class Client { if (this.myPlayer == null) return; this.myPlayer.setHealth(update.getHealth()); this.myPlayer.setReloading(update.isReloading()); - this.myPlayer.setGun(Gun.forType( - this.myPlayer.getGun().getType(), - update.getGunMaxClipCount(), - update.getGunClipSize(), - update.getGunBulletsPerRound(), - update.getGunCurrentClipBulletCount(), - update.getGunClipCount() - )); + this.myPlayer.setGun(new Gun(this.myPlayer.getGun().getType(), update.getGunCurrentClipBulletCount(), update.getGunClipCount())); } public void sendPlayerState() { diff --git a/client/src/main/java/nl/andrewlalis/aos_client/MessageTransceiver.java b/client/src/main/java/nl/andrewlalis/aos_client/MessageTransceiver.java index 64bffa2..6a859cf 100644 --- a/client/src/main/java/nl/andrewlalis/aos_client/MessageTransceiver.java +++ b/client/src/main/java/nl/andrewlalis/aos_client/MessageTransceiver.java @@ -54,6 +54,8 @@ public class MessageTransceiver extends Thread { this.client.setPlayer(msg.getPlayer()); this.client.setWorld(msg.getWorld()); established = true; + } else if (obj instanceof ConnectionRejectedMessage msg) { + throw new IOException(msg.getMessage()); } } catch (ClassNotFoundException e) { e.printStackTrace(); diff --git a/client/src/main/java/nl/andrewlalis/aos_client/Tester.java b/client/src/main/java/nl/andrewlalis/aos_client/Tester.java index 2fa9f15..1d27aea 100644 --- a/client/src/main/java/nl/andrewlalis/aos_client/Tester.java +++ b/client/src/main/java/nl/andrewlalis/aos_client/Tester.java @@ -9,7 +9,7 @@ public class Tester { }; public static void main(String[] args) { - for (int i = 0; i < 1; i++) { + for (int i = 0; i < 2; i++) { try { new Client("localhost", 8035, names[ThreadLocalRandom.current().nextInt(names.length)]); } catch (IOException e) { diff --git a/client/src/main/java/nl/andrewlalis/aos_client/control/PlayerKeyListener.java b/client/src/main/java/nl/andrewlalis/aos_client/control/PlayerKeyListener.java index e4e9222..edbe51d 100644 --- a/client/src/main/java/nl/andrewlalis/aos_client/control/PlayerKeyListener.java +++ b/client/src/main/java/nl/andrewlalis/aos_client/control/PlayerKeyListener.java @@ -50,6 +50,10 @@ public class PlayerKeyListener extends KeyAdapter { state.setMovingRight(true); } else if (e.getKeyCode() == KeyEvent.VK_R) { state.setReloading(true); + } else if (e.getKeyCode() == KeyEvent.VK_SHIFT) { + state.setSprinting(true); + } else if (e.getKeyCode() == KeyEvent.VK_CONTROL) { + state.setSneaking(true); } this.client.sendPlayerState(); } @@ -68,6 +72,10 @@ public class PlayerKeyListener extends KeyAdapter { state.setMovingRight(false); } else if (e.getKeyCode() == KeyEvent.VK_R) { state.setReloading(false); + } else if (e.getKeyCode() == KeyEvent.VK_SHIFT) { + state.setSprinting(false); + } else if (e.getKeyCode() == KeyEvent.VK_CONTROL) { + state.setSneaking(false); } this.client.sendPlayerState(); } diff --git a/client/src/main/java/nl/andrewlalis/aos_client/view/GamePanel.java b/client/src/main/java/nl/andrewlalis/aos_client/view/GamePanel.java index 3b3d5bd..5df78bd 100644 --- a/client/src/main/java/nl/andrewlalis/aos_client/view/GamePanel.java +++ b/client/src/main/java/nl/andrewlalis/aos_client/view/GamePanel.java @@ -3,7 +3,6 @@ package nl.andrewlalis.aos_client.view; import nl.andrewlalis.aos_client.Client; import nl.andrewlalis.aos_core.model.*; import nl.andrewlalis.aos_core.model.tools.Gun; -import nl.andrewlalis.aos_core.model.tools.GunType; import nl.andrewlalis.aos_core.net.chat.ChatMessage; import nl.andrewlalis.aos_core.net.chat.PlayerChatMessage; import nl.andrewlalis.aos_core.net.chat.SystemChatMessage; @@ -148,12 +147,7 @@ public class GamePanel extends JPanel { } private void drawGun(Graphics2D g2, Gun gun) { - g2.setColor(Color.GRAY); - if (gun.getType() == GunType.RIFLE) { - g2.setColor(new Color(59, 43, 0)); - } else if (gun.getType() == GunType.SHOTGUN) { - g2.setColor(new Color(18, 18, 17)); - } + g2.setColor(Color.decode(gun.getType().getColor())); Rectangle2D.Double gunBarrel = new Rectangle2D.Double( 0, 0.5, @@ -235,8 +229,8 @@ public class GamePanel extends JPanel { g2.drawString("Reloading...", 5, this.getHeight() - 10); } Gun gun = myPlayer.getGun(); - g2.drawString("Clips: " + gun.getClipCount() + " / " + gun.getMaxClipCount(), 5, this.getHeight() - 20); - g2.drawString("Bullets: " + gun.getCurrentClipBulletCount() + " / " + gun.getClipSize(), 5, this.getHeight() - 30); + g2.drawString("Clips: " + gun.getClipCount() + " / " + gun.getType().getMaxClipCount(), 5, this.getHeight() - 20); + g2.drawString("Bullets: " + gun.getCurrentClipBulletCount() + " / " + gun.getType().getClipSize(), 5, this.getHeight() - 30); if (myPlayer.getHealth() >= 66.0f) { g2.setColor(Color.GREEN); } else if (myPlayer.getHealth() >= 33.0f) { diff --git a/core/pom.xml b/core/pom.xml index 4d86160..62b7456 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -26,4 +26,19 @@ + + + + com.fasterxml.jackson.core + jackson-databind + 2.12.3 + + + + com.fasterxml.jackson.dataformat + jackson-dataformat-yaml + 2.12.3 + + + \ No newline at end of file diff --git a/core/src/main/java/nl/andrewlalis/aos_core/geom/Vec2.java b/core/src/main/java/nl/andrewlalis/aos_core/geom/Vec2.java index 991bdff..537c399 100644 --- a/core/src/main/java/nl/andrewlalis/aos_core/geom/Vec2.java +++ b/core/src/main/java/nl/andrewlalis/aos_core/geom/Vec2.java @@ -7,6 +7,9 @@ import java.util.Random; import java.util.concurrent.ThreadLocalRandom; public record Vec2(float x, float y) implements Serializable { + public static final Vec2 ZERO = new Vec2(0, 0); + public static final Vec2 UP = new Vec2(0, -1); + public float mag() { return (float) Math.sqrt(x * x + y * y); diff --git a/core/src/main/java/nl/andrewlalis/aos_core/model/Bullet.java b/core/src/main/java/nl/andrewlalis/aos_core/model/Bullet.java index d4204fa..dab8661 100644 --- a/core/src/main/java/nl/andrewlalis/aos_core/model/Bullet.java +++ b/core/src/main/java/nl/andrewlalis/aos_core/model/Bullet.java @@ -3,28 +3,35 @@ package nl.andrewlalis.aos_core.model; import nl.andrewlalis.aos_core.geom.Vec2; import nl.andrewlalis.aos_core.model.tools.Gun; -import java.util.Random; -import java.util.concurrent.ThreadLocalRandom; - public class Bullet extends PhysicsObject { private final int playerId; + private final Player player; private final Gun gun; public Bullet(Player player) { this.playerId = player.getId(); + this.player = player; this.setPosition(player.getPosition() .add(player.getOrientation().mul(1.5f)) .add(player.getOrientation().perp().mul(Player.RADIUS)) ); this.setOrientation(player.getOrientation()); - Vec2 perturbation = Vec2.random(-1, 1).mul(player.getGun().getAccuracy()); - this.setVelocity(this.getOrientation().add(perturbation).mul(player.getGun().getBulletSpeed())); + float accuracy = player.getGun().getType().getAccuracy(); + if (player.isSneaking()) { + accuracy *= Player.SNEAK_ACCURACY_MODIFIER; + } else if (player.isSprinting()) { + accuracy *= Player.SPRINT_ACCURACY_MODIFIER; + } + Vec2 perturbation = Vec2.random(-1, 1).mul(accuracy); + Vec2 localVelocity = this.getOrientation().add(perturbation).mul(player.getGun().getType().getBulletSpeed()); + this.setVelocity(player.getVelocity().add(localVelocity)); this.gun = player.getGun(); } public Bullet(Vec2 position, Vec2 velocity) { super(position, new Vec2(0, -1), velocity); this.playerId = -1; + this.player = null; this.gun = null; } @@ -32,6 +39,10 @@ public class Bullet extends PhysicsObject { return playerId; } + public Player getPlayer() { + return player; + } + public Gun getGun() { return gun; } diff --git a/core/src/main/java/nl/andrewlalis/aos_core/model/Player.java b/core/src/main/java/nl/andrewlalis/aos_core/model/Player.java index 3cfdf69..42915bf 100644 --- a/core/src/main/java/nl/andrewlalis/aos_core/model/Player.java +++ b/core/src/main/java/nl/andrewlalis/aos_core/model/Player.java @@ -2,14 +2,23 @@ package nl.andrewlalis.aos_core.model; import nl.andrewlalis.aos_core.geom.Vec2; import nl.andrewlalis.aos_core.model.tools.Gun; +import nl.andrewlalis.aos_core.model.tools.GunType; import java.util.Objects; public class Player extends PhysicsObject implements Comparable { public static final float MOVEMENT_SPEED = 10; // Movement speed, in m/s + public static final float MOVEMENT_SPEED_SPRINT = 18; + public static final float MOVEMENT_SPEED_SNEAK = 5; + public static final float MOVEMENT_ACCELERATION = 60; // Acceleration speed, in m/s^2. + public static final float MOVEMENT_THRESHOLD = 0.001f; // Threshold for stopping movement. Speeds slower than this are reduced to 0. + public static final float MOVEMENT_DECELERATION = 30; // Deceleration speed, in m/s^2. public static final float RADIUS = 0.5f; // Collision radius, in meters. public static final float RESUPPLY_COOLDOWN = 30; // Seconds between allowing resupply. public static final float MAX_HEALTH = 100.0f; + public static final float HEALTH_REGEN_RATE = 1.0f; // How quickly players regenerate health over time. + public static final float SNEAK_ACCURACY_MODIFIER = 0.5f; + public static final float SPRINT_ACCURACY_MODIFIER = 1.5f; private final int id; private final String name; @@ -23,12 +32,12 @@ public class Player extends PhysicsObject implements Comparable { private boolean reloading; private transient long lastResupply; - public Player(int id, String name, Team team) { + public Player(int id, String name, Team team, GunType gunType) { this.id = id; this.name = name; this.team = team; this.state = new PlayerControlState(); - this.gun = Gun.ak47(); + this.gun = new Gun(gunType); this.health = MAX_HEALTH; this.useWeapon(); this.lastShot = System.currentTimeMillis(); @@ -79,7 +88,7 @@ public class Player extends PhysicsObject implements Comparable { !this.state.isReloading() && !this.reloading && this.gun.getCurrentClipBulletCount() > 0 && - this.lastShot + ((long) (this.gun.getShotCooldownTime() * 1000)) < System.currentTimeMillis() && + this.lastShot + ((long) (this.gun.getType().getShotCooldownTime() * 1000)) < System.currentTimeMillis() && (this.getTeam() == null || this.getTeam().getSpawnPoint().dist(this.getPosition()) > Team.SPAWN_RADIUS); } @@ -100,10 +109,7 @@ public class Player extends PhysicsObject implements Comparable { public boolean isReloadingComplete() { long msSinceStart = System.currentTimeMillis() - this.reloadingStartedAt; - if (msSinceStart > this.gun.getReloadTime() * 1000) { - return true; - } - return false; + return msSinceStart > this.gun.getType().getReloadTime() * 1000; } public boolean isReloading() { @@ -138,6 +144,16 @@ public class Player extends PhysicsObject implements Comparable { } } + public boolean isSneaking() { + return this.state.isSneaking() && + !this.state.isSprinting(); + } + + public boolean isSprinting() { + return this.state.isSprinting() && + !this.state.isSneaking(); + } + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/core/src/main/java/nl/andrewlalis/aos_core/model/PlayerControlState.java b/core/src/main/java/nl/andrewlalis/aos_core/model/PlayerControlState.java index 4f7620b..771adc5 100644 --- a/core/src/main/java/nl/andrewlalis/aos_core/model/PlayerControlState.java +++ b/core/src/main/java/nl/andrewlalis/aos_core/model/PlayerControlState.java @@ -11,6 +11,8 @@ public class PlayerControlState implements Serializable { boolean movingRight; boolean movingForward; boolean movingBackward; + boolean sprinting; + boolean sneaking; boolean shooting; boolean reloading; @@ -49,6 +51,22 @@ public class PlayerControlState implements Serializable { this.movingBackward = movingBackward; } + public boolean isSprinting() { + return sprinting; + } + + public void setSprinting(boolean sprinting) { + this.sprinting = sprinting; + } + + public boolean isSneaking() { + return sneaking; + } + + public void setSneaking(boolean sneaking) { + this.sneaking = sneaking; + } + public boolean isShooting() { return shooting; } @@ -73,19 +91,6 @@ public class PlayerControlState implements Serializable { this.mouseLocation = mouseLocation; } - @Override - public String toString() { - return "PlayerControlState{" + - "movingLeft=" + movingLeft + - ", movingRight=" + movingRight + - ", movingForward=" + movingForward + - ", movingBackward=" + movingBackward + - ", shooting=" + shooting + - ", reloading=" + reloading + - ", mouseLocation=" + mouseLocation + - '}'; - } - public byte[] toBytes() { ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES + 2 * Float.BYTES); int flags = 0; @@ -95,6 +100,8 @@ public class PlayerControlState implements Serializable { if (this.movingBackward) flags |= 8; if (this.shooting) flags |= 16; if (this.reloading) flags |= 32; + if (this.sprinting) flags |= 64; + if (this.sneaking) flags |= 128; buffer.putInt(flags); buffer.putFloat(this.mouseLocation.x()); buffer.putFloat(this.mouseLocation.y()); @@ -111,6 +118,8 @@ public class PlayerControlState implements Serializable { s.movingBackward = (flags & 8) > 0; s.shooting = (flags & 16) > 0; s.reloading = (flags & 32) > 0; + s.sprinting = (flags & 64) > 0; + s.sneaking = (flags & 128) > 0; s.mouseLocation = new Vec2(buffer.getFloat(), buffer.getFloat()); return s; } diff --git a/core/src/main/java/nl/andrewlalis/aos_core/model/Team.java b/core/src/main/java/nl/andrewlalis/aos_core/model/Team.java index cf77a20..8683c3a 100644 --- a/core/src/main/java/nl/andrewlalis/aos_core/model/Team.java +++ b/core/src/main/java/nl/andrewlalis/aos_core/model/Team.java @@ -10,6 +10,7 @@ import java.util.List; public class Team implements Serializable { public static final float SPAWN_RADIUS = 3; public static final float SUPPLY_POINT_RADIUS = 2; + public static final boolean FRIENDLY_FIRE = false; private final String name; private final java.awt.Color color; diff --git a/core/src/main/java/nl/andrewlalis/aos_core/model/World.java b/core/src/main/java/nl/andrewlalis/aos_core/model/World.java index f972c9e..155b8c4 100644 --- a/core/src/main/java/nl/andrewlalis/aos_core/model/World.java +++ b/core/src/main/java/nl/andrewlalis/aos_core/model/World.java @@ -1,10 +1,10 @@ package nl.andrewlalis.aos_core.model; import nl.andrewlalis.aos_core.geom.Vec2; +import nl.andrewlalis.aos_core.model.tools.GunType; import java.io.Serializable; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -17,19 +17,18 @@ public class World implements Serializable { private final Vec2 size; private final List teams; + private final Map gunTypes; private final Map players; private final List bullets; private final List barricades; - private final List soundsToPlay; - public World(Vec2 size) { this.size = size; this.teams = new ArrayList<>(); + this.gunTypes = new ConcurrentHashMap<>(); this.players = new ConcurrentHashMap<>(); this.bullets = new CopyOnWriteArrayList<>(); this.barricades = new ArrayList<>(); - this.soundsToPlay = new ArrayList<>(); } public Vec2 getSize() { @@ -40,6 +39,10 @@ public class World implements Serializable { return teams; } + public Map getGunTypes() { + return gunTypes; + } + public Map getPlayers() { return this.players; } @@ -51,8 +54,4 @@ public class World implements Serializable { public List getBarricades() { return barricades; } - - public List getSoundsToPlay() { - return soundsToPlay; - } } diff --git a/core/src/main/java/nl/andrewlalis/aos_core/model/tools/Gun.java b/core/src/main/java/nl/andrewlalis/aos_core/model/tools/Gun.java index 392efd5..c8feca4 100644 --- a/core/src/main/java/nl/andrewlalis/aos_core/model/tools/Gun.java +++ b/core/src/main/java/nl/andrewlalis/aos_core/model/tools/Gun.java @@ -3,48 +3,7 @@ package nl.andrewlalis.aos_core.model.tools; import java.io.Serializable; public class Gun implements Serializable { - - private final GunType type; - - /** - * Maximum number of clips a player can carry when using this gun. - */ - private final int maxClipCount; - /** - * Number of bullets in each clip. - */ - private final int clipSize; - - /** - * Number of bullets that are fired simultaneously per round. Usually only - * shotguns fire multiple. - */ - private final int bulletsPerRound; - - /** - * How accurate shots from this gun are. - */ - private final float accuracy; - - /** - * How long (in seconds) to wait after each shot, before another is shot. - */ - private final float shotCooldownTime; - - /** - * How long (in seconds) for reloading a new clip. - */ - private final float reloadTime; - - /** - * How fast the bullet travels (in m/s). - */ - private final float bulletSpeed; - - /** - * How much damage the bullet does for a direct hit. - */ - private final float baseDamage; + GunType type; /** * Number of bullets left in the current clip. @@ -55,57 +14,20 @@ public class Gun implements Serializable { */ private int clipCount; - private Gun(GunType type, int maxClipCount, int clipSize, int bulletsPerRound, float accuracy, float shotCooldownTime, float reloadTime, float bulletSpeed, float baseDamage) { + public Gun(GunType type, int currentClipBulletCount, int clipCount) { this.type = type; - this.maxClipCount = maxClipCount; - this.clipSize = clipSize; - this.bulletsPerRound = bulletsPerRound; - this.accuracy = accuracy; - this.shotCooldownTime = shotCooldownTime; - this.reloadTime = reloadTime; - this.bulletSpeed = bulletSpeed; - this.baseDamage = baseDamage; + this.currentClipBulletCount = currentClipBulletCount; + this.clipCount = clipCount; + } - this.currentClipBulletCount = 0; - this.clipCount = maxClipCount; + public Gun(GunType type) { + this(type, 0, type.getMaxClipCount()); } public GunType getType() { return type; } - public int getMaxClipCount() { - return maxClipCount; - } - - public int getClipSize() { - return clipSize; - } - - public int getBulletsPerRound() { - return bulletsPerRound; - } - - public float getAccuracy() { - return accuracy; - } - - public float getShotCooldownTime() { - return shotCooldownTime; - } - - public float getReloadTime() { - return reloadTime; - } - - public float getBulletSpeed() { - return bulletSpeed; - } - - public float getBaseDamage() { - return baseDamage; - } - public int getCurrentClipBulletCount() { return currentClipBulletCount; } @@ -115,7 +37,7 @@ public class Gun implements Serializable { } public void refillClips() { - this.clipCount = this.maxClipCount; + this.clipCount = this.type.getMaxClipCount(); } public void decrementBulletCount() { @@ -133,36 +55,7 @@ public class Gun implements Serializable { public void reload() { if (this.clipCount > 0) { this.clipCount--; - this.currentClipBulletCount = this.clipSize; + this.currentClipBulletCount = this.type.getClipSize(); } } - - /** - * Helper method to obtain a "dummy" gun for client-side rendering. - * TODO: Improve cleanliness so this isn't necessary. - * @param type The type of gun. - * @return The gun. - */ - public static Gun forType(GunType type) { - return new Gun(type, -1, -1, -1, -1, -1, -1, -1, -1); - } - - public static Gun forType(GunType type, int maxClipCount, int clipSize, int bulletsPerRound, int currentClipBulletCount, int clipCount) { - Gun g = new Gun(type, maxClipCount, clipSize, bulletsPerRound, -1, -1, -1, -1, -1); - g.currentClipBulletCount = currentClipBulletCount; - g.clipCount = clipCount; - return g; - } - - public static Gun ak47() { - return new Gun(GunType.SMG, 4, 30, 1, 0.10f, 0.05f, 1.2f, 90, 40); - } - - public static Gun m1Garand() { - return new Gun(GunType.RIFLE, 6, 8, 1, 0.02f, 0.75f, 1.5f, 150, 100); - } - - public static Gun winchester() { - return new Gun(GunType.SHOTGUN, 8, 4, 3, 0.15f, 0.5f, 2.0f, 75, 60); - } } diff --git a/core/src/main/java/nl/andrewlalis/aos_core/model/tools/GunCategory.java b/core/src/main/java/nl/andrewlalis/aos_core/model/tools/GunCategory.java new file mode 100644 index 0000000..815259d --- /dev/null +++ b/core/src/main/java/nl/andrewlalis/aos_core/model/tools/GunCategory.java @@ -0,0 +1,24 @@ +package nl.andrewlalis.aos_core.model.tools; + +public enum GunCategory { + SHOTGUN(0), + SMG(1), + RIFLE(2); + + private final byte code; + + GunCategory(int code) { + this.code = (byte) code; + } + + public byte getCode() { + return code; + } + + public static GunCategory get(byte code) { + for (var val : values()) { + if (val.code == code) return val; + } + return null; + } +} diff --git a/core/src/main/java/nl/andrewlalis/aos_core/model/tools/GunType.java b/core/src/main/java/nl/andrewlalis/aos_core/model/tools/GunType.java index 24cbca9..82e1141 100644 --- a/core/src/main/java/nl/andrewlalis/aos_core/model/tools/GunType.java +++ b/core/src/main/java/nl/andrewlalis/aos_core/model/tools/GunType.java @@ -1,24 +1,113 @@ package nl.andrewlalis.aos_core.model.tools; -public enum GunType { - SHOTGUN(0), - SMG(1), - RIFLE(2); +import java.io.Serializable; - private final byte code; +/** + * Relatively constant configuration information about a particular type of gun, + * while not including state data for any single gun. + */ +public class GunType implements Serializable { + /** + * The name of this type of gun. Should be unique among all guns in a world. + */ + private final String name; + /** + * The category of gun. + */ + private final GunCategory category; + /** + * The color of this type of gun, in hex. + */ + private final String color; + /** + * Maximum number of clips a player can carry when using this gun. + */ + private final int maxClipCount; + /** + * Number of bullets in each clip. + */ + private final int clipSize; + /** + * Number of bullets that are fired simultaneously per round. Usually only + * shotguns fire multiple. + */ + private final int bulletsPerRound; + /** + * How accurate shots from this gun are. 0 = never miss, 1 = complete random. + */ + private final float accuracy; + /** + * How long (in seconds) to wait after each shot, before another is shot. + */ + private final float shotCooldownTime; + /** + * How long (in seconds) for reloading a new clip. + */ + private final float reloadTime; + /** + * How fast the bullet travels (in m/s). + */ + private final float bulletSpeed; + /** + * How much damage the bullet does for a direct hit. + */ + private final float baseDamage; - GunType(int code) { - this.code = (byte) code; + public GunType(String name, GunCategory category, String color, int maxClipCount, int clipSize, int bulletsPerRound, float accuracy, float shotCooldownTime, float reloadTime, float bulletSpeed, float baseDamage) { + this.name = name; + this.category = category; + this.color = color; + this.maxClipCount = maxClipCount; + this.clipSize = clipSize; + this.bulletsPerRound = bulletsPerRound; + this.accuracy = accuracy; + this.shotCooldownTime = shotCooldownTime; + this.reloadTime = reloadTime; + this.bulletSpeed = bulletSpeed; + this.baseDamage = baseDamage; } - public byte getCode() { - return code; + public String getName() { + return name; } - public static GunType get(byte code) { - for (var val : values()) { - if (val.code == code) return val; - } - return null; + public GunCategory getCategory() { + return category; + } + + public String getColor() { + return color; + } + + public int getMaxClipCount() { + return maxClipCount; + } + + public int getClipSize() { + return clipSize; + } + + public int getBulletsPerRound() { + return bulletsPerRound; + } + + public float getAccuracy() { + return accuracy; + } + + public float getShotCooldownTime() { + return shotCooldownTime; + } + + public float getReloadTime() { + return reloadTime; + } + + public float getBulletSpeed() { + return bulletSpeed; + } + + public float getBaseDamage() { + return baseDamage; } } diff --git a/core/src/main/java/nl/andrewlalis/aos_core/net/ConnectionRejectedMessage.java b/core/src/main/java/nl/andrewlalis/aos_core/net/ConnectionRejectedMessage.java new file mode 100644 index 0000000..084c8e6 --- /dev/null +++ b/core/src/main/java/nl/andrewlalis/aos_core/net/ConnectionRejectedMessage.java @@ -0,0 +1,14 @@ +package nl.andrewlalis.aos_core.net; + +public class ConnectionRejectedMessage extends Message { + private final String message; + + public ConnectionRejectedMessage(String message) { + super(Type.CONNECTION_REJECTED); + this.message = message; + } + + public String getMessage() { + return message; + } +} diff --git a/core/src/main/java/nl/andrewlalis/aos_core/net/Type.java b/core/src/main/java/nl/andrewlalis/aos_core/net/Type.java index 287d44c..9bfa249 100644 --- a/core/src/main/java/nl/andrewlalis/aos_core/net/Type.java +++ b/core/src/main/java/nl/andrewlalis/aos_core/net/Type.java @@ -7,5 +7,6 @@ public enum Type { PLAYER_JOINED, PLAYER_LEFT, PLAYER_TEAM_CHANGE, - SERVER_SHUTDOWN + SERVER_SHUTDOWN, + CONNECTION_REJECTED } diff --git a/core/src/main/java/nl/andrewlalis/aos_core/net/data/PlayerDetailUpdate.java b/core/src/main/java/nl/andrewlalis/aos_core/net/data/PlayerDetailUpdate.java index 5c52915..abb03b4 100644 --- a/core/src/main/java/nl/andrewlalis/aos_core/net/data/PlayerDetailUpdate.java +++ b/core/src/main/java/nl/andrewlalis/aos_core/net/data/PlayerDetailUpdate.java @@ -20,9 +20,9 @@ public class PlayerDetailUpdate { this.health = player.getHealth(); this.reloading = player.isReloading(); - this.gunMaxClipCount = player.getGun().getMaxClipCount(); - this.gunClipSize = player.getGun().getClipSize(); - this.gunBulletsPerRound = player.getGun().getBulletsPerRound(); + this.gunMaxClipCount = player.getGun().getType().getMaxClipCount(); + this.gunClipSize = player.getGun().getType().getClipSize(); + this.gunBulletsPerRound = player.getGun().getType().getBulletsPerRound(); this.gunCurrentClipBulletCount = player.getGun().getCurrentClipBulletCount(); this.gunClipCount = player.getGun().getClipCount(); } diff --git a/core/src/main/java/nl/andrewlalis/aos_core/net/data/PlayerUpdate.java b/core/src/main/java/nl/andrewlalis/aos_core/net/data/PlayerUpdate.java index d5e6d01..be8a8fb 100644 --- a/core/src/main/java/nl/andrewlalis/aos_core/net/data/PlayerUpdate.java +++ b/core/src/main/java/nl/andrewlalis/aos_core/net/data/PlayerUpdate.java @@ -2,12 +2,15 @@ package nl.andrewlalis.aos_core.net.data; import nl.andrewlalis.aos_core.geom.Vec2; import nl.andrewlalis.aos_core.model.Player; -import nl.andrewlalis.aos_core.model.tools.GunType; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; +/** + * The data that's sent to all clients about a player, and contains only the + * information needed to render the player on the screen. + */ public class PlayerUpdate { public static final int BYTES = Integer.BYTES + 6 * Float.BYTES + 1; @@ -15,22 +18,22 @@ public class PlayerUpdate { private final Vec2 position; private final Vec2 orientation; private final Vec2 velocity; - private final GunType gunType; + private final String gunTypeName; public PlayerUpdate(Player player) { this.id = player.getId(); this.position = player.getPosition(); this.orientation = player.getOrientation(); this.velocity = player.getVelocity(); - this.gunType = player.getGun().getType(); + this.gunTypeName = player.getGun().getType().getName(); } - public PlayerUpdate(int id, Vec2 position, Vec2 orientation, Vec2 velocity, GunType gunType) { + public PlayerUpdate(int id, Vec2 position, Vec2 orientation, Vec2 velocity, String gunTypeName) { this.id = id; this.position = position; this.orientation = orientation; this.velocity = velocity; - this.gunType = gunType; + this.gunTypeName = gunTypeName; } public int getId() { @@ -49,8 +52,8 @@ public class PlayerUpdate { return velocity; } - public GunType getGunType() { - return gunType; + public String getGunTypeName() { + return gunTypeName; } public void write(DataOutputStream out) throws IOException { @@ -61,16 +64,17 @@ public class PlayerUpdate { out.writeFloat(this.orientation.y()); out.writeFloat(this.velocity.x()); out.writeFloat(this.velocity.y()); - out.writeByte(this.gunType.getCode()); + out.writeInt(this.gunTypeName.length()); + out.writeBytes(this.gunTypeName); } public static PlayerUpdate read(DataInputStream in) throws IOException { - return new PlayerUpdate( - in.readInt(), - Vec2.read(in), - Vec2.read(in), - Vec2.read(in), - GunType.get(in.readByte()) - ); + int id = in.readInt(); + Vec2 position = Vec2.read(in); + Vec2 orientation = Vec2.read(in); + Vec2 velocity = Vec2.read(in); + int gunTypeNameLength = in.readInt(); + String gunTypeName = new String(in.readNBytes(gunTypeNameLength)); + return new PlayerUpdate(id, position, orientation, velocity, gunTypeName); } } diff --git a/server/src/main/java/module-info.java b/server/src/main/java/module-info.java index 9d1ef4b..6908209 100644 --- a/server/src/main/java/module-info.java +++ b/server/src/main/java/module-info.java @@ -2,4 +2,8 @@ module aos_server { requires java.logging; requires aos_core; requires java.desktop; + requires com.fasterxml.jackson.databind; + requires com.fasterxml.jackson.dataformat.yaml; + + opens nl.andrewlalis.aos_server.settings to com.fasterxml.jackson.databind; } \ No newline at end of file diff --git a/server/src/main/java/nl/andrewlalis/aos_server/ChatManager.java b/server/src/main/java/nl/andrewlalis/aos_server/ChatManager.java index 231eeb9..c870fae 100644 --- a/server/src/main/java/nl/andrewlalis/aos_server/ChatManager.java +++ b/server/src/main/java/nl/andrewlalis/aos_server/ChatManager.java @@ -3,6 +3,7 @@ package nl.andrewlalis.aos_server; import nl.andrewlalis.aos_core.model.Player; import nl.andrewlalis.aos_core.net.chat.ChatMessage; import nl.andrewlalis.aos_core.net.chat.PlayerChatMessage; +import nl.andrewlalis.aos_server.command.GunsCommand; import nl.andrewlalis.aos_server.command.ResetCommand; import nl.andrewlalis.aos_server.command.chat.ChatCommand; import nl.andrewlalis.aos_server.command.chat.GunCommand; @@ -25,6 +26,7 @@ public class ChatManager { this.chatCommands = new ConcurrentHashMap<>(); this.chatCommands.put("gun", new GunCommand()); this.chatCommands.put("reset", new ResetCommand(server)); + this.chatCommands.put("guns", new GunsCommand(server)); } public void handlePlayerChat(ClientHandler handler, Player player, ChatMessage msg) { diff --git a/server/src/main/java/nl/andrewlalis/aos_server/ClientHandler.java b/server/src/main/java/nl/andrewlalis/aos_server/ClientHandler.java index 42bee1d..e2b01ef 100644 --- a/server/src/main/java/nl/andrewlalis/aos_server/ClientHandler.java +++ b/server/src/main/java/nl/andrewlalis/aos_server/ClientHandler.java @@ -1,10 +1,7 @@ package nl.andrewlalis.aos_server; import nl.andrewlalis.aos_core.model.Player; -import nl.andrewlalis.aos_core.net.IdentMessage; -import nl.andrewlalis.aos_core.net.Message; -import nl.andrewlalis.aos_core.net.PlayerRegisteredMessage; -import nl.andrewlalis.aos_core.net.Type; +import nl.andrewlalis.aos_core.net.*; import nl.andrewlalis.aos_core.net.chat.ChatMessage; import java.io.EOFException; @@ -53,6 +50,10 @@ public class ClientHandler extends Thread { * @throws IOException If initialization could not be completed. */ private void initializeConnection() throws IOException { + if (this.server.getPlayerCount() > this.server.getSettings().getMaxPlayers()) { + this.send(new ConnectionRejectedMessage("Server is full.")); + throw new IOException("Client cannot join because server is full."); + } boolean connectionEstablished = false; int attempts = 0; while (!connectionEstablished && attempts < 100) { @@ -74,6 +75,10 @@ public class ClientHandler extends Thread { } } + public Server getServer() { + return server; + } + public Player getPlayer() { return player; } @@ -132,7 +137,7 @@ public class ClientHandler extends Thread { this.server.getChatManager().handlePlayerChat(this, this.player, (ChatMessage) msg); } } catch (SocketException e) { - if (e.getMessage().equals("Socket closed")) { + if (e.getMessage().equals("Socket closed") | e.getMessage().equals("Connection reset")) { this.shutdown(); } else { e.printStackTrace(); diff --git a/server/src/main/java/nl/andrewlalis/aos_server/Server.java b/server/src/main/java/nl/andrewlalis/aos_server/Server.java index 6ef0ee3..97a0802 100644 --- a/server/src/main/java/nl/andrewlalis/aos_server/Server.java +++ b/server/src/main/java/nl/andrewlalis/aos_server/Server.java @@ -1,7 +1,13 @@ package nl.andrewlalis.aos_server; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import nl.andrewlalis.aos_core.geom.Vec2; import nl.andrewlalis.aos_core.model.*; +import nl.andrewlalis.aos_core.model.tools.GunCategory; +import nl.andrewlalis.aos_core.model.tools.GunType; import nl.andrewlalis.aos_core.net.Message; import nl.andrewlalis.aos_core.net.PlayerUpdateMessage; import nl.andrewlalis.aos_core.net.Type; @@ -10,6 +16,7 @@ import nl.andrewlalis.aos_core.net.data.DataTypes; import nl.andrewlalis.aos_core.net.data.PlayerDetailUpdate; import nl.andrewlalis.aos_core.net.data.WorldUpdate; import nl.andrewlalis.aos_core.util.ByteUtils; +import nl.andrewlalis.aos_server.settings.ServerSettings; import java.awt.*; import java.io.IOException; @@ -17,12 +24,11 @@ import java.net.ServerSocket; import java.net.Socket; import java.net.SocketException; import java.util.List; -import java.util.Scanner; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ThreadLocalRandom; public class Server { - public static final int DEFAULT_PORT = 8035; + private final ServerSettings settings; private final List clientHandlers; private final ServerSocket serverSocket; @@ -34,10 +40,11 @@ public class Server { private volatile boolean running; - public Server(int port) throws IOException { + public Server(ServerSettings settings) throws IOException { + this.settings = settings; this.clientHandlers = new CopyOnWriteArrayList<>(); - this.serverSocket = new ServerSocket(port); - this.dataTransceiver = new DataTransceiver(this, port); + this.serverSocket = new ServerSocket(settings.getPort()); + this.dataTransceiver = new DataTransceiver(this, settings.getPort()); this.cli = new ServerCli(this); this.world = new World(new Vec2(50, 70)); this.initWorld(); @@ -45,7 +52,27 @@ public class Server { this.chatManager = new ChatManager(this); } + public ServerSettings getSettings() { + return settings; + } + private void initWorld() { + for (var gs : this.settings.getGunSettings()) { + this.world.getGunTypes().put(gs.getName(), new GunType( + gs.getName(), + GunCategory.valueOf(gs.getCategory().toUpperCase()), + gs.getColor(), + gs.getMaxClipCount(), + gs.getClipSize(), + gs.getBulletsPerRound(), + gs.getAccuracy(), + gs.getShotCooldownTime(), + gs.getReloadTime(), + gs.getBulletSpeed(), + gs.getBaseDamage() + )); + } + world.getBarricades().add(new Barricade(10, 10, 30, 5)); world.getBarricades().add(new Barricade(10, 55, 30, 5)); world.getBarricades().add(new Barricade(20, 30, 10, 10)); @@ -76,6 +103,10 @@ public class Server { return chatManager; } + public int getPlayerCount() { + return this.clientHandlers.size(); + } + public void acceptClientConnection() { try { Socket socket = this.serverSocket.accept(); @@ -100,7 +131,7 @@ public class Server { team = t; } } - Player p = new Player(id, name, team); + Player p = new Player(id, name, team, this.world.getGunTypes().get(this.settings.getPlayerSettings().getDefaultGun())); this.world.getPlayers().put(p.getId(), p); String message = p.getName() + " connected."; this.broadcastMessage(new SystemChatMessage(SystemChatMessage.Level.INFO, message)); @@ -215,20 +246,11 @@ public class Server { public static void main(String[] args) throws IOException { - System.out.println("Enter the port number to start the server on, or blank for default (" + DEFAULT_PORT + "):"); - Scanner sc = new Scanner(System.in); - String input = sc.nextLine(); - int port = DEFAULT_PORT; - if (input != null && !input.isBlank()) { - try { - port = Integer.parseInt(input.trim()); - } catch (NumberFormatException e) { - System.err.println("Invalid port."); - return; - } - } - - Server server = new Server(port); + ObjectMapper mapper = new ObjectMapper(new YAMLFactory()); + mapper.setPropertyNamingStrategy(PropertyNamingStrategies.KEBAB_CASE); + mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + var settings = mapper.readValue(Server.class.getClassLoader().getResourceAsStream("default_settings.yaml"), ServerSettings.class); + Server server = new Server(settings); server.run(); } } diff --git a/server/src/main/java/nl/andrewlalis/aos_server/ServerCli.java b/server/src/main/java/nl/andrewlalis/aos_server/ServerCli.java index 2c61574..e9788d0 100644 --- a/server/src/main/java/nl/andrewlalis/aos_server/ServerCli.java +++ b/server/src/main/java/nl/andrewlalis/aos_server/ServerCli.java @@ -27,6 +27,7 @@ public class ServerCli extends Thread { this.commands.put("list", new ListPlayersCommand(server)); this.commands.put("kick", new KickCommand(server)); + this.commands.put("guns", new GunsCommand(server)); } public void shutdown() { diff --git a/server/src/main/java/nl/andrewlalis/aos_server/WorldUpdater.java b/server/src/main/java/nl/andrewlalis/aos_server/WorldUpdater.java index 49319b7..5c4f489 100644 --- a/server/src/main/java/nl/andrewlalis/aos_server/WorldUpdater.java +++ b/server/src/main/java/nl/andrewlalis/aos_server/WorldUpdater.java @@ -2,7 +2,7 @@ package nl.andrewlalis.aos_server; import nl.andrewlalis.aos_core.geom.Vec2; import nl.andrewlalis.aos_core.model.*; -import nl.andrewlalis.aos_core.model.tools.GunType; +import nl.andrewlalis.aos_core.model.tools.GunCategory; import nl.andrewlalis.aos_core.net.chat.SystemChatMessage; import nl.andrewlalis.aos_core.net.data.Sound; import nl.andrewlalis.aos_core.net.data.SoundType; @@ -12,6 +12,12 @@ import java.util.ArrayList; import java.util.List; import java.util.concurrent.ThreadLocalRandom; +/** + * The world updater thread is responsible for periodically updating the server + * world data in a "tick" method. At each tick, we compute physics updates for + * every player, bullet, and other movable object in the world, and check for + * various interactions. + */ public class WorldUpdater extends Thread { public static final double TARGET_TPS = 120.0; public static final double MS_PER_TICK = 1000.0 / TARGET_TPS; @@ -66,6 +72,7 @@ public class WorldUpdater extends Thread { for (Player p : this.world.getPlayers().values()) { this.updatePlayerMovement(p, t); this.updatePlayerShooting(p); + p.setHealth(Math.min(p.getHealth() + Player.HEALTH_REGEN_RATE * t, Player.MAX_HEALTH)); this.worldUpdate.addPlayer(p); } } @@ -79,18 +86,64 @@ public class WorldUpdater extends Thread { } p.setOrientation(newOrientation); } - float vx = 0; - float vy = 0; - if (p.getState().isMovingForward()) vy += Player.MOVEMENT_SPEED; - if (p.getState().isMovingBackward()) vy -= Player.MOVEMENT_SPEED; - if (p.getState().isMovingLeft()) vx -= Player.MOVEMENT_SPEED; - if (p.getState().isMovingRight()) vx += Player.MOVEMENT_SPEED; - Vec2 forwardVector = new Vec2(0, -1); + + Vec2 forward = Vec2.UP; if (p.getTeam() != null) { - forwardVector = p.getTeam().getOrientation(); + forward = p.getTeam().getOrientation(); } - Vec2 leftVector = forwardVector.perp(); - Vec2 newPos = p.getPosition().add(forwardVector.mul(vy * t)).add(leftVector.mul(vx * t)); + Vec2 left = forward.perp(); + + // Compute changes to player velocity due to control input. + Vec2 localVelocity = p.getVelocity().rotate(-left.angle()); + float vx = localVelocity.x(); + float vy = localVelocity.y(); + if (p.getState().isMovingForward()) { + vy -= Player.MOVEMENT_ACCELERATION * t; + } + if (p.getState().isMovingBackward()) { + vy += Player.MOVEMENT_ACCELERATION * t; + } + if (p.getState().isMovingLeft()) { + vx -= Player.MOVEMENT_ACCELERATION * t; + } + if (p.getState().isMovingRight()) { + vx += Player.MOVEMENT_ACCELERATION * t; + } + // Compute deceleration. + if (p.getState().isMovingForward() == p.getState().isMovingBackward()) { + if (vy > 0) { + vy = Math.max(0.0f, vy - Player.MOVEMENT_DECELERATION * t); + } else { + vy = Math.min(0.0f, vy + Player.MOVEMENT_DECELERATION * t); + } + } + if (p.getState().isMovingLeft() == p.getState().isMovingRight()) { + if (vx > 0) { + vx = Math.max(0.0f, vx - Player.MOVEMENT_DECELERATION * t); + } else { + vx = Math.min(0.0f, vx + Player.MOVEMENT_DECELERATION * t); + } + } + + + Vec2 newLocalVelocity = new Vec2(vx, vy); + if (newLocalVelocity.mag() < Player.MOVEMENT_THRESHOLD) { + newLocalVelocity = Vec2.ZERO; + } + float speedLimit; + if (p.isSprinting()) { + speedLimit = Player.MOVEMENT_SPEED_SPRINT; + } else if (p.isSneaking()) { + speedLimit = Player.MOVEMENT_SPEED_SNEAK; + } else { + speedLimit = Player.MOVEMENT_SPEED; + } + if (newLocalVelocity.mag() > speedLimit) { + newLocalVelocity = newLocalVelocity.mul(speedLimit / newLocalVelocity.mag()); + } + p.setVelocity(newLocalVelocity.rotate(left.angle())); + + Vec2 newPos = p.getPosition().add(p.getVelocity().mul(t)); float nx = newPos.x(); float ny = newPos.y(); @@ -130,15 +183,15 @@ public class WorldUpdater extends Thread { private void updatePlayerShooting(Player p) { if (p.canUseWeapon()) { - for (int i = 0; i < p.getGun().getBulletsPerRound(); i++) { + for (int i = 0; i < p.getGun().getType().getBulletsPerRound(); i++) { Bullet b = new Bullet(p); this.world.getBullets().add(b); this.worldUpdate.addBullet(b); } SoundType soundType = SoundType.SHOT_SMG; - if (p.getGun().getType() == GunType.RIFLE) { + if (p.getGun().getType().getCategory() == GunCategory.RIFLE) { soundType = SoundType.SHOT_RIFLE; - } else if (p.getGun().getType() == GunType.SHOTGUN) { + } else if (p.getGun().getType().getCategory() == GunCategory.SHOTGUN) { soundType = SoundType.SHOT_SHOTGUN; } this.worldUpdate.addSound(new Sound(p.getPosition(), 1.0f, soundType)); @@ -184,13 +237,20 @@ public class WorldUpdater extends Thread { float y2 = b.getPosition().y(); float lineDist = oldPos.dist(b.getPosition()); for (Player p : this.world.getPlayers().values()) { + if ( + !server.getSettings().getTeamSettings().friendlyFireEnabled() && + b.getPlayer() != null && p.getTeam() != null && + p.getTeam().equals(b.getPlayer().getTeam()) + ) { + continue; // Don't check collisions against teammates if friendly fire is off. + } float n = ((p.getPosition().x() - x1) * (x2 - x1) + (p.getPosition().y() - y1) * (y2 - y1)) / lineDist; n = Math.max(Math.min(n, 1), 0); double dist = p.getPosition().dist(new Vec2(x1 + n * (x2 - x1), y1 + n * (y2 - y1))); if (dist < Player.RADIUS && (p.getTeam() == null || p.getTeam().getSpawnPoint().dist(p.getPosition()) > Team.SPAWN_RADIUS)) { // Player was shot! - float damage = (float) (((Player.RADIUS - dist) / Player.RADIUS) * b.getGun().getBaseDamage()); + float damage = (float) (((Player.RADIUS - dist) / Player.RADIUS) * b.getGun().getType().getBaseDamage()); p.takeDamage(damage); if (p.getHealth() == 0.0f) { Player shooter = this.world.getPlayers().get(b.getPlayerId()); diff --git a/server/src/main/java/nl/andrewlalis/aos_server/command/GunsCommand.java b/server/src/main/java/nl/andrewlalis/aos_server/command/GunsCommand.java new file mode 100644 index 0000000..33fbc56 --- /dev/null +++ b/server/src/main/java/nl/andrewlalis/aos_server/command/GunsCommand.java @@ -0,0 +1,31 @@ +package nl.andrewlalis.aos_server.command; + +import nl.andrewlalis.aos_core.model.Player; +import nl.andrewlalis.aos_core.model.tools.GunType; +import nl.andrewlalis.aos_core.net.chat.SystemChatMessage; +import nl.andrewlalis.aos_server.ClientHandler; +import nl.andrewlalis.aos_server.Server; +import nl.andrewlalis.aos_server.command.chat.ChatCommand; + +import java.util.stream.Collectors; + +public class GunsCommand implements Command, ChatCommand { + private final Server server; + + public GunsCommand(Server server) { + this.server = server; + } + + @Override + public void execute(String[] args) { + for (var gunType : this.server.getWorld().getGunTypes().values()) { + System.out.println(gunType.getName()); + } + } + + @Override + public void execute(ClientHandler handler, Player player, String[] args) { + String msg = handler.getServer().getWorld().getGunTypes().values().stream().map(GunType::getName).collect(Collectors.joining(", ")); + handler.send(new SystemChatMessage(SystemChatMessage.Level.INFO, msg)); + } +} diff --git a/server/src/main/java/nl/andrewlalis/aos_server/command/ListPlayersCommand.java b/server/src/main/java/nl/andrewlalis/aos_server/command/ListPlayersCommand.java index 431e39e..9163709 100644 --- a/server/src/main/java/nl/andrewlalis/aos_server/command/ListPlayersCommand.java +++ b/server/src/main/java/nl/andrewlalis/aos_server/command/ListPlayersCommand.java @@ -27,7 +27,7 @@ public class ListPlayersCommand implements Command { player.getTeam() == null ? "none" : player.getTeam().getName(), player.getHealth(), Player.MAX_HEALTH, - player.getGun().getType().name() + player.getGun().getType().getName() )) .collect(Collectors.joining("\n")); System.out.println(message); diff --git a/server/src/main/java/nl/andrewlalis/aos_server/command/chat/GunCommand.java b/server/src/main/java/nl/andrewlalis/aos_server/command/chat/GunCommand.java index 65ce95f..0a544b4 100644 --- a/server/src/main/java/nl/andrewlalis/aos_server/command/chat/GunCommand.java +++ b/server/src/main/java/nl/andrewlalis/aos_server/command/chat/GunCommand.java @@ -2,23 +2,32 @@ package nl.andrewlalis.aos_server.command.chat; import nl.andrewlalis.aos_core.model.Player; import nl.andrewlalis.aos_core.model.tools.Gun; +import nl.andrewlalis.aos_core.model.tools.GunType; import nl.andrewlalis.aos_core.net.chat.SystemChatMessage; import nl.andrewlalis.aos_server.ClientHandler; +import java.util.Locale; + public class GunCommand implements ChatCommand { @Override public void execute(ClientHandler handler, Player player, String[] args) { if (args.length < 1) { + handler.send(new SystemChatMessage(SystemChatMessage.Level.WARNING, "No gun name specified.")); return; } - String gunName = args[0]; - if (gunName.equalsIgnoreCase("smg")) { - player.setGun(Gun.ak47()); - } else if (gunName.equalsIgnoreCase("rifle")) { - player.setGun(Gun.m1Garand()); - } else if (gunName.equalsIgnoreCase("shotgun")) { - player.setGun(Gun.winchester()); + String gunName = String.join(" ", args); + GunType gunType = null; + for (GunType type : handler.getServer().getWorld().getGunTypes().values()) { + if (type.getName().equalsIgnoreCase(gunName)) { + gunType = type; + break; + } } - handler.send(new SystemChatMessage(SystemChatMessage.Level.INFO, "Changed gun to " + player.getGun().getType().name() + ".")); + if (gunType == null) { + handler.send(new SystemChatMessage(SystemChatMessage.Level.WARNING, "Unknown gun name.")); + return; + } + player.setGun(new Gun(gunType)); + handler.send(new SystemChatMessage(SystemChatMessage.Level.INFO, "Changed gun to " + player.getGun().getType().getName() + ".")); } } diff --git a/server/src/main/java/nl/andrewlalis/aos_server/settings/GunSettings.java b/server/src/main/java/nl/andrewlalis/aos_server/settings/GunSettings.java new file mode 100644 index 0000000..ad7b85f --- /dev/null +++ b/server/src/main/java/nl/andrewlalis/aos_server/settings/GunSettings.java @@ -0,0 +1,59 @@ +package nl.andrewlalis.aos_server.settings; + +public class GunSettings { + private String name; + private String category; + private String color; + private int maxClipCount; + private int clipSize; + private int bulletsPerRound; + private float accuracy; + private float shotCooldownTime; + private float reloadTime; + private float bulletSpeed; + private float baseDamage; + + public String getName() { + return name; + } + + public String getCategory() { + return category; + } + + public String getColor() { + return color; + } + + public int getMaxClipCount() { + return maxClipCount; + } + + public int getClipSize() { + return clipSize; + } + + public int getBulletsPerRound() { + return bulletsPerRound; + } + + public float getAccuracy() { + return accuracy; + } + + public float getShotCooldownTime() { + return shotCooldownTime; + } + + public float getReloadTime() { + return reloadTime; + } + + public float getBulletSpeed() { + return bulletSpeed; + } + + public float getBaseDamage() { + return baseDamage; + } +} diff --git a/server/src/main/java/nl/andrewlalis/aos_server/settings/PlayerSettings.java b/server/src/main/java/nl/andrewlalis/aos_server/settings/PlayerSettings.java new file mode 100644 index 0000000..b7bfa78 --- /dev/null +++ b/server/src/main/java/nl/andrewlalis/aos_server/settings/PlayerSettings.java @@ -0,0 +1,64 @@ +package nl.andrewlalis.aos_server.settings; + +public class PlayerSettings { + private float speed; + private float sprintSpeed; + private float sneakSpeed; + private float acceleration; + private float deceleration; + private float radius; + private float resupplyCooldown; + private float maxHealth; + private float healthRegenRate; + private float sneakAccuracyModifier; + private float sprintAccuracyModifier; + private String defaultGun; + + public float getSpeed() { + return speed; + } + + public float getSprintSpeed() { + return sprintSpeed; + } + + public float getSneakSpeed() { + return sneakSpeed; + } + + public float getAcceleration() { + return acceleration; + } + + public float getDeceleration() { + return deceleration; + } + + public float getRadius() { + return radius; + } + + public float getResupplyCooldown() { + return resupplyCooldown; + } + + public float getMaxHealth() { + return maxHealth; + } + + public float getHealthRegenRate() { + return healthRegenRate; + } + + public float getSneakAccuracyModifier() { + return sneakAccuracyModifier; + } + + public float getSprintAccuracyModifier() { + return sprintAccuracyModifier; + } + + public String getDefaultGun() { + return defaultGun; + } +} diff --git a/server/src/main/java/nl/andrewlalis/aos_server/settings/ServerSettings.java b/server/src/main/java/nl/andrewlalis/aos_server/settings/ServerSettings.java new file mode 100644 index 0000000..7aae1d6 --- /dev/null +++ b/server/src/main/java/nl/andrewlalis/aos_server/settings/ServerSettings.java @@ -0,0 +1,36 @@ +package nl.andrewlalis.aos_server.settings; + +import java.util.List; + +public class ServerSettings { + private int port; + private int maxPlayers; + private float ticksPerSecond; + private PlayerSettings playerSettings; + private TeamSettings teamSettings; + private List gunSettings; + + public int getPort() { + return port; + } + + public int getMaxPlayers() { + return maxPlayers; + } + + public float getTicksPerSecond() { + return ticksPerSecond; + } + + public PlayerSettings getPlayerSettings() { + return playerSettings; + } + + public TeamSettings getTeamSettings() { + return teamSettings; + } + + public List getGunSettings() { + return gunSettings; + } +} diff --git a/server/src/main/java/nl/andrewlalis/aos_server/settings/TeamSettings.java b/server/src/main/java/nl/andrewlalis/aos_server/settings/TeamSettings.java new file mode 100644 index 0000000..3cbabec --- /dev/null +++ b/server/src/main/java/nl/andrewlalis/aos_server/settings/TeamSettings.java @@ -0,0 +1,22 @@ +package nl.andrewlalis.aos_server.settings; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class TeamSettings { + private float spawnPointRadius; + private float supplyPointRadius; + @JsonProperty("friendly-fire") + private boolean friendlyFire; + + public float getSpawnPointRadius() { + return spawnPointRadius; + } + + public float getSupplyPointRadius() { + return supplyPointRadius; + } + + public boolean friendlyFireEnabled() { + return friendlyFire; + } +} diff --git a/server/src/main/resources/default_settings.yaml b/server/src/main/resources/default_settings.yaml new file mode 100644 index 0000000..6fdb8ec --- /dev/null +++ b/server/src/main/resources/default_settings.yaml @@ -0,0 +1,63 @@ +port: 8035 +max-players: 32 +ticks-per-second: 120 + +# Settings that control player behavior. +player-settings: + speed: 10 + sprint-speed: 18 + sneak-speed: 5 + acceleration: 60 + deceleration: 30 + radius: 0.5 + resupply-cooldown: 30 + max-health: 100 + health-regen-rate: 1.0 + sneak-accuracy-modifier: 0.5 + sprint-accuracy-modifier: 1.5 + # Should be the name of one of the guns defined in "gun-settings". + default-gun: M1 Garand + +# Settings for team mechanics. +team-settings: + spawn-point-radius: 3 + supply-point-radius: 2 + friendly-fire: false + +# The list of available guns are defined in this list. +gun-settings: + - name: AK-47 + category: SMG + color: "#2e2b26" + max-clip-count: 4 + clip-size: 30 + bullets-per-round: 1 + accuracy: 0.10 + shot-cooldown-time: 0.05 + reload-time: 1.2 + bullet-speed: 90 + base-damage: 40 + + - name: M1 Garand + category: RIFLE + color: "#452d06" + max-clip-count: 10 + clip-size: 8 + bullets-per-round: 1 + accuracy: 0.02 + shot-cooldown-time: 0.75 + reload-time: 0.75 + bullet-speed: 150 + base-damage: 100 + + - name: Winchester + category: SHOTGUN + color: "#1a1205" + max-clip-count: 8 + clip-size: 6 + bullets-per-round: 3 + accuracy: 0.15 + shot-cooldown-time: 0.5 + reload-time: 2.0 + bullet-speed: 75 + base-damage: 80 \ No newline at end of file