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 947cb72..ee7fc84 100644 --- a/client/src/main/java/nl/andrewlalis/aos_client/Client.java +++ b/client/src/main/java/nl/andrewlalis/aos_client/Client.java @@ -130,10 +130,13 @@ public class Client { } public void sendChat() { - try { - this.messageTransceiver.send(new PlayerChatMessage(this.playerId, this.chatBuffer.toString())); - } catch (IOException e) { - e.printStackTrace(); + String message = this.chatBuffer.toString().trim(); + if (!message.isBlank()) { + try { + this.messageTransceiver.send(new PlayerChatMessage(this.playerId, message)); + } catch (IOException e) { + e.printStackTrace(); + } } this.setChatting(false); } 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 60ea812..6f52e09 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 @@ -14,9 +14,11 @@ public class PlayerKeyListener extends KeyAdapter { @Override public void keyTyped(KeyEvent e) { - if (!this.client.isChatting() && (e.getKeyChar() == 't' || e.getKeyChar() == '/')) { - this.client.setChatting(true); - if (e.getKeyChar() == '/') this.client.appendToChat('/'); + if (!this.client.isChatting()) { + if ((e.getKeyChar() == 't' || e.getKeyChar() == '/')) { + this.client.setChatting(true); + if (e.getKeyChar() == '/') this.client.appendToChat('/'); + } } else if (this.client.isChatting()) { char c = e.getKeyChar(); if (c >= ' ' && c <= '~') { @@ -43,6 +45,8 @@ public class PlayerKeyListener extends KeyAdapter { state.setMovingLeft(true); } else if (e.getKeyCode() == KeyEvent.VK_D) { state.setMovingRight(true); + } else if (e.getKeyCode() == KeyEvent.VK_R) { + state.setReloading(true); } this.client.sendPlayerState(); } @@ -59,6 +63,8 @@ public class PlayerKeyListener extends KeyAdapter { state.setMovingLeft(false); } else if (e.getKeyCode() == KeyEvent.VK_D) { state.setMovingRight(false); + } else if (e.getKeyCode() == KeyEvent.VK_R) { + state.setReloading(false); } this.client.sendPlayerState(); } diff --git a/client/src/main/java/nl/andrewlalis/aos_client/view/GamePanel.java b/client/src/main/java/nl/andrewlalis/aos_client/view/GamePanel.java index 3c4b313..73ee2aa 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 @@ -2,6 +2,7 @@ package nl.andrewlalis.aos_client.view; import nl.andrewlalis.aos_client.Client; import nl.andrewlalis.aos_core.model.*; +import nl.andrewlalis.aos_core.model.tools.Gun; import nl.andrewlalis.aos_core.net.chat.ChatMessage; import nl.andrewlalis.aos_core.net.chat.PlayerChatMessage; import nl.andrewlalis.aos_core.net.chat.SystemChatMessage; @@ -48,6 +49,7 @@ public class GamePanel extends JPanel { World world = client.getWorld(); if (world != null) drawWorld(g2, world); drawChat(g2, world); + drawStatus(g2, world); } private void drawWorld(Graphics2D g2, World world) { @@ -177,4 +179,17 @@ public class GamePanel extends JPanel { g2.drawString("> " + this.client.getCurrentChatBuffer(), 5, height * 11); } } + + private void drawStatus(Graphics2D g2, World world) { + Player myPlayer = world.getPlayers().get(this.client.getPlayerId()); + if (myPlayer == null) return; + + g2.setColor(Color.WHITE); + if (myPlayer.isReloading()) { + g2.drawString("Reloading...", 5, this.getHeight() - 10); + } + Gun gun = myPlayer.getGun(); + g2.drawString("Clips: " + gun.getClipCount() + " / " + gun.getMaxClipCount(), 5, this.getHeight() - 20); + g2.drawString("Bullets: " + gun.getCurrentClipBulletCount() + " / " + gun.getClipSize(), 5, this.getHeight() - 30); + } } diff --git a/core/src/main/java/module-info.java b/core/src/main/java/module-info.java index 84ddf00..4f0b725 100644 --- a/core/src/main/java/module-info.java +++ b/core/src/main/java/module-info.java @@ -4,4 +4,5 @@ module aos_core { exports nl.andrewlalis.aos_core.model to aos_server, aos_client; exports nl.andrewlalis.aos_core.geom to aos_server, aos_client; exports nl.andrewlalis.aos_core.net.chat to aos_client, aos_server; + exports nl.andrewlalis.aos_core.model.tools to aos_client, aos_server; } \ No newline at end of file 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 f1a5913..42ef7fd 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 @@ -1,13 +1,23 @@ package nl.andrewlalis.aos_core.model; -public class Bullet extends PhysicsObject { - public static final double SPEED = 100.0; // Meters per second. +import nl.andrewlalis.aos_core.geom.Vec2; +import java.util.Random; +import java.util.concurrent.ThreadLocalRandom; + +public class Bullet extends PhysicsObject { private final int playerId; public Bullet(Player player) { - super(player.getPosition().add(player.getOrientation().mul(1.5)), player.getOrientation(), player.getOrientation().mul(SPEED)); + super( + player.getPosition().add(player.getOrientation().mul(1.5)), + player.getOrientation(), + null + ); this.playerId = player.getId(); + Random r = ThreadLocalRandom.current(); + Vec2 perturbation = new Vec2((r.nextDouble() - 0.5) * 2, (r.nextDouble() - 0.5) * 2).mul(player.getGun().getAccuracy()); + this.setVelocity(player.getOrientation().add(perturbation).mul(player.getGun().getBulletSpeed())); } public int getPlayerId() { diff --git a/core/src/main/java/nl/andrewlalis/aos_core/model/Gun.java b/core/src/main/java/nl/andrewlalis/aos_core/model/Gun.java deleted file mode 100644 index 50fb014..0000000 --- a/core/src/main/java/nl/andrewlalis/aos_core/model/Gun.java +++ /dev/null @@ -1,5 +0,0 @@ -package nl.andrewlalis.aos_core.model; - -public class Gun { - -} diff --git a/core/src/main/java/nl/andrewlalis/aos_core/model/Player.java b/core/src/main/java/nl/andrewlalis/aos_core/model/Player.java index aee38d7..15099f6 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 @@ -1,9 +1,10 @@ package nl.andrewlalis.aos_core.model; +import nl.andrewlalis.aos_core.model.tools.Gun; + import java.util.Objects; public class Player extends PhysicsObject { - public static final double SHOT_COOLDOWN = 0.1; // Time between shots, in seconds. public static final double MOVEMENT_SPEED = 10; // Movement speed, in m/s public static final double RADIUS = 0.5; // Collision radius, in meters. @@ -11,8 +12,11 @@ public class Player extends PhysicsObject { private final String name; private Team team; private PlayerControlState state; + private Gun gun; private transient long lastShot; + private transient long reloadingStartedAt; + private boolean reloading; public Player(int id, String name, Team team) { this.id = id; @@ -20,7 +24,8 @@ public class Player extends PhysicsObject { this.team = team; this.state = new PlayerControlState(); this.state.setPlayerId(this.id); - this.updateLastShot(); + this.gun = Gun.m1Garand(); + this.useWeapon(); } public int getId() { @@ -47,12 +52,51 @@ public class Player extends PhysicsObject { this.team = team; } + public Gun getGun() { + return gun; + } + + public void setGun(Gun gun) { + this.gun = gun; + } + public long getLastShot() { return lastShot; } - public void updateLastShot() { + public boolean canUseWeapon() { + return this.state.isShooting() && + !this.state.isReloading() && + !this.reloading && + this.gun.getCurrentClipBulletCount() > 0 && + this.lastShot + this.gun.getShotCooldownTime() * 1000 < System.currentTimeMillis(); + } + + public void useWeapon() { this.lastShot = System.currentTimeMillis(); + this.gun.decrementBulletCount(); + } + + 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; + if (msSinceStart > this.gun.getReloadTime() * 1000) { + return true; + } + return false; + } + + public boolean isReloading() { + return reloading; } @Override 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 56d279b..cc9aa7e 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 @@ -13,6 +13,7 @@ public class PlayerControlState implements Serializable { boolean movingBackward; boolean shooting; + boolean reloading; Vec2 mouseLocation; @@ -64,6 +65,14 @@ public class PlayerControlState implements Serializable { this.shooting = shooting; } + public boolean isReloading() { + return reloading; + } + + public void setReloading(boolean reloading) { + this.reloading = reloading; + } + public Vec2 getMouseLocation() { return mouseLocation; } 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 new file mode 100644 index 0000000..2e96c78 --- /dev/null +++ b/core/src/main/java/nl/andrewlalis/aos_core/model/tools/Gun.java @@ -0,0 +1,122 @@ +package nl.andrewlalis.aos_core.model.tools; + +import java.io.Serializable; + +public class Gun implements Serializable { + + private final GunType type; + + /** + * Maximum number of clips a player can carry when using this gun. + */ + private final int maxClipCount; + /** + * Number of bullets in each clip. + */ + private final int clipSize; + + /** + * How accurate shots from this gun are. + */ + private final double accuracy; + + /** + * How long (in seconds) to wait after each shot, before another is shot. + */ + private final double shotCooldownTime; + + /** + * How long (in seconds) for reloading a new clip. + */ + private final double reloadTime; + + /** + * How fast the bullet travels (in m/s). + */ + private final double bulletSpeed; + + /** + * Number of bullets left in the current clip. + */ + private int currentClipBulletCount; + /** + * Number of clips remaining. + */ + private int clipCount; + + private Gun(GunType type, int maxClipCount, int clipSize, double accuracy, double shotCooldownTime, double reloadTime, double bulletSpeed) { + this.type = type; + this.maxClipCount = maxClipCount; + this.clipSize = clipSize; + this.accuracy = accuracy; + this.shotCooldownTime = shotCooldownTime; + this.reloadTime = reloadTime; + this.bulletSpeed = bulletSpeed; + + this.currentClipBulletCount = 0; + this.clipCount = maxClipCount; + } + + public GunType getType() { + return type; + } + + public int getMaxClipCount() { + return maxClipCount; + } + + public int getClipSize() { + return clipSize; + } + + public double getAccuracy() { + return accuracy; + } + + public double getShotCooldownTime() { + return shotCooldownTime; + } + + public double getReloadTime() { + return reloadTime; + } + + public double getBulletSpeed() { + return bulletSpeed; + } + + public int getCurrentClipBulletCount() { + return currentClipBulletCount; + } + + public int getClipCount() { + return clipCount; + } + + public void decrementBulletCount() { + this.currentClipBulletCount = Math.max(this.currentClipBulletCount - 1, 0); + } + + public boolean canReload() { + return this.clipCount > 0; + } + + public void reload() { + if (this.clipCount > 0) { + this.clipCount--; + this.currentClipBulletCount = this.clipSize; + } + } + + public static Gun ak47() { + return new Gun(GunType.SMG, 4, 30, 0.10, 0.05, 1.2, 90); + } + + public static Gun m1Garand() { + return new Gun(GunType.RIFLE, 6, 8, 0.02, 0.75, 1.5, 150); + } + + public static Gun winchester() { + return new Gun(GunType.SHOTGUN, 8, 4, 0.15, 0.5, 2.0, 75); + } +} 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 new file mode 100644 index 0000000..48daf97 --- /dev/null +++ b/core/src/main/java/nl/andrewlalis/aos_core/model/tools/GunType.java @@ -0,0 +1,7 @@ +package nl.andrewlalis.aos_core.model.tools; + +public enum GunType { + SHOTGUN, + SMG, + RIFLE +} 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 02e9c48..6236897 100644 --- a/server/src/main/java/nl/andrewlalis/aos_server/WorldUpdater.java +++ b/server/src/main/java/nl/andrewlalis/aos_server/WorldUpdater.java @@ -116,10 +116,16 @@ public class WorldUpdater extends Thread { } private void updatePlayerShooting(Player p) { - if (p.getState().isShooting() && p.getLastShot() + Player.SHOT_COOLDOWN * 1000 < System.currentTimeMillis()) { + if (p.canUseWeapon()) { this.world.getBullets().add(new Bullet(p)); this.world.getSoundsToPlay().add("ak47shot1.wav"); - p.updateLastShot(); + p.useWeapon(); + } + if (p.getState().isReloading() && !p.isReloading() && p.getGun().canReload()) { + p.startReloading(); + } + if (p.isReloading() && p.isReloadingComplete()) { + p.finishReloading(); } }