diff --git a/src/main/java/seng302/gameServer/GameState.java b/src/main/java/seng302/gameServer/GameState.java index 764f0f66..e67d6d71 100644 --- a/src/main/java/seng302/gameServer/GameState.java +++ b/src/main/java/seng302/gameServer/GameState.java @@ -6,8 +6,12 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; +import org.xml.sax.InputSource; import seng302.gameServer.server.messages.BoatAction; import seng302.gameServer.server.messages.BoatStatus; import seng302.gameServer.server.messages.CustomizeRequestType; @@ -17,6 +21,7 @@ import seng302.gameServer.server.messages.Message; import seng302.gameServer.server.messages.RoundingBoatStatus; import seng302.gameServer.server.messages.YachtEventCodeMessage; import seng302.model.GeoPoint; +import seng302.model.Limit; import seng302.model.Player; import seng302.model.PolarTable; import seng302.model.ServerYacht; @@ -24,6 +29,7 @@ import seng302.model.mark.CompoundMark; import seng302.model.mark.Mark; import seng302.model.mark.MarkOrder; import seng302.utilities.GeoUtility; +import seng302.utilities.XMLParser; /** * A Static class to hold information about the current state of the game (model) @@ -34,6 +40,7 @@ public class GameState implements Runnable { @FunctionalInterface interface NewMessageListener { + void notify(Message message); } @@ -60,6 +67,7 @@ public class GameState implements Runnable { private static MarkOrder markOrder; private static long startTime; private static Set marks; + private static List courseLimit; private static List markListeners; @@ -82,7 +90,7 @@ public class GameState implements Runnable { yachts = new HashMap<>(); players = new ArrayList<>(); GameState.hostIpAddress = hostIpAddress; - ; + currentStage = GameStages.LOBBYING; isRaceStarted = false; //set this when game stage changes to prerace @@ -90,16 +98,35 @@ public class GameState implements Runnable { markOrder = new MarkOrder(); //This could be instantiated at some point with a select map? markListeners = new ArrayList<>(); + resetStartTime(); + + new Thread(this).start(); //Run the auto updates on the game state new Thread(this, "GameState").start(); //Run the auto updates on the game state marks = new MarkOrder().getAllMarks(); + setCourseLimit("/server_config/race.xml"); + } + + private void setCourseLimit(String url) { + DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); + documentBuilderFactory.setNamespaceAware(true); + DocumentBuilder documentBuilder; + Document document = null; + try { + documentBuilder = documentBuilderFactory.newDocumentBuilder(); + document = documentBuilder.parse(new InputSource(getClass().getResourceAsStream(url))); + } catch (Exception e) { + // sorry, we have to catch general one, otherwise we have to catch five different exceptions. + logger.trace("Failed to load course limit for boundary collision detection.", e); + } + courseLimit = XMLParser.parseRace(document).getCourseLimit(); } public static String getHostIpAddress() { return hostIpAddress; } - public static Set getMarks(){ + public static Set getMarks() { return Collections.unmodifiableSet(marks); } @@ -136,10 +163,6 @@ public class GameState implements Runnable { } public static void setCurrentStage(GameStages currentStage) { - if (currentStage == GameStages.RACING) { - startTime = System.currentTimeMillis(); - } - GameState.currentStage = currentStage; } @@ -147,10 +170,14 @@ public class GameState implements Runnable { return markOrder; } - public static long getStartTime(){ + public static long getStartTime() { return startTime; } + public static void resetStartTime(){ + startTime = System.currentTimeMillis() + MainServerThread.TIME_TILL_START; + } + public static Double getWindDirection() { return windDirection; } @@ -191,7 +218,7 @@ public class GameState implements Runnable { } catch (InterruptedException e) { System.out.println("[GameState] interrupted exception"); } - if (currentStage == GameStages.PRE_RACE) { + if (currentStage == GameStages.PRE_RACE || currentStage == GameStages.RACING) { update(); } @@ -234,12 +261,15 @@ public class GameState implements Runnable { Double timeInterval = (System.currentTimeMillis() - previousUpdateTime) / 1000000.0; previousUpdateTime = System.currentTimeMillis(); + if (System.currentTimeMillis() > startTime) { + GameState.setCurrentStage(GameStages.RACING); + } for (ServerYacht yacht : yachts.values()) { updateVelocity(yacht); yacht.runAutoPilot(); yacht.updateLocation(timeInterval); if (yacht.getBoatStatus() != BoatStatus.FINISHED) { - checkForCollision(yacht); + checkCollision(yacht); checkForLegProgression(yacht); raceFinished = false; } @@ -250,9 +280,28 @@ public class GameState implements Runnable { } } + /** + * Check if the yacht has crossed the course limit + * + * @param yacht the yacht to be tested + * @return a boolean value of if there is a boundary collision + */ + private static Boolean checkBoundaryCollision(ServerYacht yacht) { + for (int i = 0; i < courseLimit.size() - 1; i++) { + if (GeoUtility.checkCrossedLine(courseLimit.get(i), courseLimit.get(i + 1), + yacht.getLastLocation(), yacht.getLocation()) != 0) { + return true; + } + } + if (GeoUtility.checkCrossedLine(courseLimit.get(courseLimit.size() - 1), courseLimit.get(0), + yacht.getLastLocation(), yacht.getLocation()) != 0) { + return true; + } + return false; + } - public static void checkForCollision(ServerYacht serverYacht) { - ServerYacht collidedYacht = checkCollision(serverYacht); + public static void checkCollision(ServerYacht serverYacht) { + ServerYacht collidedYacht = checkYachtCollision(serverYacht); if (collidedYacht != null) { GeoPoint originalLocation = serverYacht.getLocation(); serverYacht.setLocation( @@ -271,7 +320,7 @@ public class GameState implements Runnable { new YachtEventCodeMessage(serverYacht.getSourceId()) ); } else { - Mark collidedMark = markCollidedWith(serverYacht); + Mark collidedMark = checkMarkCollision(serverYacht); if (collidedMark != null) { serverYacht.setLocation( calculateBounceBack(serverYacht, collidedMark, BOUNCE_DISTANCE_MARK) @@ -282,6 +331,17 @@ public class GameState implements Runnable { notifyMessageListeners( new YachtEventCodeMessage(serverYacht.getSourceId()) ); + } else if (checkBoundaryCollision(serverYacht)) { + serverYacht.setLocation( + calculateBounceBack(serverYacht, serverYacht.getLocation(), + BOUNCE_DISTANCE_YACHT) + ); + serverYacht.setCurrentVelocity( + serverYacht.getCurrentVelocity() * COLLISION_VELOCITY_PENALTY + ); + notifyMessageListeners( + new YachtEventCodeMessage(serverYacht.getSourceId()) + ); } } } @@ -297,7 +357,7 @@ public class GameState implements Runnable { if (velocity < maxBoatSpeed - 500) { yacht.changeVelocity(maxBoatSpeed / 100); } else if (velocity > maxBoatSpeed + 500) { - yacht.changeVelocity(-maxBoatSpeed / 100); + yacht.changeVelocity(-velocity / 200); } else { yacht.setCurrentVelocity(maxBoatSpeed); } @@ -306,7 +366,7 @@ public class GameState implements Runnable { yacht.changeVelocity(-velocity / 200); } else if (velocity > 100) { yacht.changeVelocity(-velocity / 50); - } else if (velocity <= 100){ + } else if (velocity <= 100) { yacht.setCurrentVelocity(0d); } } @@ -348,6 +408,7 @@ public class GameState implements Runnable { /** * 4 Different cases of progression in the race 1 - Passing the start line 2 - Passing any * in-race Gate 3 - Passing any in-race Mark 4 - Passing the finish line + * * @param yacht the current yacht to check for progression */ private void checkForLegProgression(ServerYacht yacht) { @@ -523,7 +584,7 @@ public class GameState implements Runnable { } } - private static Mark markCollidedWith(ServerYacht yacht) { + private static Mark checkMarkCollision(ServerYacht yacht) { Set marksInRace = GameState.getMarks(); for (Mark mark : marksInRace) { if (GeoUtility.getDistance(yacht.getLocation(), mark) @@ -539,12 +600,14 @@ public class GameState implements Runnable { * * @return The boats new position */ - private static GeoPoint calculateBounceBack(ServerYacht yacht, GeoPoint collidedWith, Double bounceDistance) { - Double heading = GeoUtility.getBearing(yacht.getLocation(), collidedWith); + private static GeoPoint calculateBounceBack(ServerYacht yacht, GeoPoint collidedWith, + Double bounceDistance) { + Double heading = GeoUtility.getBearing(yacht.getLastLocation(), collidedWith); // Invert heading heading -= 180; Integer newHeading = Math.floorMod(heading.intValue(), 360); - return GeoUtility.getGeoCoordinate(yacht.getLocation(), newHeading.doubleValue(), bounceDistance); + return GeoUtility + .getGeoCoordinate(yacht.getLocation(), newHeading.doubleValue(), bounceDistance); } /** @@ -553,11 +616,12 @@ public class GameState implements Runnable { * * @return yacht to compare to all other yachts. */ - private static ServerYacht checkCollision(ServerYacht yacht) { + private static ServerYacht checkYachtCollision(ServerYacht yacht) { for (ServerYacht otherYacht : GameState.getYachts().values()) { if (otherYacht != yacht) { - Double distance = GeoUtility.getDistance(otherYacht.getLocation(), yacht.getLocation()); + Double distance = GeoUtility + .getDistance(otherYacht.getLocation(), yacht.getLocation()); if (distance < YACHT_COLLISION_DISTANCE) { return otherYacht; } diff --git a/src/main/java/seng302/gameServer/MainServerThread.java b/src/main/java/seng302/gameServer/MainServerThread.java index bd22063c..fe0d9017 100644 --- a/src/main/java/seng302/gameServer/MainServerThread.java +++ b/src/main/java/seng302/gameServer/MainServerThread.java @@ -8,11 +8,8 @@ import java.util.ArrayList; import java.util.List; import java.util.Timer; import java.util.TimerTask; -import seng302.gameServer.server.messages.BoatSubMessage; -import seng302.gameServer.server.messages.Message; -import seng302.gameServer.server.messages.RaceStatus; -import seng302.gameServer.server.messages.RaceStatusMessage; -import seng302.gameServer.server.messages.RaceType; + +import seng302.gameServer.server.messages.*; import seng302.model.GeoPoint; import seng302.model.Player; import seng302.model.PolarTable; @@ -30,6 +27,10 @@ public class MainServerThread implements Runnable, ClientConnectionDelegate { private static final int PORT = 4942; private static final Integer CLIENT_UPDATES_PER_SECOND = 10; 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 boolean terminated; private Thread thread; @@ -166,10 +167,21 @@ public class MainServerThread implements Runnable, ClientConnectionDelegate { @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 @@ -184,10 +196,22 @@ public class MainServerThread implements Runnable, ClientConnectionDelegate { boatSubMessages.add(m); } - if (GameState.getCurrentStage() == GameStages.RACING) { - raceStatus = RaceStatus.STARTED; + 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.WARNING; + raceStatus = RaceStatus.STARTED; } return new RaceStatusMessage(1, raceStatus, GameState.getStartTime(), diff --git a/src/main/java/seng302/gameServer/ServerToClientThread.java b/src/main/java/seng302/gameServer/ServerToClientThread.java index 2f26b67e..c9ece2b4 100644 --- a/src/main/java/seng302/gameServer/ServerToClientThread.java +++ b/src/main/java/seng302/gameServer/ServerToClientThread.java @@ -218,8 +218,6 @@ public class ServerToClientThread implements Runnable, Observer { } } } catch (Exception e) { - // TODO: 24/07/17 zyt10 - fix a logic here when a client disconnected -// serverLog("ERROR OCCURRED, CLOSING SERVER CONNECTION: " + socket.getRemoteSocketAddress().toString(), 1); closeSocket(); return; } @@ -235,7 +233,7 @@ public class ServerToClientThread implements Runnable, Observer { } //@TODO calculate lat/lng values - xml.setRegatta(new Regatta("RaceVision Test Game", 57.6679590, 11.8503233)); + xml.setRegatta(new Regatta("Party Parrot Test Server", "Bermuda Test Course", 57.6679590, 11.8503233)); xml.setRace(race); XMLMessage xmlMessage; diff --git a/src/main/java/seng302/model/RaceState.java b/src/main/java/seng302/model/RaceState.java index e426dc09..d4e71971 100644 --- a/src/main/java/seng302/model/RaceState.java +++ b/src/main/java/seng302/model/RaceState.java @@ -17,10 +17,10 @@ public class RaceState { private double windSpeed; private double windDirection; - private long raceTime; + private long serverSystemTime; private long expectedStartTime; private boolean isRaceStarted = false; -// long timeTillStart; + long timeTillStart; public RaceState() { } @@ -28,7 +28,7 @@ public class RaceState { public void updateState (RaceStatusData data) { this.windSpeed = data.getWindSpeed(); this.windDirection = data.getWindDirection(); - this.raceTime = data.getCurrentTime(); + this.serverSystemTime = data.getCurrentTime(); this.expectedStartTime = data.getExpectedStartTime(); this.isRaceStarted = data.isRaceStarted(); } @@ -38,16 +38,20 @@ public class RaceState { } public void updateState (RaceStartData data) { -// this.timeTillStart = data.getRaceStartTime(); - System.out.println(data.getRaceStartTime()); + this.timeTillStart = data.getRaceStartTime(); } public String getRaceTimeStr () { - return DATE_TIME_FORMAT.format(raceTime); + long raceTime = serverSystemTime - expectedStartTime; + if (raceTime < 0) { + return "-" + DATE_TIME_FORMAT.format(-1 * (raceTime - 1000)); + } else { + return DATE_TIME_FORMAT.format(serverSystemTime - expectedStartTime); + } } public long getTimeTillStart () { - return (expectedStartTime - raceTime) / 1000; + return (expectedStartTime - serverSystemTime); } public double getWindSpeed() { @@ -59,11 +63,7 @@ public class RaceState { } public long getRaceTime() { - return raceTime; - } - - public long getExpectedStartTime() { - return expectedStartTime; + return serverSystemTime; } public boolean isRaceStarted () { diff --git a/src/main/java/seng302/model/ServerYacht.java b/src/main/java/seng302/model/ServerYacht.java index 9abbd520..28397a64 100644 --- a/src/main/java/seng302/model/ServerYacht.java +++ b/src/main/java/seng302/model/ServerYacht.java @@ -60,7 +60,7 @@ public class ServerYacht extends Observable { this.country = country; this.sailIn = false; this.isAuto = false; - this.location = new GeoPoint(57.670341, 11.826856); + this.location = new GeoPoint(57.67046, 11.83751); this.lastLocation = location; this.heading = 120.0; //In degrees this.currentVelocity = 0d; //in mms-1 diff --git a/src/main/java/seng302/model/stream/xml/generator/Regatta.java b/src/main/java/seng302/model/stream/xml/generator/Regatta.java index 4a90368a..fa802e01 100644 --- a/src/main/java/seng302/model/stream/xml/generator/Regatta.java +++ b/src/main/java/seng302/model/stream/xml/generator/Regatta.java @@ -18,10 +18,10 @@ public class Regatta { private Integer utcOffset; private Double magneticVariation; - public Regatta(String name, Double latitude, Double longitude) { + public Regatta(String name, String courseName, Double latitude, Double longitude) { this.name = name; this.id = DEFAULT_REGATTA_ID; - this.courseName = name; + this.courseName = courseName; this.latitude = latitude; this.longitude = longitude; diff --git a/src/main/java/seng302/visualiser/GameClient.java b/src/main/java/seng302/visualiser/GameClient.java index 1d4522b8..4222653d 100644 --- a/src/main/java/seng302/visualiser/GameClient.java +++ b/src/main/java/seng302/visualiser/GameClient.java @@ -12,6 +12,7 @@ import javafx.fxml.FXMLLoader; import javafx.scene.Node; import javafx.scene.input.KeyEvent; import javafx.scene.layout.Pane; +import seng302.gameServer.GameState; import seng302.gameServer.MainServerThread; import seng302.gameServer.server.messages.BoatAction; import seng302.model.ClientYacht; @@ -46,6 +47,7 @@ public class GameClient { private RegattaXMLData regattaData; private RaceXMLData courseData; private RaceState raceState = new RaceState(); + private LobbyController lobbyController; private ObservableList clientLobbyList = FXCollections.observableArrayList(); @@ -74,8 +76,18 @@ public class GameClient { socketThread.addStreamObserver(this::parsePackets); LobbyController lobbyController = loadLobby(); lobbyController.disableReadyButton(); - lobbyController.setTitle("Connected to host - IP : " + ipAddress + " Port : " + portNumber); + + if (regattaData != null){ + lobbyController.setTitle(regattaData.getRegattaName()); + lobbyController.setCourseName(regattaData.getCourseName()); + } + else{ + lobbyController.setTitle(ipAddress); + lobbyController.setCourseName(""); + } + lobbyController.addCloseListener((exitCause) -> this.loadStartScreen()); + this.lobbyController = lobbyController; } /** @@ -93,15 +105,28 @@ public class GameClient { } socketThread.addStreamObserver(this::parsePackets); LobbyController lobbyController = loadLobby(); - lobbyController.setTitle("Hosting Lobby - IP : " + ipAddress + " Port : " + portNumber); + lobbyController.setPlayerListSource(clientLobbyList); + + if (regattaData != null){ + lobbyController.setTitle("Hosting: " + regattaData.getRegattaName()); + lobbyController.setCourseName(regattaData.getCourseName()); + } + else{ + lobbyController.setTitle("Hosting: " + ipAddress); + lobbyController.setCourseName(""); + } + lobbyController.addCloseListener(exitCause -> { if (exitCause == CloseStatus.READY) { + GameState.resetStartTime(); + lobbyController.disableReadyButton(); server.startGame(); } else if (exitCause == CloseStatus.LEAVE) { loadStartScreen(); } }); + this.lobbyController = lobbyController; server.setGameClient(this); } @@ -182,13 +207,18 @@ public class GameClient { switch (packet.getType()) { case RACE_STATUS: processRaceStatusUpdate(StreamParser.extractRaceStatus(packet)); - startRaceIfAllDataReceived(); + + if (raceState.getTimeTillStart() <= 5000) { + startRaceIfAllDataReceived(); + } + break; case REGATTA_XML: regattaData = XMLParser.parseRegatta( StreamParser.extractXmlMessage(packet) ); + raceState.setTimeZone( TimeZone.getTimeZone( ZoneId.ofOffset("UTC", ZoneOffset.ofHours(regattaData.getUtcOffset())) @@ -217,6 +247,7 @@ public class GameClient { case RACE_START_STATUS: raceState.updateState(StreamParser.extractRaceStartStatus(packet)); + if (lobbyController != null) lobbyController.updateRaceState(raceState); break; case BOAT_LOCATION: diff --git a/src/main/java/seng302/visualiser/controllers/LobbyController.java b/src/main/java/seng302/visualiser/controllers/LobbyController.java index 4af72b25..86df9287 100644 --- a/src/main/java/seng302/visualiser/controllers/LobbyController.java +++ b/src/main/java/seng302/visualiser/controllers/LobbyController.java @@ -1,11 +1,7 @@ package seng302.visualiser.controllers; -import com.sun.deploy.util.SessionState.Client; -import com.sun.media.jfxmedia.logging.Logger; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; +import java.util.*; + import javafx.application.Platform; import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; @@ -21,6 +17,8 @@ import javafx.scene.text.Text; import javafx.stage.Stage; import seng302.gameServer.GameStages; import seng302.gameServer.GameState; +import seng302.model.RaceState; +import seng302.visualiser.GameClient; import seng302.visualiser.ClientToServerThread; import seng302.visualiser.GameView; @@ -76,9 +74,14 @@ public class LobbyController { private ImageView seventhImageView; @FXML private ImageView eighthImageView; + @FXML + private Text timeUntilStart; + @FXML + private Text courseNameText; private List imageViews = new ArrayList<>(); private List