Removed UDP stuff, upgrade to 2.0, chat improvements too.

This commit is contained in:
Andrew Lalis 2021-06-19 22:30:45 +02:00
parent 7ed19f3576
commit 95ba8c8746
21 changed files with 196 additions and 219 deletions

View File

@ -5,7 +5,7 @@
<parent> <parent>
<artifactId>ace-of-shades</artifactId> <artifactId>ace-of-shades</artifactId>
<groupId>nl.andrewlalis</groupId> <groupId>nl.andrewlalis</groupId>
<version>1.0</version> <version>2.0</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@ -2,18 +2,17 @@ package nl.andrewlalis.aos_client;
import nl.andrewlalis.aos_client.view.GameFrame; import nl.andrewlalis.aos_client.view.GameFrame;
import nl.andrewlalis.aos_client.view.GamePanel; import nl.andrewlalis.aos_client.view.GamePanel;
import nl.andrewlalis.aos_core.model.Player;
import nl.andrewlalis.aos_core.model.PlayerControlState; import nl.andrewlalis.aos_core.model.PlayerControlState;
import nl.andrewlalis.aos_core.model.World; import nl.andrewlalis.aos_core.model.World;
import nl.andrewlalis.aos_core.net.ChatMessage; 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 javax.swing.*;
import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.ObjectOutputStream;
import java.net.DatagramPacket;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
/** /**
* The main class for the client, which connects to a server to join and play. * The main class for the client, which connects to a server to join and play.
@ -21,15 +20,13 @@ import java.util.concurrent.ThreadLocalRandom;
public class Client { public class Client {
public static final int MAX_CHAT_MESSAGES = 10; public static final int MAX_CHAT_MESSAGES = 10;
private final int udpPort;
private DatagramReceiver datagramReceiver;
private MessageTransceiver messageTransceiver; private MessageTransceiver messageTransceiver;
private int playerId; private int playerId;
private PlayerControlState playerControlState; private PlayerControlState playerControlState;
private World world; private World world;
private final List<String> chatMessages; private final List<ChatMessage> chatMessages;
private boolean chatting = false; private boolean chatting = false;
private final StringBuilder chatBuffer; private final StringBuilder chatBuffer;
@ -37,8 +34,7 @@ public class Client {
private final GamePanel gamePanel; private final GamePanel gamePanel;
private final SoundManager soundManager; private final SoundManager soundManager;
public Client(int udpPort) { public Client() {
this.udpPort = udpPort;
this.chatMessages = new LinkedList<>(); this.chatMessages = new LinkedList<>();
this.chatBuffer = new StringBuilder(); this.chatBuffer = new StringBuilder();
this.soundManager = new SoundManager(); this.soundManager = new SoundManager();
@ -47,13 +43,11 @@ public class Client {
} }
public void connect(String serverHost, int serverPort, String username) throws IOException, ClassNotFoundException { public void connect(String serverHost, int serverPort, String username) throws IOException, ClassNotFoundException {
this.datagramReceiver = new DatagramReceiver(this, this.udpPort);
this.datagramReceiver.start();
this.messageTransceiver = new MessageTransceiver(this); this.messageTransceiver = new MessageTransceiver(this);
this.messageTransceiver.connectToServer(serverHost, serverPort, username, this.udpPort); this.messageTransceiver.connectToServer(serverHost, serverPort, username);
this.messageTransceiver.start(); this.messageTransceiver.start();
while (this.playerControlState == null) { while (this.playerControlState == null || this.world == null) {
try { try {
System.out.println("Waiting for server response and player registration..."); System.out.println("Waiting for server response and player registration...");
Thread.sleep(100); Thread.sleep(100);
@ -62,6 +56,7 @@ public class Client {
} }
} }
System.out.println("Player and world data initialized.");
GameFrame g = new GameFrame("Ace of Shades - " + serverHost + ":" + serverPort, this, this.gamePanel); GameFrame g = new GameFrame("Ace of Shades - " + serverHost + ":" + serverPort, this, this.gamePanel);
g.setVisible(true); g.setVisible(true);
this.renderer.start(); this.renderer.start();
@ -94,27 +89,24 @@ public class Client {
public void sendPlayerState() { public void sendPlayerState() {
try { try {
ByteArrayOutputStream bos = new ByteArrayOutputStream(); this.messageTransceiver.send(new PlayerControlStateMessage(this.playerControlState));
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this.playerControlState);
byte[] buffer = bos.toByteArray();
DatagramPacket packet = new DatagramPacket(buffer, buffer.length, this.messageTransceiver.getRemoteAddress(), this.messageTransceiver.getPort());
this.datagramReceiver.getDatagramSocket().send(packet);
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
} }
} }
public synchronized void addChatMessage(String text) { public synchronized void addChatMessage(ChatMessage message) {
this.chatMessages.add(text); this.chatMessages.add(message);
this.soundManager.play("chat.wav"); if (message.getClass() == PlayerChatMessage.class) {
this.soundManager.play("chat.wav");
}
while (this.chatMessages.size() > MAX_CHAT_MESSAGES) { while (this.chatMessages.size() > MAX_CHAT_MESSAGES) {
this.chatMessages.remove(0); this.chatMessages.remove(0);
} }
} }
public String[] getLatestChatMessages() { public ChatMessage[] getLatestChatMessages() {
return this.chatMessages.toArray(new String[0]); return this.chatMessages.toArray(new ChatMessage[0]);
} }
public boolean isChatting() { public boolean isChatting() {
@ -140,7 +132,7 @@ public class Client {
public void sendChat() { public void sendChat() {
try { try {
this.messageTransceiver.send(new ChatMessage(this.chatBuffer.toString())); this.messageTransceiver.send(new PlayerChatMessage(this.playerId, this.chatBuffer.toString()));
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
} }
@ -152,7 +144,6 @@ public class Client {
} }
public void shutdown() { public void shutdown() {
this.datagramReceiver.shutdown();
this.messageTransceiver.shutdown(); this.messageTransceiver.shutdown();
this.renderer.shutdown(); this.renderer.shutdown();
} }
@ -160,21 +151,18 @@ public class Client {
public static void main(String[] args) { public static void main(String[] args) {
// Randomly choose a high-level UDP port that's probably open. // String hostAndPort = JOptionPane.showInputDialog("Enter server host and port (host:port):");
int udpPort = 20000 + ThreadLocalRandom.current().nextInt(0, 10000); // 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.");
String hostAndPort = JOptionPane.showInputDialog("Enter server host and port (host:port):"); Client client = new Client();
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(udpPort);
try { try {
client.connect(host, port, username); client.connect("localhost", 8035, "andrew");
} catch (IOException | ClassNotFoundException e) { } catch (IOException | ClassNotFoundException e) {
client.shutdown(); client.shutdown();
e.printStackTrace(); e.printStackTrace();

View File

@ -1,50 +0,0 @@
package nl.andrewlalis.aos_client;
import nl.andrewlalis.aos_core.model.World;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
public class DatagramReceiver extends Thread {
private final DatagramSocket datagramSocket;
private final Client client;
private volatile boolean running;
public DatagramReceiver(Client client, int port) throws SocketException {
this.datagramSocket = new DatagramSocket(port);
this.client = client;
}
public DatagramSocket getDatagramSocket() {
return datagramSocket;
}
public void shutdown() {
this.running = false;
this.datagramSocket.close();
}
@Override
public void run() {
this.running = true;
byte[] buffer = new byte[8192];
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
while (this.running) {
try {
this.datagramSocket.receive(packet);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(packet.getData()));
Object obj = ois.readObject();
if (obj instanceof World) {
this.client.setWorld((World) obj);
}
} catch (IOException | ClassNotFoundException e) {
// Ignore any receive exception.d
}
}
}
}

View File

@ -1,12 +1,15 @@
package nl.andrewlalis.aos_client; package nl.andrewlalis.aos_client;
import nl.andrewlalis.aos_core.model.World;
import nl.andrewlalis.aos_core.net.*; import nl.andrewlalis.aos_core.net.*;
import nl.andrewlalis.aos_core.net.chat.ChatMessage;
import java.io.IOException; import java.io.IOException;
import java.io.ObjectInputStream; import java.io.ObjectInputStream;
import java.io.ObjectOutputStream; import java.io.ObjectOutputStream;
import java.net.InetAddress; import java.io.StreamCorruptedException;
import java.net.Socket; import java.net.Socket;
import java.net.SocketException;
/** /**
* This thread is responsible for handling TCP message communication with the * This thread is responsible for handling TCP message communication with the
@ -25,11 +28,12 @@ public class MessageTransceiver extends Thread {
this.client = client; this.client = client;
} }
public void connectToServer(String serverHost, int serverPort, String username, int udpPort) throws IOException, ClassNotFoundException { public void connectToServer(String serverHost, int serverPort, String username) throws IOException {
this.socket = new Socket(serverHost, serverPort); this.socket = new Socket(serverHost, serverPort);
this.out = new ObjectOutputStream(this.socket.getOutputStream()); this.out = new ObjectOutputStream(this.socket.getOutputStream());
this.in = new ObjectInputStream(this.socket.getInputStream()); this.in = new ObjectInputStream(this.socket.getInputStream());
this.send(new IdentMessage(username, udpPort)); this.send(new IdentMessage(username));
System.out.println("Sent identification packet.");
} }
public void shutdown() { public void shutdown() {
@ -43,15 +47,8 @@ public class MessageTransceiver extends Thread {
} }
} }
public InetAddress getRemoteAddress() { public synchronized void send(Message message) throws IOException {
return this.socket != null ? this.socket.getInetAddress() : null; this.out.reset();
}
public int getPort() {
return this.socket.getPort();
}
public void send(Message message) throws IOException {
this.out.writeObject(message); this.out.writeObject(message);
} }
@ -64,10 +61,20 @@ public class MessageTransceiver extends Thread {
PlayerRegisteredMessage prm = (PlayerRegisteredMessage) msg; PlayerRegisteredMessage prm = (PlayerRegisteredMessage) msg;
this.client.initPlayerData(prm.getPlayerId()); this.client.initPlayerData(prm.getPlayerId());
} else if (msg.getType() == Type.CHAT) { } else if (msg.getType() == Type.CHAT) {
this.client.addChatMessage(((ChatMessage) msg).getText()); this.client.addChatMessage((ChatMessage) msg);
} else if (msg.getType() == Type.WORLD_UPDATE) {
World world = ((WorldUpdateMessage) msg).getWorld();
this.client.setWorld(world);
}
} catch (StreamCorruptedException e) {
e.printStackTrace();
this.running = false;
} catch (SocketException e) {
if (!e.getMessage().equalsIgnoreCase("Socket closed")) {
e.printStackTrace();
} }
} catch (IOException | ClassNotFoundException e) { } catch (IOException | ClassNotFoundException e) {
// Ignore exceptions. e.printStackTrace();
} }
} }
} }

View File

@ -2,6 +2,9 @@ package nl.andrewlalis.aos_client.view;
import nl.andrewlalis.aos_client.Client; import nl.andrewlalis.aos_client.Client;
import nl.andrewlalis.aos_core.model.*; import nl.andrewlalis.aos_core.model.*;
import nl.andrewlalis.aos_core.net.chat.ChatMessage;
import nl.andrewlalis.aos_core.net.chat.PlayerChatMessage;
import nl.andrewlalis.aos_core.net.chat.SystemChatMessage;
import javax.swing.*; import javax.swing.*;
import java.awt.*; import java.awt.*;
@ -44,7 +47,7 @@ public class GamePanel extends JPanel {
World world = client.getWorld(); World world = client.getWorld();
if (world != null) drawWorld(g2, world); if (world != null) drawWorld(g2, world);
drawChat(g2, client.getLatestChatMessages()); drawChat(g2, world);
} }
private void drawWorld(Graphics2D g2, World world) { private void drawWorld(Graphics2D g2, World world) {
@ -142,16 +145,35 @@ public class GamePanel extends JPanel {
} }
} }
private void drawChat(Graphics2D g2, String[] messages) { private void drawChat(Graphics2D g2, World world) {
int height = g2.getFontMetrics().getHeight(); int height = g2.getFontMetrics().getHeight();
int y = height; int y = height;
g2.setColor(Color.WHITE); for (ChatMessage message : this.client.getLatestChatMessages()) {
for (String message : messages) { Color color = Color.WHITE;
g2.drawString(message, 5, y); String text = message.getText();
if (message instanceof SystemChatMessage sysMsg) {
if (sysMsg.getLevel() == SystemChatMessage.Level.INFO) {
color = Color.YELLOW;
} else if (sysMsg.getLevel() == SystemChatMessage.Level.WARNING) {
color = Color.ORANGE;
} else if (sysMsg.getLevel() == SystemChatMessage.Level.SEVERE) {
color = Color.RED;
}
} else if (message instanceof PlayerChatMessage pcm) {
String author = Integer.toString(pcm.getPlayerId());
if (world != null) {
Player p = world.getPlayers().get(pcm.getPlayerId());
if (p != null) author = p.getName();
}
text = author + ": " + text;
}
g2.setColor(color);
g2.drawString(text, 5, y);
y += height; y += height;
} }
if (this.client.isChatting()) { if (this.client.isChatting()) {
g2.setColor(Color.WHITE);
g2.drawString("> " + this.client.getCurrentChatBuffer(), 5, height * 11); g2.drawString("> " + this.client.getCurrentChatBuffer(), 5, height * 11);
} }
} }

View File

@ -5,7 +5,7 @@
<parent> <parent>
<artifactId>ace-of-shades</artifactId> <artifactId>ace-of-shades</artifactId>
<groupId>nl.andrewlalis</groupId> <groupId>nl.andrewlalis</groupId>
<version>1.0</version> <version>2.0</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@ -3,4 +3,5 @@ module aos_core {
exports nl.andrewlalis.aos_core.net to aos_server, aos_client; exports nl.andrewlalis.aos_core.net to aos_server, aos_client;
exports nl.andrewlalis.aos_core.model to aos_server, aos_client; 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.geom to aos_server, aos_client;
exports nl.andrewlalis.aos_core.net.chat to aos_client, aos_server;
} }

View File

@ -2,19 +2,13 @@ package nl.andrewlalis.aos_core.net;
public class IdentMessage extends Message { public class IdentMessage extends Message {
private final String name; private final String name;
private final int datagramPort;
public IdentMessage(String name, int datagramPort) { public IdentMessage(String name) {
super(Type.IDENT); super(Type.IDENT);
this.name = name; this.name = name;
this.datagramPort = datagramPort;
} }
public String getName() { public String getName() {
return name; return name;
} }
public int getDatagramPort() {
return datagramPort;
}
} }

View File

@ -0,0 +1,16 @@
package nl.andrewlalis.aos_core.net;
import nl.andrewlalis.aos_core.model.PlayerControlState;
public class PlayerControlStateMessage extends Message {
private final PlayerControlState playerControlState;
public PlayerControlStateMessage(PlayerControlState pcs) {
super(Type.PLAYER_CONTROL_STATE);
this.playerControlState = pcs;
}
public PlayerControlState getPlayerControlState() {
return playerControlState;
}
}

View File

@ -4,5 +4,7 @@ public enum Type {
IDENT, IDENT,
ACK, ACK,
PLAYER_REGISTERED, PLAYER_REGISTERED,
CHAT CHAT,
PLAYER_CONTROL_STATE,
WORLD_UPDATE
} }

View File

@ -0,0 +1,15 @@
package nl.andrewlalis.aos_core.net;
import nl.andrewlalis.aos_core.model.World;
public class WorldUpdateMessage extends Message {
private final World world;
public WorldUpdateMessage(World world) {
super(Type.WORLD_UPDATE);
this.world = world;
}
public World getWorld() {
return world;
}
}

View File

@ -1,4 +1,7 @@
package nl.andrewlalis.aos_core.net; package nl.andrewlalis.aos_core.net.chat;
import nl.andrewlalis.aos_core.net.Message;
import nl.andrewlalis.aos_core.net.Type;
public class ChatMessage extends Message { public class ChatMessage extends Message {
private final String text; private final String text;

View File

@ -0,0 +1,7 @@
package nl.andrewlalis.aos_core.net.chat;
public class CommandMessage extends PlayerChatMessage {
public CommandMessage(int id, String text) {
super(id, text);
}
}

View File

@ -0,0 +1,14 @@
package nl.andrewlalis.aos_core.net.chat;
public class PlayerChatMessage extends ChatMessage {
private final int playerId;
public PlayerChatMessage(int id, String text) {
super(text);
this.playerId = id;
}
public int getPlayerId() {
return playerId;
}
}

View File

@ -0,0 +1,16 @@
package nl.andrewlalis.aos_core.net.chat;
public class SystemChatMessage extends ChatMessage {
public enum Level {INFO, WARNING, SEVERE}
private final Level level;
public SystemChatMessage(Level level, String text) {
super(text);
this.level = level;
}
public Level getLevel() {
return level;
}
}

View File

@ -7,7 +7,7 @@
<groupId>nl.andrewlalis</groupId> <groupId>nl.andrewlalis</groupId>
<artifactId>ace-of-shades</artifactId> <artifactId>ace-of-shades</artifactId>
<packaging>pom</packaging> <packaging>pom</packaging>
<version>1.0</version> <version>2.0</version>
<modules> <modules>
<module>server</module> <module>server</module>
<module>client</module> <module>client</module>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<artifactId>ace-of-shades</artifactId> <artifactId>ace-of-shades</artifactId>
<groupId>nl.andrewlalis</groupId> <groupId>nl.andrewlalis</groupId>
<version>1.0</version> <version>2.0</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@ -1,6 +1,7 @@
package nl.andrewlalis.aos_server; package nl.andrewlalis.aos_server;
import nl.andrewlalis.aos_core.net.*; import nl.andrewlalis.aos_core.net.*;
import nl.andrewlalis.aos_core.net.chat.ChatMessage;
import java.io.IOException; import java.io.IOException;
import java.io.ObjectInputStream; import java.io.ObjectInputStream;
@ -13,7 +14,6 @@ public class ClientHandler extends Thread {
private final ObjectOutputStream out; private final ObjectOutputStream out;
private final ObjectInputStream in; private final ObjectInputStream in;
private int datagramPort = -1;
private int playerId; private int playerId;
private volatile boolean running = true; private volatile boolean running = true;
@ -25,14 +25,6 @@ public class ClientHandler extends Thread {
this.in = new ObjectInputStream(socket.getInputStream()); this.in = new ObjectInputStream(socket.getInputStream());
} }
public Socket getSocket() {
return socket;
}
public int getDatagramPort() {
return datagramPort;
}
public int getPlayerId() { public int getPlayerId() {
return playerId; return playerId;
} }
@ -41,7 +33,8 @@ public class ClientHandler extends Thread {
this.running = false; this.running = false;
} }
public void send(Message message) throws IOException { public synchronized void send(Message message) throws IOException {
this.out.reset();
this.out.writeObject(message); this.out.writeObject(message);
} }
@ -53,12 +46,11 @@ public class ClientHandler extends Thread {
Message msg = (Message) this.in.readObject(); Message msg = (Message) this.in.readObject();
if (msg.getType() == Type.IDENT) { if (msg.getType() == Type.IDENT) {
IdentMessage ident = (IdentMessage) msg; IdentMessage ident = (IdentMessage) msg;
int id = this.server.registerNewPlayer(ident.getName()); this.playerId = this.server.registerNewPlayer(ident.getName(), this);
this.playerId = id;
this.datagramPort = ident.getDatagramPort();
this.send(new PlayerRegisteredMessage(id));
} else if (msg.getType() == Type.CHAT) { } else if (msg.getType() == Type.CHAT) {
this.server.broadcastPlayerChat(this.playerId, (ChatMessage) msg); this.server.broadcastPlayerChat(this.playerId, (ChatMessage) msg);
} else if (msg.getType() == Type.PLAYER_CONTROL_STATE) {
this.server.updatePlayerState(((PlayerControlStateMessage) msg).getPlayerControlState());
} }
} catch (ClassNotFoundException e) { } catch (ClassNotFoundException e) {
e.printStackTrace(); e.printStackTrace();
@ -67,7 +59,6 @@ public class ClientHandler extends Thread {
} catch (IOException e) { } catch (IOException e) {
// Ignore this exception, consider the client disconnected. // Ignore this exception, consider the client disconnected.
} }
this.datagramPort = -1;
try { try {
this.socket.close(); this.socket.close();
} catch (IOException e) { } catch (IOException e) {

View File

@ -1,41 +0,0 @@
package nl.andrewlalis.aos_server;
import nl.andrewlalis.aos_core.model.PlayerControlState;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
public class DatagramCommunicationThread extends Thread {
private final Server server;
private final DatagramSocket socket;
public DatagramCommunicationThread(Server server, int port) throws SocketException {
this.server = server;
this.socket = new DatagramSocket(port);
}
public DatagramSocket getSocket() {
return socket;
}
@Override
public void run() {
byte[] buffer = new byte[8192];
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
while (true) {
try {
this.socket.receive(packet);
Object obj = new ObjectInputStream(new ByteArrayInputStream(buffer)).readObject();
if (obj instanceof PlayerControlState) {
this.server.updatePlayerState((PlayerControlState) obj);
}
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
}

View File

@ -2,19 +2,20 @@ package nl.andrewlalis.aos_server;
import nl.andrewlalis.aos_core.geom.Vec2; import nl.andrewlalis.aos_core.geom.Vec2;
import nl.andrewlalis.aos_core.model.*; import nl.andrewlalis.aos_core.model.*;
import nl.andrewlalis.aos_core.net.ChatMessage;
import nl.andrewlalis.aos_core.net.Message; import nl.andrewlalis.aos_core.net.Message;
import nl.andrewlalis.aos_core.net.PlayerRegisteredMessage;
import nl.andrewlalis.aos_core.net.WorldUpdateMessage;
import nl.andrewlalis.aos_core.net.chat.ChatMessage;
import nl.andrewlalis.aos_core.net.chat.PlayerChatMessage;
import nl.andrewlalis.aos_core.net.chat.SystemChatMessage;
import java.awt.*; import java.awt.*;
import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.ObjectOutputStream;
import java.net.DatagramPacket;
import java.net.ServerSocket; import java.net.ServerSocket;
import java.net.Socket; import java.net.Socket;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Scanner; import java.util.Scanner;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.ThreadLocalRandom;
public class Server { public class Server {
@ -22,14 +23,12 @@ public class Server {
private final List<ClientHandler> clientHandlers; private final List<ClientHandler> clientHandlers;
private final ServerSocket serverSocket; private final ServerSocket serverSocket;
private final DatagramCommunicationThread datagramCommunicationThread;
private final World world; private final World world;
private final WorldUpdater worldUpdater; private final WorldUpdater worldUpdater;
public Server(int port) throws IOException { public Server(int port) throws IOException {
this.clientHandlers = new ArrayList<>(); this.clientHandlers = new CopyOnWriteArrayList<>();
this.serverSocket = new ServerSocket(port); this.serverSocket = new ServerSocket(port);
this.datagramCommunicationThread = new DatagramCommunicationThread(this, port);
this.world = new World(new Vec2(50, 70)); this.world = new World(new Vec2(50, 70));
world.getBarricades().add(new Barricade(10, 10, 30, 5)); world.getBarricades().add(new Barricade(10, 10, 30, 5));
@ -42,19 +41,17 @@ public class Server {
world.getTeams().add(new Team("Blue", Color.BLUE, new Vec2(world.getSize().x() - 3, world.getSize().y() - 3), new Vec2(0, -1))); world.getTeams().add(new Team("Blue", Color.BLUE, new Vec2(world.getSize().x() - 3, world.getSize().y() - 3), new Vec2(0, -1)));
this.worldUpdater = new WorldUpdater(this, this.world); this.worldUpdater = new WorldUpdater(this, this.world);
System.out.println("Started AOS-Server TCP/UDP on port " + port); System.out.println("Started AOS-Server TCP on port " + port);
} }
public void acceptClientConnection() throws IOException { public void acceptClientConnection() throws IOException {
Socket socket = this.serverSocket.accept(); Socket socket = this.serverSocket.accept();
var t = new ClientHandler(this, socket); var t = new ClientHandler(this, socket);
t.start(); t.start();
synchronized (this.clientHandlers) { this.clientHandlers.add(t);
this.clientHandlers.add(t);
}
} }
public int registerNewPlayer(String name) { public int registerNewPlayer(String name, ClientHandler handler) {
int id = ThreadLocalRandom.current().nextInt(1, Integer.MAX_VALUE); int id = ThreadLocalRandom.current().nextInt(1, Integer.MAX_VALUE);
Team team = null; Team team = null;
for (Team t : this.world.getTeams()) { for (Team t : this.world.getTeams()) {
@ -65,48 +62,46 @@ public class Server {
} }
} }
Player p = new Player(id, name, team); Player p = new Player(id, name, team);
System.out.println("Client connected: " + p.getId() + ", " + p.getName());
this.broadcastMessage(new ChatMessage(name + " connected."));
this.world.getPlayers().put(p.getId(), p); this.world.getPlayers().put(p.getId(), p);
try {
handler.send(new PlayerRegisteredMessage(id));
} catch (IOException e) {
e.printStackTrace();
}
String message = p.getName() + " connected.";
this.broadcastMessage(new SystemChatMessage(SystemChatMessage.Level.INFO, message));
System.out.println(message);
p.setPosition(new Vec2(this.world.getSize().x() / 2.0, this.world.getSize().y() / 2.0)); p.setPosition(new Vec2(this.world.getSize().x() / 2.0, this.world.getSize().y() / 2.0));
if (team != null) { if (team != null) {
team.getPlayers().add(p); team.getPlayers().add(p);
p.setPosition(team.getSpawnPoint()); p.setPosition(team.getSpawnPoint());
p.setOrientation(team.getOrientation()); p.setOrientation(team.getOrientation());
this.broadcastMessage(new ChatMessage(name + " joined team " + team.getName())); message = name + " joined team " + team.getName() + ".";
System.out.println("Player joined team " + team.getName()); this.broadcastMessage(new SystemChatMessage(SystemChatMessage.Level.INFO, message));
} }
return id; return id;
} }
public void clientDisconnected(ClientHandler clientHandler) { public void clientDisconnected(ClientHandler clientHandler) {
Player player = this.world.getPlayers().get(clientHandler.getPlayerId()); Player player = this.world.getPlayers().get(clientHandler.getPlayerId());
synchronized (this.clientHandlers) { this.clientHandlers.remove(clientHandler);
this.clientHandlers.remove(clientHandler); clientHandler.shutdown();
clientHandler.shutdown();
}
this.world.getPlayers().remove(player.getId()); this.world.getPlayers().remove(player.getId());
if (player.getTeam() != null) { if (player.getTeam() != null) {
player.getTeam().getPlayers().remove(player); player.getTeam().getPlayers().remove(player);
} }
this.broadcastMessage(new ChatMessage(player.getName() + " disconnected.")); String message = player.getName() + " disconnected.";
System.out.println("Client disconnected: " + player.getId() + ", " + player.getName()); this.broadcastMessage(new SystemChatMessage(SystemChatMessage.Level.INFO, message));
System.out.println(message);
} }
public void sendWorldToClients() { public void sendWorldToClients() {
try { for (ClientHandler handler : this.clientHandlers) {
ByteArrayOutputStream bos = new ByteArrayOutputStream(); try {
new ObjectOutputStream(bos).writeObject(this.world); handler.send(new WorldUpdateMessage(this.world));
byte[] data = bos.toByteArray(); } catch (IOException e) {
DatagramPacket packet = new DatagramPacket(data, data.length); e.printStackTrace();
for (ClientHandler handler : this.clientHandlers) {
if (handler.getDatagramPort() == -1) continue;
packet.setAddress(handler.getSocket().getInetAddress());
packet.setPort(handler.getDatagramPort());
this.datagramCommunicationThread.getSocket().send(packet);
} }
} catch (Exception e) {
e.printStackTrace();
} }
} }
@ -130,7 +125,7 @@ public class Server {
public void broadcastPlayerChat(int playerId, ChatMessage msg) { public void broadcastPlayerChat(int playerId, ChatMessage msg) {
Player p = this.world.getPlayers().get(playerId); Player p = this.world.getPlayers().get(playerId);
if (p == null) return; if (p == null) return;
this.broadcastMessage(new ChatMessage(p.getName() + ": " + msg.getText())); this.broadcastMessage(new PlayerChatMessage(p.getId(), msg.getText()));
} }
@ -150,12 +145,9 @@ public class Server {
} }
Server server = new Server(port); Server server = new Server(port);
server.datagramCommunicationThread.start();
server.worldUpdater.start(); server.worldUpdater.start();
while (true) { while (true) {
server.acceptClientConnection(); server.acceptClientConnection();
} }
} }
} }

View File

@ -5,7 +5,7 @@ import nl.andrewlalis.aos_core.model.Barricade;
import nl.andrewlalis.aos_core.model.Bullet; import nl.andrewlalis.aos_core.model.Bullet;
import nl.andrewlalis.aos_core.model.Player; import nl.andrewlalis.aos_core.model.Player;
import nl.andrewlalis.aos_core.model.World; import nl.andrewlalis.aos_core.model.World;
import nl.andrewlalis.aos_core.net.ChatMessage; import nl.andrewlalis.aos_core.net.chat.SystemChatMessage;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -155,7 +155,7 @@ public class WorldUpdater extends Thread {
double dist = p.getPosition().dist(new Vec2(x1 + n * (x2 - x1), y1 + n * (y2 - y1))); double dist = p.getPosition().dist(new Vec2(x1 + n * (x2 - x1), y1 + n * (y2 - y1)));
if (dist < Player.RADIUS) { if (dist < Player.RADIUS) {
Player killer = this.world.getPlayers().get(b.getPlayerId()); Player killer = this.world.getPlayers().get(b.getPlayerId());
this.server.broadcastMessage(new ChatMessage(p.getName() + " was shot by " + killer.getName() + ".")); this.server.broadcastMessage(new SystemChatMessage(SystemChatMessage.Level.SEVERE, p.getName() + " was shot by " + killer.getName() + "."));
world.getSoundsToPlay().add("death.wav"); world.getSoundsToPlay().add("death.wav");
if (p.getTeam() != null) { if (p.getTeam() != null) {
p.setPosition(p.getTeam().getSpawnPoint()); p.setPosition(p.getTeam().getSpawnPoint());