AceOfShades/server/src/main/java/nl/andrewlalis/aos_server/Server.java

270 lines
8.4 KiB
Java

package nl.andrewlalis.aos_server;
import nl.andrewlalis.aos_core.geom.Vec2;
import nl.andrewlalis.aos_core.model.*;
import nl.andrewlalis.aos_core.model.tools.GunCategory;
import nl.andrewlalis.aos_core.model.tools.GunType;
import nl.andrewlalis.aos_core.net.Message;
import nl.andrewlalis.aos_core.net.PlayerUpdateMessage;
import nl.andrewlalis.aos_core.net.Type;
import nl.andrewlalis.aos_core.net.chat.SystemChatMessage;
import nl.andrewlalis.aos_core.net.data.DataTypes;
import nl.andrewlalis.aos_core.net.data.PlayerDetailUpdate;
import nl.andrewlalis.aos_core.net.data.WorldUpdate;
import nl.andrewlalis.aos_core.util.ByteUtils;
import nl.andrewlalis.aos_server.settings.ServerSettings;
import nl.andrewlalis.aos_server.settings.SettingsLoader;
import java.awt.*;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ThreadLocalRandom;
public class Server {
private final ServerSettings settings;
private final List<ClientHandler> clientHandlers;
private final ServerSocket serverSocket;
private final DataTransceiver dataTransceiver;
private final World world;
private final WorldUpdater worldUpdater;
private final ServerCli cli;
private final ChatManager chatManager;
private RegistryManager registryManager;
private volatile boolean running;
public Server(ServerSettings settings) throws IOException {
this.settings = settings;
this.clientHandlers = new CopyOnWriteArrayList<>();
this.serverSocket = new ServerSocket(settings.getPort());
this.dataTransceiver = new DataTransceiver(this, settings.getPort());
this.cli = new ServerCli(this);
this.world = new World(new Vec2(50, 70));
this.initWorld();
this.worldUpdater = new WorldUpdater(this, this.world);
this.chatManager = new ChatManager(this);
if (settings.getRegistrySettings().isDiscoverable()) {
this.registryManager = new RegistryManager(this);
}
}
public ServerSettings getSettings() {
return settings;
}
private void initWorld() {
for (var gs : this.settings.getGunSettings()) {
this.world.getGunTypes().put(gs.getName(), new GunType(
gs.getName(),
GunCategory.valueOf(gs.getCategory().toUpperCase()),
gs.getColor(),
gs.getMaxClipCount(),
gs.getClipSize(),
gs.getBulletsPerRound(),
gs.getAccuracy(),
gs.getShotCooldownTime(),
gs.getReloadTime(),
gs.getBulletSpeed(),
gs.getBaseDamage(),
gs.getRecoil()
));
}
world.getBarricades().add(new Barricade(10, 10, 30, 5));
world.getBarricades().add(new Barricade(10, 55, 30, 5));
world.getBarricades().add(new Barricade(20, 30, 10, 10));
world.getBarricades().add(new Barricade(0, 30, 10, 10));
world.getBarricades().add(new Barricade(40, 30, 10, 10));
world.getTeams().put((byte) 1, new Team(
(byte) 1,
"Red",
Color.RED,
new Vec2(3, 3),
new Vec2(15, 3),
new Vec2(0, 1)
));
world.getTeams().put((byte) 2, new Team(
(byte) 2,
"Blue",
Color.BLUE,
new Vec2(world.getSize().x() - 3, world.getSize().y() - 3),
new Vec2(world.getSize().x() - 15, world.getSize().y() - 3),
new Vec2(0, -1)
));
}
public World getWorld() {
return world;
}
public ChatManager getChatManager() {
return chatManager;
}
public int getPlayerCount() {
return this.clientHandlers.size();
}
public void acceptClientConnection() {
try {
Socket socket = this.serverSocket.accept();
var t = new ClientHandler(this, socket);
t.start();
this.clientHandlers.add(t);
} catch (IOException e) {
if (e instanceof SocketException && !this.running && e.getMessage().equalsIgnoreCase("Socket closed")) {
return; // Ignore this exception, since it is expected on shutdown.
}
e.printStackTrace();
}
}
public Player registerNewPlayer(String name) {
int id = ThreadLocalRandom.current().nextInt(1, Integer.MAX_VALUE);
Team team = null;
for (Team t : this.world.getTeams().values()) {
if (team == null) {
team = t;
} else if (t.getPlayers().size() < team.getPlayers().size()) {
team = t;
}
}
Player p = new Player(id, name, team, this.world.getGunTypes().get(this.settings.getPlayerSettings().getDefaultGun()), settings.getPlayerSettings().getMaxHealth());
this.world.getPlayers().put(p.getId(), p);
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.0f, this.world.getSize().y() / 2.0f));
if (team != null) {
team.getPlayers().add(p);
p.setPosition(team.getSpawnPoint());
p.setOrientation(team.getOrientation());
message = name + " joined team " + team.getName() + ".";
this.broadcastMessage(new SystemChatMessage(SystemChatMessage.Level.INFO, message));
}
this.broadcastMessage(new PlayerUpdateMessage(Type.PLAYER_JOINED, p));
return p;
}
public void clientDisconnected(ClientHandler clientHandler) {
Player player = clientHandler.getPlayer();
this.clientHandlers.remove(clientHandler);
clientHandler.shutdown();
this.world.getPlayers().remove(player.getId());
if (player.getTeam() != null) {
player.getTeam().getPlayers().remove(player);
}
String message = player.getName() + " disconnected.";
this.broadcastMessage(new SystemChatMessage(SystemChatMessage.Level.INFO, message));
System.out.println(message);
this.broadcastMessage(new PlayerUpdateMessage(Type.PLAYER_LEFT, player));
}
public void kickPlayer(Player player) {
for (ClientHandler handler : this.clientHandlers) {
if (handler.getPlayer().getId() == player.getId()) {
handler.shutdown();
return;
}
}
}
public void sendWorldUpdate(WorldUpdate update) {
try {
byte[] data = update.toBytes();
byte[] finalData = new byte[data.length + 1];
finalData[0] = DataTypes.WORLD_DATA;
System.arraycopy(data, 0, finalData, 1, data.length);
for (ClientHandler handler : this.clientHandlers) {
if (handler.getClientUdpPort() == -1) continue;
this.dataTransceiver.send(finalData, handler.getClientAddress(), handler.getClientUdpPort());
byte[] detailData = ByteUtils.prefix(DataTypes.PLAYER_DETAIL, new PlayerDetailUpdate(handler.getPlayer()).toBytes());
this.dataTransceiver.send(detailData, handler.getClientAddress(), handler.getClientUdpPort());
}
} catch (IOException e) {
e.printStackTrace();
}
}
public void updatePlayerState(int playerId, PlayerControlState state) {
Player p = this.world.getPlayers().get(playerId);
if (p != null) {
p.setState(state);
}
}
public void resetGame() {
for (Team t : this.world.getTeams().values()) {
t.resetScore();
for (Player p : t.getPlayers()) {
p.resetStats();
p.respawn(settings.getPlayerSettings().getMaxHealth());
}
}
broadcastMessage(new SystemChatMessage(SystemChatMessage.Level.INFO, "Game has been reset."));
}
public void broadcastMessage(Message message) {
for (ClientHandler handler : this.clientHandlers) {
handler.send(message);
}
}
public void sendTeamMessage(Team team, Message message) {
for (ClientHandler handler : this.clientHandlers) {
if (team.equals(handler.getPlayer().getTeam())) {
handler.send(message);
}
}
}
public void shutdown() {
this.running = false;
try {
this.serverSocket.close();
for (ClientHandler handler : this.clientHandlers) {
handler.send(new Message(Type.SERVER_SHUTDOWN));
handler.shutdown();
}
} catch (Exception e) {
System.err.println("Could not close server socket on shutdown: " + e.getMessage());
}
}
public void run() {
this.running = true;
this.dataTransceiver.start();
this.worldUpdater.start();
this.cli.start();
System.out.println("Started AOS-Server TCP/UDP on port " + this.serverSocket.getLocalPort() + "; now accepting connections.");
while (this.running) {
this.acceptClientConnection();
}
this.shutdown();
System.out.println("Stopped accepting new client connections.");
this.worldUpdater.shutdown();
System.out.println("Stopped world updater.");
this.cli.shutdown();
System.out.println("Stopped CLI interface.");
this.dataTransceiver.shutdown();
System.out.println("Stopped data transceiver.");
if (this.registryManager != null) {
this.registryManager.shutdown();
System.out.println("Stopped registry communications.");
}
}
public static void main(String[] args) throws IOException {
Server server = new Server(SettingsLoader.load());
server.run();
}
}