mirror of
https://github.com/michaelrausch/Party-Parrots-At-Sea.git
synced 2026-05-09 14:28:43 +00:00
b346d5a706
- When a server is closed, it will disappear from the server list - When a player joins a server, the number of spaces left will decrease - Servers now disappear instead of duplicating - Added tests for ServerDescription - Added documentation for new classes Tags: #story[1247]
388 lines
13 KiB
Java
388 lines
13 KiB
Java
package seng302.gameServer;
|
|
|
|
import org.slf4j.Logger;
|
|
import org.slf4j.LoggerFactory;
|
|
import org.w3c.dom.Document;
|
|
import org.xml.sax.InputSource;
|
|
import org.xml.sax.SAXException;
|
|
import seng302.gameServer.messages.*;
|
|
import seng302.model.GeoPoint;
|
|
import seng302.model.Player;
|
|
import seng302.model.PolarTable;
|
|
import seng302.model.ServerYacht;
|
|
import seng302.model.mark.CompoundMark;
|
|
import seng302.model.stream.xml.parser.RegattaXMLData;
|
|
import seng302.utilities.GeoUtility;
|
|
import seng302.utilities.XMLGenerator;
|
|
import seng302.utilities.XMLParser;
|
|
|
|
import javax.xml.parsers.DocumentBuilder;
|
|
import javax.xml.parsers.DocumentBuilderFactory;
|
|
import javax.xml.parsers.ParserConfigurationException;
|
|
import java.io.IOException;
|
|
import java.io.StringReader;
|
|
import java.net.ServerSocket;
|
|
import java.time.LocalDateTime;
|
|
import java.util.*;
|
|
|
|
/**
|
|
* A class describing the overall server, which creates and collects server threads for each client
|
|
* Created by wmu16 on 13/07/17.
|
|
*/
|
|
public class MainServerThread implements Runnable, ClientConnectionDelegate {
|
|
|
|
private static final int PORT = 4942;
|
|
private static final Integer CLIENT_UPDATES_PER_SECOND = 60;
|
|
private static final int LOG_LEVEL = 1;
|
|
private static final int WARNING_TIME = 10 * -1000;
|
|
private static final int PREPATORY_TIME = 5 * -1000;
|
|
public static final int TIME_TILL_START = 10 * 1000;
|
|
|
|
private static final int MAX_WIND_SPEED = 12000;
|
|
private static final int MIN_WIND_SPEED = 8000;
|
|
|
|
public static int windSpeed = 1000;
|
|
|
|
private boolean terminated;
|
|
|
|
private Thread thread;
|
|
|
|
private ServerSocket serverSocket = null;
|
|
private ArrayList<ServerToClientThread> serverToClientThreads = new ArrayList<>();
|
|
private Logger logger = LoggerFactory.getLogger(MainServerThread.class);
|
|
|
|
private void startAdvertisingServer(){
|
|
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
|
|
DocumentBuilder db;
|
|
Document doc;
|
|
XMLGenerator generator = new XMLGenerator();
|
|
|
|
try {
|
|
db = dbf.newDocumentBuilder();
|
|
String regatta = generator.getRegattaAsXml();
|
|
StringReader stringReader = new StringReader(regatta);
|
|
InputSource is = new InputSource(stringReader);
|
|
doc = db.parse(is);
|
|
} catch (ParserConfigurationException | IOException | SAXException e) {
|
|
logger.warn("Couldn't load race regatta");
|
|
return;
|
|
}
|
|
|
|
RegattaXMLData regattaXMLData = XMLParser.parseRegatta(doc);
|
|
|
|
Integer spacesLeft = GameState.getSpacesLeft();
|
|
|
|
// No spaces left on server
|
|
if (spacesLeft < 1){
|
|
return;
|
|
}
|
|
|
|
// Start advertising server
|
|
try{
|
|
ServerAdvertiser.getInstance().setMapName(regattaXMLData.getCourseName()).setSpacesLeft(spacesLeft);
|
|
ServerAdvertiser.getInstance().registerGame(PORT, regattaXMLData.getRegattaName());
|
|
} catch (IOException e) {
|
|
logger.warn("Could not register server");
|
|
}
|
|
}
|
|
|
|
public MainServerThread() {
|
|
new GameState("localhost");
|
|
try {
|
|
serverSocket = new ServerSocket(PORT);
|
|
} catch (IOException e) {
|
|
serverLog("IO error in server thread handler upon trying to make new server socket", 0);
|
|
}
|
|
|
|
startAdvertisingServer();
|
|
|
|
PolarTable.parsePolarFile(getClass().getResourceAsStream("/config/acc_polars.csv"));
|
|
GameState.addMarkPassListener(this::broadcastMessage);
|
|
terminated = false;
|
|
thread = new Thread(this, "MainServer");
|
|
startUpdatingWind();
|
|
thread.start();
|
|
}
|
|
|
|
|
|
public void run() {
|
|
|
|
new HeartbeatThread(this);
|
|
new ServerListenThread(serverSocket, this);
|
|
|
|
//You should handle interrupts in some way, so that the thread won't keep on forever if you exit the app.
|
|
while (!terminated) {
|
|
try {
|
|
Thread.sleep(1000 / CLIENT_UPDATES_PER_SECOND);
|
|
} catch (InterruptedException e) {
|
|
serverLog("Interrupted exception in Main Server Thread thread sleep", 1);
|
|
}
|
|
if (GameState.getCurrentStage() == GameStages.LOBBYING && GameState
|
|
.getCustomizationFlag()) {
|
|
// TODO: 16/08/17 ajm412: This can probably be done in a nicer way via those fancy functional interfaces.
|
|
for (ServerToClientThread thread : serverToClientThreads) {
|
|
thread.sendSetupMessages();
|
|
}
|
|
GameState.resetCustomizationFlag();
|
|
}
|
|
|
|
if (GameState.getCurrentStage() == GameStages.PRE_RACE) {
|
|
updateClients();
|
|
}
|
|
|
|
//RACING
|
|
if (GameState.getCurrentStage() == GameStages.RACING) {
|
|
updateClients();
|
|
}
|
|
|
|
//FINISHED
|
|
else if (GameState.getCurrentStage() == GameStages.FINISHED) {
|
|
terminate();
|
|
}
|
|
}
|
|
|
|
// TODO: 14/07/17 wmu16 - Send out disconnect packet to clients
|
|
try {
|
|
for (ServerToClientThread serverToClientThread : serverToClientThreads) {
|
|
serverToClientThread.terminate();
|
|
}
|
|
serverSocket.close();
|
|
return;
|
|
} catch (IOException e) {
|
|
System.out.println("IO error in server thread handler upon closing socket");
|
|
}
|
|
}
|
|
|
|
public void updateClients() {
|
|
for (ServerToClientThread serverToClientThread : serverToClientThreads) {
|
|
serverToClientThread.sendBoatLocationPackets();
|
|
}
|
|
}
|
|
|
|
private void broadcastMessage(Message message) {
|
|
for (ServerToClientThread serverToClientThread : serverToClientThreads) {
|
|
serverToClientThread.sendMessage(message);
|
|
}
|
|
}
|
|
|
|
private static void updateWind(){
|
|
Integer direction = GameState.getWindDirection().intValue();
|
|
Integer windSpeed = GameState.getWindSpeedMMS().intValue();
|
|
|
|
Random random = new Random();
|
|
|
|
if (Math.floorMod(random.nextInt(), 2) == 0){
|
|
direction += random.nextInt(4);
|
|
windSpeed += random.nextInt(20) + 50;
|
|
}
|
|
else{
|
|
direction -= random.nextInt(4);
|
|
windSpeed -= random.nextInt(20) + 50;
|
|
}
|
|
|
|
direction = Math.floorMod(direction, 360);
|
|
|
|
if (windSpeed > MAX_WIND_SPEED){
|
|
windSpeed -= random.nextInt(1000);
|
|
}
|
|
|
|
if (windSpeed <= MIN_WIND_SPEED){
|
|
windSpeed += random.nextInt(1000);
|
|
}
|
|
|
|
GameState.setWindSpeed(Double.valueOf(windSpeed));
|
|
GameState.setWindDirection(direction.doubleValue());
|
|
}
|
|
|
|
private static void startUpdatingWind(){
|
|
Timer timer = new Timer();
|
|
timer.schedule(new TimerTask() {
|
|
@Override
|
|
public void run() {
|
|
updateWind();
|
|
}
|
|
}, 0, 500);
|
|
}
|
|
|
|
|
|
static void serverLog(String message, int logLevel) {
|
|
if (logLevel <= LOG_LEVEL) {
|
|
System.out.println(
|
|
"[SERVER " + LocalDateTime.now().toLocalTime().toString() + "] " + message);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A client has tried to connect to the server
|
|
*
|
|
* @param serverToClientThread The player that connected
|
|
*/
|
|
@Override
|
|
public void clientConnected(ServerToClientThread serverToClientThread) {
|
|
serverLog("Player Connected From " + serverToClientThread.getThread().getName(), 0);
|
|
serverToClientThreads.add(serverToClientThread);
|
|
serverToClientThread.addConnectionListener(() -> {
|
|
for (ServerToClientThread thread : serverToClientThreads) {
|
|
thread.sendSetupMessages();
|
|
}
|
|
});
|
|
serverToClientThread.addDisconnectListener(this::clientDisconnected);
|
|
|
|
try {
|
|
ServerAdvertiser.getInstance().setSpacesLeft(GameState.getSpacesLeft());
|
|
} catch (IOException e) {
|
|
logger.warn("Couldn't update advertisement");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A player has left the game, remove the player from the GameState
|
|
*
|
|
* @param player The player that left
|
|
*/
|
|
@Override
|
|
public void clientDisconnected(Player player) {
|
|
// try {
|
|
// player.getSocket().close();
|
|
// } catch (Exception e) {
|
|
// serverLog("Cannot disconnect the socket for the disconnected player.", 0);
|
|
// }
|
|
serverLog("Player " + player.getYacht().getSourceId() + "'s socket disconnected", 0);
|
|
GameState.removeYacht(player.getYacht().getSourceId());
|
|
GameState.removePlayer(player);
|
|
ServerToClientThread closedConnection = null;
|
|
for (ServerToClientThread serverToClientThread : serverToClientThreads) {
|
|
if (serverToClientThread.getSocket() == player.getSocket()) {
|
|
closedConnection = serverToClientThread;
|
|
} else if (GameState.getCurrentStage() != GameStages.RACING){
|
|
serverToClientThread.sendSetupMessages();
|
|
}
|
|
}
|
|
serverToClientThreads.remove(closedConnection);
|
|
|
|
try {
|
|
ServerAdvertiser.getInstance().setSpacesLeft(GameState.getSpacesLeft());
|
|
} catch (IOException e) {
|
|
logger.warn("Couldn't update advertisement");
|
|
}
|
|
|
|
closedConnection.terminate();
|
|
}
|
|
|
|
public void startGame() {
|
|
try {
|
|
ServerAdvertiser.getInstance().unregister();
|
|
} catch (IOException e) {
|
|
logger.warn("Error unregistered server");
|
|
}
|
|
|
|
initialiseBoatPositions();
|
|
Timer t = new Timer();
|
|
|
|
t.schedule(new TimerTask() {
|
|
@Override
|
|
public void run() {
|
|
broadcastMessage(makeRaceStatusMessage());
|
|
if (GameState.getCurrentStage() == GameStages.PRE_RACE || GameState.getCurrentStage() == GameStages.LOBBYING) {
|
|
broadcastMessage(makeRaceStartMessage());
|
|
}
|
|
}
|
|
}, 0, 500);
|
|
}
|
|
|
|
|
|
private RaceStartStatusMessage makeRaceStartMessage() {
|
|
Long raceStartTime = GameState.getStartTime();
|
|
|
|
return new RaceStartStatusMessage(1, raceStartTime ,
|
|
1, RaceStartNotificationType.SET_RACE_START_TIME);
|
|
}
|
|
|
|
private RaceStatusMessage makeRaceStatusMessage() {
|
|
// variables taken from GameServerThread
|
|
|
|
List<BoatSubMessage> boatSubMessages = new ArrayList<>();
|
|
RaceStatus raceStatus;
|
|
|
|
for (Player player : GameState.getPlayers()) {
|
|
ServerYacht y = player.getYacht();
|
|
BoatSubMessage m = new BoatSubMessage(y.getSourceId(), y.getBoatStatus(),
|
|
y.getLegNumber(),
|
|
0, 0, 1234L,
|
|
1234L);
|
|
boatSubMessages.add(m);
|
|
}
|
|
|
|
long timeTillStart = System.currentTimeMillis() - GameState.getStartTime();
|
|
|
|
if (GameState.getCurrentStage() == GameStages.LOBBYING) {
|
|
raceStatus = RaceStatus.PRESTART;
|
|
} else if (GameState.getCurrentStage() == GameStages.PRE_RACE) {
|
|
raceStatus = RaceStatus.PRESTART;
|
|
|
|
if (timeTillStart > WARNING_TIME) {
|
|
raceStatus = RaceStatus.WARNING;
|
|
}
|
|
|
|
if (timeTillStart > PREPATORY_TIME) {
|
|
raceStatus = RaceStatus.PREPARATORY;
|
|
}
|
|
} else {
|
|
raceStatus = RaceStatus.STARTED;
|
|
}
|
|
|
|
return new RaceStatusMessage(1, raceStatus, GameState.getStartTime(),
|
|
GameState.getWindDirection(),
|
|
GameState.getWindSpeedMMS().longValue(), GameState.getPlayers().size(),
|
|
RaceType.MATCH_RACE, 1, boatSubMessages);
|
|
}
|
|
|
|
public void terminate() {
|
|
terminated = true;
|
|
}
|
|
|
|
/**
|
|
* Initialise boats to specific spaced out geopoints behind starting line.
|
|
*/
|
|
private void initialiseBoatPositions() {
|
|
// Getting the start line compound marks
|
|
// if (gameClient== null) {
|
|
// return;
|
|
// }
|
|
CompoundMark cm = GameState.getMarkOrder().getMarkOrder().get(0);
|
|
GeoPoint startMark1 = cm.getSubMark(1);
|
|
GeoPoint startMark2 = cm.getSubMark(2);
|
|
|
|
// Calculating midpoint
|
|
Double perpendicularAngle = GeoUtility.getBearing(startMark1, startMark2);
|
|
Double length = GeoUtility.getDistance(startMark1, startMark2);
|
|
GeoPoint midpoint = GeoUtility.getGeoCoordinate(startMark1, perpendicularAngle, length / 2);
|
|
|
|
// Setting each boats position side by side
|
|
double DISTANCE_FACTOR = 50.0; // distance apart in meters
|
|
int boatIndex = 0;
|
|
for (ServerYacht yacht : GameState.getYachts().values()) {
|
|
int distanceApart = boatIndex / 2;
|
|
|
|
if (boatIndex % 2 == 1 && boatIndex != 0) {
|
|
distanceApart++;
|
|
distanceApart *= -1;
|
|
}
|
|
|
|
GeoPoint spawnMark = GeoUtility
|
|
.getGeoCoordinate(midpoint, perpendicularAngle, distanceApart * DISTANCE_FACTOR);
|
|
|
|
if (yacht.getHeading() < perpendicularAngle) {
|
|
spawnMark = GeoUtility
|
|
.getGeoCoordinate(spawnMark, perpendicularAngle + 90, DISTANCE_FACTOR);
|
|
} else {
|
|
spawnMark = GeoUtility
|
|
.getGeoCoordinate(spawnMark, perpendicularAngle + 270, DISTANCE_FACTOR);
|
|
}
|
|
|
|
yacht.setLocation(spawnMark);
|
|
boatIndex++;
|
|
}
|
|
}
|
|
}
|