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 5d678fa..4d0e115 100644 --- a/client/src/main/java/nl/andrewlalis/aos_client/Client.java +++ b/client/src/main/java/nl/andrewlalis/aos_client/Client.java @@ -10,9 +10,13 @@ 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.Gun; +import nl.andrewlalis.aos_core.model.tools.GunType; +import nl.andrewlalis.aos_core.model.tools.Tool; 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.GunData; +import nl.andrewlalis.aos_core.net.data.tool.ToolData; import java.io.IOException; @@ -23,7 +27,6 @@ public class Client { private final MessageTransceiver messageTransceiver; private World world; - private Player myPlayer; private final GameRenderer renderer; private final SoundManager soundManager; @@ -123,8 +126,6 @@ public class Client { public void updatePlayer(PlayerDetailUpdate update) { if (this.myPlayer == null) return; this.myPlayer.setHealth(update.getHealth()); - this.myPlayer.setReloading(update.isReloading()); - this.myPlayer.setGun(new Gun(this.myPlayer.getGun().getType(), update.getGunCurrentClipBulletCount(), update.getGunClipCount())); } /** 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 new file mode 100644 index 0000000..642dea7 --- /dev/null +++ b/client/src/main/java/nl/andrewlalis/aos_client/model/ClientPlayerData.java @@ -0,0 +1,27 @@ +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 new file mode 100644 index 0000000..8356201 --- /dev/null +++ b/client/src/main/java/nl/andrewlalis/aos_client/model/PlayerData.java @@ -0,0 +1,40 @@ +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 5f41dcb..dd29898 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 @@ -204,7 +204,7 @@ public class GamePanel extends JPanel { * @param gun The gun to draw. */ private void drawGun(Graphics2D g2, Gun gun) { - g2.setColor(Color.decode(gun.getType().getColor())); + g2.setColor(Color.decode(gun.getType().color())); Rectangle2D.Double gunBarrel = new Rectangle2D.Double( 0, 0.5, @@ -307,8 +307,8 @@ public class GamePanel extends JPanel { g2.drawString("Reloading...", 5, this.getHeight() - 10); } Gun gun = myPlayer.getGun(); - g2.drawString("Clips: " + gun.getClipCount() + " / " + gun.getType().getMaxClipCount(), 5, this.getHeight() - 20); - g2.drawString("Bullets: " + gun.getCurrentClipBulletCount() + " / " + gun.getType().getClipSize(), 5, this.getHeight() - 30); + 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); diff --git a/core/src/main/java/module-info.java b/core/src/main/java/module-info.java index 41bd9ae..30193f9 100644 --- a/core/src/main/java/module-info.java +++ b/core/src/main/java/module-info.java @@ -4,6 +4,7 @@ module aos_core { exports nl.andrewlalis.aos_core.net to aos_server, aos_client; exports nl.andrewlalis.aos_core.net.chat to aos_client, aos_server; exports nl.andrewlalis.aos_core.net.data to aos_server, aos_client; + 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; 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 e5aeaca..03d2f70 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 @@ -7,7 +7,7 @@ import nl.andrewlalis.aos_core.model.tools.Gun; * Represents a single projectile bullet fired from a player's gun. When shot by * a player, a newly-spawned bullet will be initialized with a velocity in the * general direction of the gun, with some perturbation according to the gun's - * accuracy and player's sprinting/sneaking status. + * inaccuracy and player's sprinting/sneaking status. */ public class Bullet extends PhysicsObject { private final int playerId; @@ -34,14 +34,14 @@ public class Bullet extends PhysicsObject { .add(player.getOrientation().perp().mul(Player.RADIUS)) ); this.setOrientation(player.getOrientation()); - float accuracy = player.getGun().getType().getAccuracy(); + float inaccuracy = player.getGun().getType().inaccuracy(); if (player.isSneaking()) { - accuracy *= sneakAccuracyModifier; + inaccuracy *= sneakAccuracyModifier; } else if (player.isSprinting()) { - accuracy *= sprintAccuracyModifier; + inaccuracy *= sprintAccuracyModifier; } - Vec2 perturbation = Vec2.random(-1, 1).mul(accuracy); - Vec2 localVelocity = this.getOrientation().add(perturbation).mul(player.getGun().getType().getBulletSpeed()); + Vec2 perturbation = Vec2.random(-1, 1).mul(inaccuracy); + Vec2 localVelocity = this.getOrientation().add(perturbation).mul(player.getGun().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 3d73703..d9e258f 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 @@ -3,7 +3,11 @@ 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.Tool; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import java.util.Objects; public class Player extends PhysicsObject implements Comparable { @@ -14,14 +18,10 @@ public class Player extends PhysicsObject implements Comparable { private final String name; private Team team; private PlayerControlState state; - private Gun gun; + private List tools; + private Tool selectedTool; private float health; - private transient long lastShot; - private transient long reloadingStartedAt; - private boolean reloading; - private transient long lastResupply; - // Stats private transient int killCount; private transient int deathCount; @@ -29,15 +29,18 @@ public class Player extends PhysicsObject implements Comparable { private transient int resupplyCount; private transient int killStreak; + private transient long lastResupply; + public Player(int id, String name, Team team, GunType gunType, float maxHealth) { this.id = id; this.name = name; this.team = team; this.state = new PlayerControlState(); - this.gun = new Gun(gunType); this.health = maxHealth; - this.useWeapon(); - this.lastShot = System.currentTimeMillis(); + this.tools = new ArrayList<>(); + var gun = new Gun(gunType); + this.tools.add(gun); + this.selectedTool = gun; } public int getId() { @@ -64,56 +67,23 @@ public class Player extends PhysicsObject implements Comparable { this.team = team; } - public Gun getGun() { - return gun; + public List getTools() { + return this.tools; } - public void setGun(Gun gun) { - this.gun = gun; + public Tool getSelectedTool() { + return this.selectedTool; } public void setHealth(float health) { this.health = health; } - public void setReloading(boolean reloading) { - this.reloading = reloading; - } - - public boolean canUseWeapon() { + public boolean canUseGun() { return this.state.isShooting() && - !this.state.isReloading() && - !this.reloading && - this.gun.getCurrentClipBulletCount() > 0 && - this.lastShot + ((long) (this.gun.getType().getShotCooldownTime() * 1000)) < System.currentTimeMillis() && + this.selectedTool instanceof Gun gun && gun.isUsable() && (this.getTeam() == null || this.getTeam().getSpawnPoint().dist(this.getPosition()) > Team.SPAWN_RADIUS); } - - public void useWeapon() { - this.lastShot = System.currentTimeMillis(); - this.gun.decrementBulletCount(); - this.shotCount++; - } - - public void startReloading() { - this.reloading = true; - this.reloadingStartedAt = System.currentTimeMillis(); - } - - public void finishReloading() { - this.gun.reload(); - this.reloading = false; - } - - public boolean isReloadingComplete() { - long msSinceStart = System.currentTimeMillis() - this.reloadingStartedAt; - return msSinceStart > this.gun.getType().getReloadTime() * 1000; - } - - public boolean isReloading() { - return reloading; - } - public boolean canResupply(float resupplyCooldown) { return this.team != null && this.team.getSupplyPoint().dist(this.getPosition()) < Team.SUPPLY_POINT_RADIUS && @@ -121,9 +91,11 @@ public class Player extends PhysicsObject implements Comparable { } public void resupply(float maxHealth) { - this.lastResupply = System.currentTimeMillis(); - this.gun.refillClips(); + for (Tool t : this.tools) { + t.resupply(); + } this.health = maxHealth; + this.lastResupply = System.currentTimeMillis(); this.resupplyCount++; } @@ -137,7 +109,9 @@ public class Player extends PhysicsObject implements Comparable { public void respawn(float maxHealth) { this.resupply(maxHealth); - this.gun.emptyCurrentClip(); + for (Tool t : this.tools) { + t.reset(); + } if (this.team != null) { this.setPosition(this.team.getSpawnPoint().add(Vec2.random(-Team.SPAWN_RADIUS / 2, Team.SPAWN_RADIUS / 2))); } 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 new file mode 100644 index 0000000..f88e0a2 --- /dev/null +++ b/core/src/main/java/nl/andrewlalis/aos_core/model/tools/Grenade.java @@ -0,0 +1,47 @@ +package nl.andrewlalis.aos_core.model.tools; + +/** + * The grenade tool, when equipped, allows the player to throw grenades into the + * world, if there are some grenades available. + */ +public class Grenade implements Tool { + private final int maxGrenades = 3; + + private int grenades; + + public Grenade(int grenades) { + this.grenades = grenades; + } + + public Grenade() { + this.grenades = maxGrenades; + } + + public int getGrenadesRemaining() { + return grenades; + } + + public int getMaxGrenades() { + return maxGrenades; + } + + @Override + public void use() { + this.grenades--; + } + + @Override + public void resupply() { + this.grenades = this.maxGrenades; + } + + @Override + public void reset() { + this.resupply(); + } + + @Override + public boolean isUsable() { + return this.grenades > 0; + } +} 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 c8feca4..8b86b96 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 @@ -1,8 +1,9 @@ package nl.andrewlalis.aos_core.model.tools; -import java.io.Serializable; - -public class Gun implements Serializable { +/** + * A type of tool that, when equipped, allows the player to shoot bullets. + */ +public class Gun implements Tool { GunType type; /** @@ -14,14 +15,20 @@ public class Gun implements Serializable { */ private int clipCount; + private transient long lastShot; + private transient long reloadingStartedAt; + private boolean reloading; + public Gun(GunType type, int currentClipBulletCount, int clipCount) { this.type = type; this.currentClipBulletCount = currentClipBulletCount; this.clipCount = clipCount; + + this.lastShot = System.currentTimeMillis(); } public Gun(GunType type) { - this(type, 0, type.getMaxClipCount()); + this(type, 0, type.maxClipCount()); } public GunType getType() { @@ -36,10 +43,6 @@ public class Gun implements Serializable { return clipCount; } - public void refillClips() { - this.clipCount = this.type.getMaxClipCount(); - } - public void decrementBulletCount() { this.currentClipBulletCount = Math.max(this.currentClipBulletCount - 1, 0); } @@ -52,10 +55,42 @@ public class Gun implements Serializable { return this.clipCount > 0; } + public boolean isReloading() { + return reloading; + } + + public void startReloading() { + this.reloading = true; + this.reloadingStartedAt = System.currentTimeMillis(); + } + + public boolean isReloadingComplete() { + return this.reloading && (System.currentTimeMillis() - this.reloadingStartedAt) > this.type.reloadTime() * 1000; + } + public void reload() { if (this.clipCount > 0) { this.clipCount--; - this.currentClipBulletCount = this.type.getClipSize(); + this.currentClipBulletCount = this.type.clipSize(); } + this.reloading = false; + } + + @Override + public void use() { + this.lastShot = System.currentTimeMillis(); + this.currentClipBulletCount--; + } + + @Override + public void resupply() { + this.clipCount = this.type.maxClipCount(); + } + + @Override + public boolean isUsable() { + return !this.reloading && + this.currentClipBulletCount > 0 && + this.lastShot + (this.type.shotCooldownTime() * 1000) < System.currentTimeMillis(); } } 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 27cae1e..54b4e7a 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,122 +1,19 @@ package nl.andrewlalis.aos_core.model.tools; -import java.io.Serializable; - /** - * Relatively constant configuration information about a particular type of gun, - * while not including state data for any single gun. + * Information about a particular type of 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; - /** - * How fast the gun pushes the player backwards when shot (in m/s). - */ - private final float recoil; - - public GunType(String name, GunCategory category, String color, int maxClipCount, int clipSize, int bulletsPerRound, float accuracy, float shotCooldownTime, float reloadTime, float bulletSpeed, float baseDamage, float recoil) { - 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; - this.recoil = recoil; - } - - public String getName() { - return name; - } - - 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; - } - - public float getRecoil() { - return recoil; - } -} +public record GunType( + String name, + GunCategory category, + String color, + int maxClipCount, + int clipSize, + int bulletsPerRound, + float inaccuracy, + float shotCooldownTime, + float reloadTime, + float bulletSpeed, + float baseDamage, + float recoil +) {} 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 new file mode 100644 index 0000000..de59f58 --- /dev/null +++ b/core/src/main/java/nl/andrewlalis/aos_core/model/tools/Tool.java @@ -0,0 +1,30 @@ +package nl.andrewlalis.aos_core.model.tools; + +import java.io.Serializable; + +/** + * Represents some sort of usable tool item that players can equip and use. + */ +public interface Tool extends Serializable { + /** + * Uses the tool. + */ + void use(); + + /** + * Resupplies the tool, when a player is resupplied at their team's area. + */ + void resupply(); + + /** + * Resets the tool to its preferred initial state. This is useful for things + * like respawning. + */ + 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. + */ + boolean isUsable(); +} 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 abb03b4..2fbacfd 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 @@ -1,105 +1,75 @@ 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.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.ToolData; import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; public class PlayerDetailUpdate { - public static final int BYTES = Float.BYTES + 1 + 5 * Integer.BYTES; - private final float health; - private final boolean reloading; - private final int gunMaxClipCount; - private final int gunClipSize; - private final int gunBulletsPerRound; - private final int gunCurrentClipBulletCount; - private final int gunClipCount; + private List tools; + private int selectedToolIndex; public PlayerDetailUpdate(Player player) { this.health = player.getHealth(); - this.reloading = player.isReloading(); - - 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(); + this.tools = new ArrayList<>(player.getTools().size()); + for (int i = 0; i < player.getTools().size(); i++) { + var t = player.getTools().get(i); + 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; + } + } } - private PlayerDetailUpdate(float health, boolean reloading, int gunMaxClipCount, int gunClipSize, int gunBulletsPerRound, int gunCurrentClipBulletCount, int gunClipCount) { + private PlayerDetailUpdate(float health, List tools, int selectedToolIndex) { this.health = health; - this.reloading = reloading; - this.gunMaxClipCount = gunMaxClipCount; - this.gunClipSize = gunClipSize; - this.gunBulletsPerRound = gunBulletsPerRound; - this.gunCurrentClipBulletCount = gunCurrentClipBulletCount; - this.gunClipCount = gunClipCount; } public float getHealth() { return health; } - public boolean isReloading() { - return reloading; - } - - public int getGunMaxClipCount() { - return gunMaxClipCount; - } - - public int getGunClipSize() { - return gunClipSize; - } - - public int getGunBulletsPerRound() { - return gunBulletsPerRound; - } - - public int getGunCurrentClipBulletCount() { - return gunCurrentClipBulletCount; - } - - public int getGunClipCount() { - return gunClipCount; - } - - @Override - public String toString() { - return "PlayerDetailUpdate{" + - "health=" + health + - ", reloading=" + reloading + - ", gunMaxClipCount=" + gunMaxClipCount + - ", gunClipSize=" + gunClipSize + - ", gunBulletsPerRound=" + gunBulletsPerRound + - ", gunCurrentClipBulletCount=" + gunCurrentClipBulletCount + - ", gunClipCount=" + gunClipCount + - '}'; + public List getTools() { + return tools; } public byte[] toBytes() { - ByteBuffer buffer = ByteBuffer.allocate(BYTES); - buffer.putFloat(health); - buffer.put((byte) (this.reloading ? 1 : 0)); - buffer.putInt(this.gunMaxClipCount); - buffer.putInt(this.gunClipSize); - buffer.putInt(this.gunBulletsPerRound); - buffer.putInt(this.gunCurrentClipBulletCount); - buffer.putInt(this.gunClipCount); + int size = Float.BYTES + 2 * Integer.BYTES; + for (var td : this.tools) { + size += td.getByteSize(); + } + ByteBuffer buffer = ByteBuffer.allocate(size); + buffer.putFloat(this.health); + buffer.putInt(this.selectedToolIndex); + buffer.putInt(this.tools.size()); + for (var td : this.tools) { + td.write(buffer); + } return buffer.array(); } public static PlayerDetailUpdate fromBytes(byte[] bytes) { ByteBuffer buffer = ByteBuffer.wrap(bytes); - return new PlayerDetailUpdate( - buffer.getFloat(), - buffer.get() == 1, - buffer.getInt(), - buffer.getInt(), - buffer.getInt(), - buffer.getInt(), - buffer.getInt() - ); + float health = buffer.getFloat(); + int selectedToolIndex = buffer.getInt(); + int toolCount = buffer.getInt(); + List tools = new ArrayList<>(toolCount); + for (int i = 0; i < toolCount; i++) { + tools.add(ToolData.read(buffer)); + } + return new PlayerDetailUpdate(health, tools, selectedToolIndex); } } 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 be8a8fb..5bc4b3a 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,6 +2,11 @@ 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.Grenade; +import nl.andrewlalis.aos_core.model.tools.Gun; +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 java.io.DataInputStream; import java.io.DataOutputStream; @@ -18,22 +23,28 @@ public class PlayerUpdate { private final Vec2 position; private final Vec2 orientation; private final Vec2 velocity; - private final String gunTypeName; + private final ToolData selectedTool; public PlayerUpdate(Player player) { this.id = player.getId(); this.position = player.getPosition(); this.orientation = player.getOrientation(); this.velocity = player.getVelocity(); - this.gunTypeName = player.getGun().getType().getName(); + if (player.getSelectedTool() instanceof Gun gun) { + this.selectedTool = new GunData(gun); + } else if (player.getSelectedTool() instanceof Grenade grenade) { + this.selectedTool = new GrenadeData(grenade); + } else { + throw new IllegalArgumentException("Invalid selected tool."); + } } - public PlayerUpdate(int id, Vec2 position, Vec2 orientation, Vec2 velocity, String gunTypeName) { + public PlayerUpdate(int id, Vec2 position, Vec2 orientation, Vec2 velocity) { this.id = id; this.position = position; this.orientation = orientation; this.velocity = velocity; - this.gunTypeName = gunTypeName; + this.selectedTool = null; } public int getId() { @@ -52,10 +63,6 @@ public class PlayerUpdate { return velocity; } - public String getGunTypeName() { - return gunTypeName; - } - public void write(DataOutputStream out) throws IOException { out.writeInt(this.id); out.writeFloat(this.position.x()); @@ -64,8 +71,6 @@ public class PlayerUpdate { out.writeFloat(this.orientation.y()); out.writeFloat(this.velocity.x()); out.writeFloat(this.velocity.y()); - out.writeInt(this.gunTypeName.length()); - out.writeBytes(this.gunTypeName); } public static PlayerUpdate read(DataInputStream in) throws IOException { @@ -73,8 +78,6 @@ public class PlayerUpdate { 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/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 new file mode 100644 index 0000000..ea621bd --- /dev/null +++ b/core/src/main/java/nl/andrewlalis/aos_core/net/data/tool/GrenadeData.java @@ -0,0 +1,37 @@ +package nl.andrewlalis.aos_core.net.data.tool; + +import nl.andrewlalis.aos_core.model.tools.Grenade; + +import java.nio.ByteBuffer; + +public class GrenadeData extends ToolData { + private int grenades; + private int maxGrenades; + + public GrenadeData() { + super((byte) 1); + } + + public GrenadeData(Grenade grenade) { + this(); + this.grenades = grenade.getGrenadesRemaining(); + this.maxGrenades = grenade.getMaxGrenades(); + } + + @Override + public int getByteSize() { + return 2 * Integer.BYTES; + } + + @Override + protected void putData(ByteBuffer buffer) { + buffer.putInt(this.grenades); + buffer.putInt(this.maxGrenades); + } + + @Override + protected void getData(ByteBuffer buffer) { + this.grenades = buffer.getInt(); + this.maxGrenades = buffer.getInt(); + } +} 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 new file mode 100644 index 0000000..b23d27e --- /dev/null +++ b/core/src/main/java/nl/andrewlalis/aos_core/net/data/tool/GunData.java @@ -0,0 +1,77 @@ +package nl.andrewlalis.aos_core.net.data.tool; + +import nl.andrewlalis.aos_core.model.tools.Gun; + +import java.nio.ByteBuffer; + +public class GunData extends ToolData { + private boolean reloading; + private int clipCount; + private int currentClipBulletCount; + private int bulletsPerRound; + private int clipSize; + private int maxClipCount; + + public GunData() { + super((byte) 0); + } + + public GunData(Gun gun) { + this(); + this.reloading = gun.isReloading(); + this.clipCount = gun.getClipCount(); + this.currentClipBulletCount = gun.getCurrentClipBulletCount(); + this.bulletsPerRound = gun.getType().bulletsPerRound(); + this.clipSize = gun.getType().clipSize(); + this.maxClipCount = gun.getType().maxClipCount(); + } + + public boolean isReloading() { + return reloading; + } + + public int getClipCount() { + return clipCount; + } + + public int getCurrentClipBulletCount() { + return currentClipBulletCount; + } + + public int getBulletsPerRound() { + return bulletsPerRound; + } + + public int getClipSize() { + return clipSize; + } + + public int getMaxClipCount() { + return maxClipCount; + } + + @Override + public int getByteSize() { + return 1 + 5 * Integer.BYTES; + } + + @Override + protected void putData(ByteBuffer buffer) { + buffer.put((byte) (this.reloading ? 1 : 0)); + buffer.putInt(this.clipCount); + buffer.putInt(this.currentClipBulletCount); + buffer.putInt(this.bulletsPerRound); + buffer.putInt(this.clipSize); + buffer.putInt(this.maxClipCount); + } + + @Override + protected void getData(ByteBuffer buffer) { + this.reloading = buffer.get() == 1; + this.clipCount = buffer.getInt(); + this.currentClipBulletCount = buffer.getInt(); + this.bulletsPerRound = buffer.getInt(); + this.clipSize = buffer.getInt(); + this.maxClipCount = buffer.getInt(); + } +} 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 new file mode 100644 index 0000000..b3090d5 --- /dev/null +++ b/core/src/main/java/nl/andrewlalis/aos_core/net/data/tool/ToolData.java @@ -0,0 +1,55 @@ +package nl.andrewlalis.aos_core.net.data.tool; + +import java.nio.ByteBuffer; +import java.util.HashMap; +import java.util.Map; + +public abstract class ToolData { + private static final Map> dataMapping = new HashMap<>(); + static { + dataMapping.put((byte) 0, GunData.class); + dataMapping.put((byte) 1, GrenadeData.class); + } + + private final byte toolType; + + public ToolData(byte toolType) { + this.toolType = toolType; + } + + public void write(ByteBuffer buffer) { + buffer.put(this.toolType); + this.putData(buffer); + } + + public static ToolData read(ByteBuffer buffer) { + byte type = buffer.get(); + var dataClass = dataMapping.get(type); + if (dataClass == null) { + System.err.println("Invalid tool data type byte."); + return null; + } + try { + var data = dataClass.getConstructor().newInstance(); + data.getData(buffer); + return data; + } catch (ReflectiveOperationException e) { + e.printStackTrace(); + return null; + } + } + + public abstract int getByteSize(); + + /** + * Writes data for this tool data object into the given buffer. + * @param buffer The byte buffer to write data into. + */ + protected abstract void putData(ByteBuffer buffer); + + /** + * Reads data for this tool data object from the given buffer. + * @param buffer The byte buffer to read data from. + */ + protected abstract void getData(ByteBuffer buffer); +} 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 b8651ff..9b47fa6 100644 --- a/server/src/main/java/nl/andrewlalis/aos_server/WorldUpdater.java +++ b/server/src/main/java/nl/andrewlalis/aos_server/WorldUpdater.java @@ -184,23 +184,23 @@ public class WorldUpdater extends Thread { } private void updatePlayerShooting(Player p) { - if (p.canUseWeapon()) { - for (int i = 0; i < p.getGun().getType().getBulletsPerRound(); i++) { + 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().getCategory() == GunCategory.RIFLE) { + if (p.getGun().getType().category() == GunCategory.RIFLE) { soundType = SoundType.SHOT_RIFLE; - } else if (p.getGun().getType().getCategory() == GunCategory.SHOTGUN) { + } else if (p.getGun().getType().category() == GunCategory.SHOTGUN) { soundType = SoundType.SHOT_SHOTGUN; - } else if (p.getGun().getType().getCategory() == GunCategory.MACHINE) { + } 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().getRecoil()))); + p.setVelocity(p.getVelocity().add(p.getOrientation().mul(-1 * p.getGun().getType().recoil()))); } if (p.getState().isReloading() && !p.isReloading() && p.getGun().canReload()) { p.startReloading(); @@ -255,7 +255,7 @@ public class WorldUpdater extends Thread { 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().getType().getBaseDamage()); + float damage = (float) (((Player.RADIUS - dist) / Player.RADIUS) * b.getGun().getType().baseDamage()); 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 index 33fbc56..13b2378 100644 --- a/server/src/main/java/nl/andrewlalis/aos_server/command/GunsCommand.java +++ b/server/src/main/java/nl/andrewlalis/aos_server/command/GunsCommand.java @@ -19,13 +19,13 @@ public class GunsCommand implements Command, ChatCommand { @Override public void execute(String[] args) { for (var gunType : this.server.getWorld().getGunTypes().values()) { - System.out.println(gunType.getName()); + System.out.println(gunType.name()); } } @Override public void execute(ClientHandler handler, Player player, String[] args) { - String msg = handler.getServer().getWorld().getGunTypes().values().stream().map(GunType::getName).collect(Collectors.joining(", ")); + String msg = handler.getServer().getWorld().getGunTypes().values().stream().map(GunType::name).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 c71839c..81a90c3 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 @@ -1,6 +1,5 @@ package nl.andrewlalis.aos_server.command; -import nl.andrewlalis.aos_core.model.Player; import nl.andrewlalis.aos_server.Server; import java.util.stream.Collectors; @@ -21,13 +20,12 @@ public class ListPlayersCommand implements Command { String message = this.server.getWorld().getPlayers().values().stream() .sorted() .map(player -> String.format( - "%d | %s Team: %s, Health: %.1f / %.1f, Gun: %s", + "%d | %s Team: %s, Health: %.1f / %.1f", player.getId(), player.getName(), player.getTeam() == null ? "none" : player.getTeam().getName(), player.getHealth(), - this.server.getSettings().getPlayerSettings().getMaxHealth(), - player.getGun().getType().getName() + this.server.getSettings().getPlayerSettings().getMaxHealth() )) .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 5c46e42..c3b0974 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 @@ -18,7 +18,7 @@ public class GunCommand implements ChatCommand { String gunName = String.join(" ", args); GunType gunType = null; for (GunType type : handler.getServer().getWorld().getGunTypes().values()) { - if (type.getName().equalsIgnoreCase(gunName)) { + if (type.name().equalsIgnoreCase(gunName)) { gunType = type; break; } @@ -28,6 +28,6 @@ public class GunCommand implements ChatCommand { return; } player.setGun(new Gun(gunType)); - handler.send(new SystemChatMessage(SystemChatMessage.Level.INFO, "Changed gun to " + player.getGun().getType().getName() + ".")); + handler.send(new SystemChatMessage(SystemChatMessage.Level.INFO, "Changed gun to " + player.getGun().getType().name() + ".")); } } diff --git a/server/src/main/resources/default_settings.yaml b/server/src/main/resources/default_settings.yaml index 96fa788..8245cf5 100644 --- a/server/src/main/resources/default_settings.yaml +++ b/server/src/main/resources/default_settings.yaml @@ -52,9 +52,9 @@ player-settings: max-health: 100 # How quickly players regenerate health, in points per second. Set to 0 to disable regeneration. health-regen-rate: 1.0 - # How much sneaking affects gun accuracy. Values less than 0 increase accuracy, and greater than 0 decrease accuracy. + # How much sneaking affects gun inaccuracy. Values less than 0 increase inaccuracy, and greater than 0 decrease inaccuracy. sneak-accuracy-modifier: 0.5 - # How much sprinting affects gun accuracy. Values less than 0 increase accuracy, and greater than 0 decrease accuracy. + # How much sprinting affects gun inaccuracy. Values less than 0 increase inaccuracy, and greater than 0 decrease inaccuracy. sprint-accuracy-modifier: 1.5 # Should be the name of one of the guns defined in "gun-settings". default-gun: M1 Garand @@ -78,7 +78,7 @@ gun-settings: max-clip-count: 4 # The maximum number of clips which a player can hold for this gun. clip-size: 30 # The number of rounds in each clip. bullets-per-round: 1 # The number of bullets spawned for each round fired. - accuracy: 0.10 # The accuracy of the gun, or rather deviation. Increase this to decrease accuracy. + inaccuracy: 0.10 # The inaccuracy of the gun, or rather deviation. Increase this to decrease inaccuracy. shot-cooldown-time: 0.05 # How many seconds to wait after shooting before you can shoot again. reload-time: 1.2 # How many seconds to wait while reloading. bullet-speed: 90 # How fast the bullets from this gun fly, in meters per second. @@ -91,7 +91,7 @@ gun-settings: max-clip-count: 5 clip-size: 16 bullets-per-round: 1 - accuracy: 0.20 + inaccuracy: 0.20 shot-cooldown-time: 0.25 reload-time: 1.0 bullet-speed: 40 @@ -104,7 +104,7 @@ gun-settings: max-clip-count: 3 clip-size: 500 bullets-per-round: 1 - accuracy: 0.10 + inaccuracy: 0.10 shot-cooldown-time: 0.04 reload-time: 3 bullet-speed: 60 @@ -117,7 +117,7 @@ gun-settings: max-clip-count: 3 clip-size: 100 bullets-per-round: 1 - accuracy: 0.08 + inaccuracy: 0.08 shot-cooldown-time: 0.03 reload-time: 3.5 bullet-speed: 80 @@ -130,7 +130,7 @@ gun-settings: max-clip-count: 10 clip-size: 8 bullets-per-round: 1 - accuracy: 0.02 + inaccuracy: 0.02 shot-cooldown-time: 0.75 reload-time: 0.75 bullet-speed: 150 @@ -143,7 +143,7 @@ gun-settings: max-clip-count: 8 clip-size: 6 bullets-per-round: 5 - accuracy: 0.15 + inaccuracy: 0.15 shot-cooldown-time: 0.5 reload-time: 2.0 bullet-speed: 75