diff --git a/client/pom.xml b/client/pom.xml index a60db88..1b9abab 100644 --- a/client/pom.xml +++ b/client/pom.xml @@ -5,7 +5,7 @@ ace-of-shades nl.andrewlalis - 0.5.0 + 0.6.0 4.0.0 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 4d0e115..ef9a384 100644 --- a/client/src/main/java/nl/andrewlalis/aos_client/Client.java +++ b/client/src/main/java/nl/andrewlalis/aos_client/Client.java @@ -9,14 +9,15 @@ import nl.andrewlalis.aos_client.view.GameRenderer; import nl.andrewlalis.aos_core.model.Player; import nl.andrewlalis.aos_core.model.Team; import nl.andrewlalis.aos_core.model.World; +import nl.andrewlalis.aos_core.model.tools.Grenade; import nl.andrewlalis.aos_core.model.tools.Gun; -import nl.andrewlalis.aos_core.model.tools.GunType; -import nl.andrewlalis.aos_core.model.tools.Tool; +import nl.andrewlalis.aos_core.model.tools.Knife; 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.net.data.tool.GrenadeData; import nl.andrewlalis.aos_core.net.data.tool.GunData; -import nl.andrewlalis.aos_core.net.data.tool.ToolData; +import nl.andrewlalis.aos_core.net.data.tool.KnifeData; import java.io.IOException; @@ -27,6 +28,7 @@ public class Client { private final MessageTransceiver messageTransceiver; private World world; + private Player player; private final GameRenderer renderer; private final SoundManager soundManager; @@ -49,7 +51,7 @@ public class Client { this.messageTransceiver.start(); this.chatManager.bindTransceiver(this.messageTransceiver); - while (this.myPlayer == null || this.world == null) { + while (this.player == null || this.world == null) { try { System.out.println("Waiting for server response and player registration..."); Thread.sleep(100); @@ -91,10 +93,12 @@ public class Client { player.setPosition(p.getPosition()); player.setOrientation(p.getOrientation()); player.setVelocity(p.getVelocity()); - player.setGun(new Gun(this.world.getGunTypes().get(p.getGunTypeName()))); if (player.getVelocity().mag() > 0) { - this.soundManager.playWalking(player, myPlayer); + this.soundManager.playWalking(player, null); } + player.getTools().clear(); + player.getTools().add(p.getSelectedTool().toTool(this.world)); + player.setSelectedToolIndex(0); } } for (var t : update.getTeamUpdates()) { @@ -103,7 +107,7 @@ public class Client { team.setScore(t.getScore()); } } - this.soundManager.play(update.getSoundsToPlay(), myPlayer); + this.soundManager.play(update.getSoundsToPlay(), null); } public void setWorld(World world) { @@ -111,11 +115,11 @@ public class Client { } public void setPlayer(Player player) { - this.myPlayer = player; + this.player = player; } public Player getPlayer() { - return myPlayer; + return this.player; } /** @@ -124,17 +128,34 @@ public class Client { * @param update The updated player information from the server. */ public void updatePlayer(PlayerDetailUpdate update) { - if (this.myPlayer == null) return; - this.myPlayer.setHealth(update.getHealth()); + if (this.player != null) { + this.player.setHealth(update.getHealth()); + this.player.getTools().clear(); + for (var td : update.getTools()) { + if (td instanceof KnifeData knifeData) { + this.player.getTools().add(new Knife()); + } else if (td instanceof GunData gunData) { + this.player.getTools().add(new Gun( + this.world.getGunTypeById(gunData.getTypeId()), + gunData.getCurrentClipBulletCount(), + gunData.getClipCount(), + gunData.isReloading() + )); + } else if (td instanceof GrenadeData grenadeData) { + this.player.getTools().add(new Grenade(grenadeData.getGrenades(), grenadeData.getMaxGrenades())); + } + } + this.player.setSelectedToolIndex(update.getSelectedToolIndex()); + } } /** * Sends a player control state message to the server, which indicates that * the player's controls have been updated, due to a key or mouse event. */ - public void sendPlayerState() { + public void sendControlState() { try { - this.messageTransceiver.sendData(DataTypes.PLAYER_CONTROL_STATE, myPlayer.getId(), myPlayer.getState().toBytes()); + this.messageTransceiver.sendData(DataTypes.PLAYER_CONTROL_STATE, this.player.getId(), this.player.getState().toBytes()); } catch (IOException e) { e.printStackTrace(); } 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 f9fb041..507822f 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 @@ -28,7 +28,7 @@ public class PlayerKeyListener extends KeyAdapter { s.setReloading(false); s.setSneaking(false); s.setSprinting(false); - this.client.sendPlayerState(); + this.client.sendControlState(); if (e.getKeyChar() == '/') this.chatManager.appendToChat('/'); } } else if (this.chatManager.isChatting()) { @@ -63,8 +63,12 @@ public class PlayerKeyListener extends KeyAdapter { state.setSprinting(true); } else if (e.getKeyCode() == KeyEvent.VK_CONTROL) { state.setSneaking(true); + } else if (e.getKeyCode() == KeyEvent.VK_Q) { + state.setSelectingPreviousTool(true); + } else if (e.getKeyCode() == KeyEvent.VK_E) { + state.setSelectingNextTool(true); } - this.client.sendPlayerState(); + this.client.sendControlState(); } @Override @@ -85,7 +89,11 @@ public class PlayerKeyListener extends KeyAdapter { state.setSprinting(false); } else if (e.getKeyCode() == KeyEvent.VK_CONTROL) { state.setSneaking(false); + } else if (e.getKeyCode() == KeyEvent.VK_Q) { + state.setSelectingPreviousTool(false); + } else if (e.getKeyCode() == KeyEvent.VK_E) { + state.setSelectingNextTool(false); } - this.client.sendPlayerState(); + this.client.sendControlState(); } } diff --git a/client/src/main/java/nl/andrewlalis/aos_client/control/PlayerMouseListener.java b/client/src/main/java/nl/andrewlalis/aos_client/control/PlayerMouseListener.java index 8a8f893..14eb0e7 100644 --- a/client/src/main/java/nl/andrewlalis/aos_client/control/PlayerMouseListener.java +++ b/client/src/main/java/nl/andrewlalis/aos_client/control/PlayerMouseListener.java @@ -8,6 +8,15 @@ import javax.swing.event.MouseInputAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseWheelEvent; +/** + * Listens for changes to the player's mouse state. This handles the following + * possible control events: + * + */ public class PlayerMouseListener extends MouseInputAdapter { private static final float MOUSE_UPDATES_PER_SECOND = 60.0f; private static final long MS_PER_MOUSE_UPDATE = (long) (1000.0f / MOUSE_UPDATES_PER_SECOND); @@ -25,16 +34,16 @@ public class PlayerMouseListener extends MouseInputAdapter { @Override public void mousePressed(MouseEvent e) { if (e.getButton() == MouseEvent.BUTTON1) { - client.getPlayer().getState().setShooting(true); - client.sendPlayerState(); + client.getPlayer().getState().setUsingTool(true); + client.sendControlState(); } } @Override public void mouseReleased(MouseEvent e) { if (e.getButton() == MouseEvent.BUTTON1) { - client.getPlayer().getState().setShooting(false); - client.sendPlayerState(); + client.getPlayer().getState().setUsingTool(false); + client.sendControlState(); } } @@ -54,7 +63,7 @@ public class PlayerMouseListener extends MouseInputAdapter { client.getPlayer().getState().setMouseLocation(centeredMouseLocation); long now = System.currentTimeMillis(); if (now - this.lastMouseMove > MS_PER_MOUSE_UPDATE) { - client.sendPlayerState(); + client.sendControlState(); this.lastMouseMove = now; } } @@ -64,10 +73,10 @@ public class PlayerMouseListener extends MouseInputAdapter { Vec2 c = new Vec2(this.gamePanel.getWidth() / 2.0f, this.gamePanel.getHeight() / 2.0f); Vec2 centeredMouseLocation = new Vec2(e.getX(), e.getY()).sub(c); client.getPlayer().getState().setMouseLocation(centeredMouseLocation); - client.getPlayer().getState().setShooting(true); + client.getPlayer().getState().setUsingTool(true); long now = System.currentTimeMillis(); if (now - this.lastMouseMove > MS_PER_MOUSE_UPDATE) { - client.sendPlayerState(); + client.sendControlState(); this.lastMouseMove = now; } } diff --git a/client/src/main/java/nl/andrewlalis/aos_client/model/ClientPlayerData.java b/client/src/main/java/nl/andrewlalis/aos_client/model/ClientPlayerData.java deleted file mode 100644 index 642dea7..0000000 --- a/client/src/main/java/nl/andrewlalis/aos_client/model/ClientPlayerData.java +++ /dev/null @@ -1,27 +0,0 @@ -package nl.andrewlalis.aos_client.model; - -import nl.andrewlalis.aos_core.net.data.tool.ToolData; - -import java.util.List; - -/** - * Information about the client's player, which contains more detailed data than - * what is provided for any random player in the world. - */ -public class ClientPlayerData extends PlayerData { - private float health; - private List tools; - private int selectedToolIndex; - - public float getHealth() { - return health; - } - - public List getTools() { - return tools; - } - - public int getSelectedToolIndex() { - return selectedToolIndex; - } -} diff --git a/client/src/main/java/nl/andrewlalis/aos_client/model/PlayerData.java b/client/src/main/java/nl/andrewlalis/aos_client/model/PlayerData.java deleted file mode 100644 index 8356201..0000000 --- a/client/src/main/java/nl/andrewlalis/aos_client/model/PlayerData.java +++ /dev/null @@ -1,40 +0,0 @@ -package nl.andrewlalis.aos_client.model; - -import nl.andrewlalis.aos_core.geom.Vec2; - -/** - * The data about a player which the client needs to know in order to render it. - */ -public class PlayerData { - private int id; - private String name; - private byte teamId; - - private Vec2 position; - private Vec2 orientation; - private Vec2 velocity; - - public int getId() { - return id; - } - - public String getName() { - return name; - } - - public byte getTeamId() { - return teamId; - } - - public Vec2 getPosition() { - return position; - } - - public Vec2 getOrientation() { - return orientation; - } - - public Vec2 getVelocity() { - return velocity; - } -} 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 dd29898..6e78c78 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 @@ -1,8 +1,9 @@ package nl.andrewlalis.aos_client.view; -import nl.andrewlalis.aos_client.net.ChatManager; import nl.andrewlalis.aos_client.Client; +import nl.andrewlalis.aos_client.net.ChatManager; import nl.andrewlalis.aos_core.model.*; +import nl.andrewlalis.aos_core.model.tools.Grenade; import nl.andrewlalis.aos_core.model.tools.Gun; import nl.andrewlalis.aos_core.net.chat.ChatMessage; import nl.andrewlalis.aos_core.net.chat.ChatType; @@ -84,7 +85,7 @@ public class GamePanel extends JPanel { * @param world The world to render. */ private void drawWorld(Graphics2D g2, World world) { - Player myPlayer = client.getPlayer(); + Player myPlayer = this.client.getPlayer(); if (myPlayer == null) return; double scale = this.scales[this.scaleIndex]; AffineTransform pre = g2.getTransform(); @@ -193,7 +194,9 @@ public class GamePanel extends JPanel { Color playerColor = p.getTeam() != null ? p.getTeam().getColor() : Color.BLACK; g2.setColor(playerColor); g2.fill(dot); - this.drawGun(g2, p.getGun()); + if (p.getSelectedTool() instanceof Gun gun) { + this.drawGun(g2, gun); + } g2.setTransform(pre); } } @@ -302,21 +305,35 @@ public class GamePanel extends JPanel { Player myPlayer = this.client.getPlayer(); if (myPlayer == null) return; + int lineHeight = this.getHeight() - 10; g2.setColor(Color.WHITE); - if (myPlayer.isReloading()) { - g2.drawString("Reloading...", 5, this.getHeight() - 10); + if (myPlayer.getSelectedTool() != null) { + g2.drawString(myPlayer.getSelectedTool().getName(), 5, lineHeight); + lineHeight -= 10; } - Gun gun = myPlayer.getGun(); - g2.drawString("Clips: " + gun.getClipCount() + " / " + gun.getType().maxClipCount(), 5, this.getHeight() - 20); - g2.drawString("Bullets: " + gun.getCurrentClipBulletCount() + " / " + gun.getType().clipSize(), 5, this.getHeight() - 30); - g2.setColor(Color.GREEN); - g2.drawString(String.format("Health: %.1f", myPlayer.getHealth()), 5, this.getHeight() - 40); - int y = this.getHeight() - 60; + if (myPlayer.getSelectedTool() instanceof Gun gun) { + if (gun.isReloading()) { + g2.drawString("Reloading...", 5, lineHeight); + lineHeight -= 10; + } + g2.drawString("Clips: " + gun.getClipCount() + " / " + gun.getType().maxClipCount(), 5, lineHeight); + lineHeight -= 10; + g2.drawString("Bullets: " + gun.getCurrentClipBulletCount() + " / " + gun.getType().clipSize(), 5, lineHeight); + lineHeight -= 10; + } else if (myPlayer.getSelectedTool() instanceof Grenade grenade) { + g2.drawString(grenade.getGrenadesRemaining() + " / " + grenade.getMaxGrenades() + " grenades", 5, lineHeight); + lineHeight -= 10; + } + + g2.setColor(Color.GREEN); + g2.drawString(String.format("Health: %.1f", myPlayer.getHealth()), 5, lineHeight); + lineHeight -= 10; + for (Team t : world.getTeams().values()) { g2.setColor(t.getColor()); - g2.drawString("Team " + t.getName() + ": " + t.getScore(), 5, y); - y -= 15; + g2.drawString("Team " + t.getName() + ": " + t.getScore(), 5, lineHeight); + lineHeight -= 15; } } } diff --git a/core/pom.xml b/core/pom.xml index ea6f593..02e60fa 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -5,7 +5,7 @@ ace-of-shades nl.andrewlalis - 0.5.0 + 0.6.0 4.0.0 diff --git a/core/src/main/java/module-info.java b/core/src/main/java/module-info.java index 30193f9..363ff2e 100644 --- a/core/src/main/java/module-info.java +++ b/core/src/main/java/module-info.java @@ -7,7 +7,7 @@ module aos_core { exports nl.andrewlalis.aos_core.net.data.tool to aos_server, aos_client; exports nl.andrewlalis.aos_core.model to aos_server, aos_client; - exports nl.andrewlalis.aos_core.model.tools to aos_client, aos_server; + exports nl.andrewlalis.aos_core.model.tools to aos_server, aos_client; exports nl.andrewlalis.aos_core.geom to aos_server, aos_client; exports nl.andrewlalis.aos_core.util to aos_server, aos_client; 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 03d2f70..583dd07 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 @@ -14,10 +14,10 @@ public class Bullet extends PhysicsObject { private final Player player; private final Gun gun; - public Bullet(Player player, float sneakAccuracyModifier, float sprintAccuracyModifier) { + public Bullet(Player player, Gun gun, float sneakAccuracyModifier, float sprintAccuracyModifier) { this.playerId = player.getId(); this.player = player; - this.gun = player.getGun(); + this.gun = gun; this.setPhysicsProperties(sneakAccuracyModifier, sprintAccuracyModifier); } @@ -34,14 +34,14 @@ public class Bullet extends PhysicsObject { .add(player.getOrientation().perp().mul(Player.RADIUS)) ); this.setOrientation(player.getOrientation()); - float inaccuracy = player.getGun().getType().inaccuracy(); + float inaccuracy = this.gun.getType().inaccuracy(); if (player.isSneaking()) { inaccuracy *= sneakAccuracyModifier; } else if (player.isSprinting()) { inaccuracy *= sprintAccuracyModifier; } Vec2 perturbation = Vec2.random(-1, 1).mul(inaccuracy); - Vec2 localVelocity = this.getOrientation().add(perturbation).mul(player.getGun().getType().bulletSpeed()); + Vec2 localVelocity = this.getOrientation().add(perturbation).mul(this.gun.getType().bulletSpeed()); this.setVelocity(player.getVelocity().add(localVelocity)); } 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 d9e258f..8433be0 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,45 +2,38 @@ 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 nl.andrewlalis.aos_core.model.tools.Knife; import nl.andrewlalis.aos_core.model.tools.Tool; -import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.Objects; +import java.util.concurrent.CopyOnWriteArrayList; public class Player extends PhysicsObject implements Comparable { public static final float MOVEMENT_THRESHOLD = 0.001f; // Threshold for stopping movement. Speeds slower than this are reduced to 0. public static final float RADIUS = 0.5f; // Collision radius, in meters. + public static final float TOOL_CHANGE_TIME = 0.5f; // Cooldown when swapping weapons, in seconds. private final int id; private final String name; private Team team; private PlayerControlState state; - private List tools; - private Tool selectedTool; + private final List tools; + private int selectedToolIndex; private float health; - // Stats - private transient int killCount; - private transient int deathCount; - private transient int shotCount; - private transient int resupplyCount; - private transient int killStreak; - private transient long lastResupply; + private transient long lastToolChange; - public Player(int id, String name, Team team, GunType gunType, float maxHealth) { + public Player(int id, String name, Team team, float maxHealth) { this.id = id; this.name = name; this.team = team; this.state = new PlayerControlState(); this.health = maxHealth; - this.tools = new ArrayList<>(); - var gun = new Gun(gunType); - this.tools.add(gun); - this.selectedTool = gun; + this.tools = new CopyOnWriteArrayList<>(); + this.tools.add(new Knife()); + this.selectedToolIndex = 0; } public int getId() { @@ -72,7 +65,30 @@ public class Player extends PhysicsObject implements Comparable { } public Tool getSelectedTool() { - return this.selectedTool; + if (this.tools.isEmpty()) return null; + return this.tools.get(this.selectedToolIndex); + } + + public boolean canChangeSelectedTool() { + return System.currentTimeMillis() - this.lastToolChange > TOOL_CHANGE_TIME * 1000; + } + + public void setSelectedToolIndex(int index) { + if (index > this.tools.size() - 1) { + index = 0; + } else if (index < 0) { + index = this.tools.size() - 1; + } + this.selectedToolIndex = index; + this.lastToolChange = System.currentTimeMillis(); + } + + public void selectNextTool() { + this.setSelectedToolIndex(this.selectedToolIndex + 1); + } + + public void selectPreviousTool() { + this.setSelectedToolIndex(this.selectedToolIndex - 1); } public void setHealth(float health) { @@ -80,10 +96,11 @@ public class Player extends PhysicsObject implements Comparable { } public boolean canUseGun() { - return this.state.isShooting() && - this.selectedTool instanceof Gun gun && gun.isUsable() && + return this.state.isUsingTool() && + this.getSelectedTool() instanceof Gun gun && gun.isUsable() && (this.getTeam() == null || this.getTeam().getSpawnPoint().dist(this.getPosition()) > Team.SPAWN_RADIUS); } + public boolean canResupply(float resupplyCooldown) { return this.team != null && this.team.getSupplyPoint().dist(this.getPosition()) < Team.SUPPLY_POINT_RADIUS && @@ -96,7 +113,6 @@ public class Player extends PhysicsObject implements Comparable { } this.health = maxHealth; this.lastResupply = System.currentTimeMillis(); - this.resupplyCount++; } public float getHealth() { @@ -127,43 +143,6 @@ public class Player extends PhysicsObject implements Comparable { !this.state.isSneaking(); } - public int getKillCount() { - return killCount; - } - - public int getDeathCount() { - return deathCount; - } - - public int getShotCount() { - return shotCount; - } - - public int getResupplyCount() { - return resupplyCount; - } - - public int getKillStreak() { - return killStreak; - } - - public void incrementDeathCount() { - this.deathCount++; - this.killStreak = 0; - } - - public void incrementKillCount() { - this.killCount++; - this.killStreak++; - } - - public void resetStats() { - this.killCount = 0; - this.deathCount = 0; - this.shotCount = 0; - this.resupplyCount = 0; - } - @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 771adc5..a6f8468 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 @@ -1,11 +1,15 @@ package nl.andrewlalis.aos_core.model; import nl.andrewlalis.aos_core.geom.Vec2; -import nl.andrewlalis.aos_core.net.data.DataTypes; import java.io.Serializable; import java.nio.ByteBuffer; +/** + * This object represents the player's controls at a given point in time. This + * object is sent by the client to the server each time the client's inputs + * change somehow. + */ public class PlayerControlState implements Serializable { boolean movingLeft; boolean movingRight; @@ -14,9 +18,12 @@ public class PlayerControlState implements Serializable { boolean sprinting; boolean sneaking; - boolean shooting; + boolean usingTool; boolean reloading; + boolean selectingPreviousTool; + boolean selectingNextTool; + Vec2 mouseLocation; public boolean isMovingLeft() { @@ -67,12 +74,12 @@ public class PlayerControlState implements Serializable { this.sneaking = sneaking; } - public boolean isShooting() { - return shooting; + public boolean isUsingTool() { + return usingTool; } - public void setShooting(boolean shooting) { - this.shooting = shooting; + public void setUsingTool(boolean usingTool) { + this.usingTool = usingTool; } public boolean isReloading() { @@ -83,6 +90,22 @@ public class PlayerControlState implements Serializable { this.reloading = reloading; } + public boolean isSelectingPreviousTool() { + return selectingPreviousTool; + } + + public void setSelectingPreviousTool(boolean selectingPreviousTool) { + this.selectingPreviousTool = selectingPreviousTool; + } + + public boolean isSelectingNextTool() { + return selectingNextTool; + } + + public void setSelectingNextTool(boolean selectingNextTool) { + this.selectingNextTool = selectingNextTool; + } + public Vec2 getMouseLocation() { return mouseLocation; } @@ -98,10 +121,12 @@ public class PlayerControlState implements Serializable { if (this.movingRight) flags |= 2; if (this.movingForward) flags |= 4; if (this.movingBackward) flags |= 8; - if (this.shooting) flags |= 16; + if (this.usingTool) flags |= 16; if (this.reloading) flags |= 32; if (this.sprinting) flags |= 64; if (this.sneaking) flags |= 128; + if (this.selectingPreviousTool) flags |= 256; + if (this.selectingNextTool) flags |= 512; buffer.putInt(flags); buffer.putFloat(this.mouseLocation.x()); buffer.putFloat(this.mouseLocation.y()); @@ -116,10 +141,12 @@ public class PlayerControlState implements Serializable { s.movingRight = (flags & 2) > 0; s.movingForward = (flags & 4) > 0; s.movingBackward = (flags & 8) > 0; - s.shooting = (flags & 16) > 0; + s.usingTool = (flags & 16) > 0; s.reloading = (flags & 32) > 0; s.sprinting = (flags & 64) > 0; s.sneaking = (flags & 128) > 0; + s.selectingPreviousTool = (flags & 256) > 0; + s.selectingNextTool = (flags & 512) > 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 113f3f2..5f7572e 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 @@ -14,7 +14,7 @@ public class Team implements Serializable { private final byte id; private final String name; - private final java.awt.Color color; + private final Color color; private final Vec2 spawnPoint; private final Vec2 supplyPoint; private final Vec2 orientation; 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 47e9485..cb778f8 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 @@ -43,6 +43,13 @@ public class World implements Serializable { return gunTypes; } + public GunType getGunTypeById(byte id) { + for (var t : this.gunTypes.values()) { + if (t.id() == id) return t; + } + return null; + } + public Map getPlayers() { return this.players; } diff --git a/core/src/main/java/nl/andrewlalis/aos_core/model/tools/Grenade.java b/core/src/main/java/nl/andrewlalis/aos_core/model/tools/Grenade.java index f88e0a2..b904739 100644 --- a/core/src/main/java/nl/andrewlalis/aos_core/model/tools/Grenade.java +++ b/core/src/main/java/nl/andrewlalis/aos_core/model/tools/Grenade.java @@ -5,16 +5,17 @@ package nl.andrewlalis.aos_core.model.tools; * world, if there are some grenades available. */ public class Grenade implements Tool { - private final int maxGrenades = 3; + private final int maxGrenades; private int grenades; - public Grenade(int grenades) { + public Grenade(int grenades, int maxGrenades) { this.grenades = grenades; + this.maxGrenades = maxGrenades; } public Grenade() { - this.grenades = maxGrenades; + this(3, 3); } public int getGrenadesRemaining() { @@ -25,6 +26,14 @@ public class Grenade implements Tool { return maxGrenades; } + /** + * @return The name of the tool, as it should be shown to the players. + */ + @Override + public String getName() { + return "Grenade"; + } + @Override public void use() { this.grenades--; 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 8b86b96..f63fb96 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 @@ -19,16 +19,17 @@ public class Gun implements Tool { private transient long reloadingStartedAt; private boolean reloading; - public Gun(GunType type, int currentClipBulletCount, int clipCount) { + public Gun(GunType type, int currentClipBulletCount, int clipCount, boolean reloading) { this.type = type; this.currentClipBulletCount = currentClipBulletCount; this.clipCount = clipCount; + this.reloading = reloading; this.lastShot = System.currentTimeMillis(); } public Gun(GunType type) { - this(type, 0, type.maxClipCount()); + this(type, 0, type.maxClipCount(), false); } public GunType getType() { @@ -76,6 +77,14 @@ public class Gun implements Tool { this.reloading = false; } + /** + * @return The name of the tool, as it should be shown to the players. + */ + @Override + public String getName() { + return this.getType().name(); + } + @Override public void use() { this.lastShot = System.currentTimeMillis(); @@ -91,6 +100,15 @@ public class Gun implements Tool { public boolean isUsable() { return !this.reloading && this.currentClipBulletCount > 0 && - this.lastShot + (this.type.shotCooldownTime() * 1000) < System.currentTimeMillis(); + (this.lastShot + (long) (this.type.shotCooldownTime() * 1000)) < System.currentTimeMillis(); + } + + @Override + public void reset() { + this.reloading = false; + this.currentClipBulletCount = this.type.clipSize(); + this.clipCount = this.type.maxClipCount(); + this.lastShot = 0; + this.reloadingStartedAt = 0; } } 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 54b4e7a..4aea1ea 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,9 +1,12 @@ package nl.andrewlalis.aos_core.model.tools; +import java.io.Serializable; + /** * Information about a particular type of gun. */ -public record GunType( +public record GunType ( + byte id, String name, GunCategory category, String color, @@ -16,4 +19,4 @@ public record GunType( float bulletSpeed, float baseDamage, float recoil -) {} +) implements Serializable {} diff --git a/core/src/main/java/nl/andrewlalis/aos_core/model/tools/Knife.java b/core/src/main/java/nl/andrewlalis/aos_core/model/tools/Knife.java new file mode 100644 index 0000000..36b3b51 --- /dev/null +++ b/core/src/main/java/nl/andrewlalis/aos_core/model/tools/Knife.java @@ -0,0 +1,45 @@ +package nl.andrewlalis.aos_core.model.tools; + +public class Knife implements Tool { + /** + * @return The name of the tool, as it should be shown to the players. + */ + @Override + public String getName() { + return "Knife"; + } + + /** + * Uses the tool. + */ + @Override + public void use() { + + } + + /** + * Resupplies the tool, when a player is resupplied at their team's area. + */ + @Override + public void resupply() { + + } + + /** + * Resets the tool to its preferred initial state. This is useful for things + * like respawning. + */ + @Override + public void reset() { + + } + + /** + * @return True if the player may use the tool to perform an action, or + * false if it's not possible to do so. + */ + @Override + public boolean isUsable() { + return true; + } +} diff --git a/core/src/main/java/nl/andrewlalis/aos_core/model/tools/Tool.java b/core/src/main/java/nl/andrewlalis/aos_core/model/tools/Tool.java index de59f58..2b67993 100644 --- a/core/src/main/java/nl/andrewlalis/aos_core/model/tools/Tool.java +++ b/core/src/main/java/nl/andrewlalis/aos_core/model/tools/Tool.java @@ -6,6 +6,11 @@ import java.io.Serializable; * Represents some sort of usable tool item that players can equip and use. */ public interface Tool extends Serializable { + /** + * @return The name of the tool, as it should be shown to the players. + */ + String getName(); + /** * Uses the tool. */ 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 2fbacfd..4711387 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 @@ -3,9 +3,11 @@ package nl.andrewlalis.aos_core.net.data; import nl.andrewlalis.aos_core.model.Player; import nl.andrewlalis.aos_core.model.tools.Grenade; import nl.andrewlalis.aos_core.model.tools.Gun; +import nl.andrewlalis.aos_core.model.tools.Knife; import nl.andrewlalis.aos_core.model.tools.Tool; import nl.andrewlalis.aos_core.net.data.tool.GrenadeData; import nl.andrewlalis.aos_core.net.data.tool.GunData; +import nl.andrewlalis.aos_core.net.data.tool.KnifeData; import nl.andrewlalis.aos_core.net.data.tool.ToolData; import java.nio.ByteBuffer; @@ -14,28 +16,33 @@ import java.util.List; public class PlayerDetailUpdate { private final float health; - - private List tools; - private int selectedToolIndex; + private final List tools; + private final int selectedToolIndex; public PlayerDetailUpdate(Player player) { this.health = player.getHealth(); this.tools = new ArrayList<>(player.getTools().size()); + int toolIndex = 0; for (int i = 0; i < player.getTools().size(); i++) { var t = player.getTools().get(i); - if (t instanceof Gun g) { + if (t instanceof Knife k) { + this.tools.add(new KnifeData(k)); + } else if (t instanceof Gun g) { this.tools.add(new GunData(g)); } else if (t instanceof Grenade g) { this.tools.add(new GrenadeData(g)); } if (t.equals(player.getSelectedTool())) { - selectedToolIndex = i; + toolIndex = i; } } + this.selectedToolIndex = toolIndex; } private PlayerDetailUpdate(float health, List tools, int selectedToolIndex) { this.health = health; + this.tools = tools; + this.selectedToolIndex = selectedToolIndex; } public float getHealth() { @@ -46,10 +53,14 @@ public class PlayerDetailUpdate { return tools; } + public int getSelectedToolIndex() { + return this.selectedToolIndex; + } + public byte[] toBytes() { int size = Float.BYTES + 2 * Integer.BYTES; for (var td : this.tools) { - size += td.getByteSize(); + size += 1 + td.getByteSize(); } ByteBuffer buffer = ByteBuffer.allocate(size); buffer.putFloat(this.health); 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 5bc4b3a..50681db 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 @@ -4,21 +4,22 @@ import nl.andrewlalis.aos_core.geom.Vec2; import nl.andrewlalis.aos_core.model.Player; import nl.andrewlalis.aos_core.model.tools.Grenade; import nl.andrewlalis.aos_core.model.tools.Gun; +import nl.andrewlalis.aos_core.model.tools.Knife; import nl.andrewlalis.aos_core.net.data.tool.GrenadeData; import nl.andrewlalis.aos_core.net.data.tool.GunData; +import nl.andrewlalis.aos_core.net.data.tool.KnifeData; import nl.andrewlalis.aos_core.net.data.tool.ToolData; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; +import java.nio.ByteBuffer; /** * 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; - private final int id; private final Vec2 position; private final Vec2 orientation; @@ -30,7 +31,9 @@ public class PlayerUpdate { this.position = player.getPosition(); this.orientation = player.getOrientation(); this.velocity = player.getVelocity(); - if (player.getSelectedTool() instanceof Gun gun) { + if (player.getSelectedTool() instanceof Knife knife) { + this.selectedTool = new KnifeData(knife); + } else if (player.getSelectedTool() instanceof Gun gun) { this.selectedTool = new GunData(gun); } else if (player.getSelectedTool() instanceof Grenade grenade) { this.selectedTool = new GrenadeData(grenade); @@ -39,12 +42,12 @@ public class PlayerUpdate { } } - public PlayerUpdate(int id, Vec2 position, Vec2 orientation, Vec2 velocity) { + public PlayerUpdate(int id, Vec2 position, Vec2 orientation, Vec2 velocity, ToolData selectedTool) { this.id = id; this.position = position; this.orientation = orientation; this.velocity = velocity; - this.selectedTool = null; + this.selectedTool = selectedTool; } public int getId() { @@ -63,6 +66,10 @@ public class PlayerUpdate { return velocity; } + public ToolData getSelectedTool() { + return selectedTool; + } + public void write(DataOutputStream out) throws IOException { out.writeInt(this.id); out.writeFloat(this.position.x()); @@ -71,6 +78,10 @@ public class PlayerUpdate { out.writeFloat(this.orientation.y()); out.writeFloat(this.velocity.x()); out.writeFloat(this.velocity.y()); + out.writeInt(1 + this.selectedTool.getByteSize()); + ByteBuffer buffer = ByteBuffer.allocate(1 + this.selectedTool.getByteSize()); + this.selectedTool.write(buffer); + out.write(buffer.array()); } public static PlayerUpdate read(DataInputStream in) throws IOException { @@ -78,6 +89,9 @@ public class PlayerUpdate { Vec2 position = Vec2.read(in); Vec2 orientation = Vec2.read(in); Vec2 velocity = Vec2.read(in); - return new PlayerUpdate(id, position, orientation, velocity, gunTypeName); + int toolByteSize = in.readInt(); + ByteBuffer buffer = ByteBuffer.wrap(in.readNBytes(toolByteSize)); + ToolData tool = ToolData.read(buffer); + return new PlayerUpdate(id, position, orientation, velocity, tool); } } diff --git a/core/src/main/java/nl/andrewlalis/aos_core/net/data/WorldUpdate.java b/core/src/main/java/nl/andrewlalis/aos_core/net/data/WorldUpdate.java index d5f0f52..c8316c7 100644 --- a/core/src/main/java/nl/andrewlalis/aos_core/net/data/WorldUpdate.java +++ b/core/src/main/java/nl/andrewlalis/aos_core/net/data/WorldUpdate.java @@ -75,12 +75,7 @@ public class WorldUpdate { } public byte[] toBytes() throws IOException { - int size = 3 * Integer.BYTES + // List size integers. - this.playerUpdates.size() * PlayerUpdate.BYTES + - this.bulletUpdates.size() * BulletUpdate.BYTES + - this.teamUpdates.size() * TeamUpdate.BYTES + - this.soundsToPlay.size() * SoundData.BYTES; - ByteArrayOutputStream out = new ByteArrayOutputStream(size); + ByteArrayOutputStream out = new ByteArrayOutputStream(1024); DataOutputStream dataOut = new DataOutputStream(out); dataOut.writeInt(this.playerUpdates.size()); for (var u : this.playerUpdates) { diff --git a/core/src/main/java/nl/andrewlalis/aos_core/net/data/tool/GrenadeData.java b/core/src/main/java/nl/andrewlalis/aos_core/net/data/tool/GrenadeData.java index ea621bd..4cc87d4 100644 --- a/core/src/main/java/nl/andrewlalis/aos_core/net/data/tool/GrenadeData.java +++ b/core/src/main/java/nl/andrewlalis/aos_core/net/data/tool/GrenadeData.java @@ -1,6 +1,8 @@ package nl.andrewlalis.aos_core.net.data.tool; +import nl.andrewlalis.aos_core.model.World; import nl.andrewlalis.aos_core.model.tools.Grenade; +import nl.andrewlalis.aos_core.model.tools.Tool; import java.nio.ByteBuffer; @@ -18,6 +20,14 @@ public class GrenadeData extends ToolData { this.maxGrenades = grenade.getMaxGrenades(); } + public int getGrenades() { + return grenades; + } + + public int getMaxGrenades() { + return maxGrenades; + } + @Override public int getByteSize() { return 2 * Integer.BYTES; @@ -34,4 +44,9 @@ public class GrenadeData extends ToolData { this.grenades = buffer.getInt(); this.maxGrenades = buffer.getInt(); } + + @Override + public Tool toTool(World world) { + return new Grenade(this.grenades, this.maxGrenades); + } } diff --git a/core/src/main/java/nl/andrewlalis/aos_core/net/data/tool/GunData.java b/core/src/main/java/nl/andrewlalis/aos_core/net/data/tool/GunData.java index b23d27e..f2aecb5 100644 --- a/core/src/main/java/nl/andrewlalis/aos_core/net/data/tool/GunData.java +++ b/core/src/main/java/nl/andrewlalis/aos_core/net/data/tool/GunData.java @@ -1,10 +1,13 @@ package nl.andrewlalis.aos_core.net.data.tool; +import nl.andrewlalis.aos_core.model.World; import nl.andrewlalis.aos_core.model.tools.Gun; +import nl.andrewlalis.aos_core.model.tools.Tool; import java.nio.ByteBuffer; public class GunData extends ToolData { + private byte typeId; private boolean reloading; private int clipCount; private int currentClipBulletCount; @@ -18,6 +21,7 @@ public class GunData extends ToolData { public GunData(Gun gun) { this(); + this.typeId = gun.getType().id(); this.reloading = gun.isReloading(); this.clipCount = gun.getClipCount(); this.currentClipBulletCount = gun.getCurrentClipBulletCount(); @@ -26,6 +30,10 @@ public class GunData extends ToolData { this.maxClipCount = gun.getType().maxClipCount(); } + public byte getTypeId() { + return typeId; + } + public boolean isReloading() { return reloading; } @@ -52,11 +60,12 @@ public class GunData extends ToolData { @Override public int getByteSize() { - return 1 + 5 * Integer.BYTES; + return 2 * Byte.BYTES + 5 * Integer.BYTES; } @Override protected void putData(ByteBuffer buffer) { + buffer.put(this.getTypeId()); buffer.put((byte) (this.reloading ? 1 : 0)); buffer.putInt(this.clipCount); buffer.putInt(this.currentClipBulletCount); @@ -67,6 +76,7 @@ public class GunData extends ToolData { @Override protected void getData(ByteBuffer buffer) { + this.typeId = buffer.get(); this.reloading = buffer.get() == 1; this.clipCount = buffer.getInt(); this.currentClipBulletCount = buffer.getInt(); @@ -74,4 +84,9 @@ public class GunData extends ToolData { this.clipSize = buffer.getInt(); this.maxClipCount = buffer.getInt(); } + + @Override + public Tool toTool(World world) { + return new Gun(world.getGunTypeById(this.getTypeId()), this.currentClipBulletCount, this.clipCount, this.reloading); + } } diff --git a/core/src/main/java/nl/andrewlalis/aos_core/net/data/tool/KnifeData.java b/core/src/main/java/nl/andrewlalis/aos_core/net/data/tool/KnifeData.java new file mode 100644 index 0000000..46d27b1 --- /dev/null +++ b/core/src/main/java/nl/andrewlalis/aos_core/net/data/tool/KnifeData.java @@ -0,0 +1,37 @@ +package nl.andrewlalis.aos_core.net.data.tool; + +import nl.andrewlalis.aos_core.model.World; +import nl.andrewlalis.aos_core.model.tools.Knife; +import nl.andrewlalis.aos_core.model.tools.Tool; + +import java.nio.ByteBuffer; + +public class KnifeData extends ToolData { + public KnifeData() { + super((byte) 2); + } + + public KnifeData(Knife knife) { + super((byte) 2); + } + + @Override + public int getByteSize() { + return 1; + } + + @Override + protected void putData(ByteBuffer buffer) { + buffer.put((byte) 0); + } + + @Override + protected void getData(ByteBuffer buffer) { + buffer.get(); + } + + @Override + public Tool toTool(World world) { + return new Knife(); + } +} diff --git a/core/src/main/java/nl/andrewlalis/aos_core/net/data/tool/ToolData.java b/core/src/main/java/nl/andrewlalis/aos_core/net/data/tool/ToolData.java index b3090d5..a65bdd6 100644 --- a/core/src/main/java/nl/andrewlalis/aos_core/net/data/tool/ToolData.java +++ b/core/src/main/java/nl/andrewlalis/aos_core/net/data/tool/ToolData.java @@ -1,5 +1,8 @@ package nl.andrewlalis.aos_core.net.data.tool; +import nl.andrewlalis.aos_core.model.World; +import nl.andrewlalis.aos_core.model.tools.Tool; + import java.nio.ByteBuffer; import java.util.HashMap; import java.util.Map; @@ -9,6 +12,7 @@ public abstract class ToolData { static { dataMapping.put((byte) 0, GunData.class); dataMapping.put((byte) 1, GrenadeData.class); + dataMapping.put((byte) 2, KnifeData.class); } private final byte toolType; @@ -52,4 +56,11 @@ public abstract class ToolData { * @param buffer The byte buffer to read data from. */ protected abstract void getData(ByteBuffer buffer); + + /** + * Converts this data back into a Tool instance. + * @param world The world in which the tool is being used. + * @return The tool that this data represents. + */ + public abstract Tool toTool(World world); } diff --git a/network.md b/network.md new file mode 100644 index 0000000..48675b1 --- /dev/null +++ b/network.md @@ -0,0 +1,8 @@ +# Networking Protocol +In order to make Ace of Shades as performant as possible, the application makes use of a custom, rather low-level system of communication between the server and client. This document explains how this protocol works. + +## Tick Updates +Every time the server computes one game tick, it sends a packet to each client. This packet includes the following information: + +- Updated position, velocity, and orientation data for all physics objects. +- diff --git a/pom.xml b/pom.xml index b3bf81b..5990a35 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ nl.andrewlalis ace-of-shades pom - 0.5.0 + 0.6.0 server client diff --git a/server-registry/pom.xml b/server-registry/pom.xml index 83aa913..b24c3c7 100644 --- a/server-registry/pom.xml +++ b/server-registry/pom.xml @@ -5,7 +5,7 @@ ace-of-shades nl.andrewlalis - 0.5.0 + 0.6.0 4.0.0 diff --git a/server/pom.xml b/server/pom.xml index 334f210..ba71cdd 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -5,7 +5,7 @@ ace-of-shades nl.andrewlalis - 0.5.0 + 0.6.0 4.0.0 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 b2cfad6..32ca381 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,12 @@ package nl.andrewlalis.aos_server; import nl.andrewlalis.aos_core.geom.Vec2; -import nl.andrewlalis.aos_core.model.*; +import nl.andrewlalis.aos_core.model.Barricade; +import nl.andrewlalis.aos_core.model.Player; +import nl.andrewlalis.aos_core.model.Team; +import nl.andrewlalis.aos_core.model.World; +import nl.andrewlalis.aos_core.model.tools.Grenade; +import nl.andrewlalis.aos_core.model.tools.Gun; import nl.andrewlalis.aos_core.model.tools.GunCategory; import nl.andrewlalis.aos_core.model.tools.GunType; import nl.andrewlalis.aos_core.net.Message; @@ -9,6 +14,7 @@ import nl.andrewlalis.aos_core.net.PlayerUpdateMessage; import nl.andrewlalis.aos_core.net.Type; import nl.andrewlalis.aos_core.net.chat.SystemChatMessage; import nl.andrewlalis.aos_core.net.data.DataTypes; +import nl.andrewlalis.aos_core.model.PlayerControlState; import nl.andrewlalis.aos_core.net.data.PlayerDetailUpdate; import nl.andrewlalis.aos_core.net.data.WorldUpdate; import nl.andrewlalis.aos_core.util.ByteUtils; @@ -59,8 +65,10 @@ public class Server { } private void initWorld() { + byte gunTypeId = 0; for (var gs : this.settings.getGunSettings()) { this.world.getGunTypes().put(gs.getName(), new GunType( + gunTypeId++, gs.getName(), GunCategory.valueOf(gs.getCategory().toUpperCase()), gs.getColor(), @@ -136,7 +144,9 @@ public class Server { team = t; } } - Player p = new Player(id, name, team, this.world.getGunTypes().get(this.settings.getPlayerSettings().getDefaultGun()), settings.getPlayerSettings().getMaxHealth()); + Player p = new Player(id, name, team, settings.getPlayerSettings().getMaxHealth()); + p.getTools().add(new Gun(this.world.getGunTypes().get(this.settings.getPlayerSettings().getDefaultGun()))); + p.getTools().add(new Grenade(3, 3)); this.world.getPlayers().put(p.getId(), p); String message = p.getName() + " connected."; this.broadcastMessage(new SystemChatMessage(SystemChatMessage.Level.INFO, message)); @@ -204,7 +214,6 @@ public class Server { for (Team t : this.world.getTeams().values()) { t.resetScore(); for (Player p : t.getPlayers()) { - p.resetStats(); p.respawn(settings.getPlayerSettings().getMaxHealth()); } } 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 9b47fa6..d1379e3 100644 --- a/server/src/main/java/nl/andrewlalis/aos_server/WorldUpdater.java +++ b/server/src/main/java/nl/andrewlalis/aos_server/WorldUpdater.java @@ -2,6 +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.Gun; import nl.andrewlalis.aos_core.model.tools.GunCategory; import nl.andrewlalis.aos_core.net.chat.SystemChatMessage; import nl.andrewlalis.aos_core.net.data.SoundData; @@ -71,7 +72,7 @@ public class WorldUpdater extends Thread { private void updatePlayers(float t) { for (Player p : this.world.getPlayers().values()) { this.updatePlayerMovement(p, t); - this.updatePlayerShooting(p); + this.updatePlayerInteraction(p); p.setHealth(Math.min(p.getHealth() + server.getSettings().getPlayerSettings().getHealthRegenRate() * t, server.getSettings().getPlayerSettings().getMaxHealth())); this.worldUpdate.addPlayer(p); } @@ -183,31 +184,38 @@ public class WorldUpdater extends Thread { } } - private void updatePlayerShooting(Player p) { - if (p.canUseGun()) { - for (int i = 0; i < p.getGun().getType().bulletsPerRound(); i++) { - Bullet b = new Bullet(p, server.getSettings().getPlayerSettings().getSneakAccuracyModifier(), server.getSettings().getPlayerSettings().getSprintAccuracyModifier()); - this.world.getBullets().add(b); - this.worldUpdate.addBullet(b); - } - SoundType soundType = SoundType.SHOT_SMG; - if (p.getGun().getType().category() == GunCategory.RIFLE) { - soundType = SoundType.SHOT_RIFLE; - } else if (p.getGun().getType().category() == GunCategory.SHOTGUN) { - soundType = SoundType.SHOT_SHOTGUN; - } else if (p.getGun().getType().category() == GunCategory.MACHINE) { - soundType = ThreadLocalRandom.current().nextFloat() < 0.8f ? SoundType.SHOT_MACHINE_GUN_1 : SoundType.SHOT_MACHINE_GUN_2; - } - this.worldUpdate.addSound(new SoundData(p.getPosition(), 1.0f, soundType)); - p.useWeapon(); - p.setVelocity(p.getVelocity().add(p.getOrientation().mul(-1 * p.getGun().getType().recoil()))); + private void updatePlayerInteraction(Player p) { + if ((p.getState().isSelectingNextTool() ^ p.getState().isSelectingPreviousTool()) && p.canChangeSelectedTool()) { + if (p.getState().isSelectingNextTool()) p.selectNextTool(); + if (p.getState().isSelectingPreviousTool()) p.selectPreviousTool(); + return; // Don't immediately do any action with the newly-selected tool. } - if (p.getState().isReloading() && !p.isReloading() && p.getGun().canReload()) { - p.startReloading(); - } - if (p.isReloading() && p.isReloadingComplete()) { - p.finishReloading(); - this.worldUpdate.addSound(new SoundData(p.getPosition(), 1.0f, SoundType.RELOAD)); + if (p.getSelectedTool() instanceof Gun gun) { + if (p.canUseGun()) { + for (int i = 0; i < gun.getType().bulletsPerRound(); i++) { + Bullet b = new Bullet(p, gun, server.getSettings().getPlayerSettings().getSneakAccuracyModifier(), server.getSettings().getPlayerSettings().getSprintAccuracyModifier()); + this.world.getBullets().add(b); + this.worldUpdate.addBullet(b); + } + SoundType soundType = SoundType.SHOT_SMG; + if (gun.getType().category() == GunCategory.RIFLE) { + soundType = SoundType.SHOT_RIFLE; + } else if (gun.getType().category() == GunCategory.SHOTGUN) { + soundType = SoundType.SHOT_SHOTGUN; + } else if (gun.getType().category() == GunCategory.MACHINE) { + soundType = ThreadLocalRandom.current().nextFloat() < 0.8f ? SoundType.SHOT_MACHINE_GUN_1 : SoundType.SHOT_MACHINE_GUN_2; + } + this.worldUpdate.addSound(new SoundData(p.getPosition(), 1.0f, soundType)); + gun.use(); + p.setVelocity(p.getVelocity().add(p.getOrientation().mul(-1 * gun.getType().recoil()))); + } + if (p.getState().isReloading() && !gun.isReloading() && gun.canReload()) { + gun.startReloading(); + } + if (gun.isReloading() && gun.isReloadingComplete()) { + gun.reload(); + this.worldUpdate.addSound(new SoundData(p.getPosition(), 1.0f, SoundType.RELOAD)); + } } } @@ -261,12 +269,10 @@ public class WorldUpdater extends Thread { Player shooter = this.world.getPlayers().get(b.getPlayerId()); this.server.broadcastMessage(new SystemChatMessage(SystemChatMessage.Level.SEVERE, p.getName() + " was shot by " + shooter.getName() + ".")); this.worldUpdate.addSound(new SoundData(p.getPosition(), 1.0f, SoundType.DEATH)); - shooter.incrementKillCount(); if (shooter.getTeam() != null) { shooter.getTeam().incrementScore(); this.worldUpdate.addTeam(shooter.getTeam()); } - p.incrementDeathCount(); p.respawn(server.getSettings().getPlayerSettings().getMaxHealth()); } } 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 c3b0974..e26251a 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 @@ -6,8 +6,6 @@ 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) { @@ -27,7 +25,16 @@ public class GunCommand implements ChatCommand { handler.send(new SystemChatMessage(SystemChatMessage.Level.WARNING, "Unknown gun name. Use /guns to see available guns.")); return; } - player.setGun(new Gun(gunType)); - handler.send(new SystemChatMessage(SystemChatMessage.Level.INFO, "Changed gun to " + player.getGun().getType().name() + ".")); + boolean gunSet = false; + for (int i = 0; i < player.getTools().size(); i++) { + if (player.getTools().get(i) instanceof Gun) { + player.getTools().set(i, new Gun(gunType)); + gunSet = true; + } + } + if (!gunSet) { + player.getTools().add(new Gun(gunType)); + } + handler.send(new SystemChatMessage(SystemChatMessage.Level.INFO, "Changed gun to " + gunType.name() + ".")); } } diff --git a/server/src/main/java/nl/andrewlalis/aos_server/command/chat/KillDeathRatioCommand.java b/server/src/main/java/nl/andrewlalis/aos_server/command/chat/KillDeathRatioCommand.java index 2d86915..5dfe24d 100644 --- a/server/src/main/java/nl/andrewlalis/aos_server/command/chat/KillDeathRatioCommand.java +++ b/server/src/main/java/nl/andrewlalis/aos_server/command/chat/KillDeathRatioCommand.java @@ -7,7 +7,8 @@ import nl.andrewlalis.aos_server.ClientHandler; public class KillDeathRatioCommand implements ChatCommand { @Override public void execute(ClientHandler handler, Player player, String[] args) { - float ratio = player.getKillCount() / ((float) player.getDeathCount()); - handler.send(new SystemChatMessage(SystemChatMessage.Level.INFO, String.format("Your Kill/Death ratio is %.2f.", ratio))); +// float ratio = player.getKillCount() / ((float) player.getDeathCount()); +// handler.send(new SystemChatMessage(SystemChatMessage.Level.INFO, String.format("Your Kill/Death ratio is %.2f.", ratio))); + handler.send(new SystemChatMessage(SystemChatMessage.Level.WARNING, "K/D command not yet implemented.")); } } diff --git a/server/src/main/java/nl/andrewlalis/aos_server/model/PlayerStatistics.java b/server/src/main/java/nl/andrewlalis/aos_server/model/PlayerStatistics.java new file mode 100644 index 0000000..889ee38 --- /dev/null +++ b/server/src/main/java/nl/andrewlalis/aos_server/model/PlayerStatistics.java @@ -0,0 +1,9 @@ +package nl.andrewlalis.aos_server.model; + +public class PlayerStatistics { + private transient int killCount; + private transient int deathCount; + private transient int shotCount; + private transient int resupplyCount; + private transient int killStreak; +}