Improved client connection.
This commit is contained in:
parent
bd02d89995
commit
4bc994d07e
|
@ -1,5 +1,6 @@
|
|||
package nl.andrewlalis.aos_client;
|
||||
|
||||
import nl.andrewlalis.aos_client.view.ConnectDialog;
|
||||
import nl.andrewlalis.aos_client.view.GameFrame;
|
||||
import nl.andrewlalis.aos_client.view.GamePanel;
|
||||
import nl.andrewlalis.aos_core.model.PlayerControlState;
|
||||
|
@ -8,7 +9,6 @@ import nl.andrewlalis.aos_core.net.PlayerControlStateMessage;
|
|||
import nl.andrewlalis.aos_core.net.chat.ChatMessage;
|
||||
import nl.andrewlalis.aos_core.net.chat.PlayerChatMessage;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.io.IOException;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
@ -153,22 +153,7 @@ public class Client {
|
|||
|
||||
|
||||
public static void main(String[] args) {
|
||||
String hostAndPort = JOptionPane.showInputDialog("Enter server host and port (host:port):");
|
||||
if (hostAndPort == null) throw new IllegalArgumentException("A host and port is required.");
|
||||
String[] parts = hostAndPort.split(":");
|
||||
if (parts.length != 2) throw new IllegalArgumentException("Invalid host:port.");
|
||||
String host = parts[0].trim();
|
||||
int port = Integer.parseInt(parts[1]);
|
||||
String username = JOptionPane.showInputDialog("Enter a username:");
|
||||
if (username == null || username.isBlank()) throw new IllegalArgumentException("Username is required.");
|
||||
|
||||
Client client = new Client();
|
||||
try {
|
||||
client.connect(host, port, username);
|
||||
} catch (IOException | ClassNotFoundException e) {
|
||||
client.shutdown();
|
||||
e.printStackTrace();
|
||||
JOptionPane.showMessageDialog(null, "Could not connect:\n" + e.getMessage(), "Connection Error", JOptionPane.WARNING_MESSAGE);
|
||||
}
|
||||
ConnectDialog dialog = new ConnectDialog();
|
||||
dialog.setVisible(true);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,109 @@
|
|||
package nl.andrewlalis.aos_client.view;
|
||||
|
||||
import nl.andrewlalis.aos_client.Client;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.KeyAdapter;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class ConnectDialog extends JDialog {
|
||||
private static final Pattern addressPattern = Pattern.compile("(.+):(\\d+)");
|
||||
|
||||
public ConnectDialog() {
|
||||
super((Frame) null, "Connect to Server", false);
|
||||
JPanel inputPanel = new JPanel(new GridBagLayout());
|
||||
GridBagConstraints c = new GridBagConstraints();
|
||||
c.insets = new Insets(5, 5, 5, 5);
|
||||
|
||||
c.gridx = 0;
|
||||
c.gridy = 0;
|
||||
inputPanel.add(new JLabel("Address"), c);
|
||||
JTextField addressField = new JTextField(20);
|
||||
c.gridx = 1;
|
||||
inputPanel.add(addressField, c);
|
||||
|
||||
c.gridy = 1;
|
||||
c.gridx = 0;
|
||||
inputPanel.add(new JLabel("Username"), c);
|
||||
JTextField usernameField = new JTextField(20);
|
||||
c.gridx = 1;
|
||||
inputPanel.add(usernameField, c);
|
||||
|
||||
var enterListener = new KeyAdapter() {
|
||||
@Override
|
||||
public void keyPressed(KeyEvent e) {
|
||||
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
|
||||
if (validateInput(addressField, usernameField)) {
|
||||
connect(addressField, usernameField);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
addressField.addKeyListener(enterListener);
|
||||
usernameField.addKeyListener(enterListener);
|
||||
|
||||
JPanel buttonPanel = new JPanel(new FlowLayout());
|
||||
JButton cancelButton = new JButton("Cancel");
|
||||
cancelButton.addActionListener(e -> this.dispose());
|
||||
JButton connectButton = new JButton("Connect");
|
||||
connectButton.addActionListener(e -> {
|
||||
if (validateInput(addressField, usernameField)) {
|
||||
connect(addressField, usernameField);
|
||||
}
|
||||
});
|
||||
buttonPanel.add(cancelButton);
|
||||
buttonPanel.add(connectButton);
|
||||
|
||||
JPanel mainPanel = new JPanel(new BorderLayout());
|
||||
mainPanel.add(inputPanel, BorderLayout.CENTER);
|
||||
mainPanel.add(buttonPanel, BorderLayout.SOUTH);
|
||||
|
||||
this.setContentPane(mainPanel);
|
||||
this.pack();
|
||||
this.setLocationRelativeTo(null);
|
||||
this.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
|
||||
}
|
||||
|
||||
private boolean validateInput(JTextField addressField, JTextField usernameField) {
|
||||
List<String> warnings = new ArrayList<>();
|
||||
if (addressField.getText() == null || addressField.getText().isBlank()) {
|
||||
warnings.add("Address must not be empty.");
|
||||
}
|
||||
if (usernameField.getText() == null || usernameField.getText().isBlank()) {
|
||||
warnings.add("Username must not be empty.");
|
||||
}
|
||||
if (addressField.getText() != null && !addressPattern.matcher(addressField.getText()).matches()) {
|
||||
warnings.add("Address must be in the form HOST:PORT.");
|
||||
}
|
||||
if (!warnings.isEmpty()) {
|
||||
JOptionPane.showMessageDialog(
|
||||
this,
|
||||
String.join("\n", warnings),
|
||||
"Invalid Input",
|
||||
JOptionPane.WARNING_MESSAGE
|
||||
);
|
||||
}
|
||||
return warnings.isEmpty();
|
||||
}
|
||||
|
||||
private void connect(JTextField addressField, JTextField usernameField) {
|
||||
String hostAndPort = addressField.getText();
|
||||
String[] parts = hostAndPort.split(":");
|
||||
String host = parts[0].trim();
|
||||
int port = Integer.parseInt(parts[1]);
|
||||
String username = usernameField.getText();
|
||||
Client client = new Client();
|
||||
try {
|
||||
client.connect(host, port, username);
|
||||
} catch (IOException | ClassNotFoundException ex) {
|
||||
client.shutdown();
|
||||
ex.printStackTrace();
|
||||
JOptionPane.showMessageDialog(null, "Could not connect:\n" + ex.getMessage(), "Connection Error", JOptionPane.WARNING_MESSAGE);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -48,9 +48,11 @@ public class GamePanel extends JPanel {
|
|||
g2.clearRect(0, 0, this.getWidth(), this.getHeight());
|
||||
|
||||
World world = client.getWorld();
|
||||
if (world != null) drawWorld(g2, world);
|
||||
if (world != null) {
|
||||
drawWorld(g2, world);
|
||||
drawStatus(g2, world);
|
||||
}
|
||||
drawChat(g2, world);
|
||||
drawStatus(g2, world);
|
||||
}
|
||||
|
||||
private void drawWorld(Graphics2D g2, World world) {
|
||||
|
@ -234,5 +236,20 @@ public class GamePanel extends JPanel {
|
|||
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);
|
||||
if (myPlayer.getHealth() >= 66.0f) {
|
||||
g2.setColor(Color.GREEN);
|
||||
} else if (myPlayer.getHealth() >= 33.0f) {
|
||||
g2.setColor(Color.YELLOW);
|
||||
} else {
|
||||
g2.setColor(Color.RED);
|
||||
}
|
||||
g2.drawString(String.format("Health: %.1f / %.1f", myPlayer.getHealth(), Player.MAX_HEALTH), 5, this.getHeight() - 40);
|
||||
|
||||
int y = this.getHeight() - 60;
|
||||
for (Team t : world.getTeams()) {
|
||||
g2.setColor(t.getColor());
|
||||
g2.drawString("Team " + t.getName() + ": " + t.getScore(), 5, y);
|
||||
y -= 15;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package nl.andrewlalis.aos_core.geom;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
public record Vec2(double x, double y) implements Serializable {
|
||||
|
||||
|
@ -56,4 +58,11 @@ public record Vec2(double x, double y) implements Serializable {
|
|||
public String toString() {
|
||||
return "[ " + x + ", " + y + " ]";
|
||||
}
|
||||
|
||||
public static Vec2 random(double min, double max) {
|
||||
Random r = ThreadLocalRandom.current();
|
||||
double x = r.nextDouble() * (max - min) + min;
|
||||
double y = r.nextDouble() * (max - min) + min;
|
||||
return new Vec2(x, y);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,26 +1,34 @@
|
|||
package nl.andrewlalis.aos_core.model;
|
||||
|
||||
import nl.andrewlalis.aos_core.geom.Vec2;
|
||||
import nl.andrewlalis.aos_core.model.tools.Gun;
|
||||
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
public class Bullet extends PhysicsObject {
|
||||
private final int playerId;
|
||||
private final Gun gun;
|
||||
|
||||
public Bullet(Player player) {
|
||||
super(
|
||||
player.getPosition().add(player.getOrientation().mul(1.5)),
|
||||
player.getOrientation(),
|
||||
null
|
||||
);
|
||||
this.playerId = player.getId();
|
||||
this.setPosition(player.getPosition()
|
||||
.add(player.getOrientation().mul(1.5))
|
||||
.add(player.getOrientation().perp().mul(Player.RADIUS))
|
||||
);
|
||||
this.setOrientation(player.getOrientation());
|
||||
|
||||
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()));
|
||||
this.setVelocity(this.getOrientation().add(perturbation).mul(player.getGun().getBulletSpeed()));
|
||||
this.gun = player.getGun();
|
||||
}
|
||||
|
||||
public int getPlayerId() {
|
||||
return playerId;
|
||||
}
|
||||
|
||||
public Gun getGun() {
|
||||
return gun;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package nl.andrewlalis.aos_core.model;
|
||||
|
||||
import nl.andrewlalis.aos_core.geom.Vec2;
|
||||
import nl.andrewlalis.aos_core.model.tools.Gun;
|
||||
|
||||
import java.util.Objects;
|
||||
|
@ -8,12 +9,14 @@ public class Player extends PhysicsObject {
|
|||
public static final double MOVEMENT_SPEED = 10; // Movement speed, in m/s
|
||||
public static final double RADIUS = 0.5; // Collision radius, in meters.
|
||||
public static final double RESUPPLY_COOLDOWN = 30; // Seconds between allowing resupply.
|
||||
public static final float MAX_HEALTH = 100.0f;
|
||||
|
||||
private final int id;
|
||||
private final String name;
|
||||
private Team team;
|
||||
private PlayerControlState state;
|
||||
private Gun gun;
|
||||
private float health;
|
||||
|
||||
private transient long lastShot;
|
||||
private transient long reloadingStartedAt;
|
||||
|
@ -27,6 +30,7 @@ public class Player extends PhysicsObject {
|
|||
this.state = new PlayerControlState();
|
||||
this.state.setPlayerId(this.id);
|
||||
this.gun = Gun.winchester();
|
||||
this.health = MAX_HEALTH;
|
||||
this.useWeapon();
|
||||
}
|
||||
|
||||
|
@ -111,6 +115,23 @@ public class Player extends PhysicsObject {
|
|||
public void resupply() {
|
||||
this.lastResupply = System.currentTimeMillis();
|
||||
this.gun.refillClips();
|
||||
this.health = MAX_HEALTH;
|
||||
}
|
||||
|
||||
public float getHealth() {
|
||||
return health;
|
||||
}
|
||||
|
||||
public void takeDamage(float damage) {
|
||||
this.health = Math.max(this.health - damage, 0.0f);
|
||||
}
|
||||
|
||||
public void respawn() {
|
||||
this.resupply();
|
||||
this.gun.emptyCurrentClip();
|
||||
if (this.team != null) {
|
||||
this.setPosition(this.team.getSpawnPoint().add(Vec2.random(-Team.SPAWN_RADIUS / 2, Team.SPAWN_RADIUS / 2)));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -19,6 +19,8 @@ public class Team implements Serializable {
|
|||
|
||||
private final List<Player> players;
|
||||
|
||||
private int score;
|
||||
|
||||
public Team(String name, Color color, Vec2 spawnPoint, Vec2 supplyPoint, Vec2 orientation) {
|
||||
this.name = name;
|
||||
this.color = color;
|
||||
|
@ -26,6 +28,7 @@ public class Team implements Serializable {
|
|||
this.supplyPoint = supplyPoint;
|
||||
this.orientation = orientation;
|
||||
this.players = new ArrayList<>();
|
||||
this.score = 0;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
|
@ -51,4 +54,16 @@ public class Team implements Serializable {
|
|||
public List<Player> getPlayers() {
|
||||
return players;
|
||||
}
|
||||
|
||||
public int getScore() {
|
||||
return score;
|
||||
}
|
||||
|
||||
public void incrementScore() {
|
||||
this.score++;
|
||||
}
|
||||
|
||||
public void resetScore() {
|
||||
this.score = 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,6 +41,11 @@ public class Gun implements Serializable {
|
|||
*/
|
||||
private final double bulletSpeed;
|
||||
|
||||
/**
|
||||
* How much damage the bullet does for a direct hit.
|
||||
*/
|
||||
private final double baseDamage;
|
||||
|
||||
/**
|
||||
* Number of bullets left in the current clip.
|
||||
*/
|
||||
|
@ -50,7 +55,7 @@ public class Gun implements Serializable {
|
|||
*/
|
||||
private int clipCount;
|
||||
|
||||
private Gun(GunType type, int maxClipCount, int clipSize, int bulletsPerRound, double accuracy, double shotCooldownTime, double reloadTime, double bulletSpeed) {
|
||||
private Gun(GunType type, int maxClipCount, int clipSize, int bulletsPerRound, double accuracy, double shotCooldownTime, double reloadTime, double bulletSpeed, double baseDamage) {
|
||||
this.type = type;
|
||||
this.maxClipCount = maxClipCount;
|
||||
this.clipSize = clipSize;
|
||||
|
@ -59,6 +64,7 @@ public class Gun implements Serializable {
|
|||
this.shotCooldownTime = shotCooldownTime;
|
||||
this.reloadTime = reloadTime;
|
||||
this.bulletSpeed = bulletSpeed;
|
||||
this.baseDamage = baseDamage;
|
||||
|
||||
this.currentClipBulletCount = 0;
|
||||
this.clipCount = maxClipCount;
|
||||
|
@ -96,6 +102,10 @@ public class Gun implements Serializable {
|
|||
return bulletSpeed;
|
||||
}
|
||||
|
||||
public double getBaseDamage() {
|
||||
return baseDamage;
|
||||
}
|
||||
|
||||
public int getCurrentClipBulletCount() {
|
||||
return currentClipBulletCount;
|
||||
}
|
||||
|
@ -112,6 +122,10 @@ public class Gun implements Serializable {
|
|||
this.currentClipBulletCount = Math.max(this.currentClipBulletCount - 1, 0);
|
||||
}
|
||||
|
||||
public void emptyCurrentClip() {
|
||||
this.currentClipBulletCount = 0;
|
||||
}
|
||||
|
||||
public boolean canReload() {
|
||||
return this.clipCount > 0;
|
||||
}
|
||||
|
@ -124,14 +138,14 @@ public class Gun implements Serializable {
|
|||
}
|
||||
|
||||
public static Gun ak47() {
|
||||
return new Gun(GunType.SMG, 4, 30, 1, 0.10, 0.05, 1.2, 90);
|
||||
return new Gun(GunType.SMG, 4, 30, 1, 0.10, 0.05, 1.2, 90, 40);
|
||||
}
|
||||
|
||||
public static Gun m1Garand() {
|
||||
return new Gun(GunType.RIFLE, 6, 8, 1, 0.02, 0.75, 1.5, 150);
|
||||
return new Gun(GunType.RIFLE, 6, 8, 1, 0.02, 0.75, 1.5, 150, 100);
|
||||
}
|
||||
|
||||
public static Gun winchester() {
|
||||
return new Gun(GunType.SHOTGUN, 8, 4, 3, 0.15, 0.5, 2.0, 75);
|
||||
return new Gun(GunType.SHOTGUN, 8, 4, 3, 0.15, 0.5, 2.0, 75, 60);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -118,6 +118,15 @@ public class Server {
|
|||
}
|
||||
}
|
||||
|
||||
public void resetGame() {
|
||||
for (Team t : this.world.getTeams()) {
|
||||
t.resetScore();
|
||||
for (Player p : t.getPlayers()) {
|
||||
p.respawn();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void broadcastMessage(Message message) {
|
||||
for (ClientHandler handler : this.clientHandlers) {
|
||||
handler.send(message);
|
||||
|
@ -152,6 +161,9 @@ public class Server {
|
|||
player.setGun(Gun.winchester());
|
||||
}
|
||||
handler.send(new SystemChatMessage(SystemChatMessage.Level.INFO, "Changed gun to " + player.getGun().getType().name() + "."));
|
||||
} else if (command.equalsIgnoreCase("reset")) {
|
||||
this.resetGame();
|
||||
this.broadcastMessage(new SystemChatMessage(SystemChatMessage.Level.INFO, "Game has been reset."));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -77,9 +77,12 @@ public class WorldUpdater extends Thread {
|
|||
if (p.getState().isMovingBackward()) vy -= Player.MOVEMENT_SPEED;
|
||||
if (p.getState().isMovingLeft()) vx -= Player.MOVEMENT_SPEED;
|
||||
if (p.getState().isMovingRight()) vx += Player.MOVEMENT_SPEED;
|
||||
Vec2 forwardVector = p.getOrientation().mul(vy);
|
||||
Vec2 leftVector = p.getOrientation().perp().mul(vx);
|
||||
Vec2 newPos = p.getPosition().add(forwardVector.mul(t)).add(leftVector.mul(t));
|
||||
Vec2 forwardVector = new Vec2(0, -1);
|
||||
if (p.getTeam() != null) {
|
||||
forwardVector = p.getTeam().getOrientation();
|
||||
}
|
||||
Vec2 leftVector = forwardVector.perp();
|
||||
Vec2 newPos = p.getPosition().add(forwardVector.mul(vy * t)).add(leftVector.mul(vx * t));
|
||||
double nx = newPos.x();
|
||||
double ny = newPos.y();
|
||||
|
||||
|
@ -171,11 +174,18 @@ public class WorldUpdater extends Thread {
|
|||
n = Math.max(Math.min(n, 1), 0);
|
||||
double dist = p.getPosition().dist(new Vec2(x1 + n * (x2 - x1), y1 + n * (y2 - y1)));
|
||||
if (dist < Player.RADIUS && (p.getTeam() == null || p.getTeam().getSpawnPoint().dist(p.getPosition()) > Team.SPAWN_RADIUS)) {
|
||||
Player killer = this.world.getPlayers().get(b.getPlayerId());
|
||||
this.server.broadcastMessage(new SystemChatMessage(SystemChatMessage.Level.SEVERE, p.getName() + " was shot by " + killer.getName() + "."));
|
||||
world.getSoundsToPlay().add("death.wav");
|
||||
if (p.getTeam() != null) {
|
||||
p.setPosition(p.getTeam().getSpawnPoint());
|
||||
|
||||
// Player was shot!
|
||||
float damage = (float) (((Player.RADIUS - dist) / Player.RADIUS) * b.getGun().getBaseDamage());
|
||||
p.takeDamage(damage);
|
||||
if (p.getHealth() == 0.0f) {
|
||||
Player shooter = this.world.getPlayers().get(b.getPlayerId());
|
||||
this.server.broadcastMessage(new SystemChatMessage(SystemChatMessage.Level.SEVERE, p.getName() + " was shot by " + shooter.getName() + "."));
|
||||
world.getSoundsToPlay().add("death.wav");
|
||||
if (shooter.getTeam() != null) {
|
||||
shooter.getTeam().incrementScore();
|
||||
}
|
||||
p.respawn();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue