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