diff --git a/src/main/java/seng302/gameServer/GameState.java b/src/main/java/seng302/gameServer/GameState.java index 43b3101e..9a3aa737 100644 --- a/src/main/java/seng302/gameServer/GameState.java +++ b/src/main/java/seng302/gameServer/GameState.java @@ -6,16 +6,19 @@ 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 seng302.gameServer.messages.BoatAction; -import seng302.gameServer.messages.BoatStatus; -import seng302.gameServer.messages.MarkRoundingMessage; -import seng302.gameServer.messages.MarkType; -import seng302.gameServer.messages.Message; -import seng302.gameServer.messages.RoundingBoatStatus; -import seng302.gameServer.messages.YachtEventCodeMessage; +import seng302.gameServer.server.messages.BoatAction; +import seng302.gameServer.server.messages.BoatStatus; +import seng302.gameServer.server.messages.MarkRoundingMessage; +import seng302.gameServer.server.messages.MarkType; +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; @@ -23,6 +26,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) @@ -33,6 +37,7 @@ public class GameState implements Runnable { @FunctionalInterface interface NewMessageListener { + void notify(Message message); } @@ -59,6 +64,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; @@ -81,7 +87,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 @@ -89,16 +95,34 @@ public class GameState implements Runnable { markOrder = new MarkOrder(); //This could be instantiated at some point with a select map? markListeners = new ArrayList<>(); - new Thread(this).start(); //Run the auto updates on the game state + resetStartTime(); + + 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); } @@ -135,10 +159,6 @@ public class GameState implements Runnable { } public static void setCurrentStage(GameStages currentStage) { - if (currentStage == GameStages.RACING) { - startTime = System.currentTimeMillis(); - } - GameState.currentStage = currentStage; } @@ -146,10 +166,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; } @@ -184,13 +208,13 @@ public class GameState implements Runnable { @Override public void run() { - while (true) { + while (currentStage != GameStages.FINISHED) { try { Thread.sleep(1000 / STATE_UPDATES_PER_SECOND); } catch (InterruptedException e) { System.out.println("[GameState] interrupted exception"); } - if (currentStage == GameStages.PRE_RACE) { + if (currentStage == GameStages.PRE_RACE || currentStage == GameStages.RACING) { update(); } @@ -229,22 +253,51 @@ public class GameState implements Runnable { * Called periodically in this GameState thread to update the GameState values */ public void update() { + Boolean raceFinished = true; + 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.getFinishedRace()) { - checkForCollision(yacht); + if (yacht.getBoatStatus() != BoatStatus.FINISHED) { + checkCollision(yacht); checkForLegProgression(yacht); + raceFinished = false; } } + + if (raceFinished) { + currentStage = GameStages.FINISHED; + } } + /** + * 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( @@ -263,7 +316,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) @@ -274,6 +327,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()) + ); } } } @@ -289,7 +353,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); } @@ -298,7 +362,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); } } @@ -340,11 +404,13 @@ 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) { Integer currentMarkSeqID = yacht.getCurrentMarkSeqID(); CompoundMark currentMark = markOrder.getCurrentMark(currentMarkSeqID); +// System.out.println(yacht.getCurrentMarkSeqID()); Boolean hasProgressed; if (currentMarkSeqID == 0) { @@ -493,7 +559,6 @@ public class GameState implements Runnable { Boolean isClockwiseCross = GeoUtility.isClockwise(mark1, mark2, prevMark.getMidPoint()); if (crossedLine == 1 && isClockwiseCross || crossedLine == 2 && !isClockwiseCross) { yacht.setClosestCurrentMark(mark1); - yacht.setIsFinished(true); yacht.setBoatStatus(BoatStatus.FINISHED); return true; } @@ -503,7 +568,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) @@ -519,12 +584,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); } /** @@ -533,11 +600,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/HeartbeatThread.java b/src/main/java/seng302/gameServer/HeartbeatThread.java index 8b9cd288..b9367134 100644 --- a/src/main/java/seng302/gameServer/HeartbeatThread.java +++ b/src/main/java/seng302/gameServer/HeartbeatThread.java @@ -13,7 +13,7 @@ import seng302.gameServer.messages.Message; * Will call .clientDisconnected on the delegate when a heartbeat message * cannot be sent to a player */ -public class HeartbeatThread extends Thread{ +public class HeartbeatThread implements Runnable { private final int HEARTBEAT_PERIOD = 200; private ClientConnectionDelegate delegate; private Integer seqNum; @@ -23,6 +23,9 @@ public class HeartbeatThread extends Thread{ this.delegate = delegate; seqNum = 0; disconnectedPlayers = new Stack<>(); + + Thread thread = new Thread(this, "HeartBeat"); + thread.start(); } /** diff --git a/src/main/java/seng302/gameServer/MainServerThread.java b/src/main/java/seng302/gameServer/MainServerThread.java index f6c64c46..51c58b01 100644 --- a/src/main/java/seng302/gameServer/MainServerThread.java +++ b/src/main/java/seng302/gameServer/MainServerThread.java @@ -1,12 +1,14 @@ package seng302.gameServer; +import gherkin.lexer.Fi; import java.io.IOException; import java.net.ServerSocket; import java.time.LocalDateTime; import java.util.ArrayList; +import java.util.List; import java.util.Timer; import java.util.TimerTask; -import seng302.gameServer.messages.Message; +import seng302.gameServer.server.messages.*; import seng302.model.GeoPoint; import seng302.model.Player; import seng302.model.PolarTable; @@ -24,6 +26,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; @@ -43,20 +49,15 @@ public class MainServerThread implements Runnable, ClientConnectionDelegate { PolarTable.parsePolarFile(getClass().getResourceAsStream("/config/acc_polars.csv")); GameState.addMarkPassListener(this::broadcastMessage); terminated = false; - thread = new Thread(this); + thread = new Thread(this, "MainServer"); thread.start(); } public void run() { - ServerListenThread serverListenThread; - HeartbeatThread heartbeatThread; - serverListenThread = new ServerListenThread(serverSocket, this); - heartbeatThread = new HeartbeatThread(this); - - heartbeatThread.start(); - serverListenThread.start(); + 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) { @@ -77,7 +78,7 @@ public class MainServerThread implements Runnable, ClientConnectionDelegate { //FINISHED else if (GameState.getCurrentStage() == GameStages.FINISHED) { - + terminate(); } } @@ -159,14 +160,60 @@ public class MainServerThread implements Runnable, ClientConnectionDelegate { t.schedule(new TimerTask() { @Override public void run() { - - for (ServerToClientThread serverToClientThread : serverToClientThreads) { - serverToClientThread.sendRaceStatusMessage(); + 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 boatSubMessages = new ArrayList<>(); + RaceStatus raceStatus; + + for (Player player : GameState.getPlayers()) { + ServerYacht y = player.getYacht(); + BoatSubMessage m = new BoatSubMessage(y.getSourceId(), y.getBoatStatus(), 0, + 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; } diff --git a/src/main/java/seng302/gameServer/ServerListenThread.java b/src/main/java/seng302/gameServer/ServerListenThread.java index 60469bb7..e05b76a9 100644 --- a/src/main/java/seng302/gameServer/ServerListenThread.java +++ b/src/main/java/seng302/gameServer/ServerListenThread.java @@ -8,13 +8,16 @@ import java.net.Socket; * A class for a thread to listen to connections * Created by wmu16 on 11/07/17. */ -public class ServerListenThread extends Thread{ +public class ServerListenThread implements Runnable { private ServerSocket serverSocket; private ClientConnectionDelegate delegate; public ServerListenThread(ServerSocket serverSocket, ClientConnectionDelegate delegate){ this.serverSocket = serverSocket; this.delegate = delegate; + + Thread thread = new Thread(this, "ServerListen"); + thread.start(); } /** diff --git a/src/main/java/seng302/gameServer/ServerToClientThread.java b/src/main/java/seng302/gameServer/ServerToClientThread.java index 7c9c2adb..479333bd 100644 --- a/src/main/java/seng302/gameServer/ServerToClientThread.java +++ b/src/main/java/seng302/gameServer/ServerToClientThread.java @@ -88,7 +88,7 @@ public class ServerToClientThread implements Runnable, Observer { return; } - thread = new Thread(this); + thread = new Thread(this, "ServerToClient"); thread.start(); } @@ -209,8 +209,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; } @@ -226,7 +224,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; @@ -318,31 +316,6 @@ public class ServerToClientThread implements Runnable, Observer { return thread; } - public void sendRaceStatusMessage() { - // variables taken from GameServerThread - - List boatSubMessages = new ArrayList<>(); - RaceStatus raceStatus; - - for (Player player : GameState.getPlayers()) { - ServerYacht y = player.getYacht(); - BoatSubMessage m = new BoatSubMessage(y.getSourceId(), y.getBoatStatus(), 0, - 0, 0, 1234L, - 1234L); - boatSubMessages.add(m); - } - - if (GameState.getCurrentStage() == GameStages.RACING) { - raceStatus = RaceStatus.STARTED; - } else { - raceStatus = RaceStatus.WARNING; - } - - sendMessage(new RaceStatusMessage(1, raceStatus, GameState.getStartTime(), GameState.getWindDirection(), - GameState.getWindSpeedMMS().longValue(), GameState.getPlayers().size(), - RaceType.MATCH_RACE, 1, boatSubMessages)); - } - public Socket getSocket() { return socket; } diff --git a/src/main/java/seng302/model/ClientYacht.java b/src/main/java/seng302/model/ClientYacht.java index a2b62bde..0ca2298f 100644 --- a/src/main/java/seng302/model/ClientYacht.java +++ b/src/main/java/seng302/model/ClientYacht.java @@ -47,7 +47,7 @@ public class ClientYacht extends Observable { private String country; private Long estimateTimeAtFinish; - private Boolean sailIn = false; + private Boolean sailIn = true; private Integer currentMarkSeqID = 0; private Long markRoundTime; private Long timeTillNext; diff --git a/src/main/java/seng302/model/RaceState.java b/src/main/java/seng302/model/RaceState.java index b495b345..af7b8d9b 100644 --- a/src/main/java/seng302/model/RaceState.java +++ b/src/main/java/seng302/model/RaceState.java @@ -24,10 +24,11 @@ public class RaceState { // private final DateFormat DATE_TIME_FORMAT = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss"); private final DateFormat DATE_TIME_FORMAT = new SimpleDateFormat("HH:mm:ss"); private double windSpeed; - private ReadOnlyDoubleWrapper windDirection = new ReadOnlyDoubleWrapper(); - private long raceTime; + private double windDirection; + private long serverSystemTime; private long expectedStartTime; private boolean isRaceStarted = false; + long timeTillStart; private List collisions = new ArrayList<>(); private List collisionListeners = new ArrayList<>(); @@ -36,8 +37,8 @@ public class RaceState { public void updateState (RaceStatusData data) { this.windSpeed = data.getWindSpeed(); - this.windDirection.set(data.getWindDirection()); - this.raceTime = data.getCurrentTime(); + this.windDirection = data.getWindDirection(); + this.serverSystemTime = data.getCurrentTime(); this.expectedStartTime = data.getExpectedStartTime(); this.isRaceStarted = data.isRaceStarted(); } @@ -47,16 +48,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() { @@ -68,11 +73,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 f46e8753..60f646a4 100644 --- a/src/main/java/seng302/model/ServerYacht.java +++ b/src/main/java/seng302/model/ServerYacht.java @@ -42,12 +42,11 @@ public class ServerYacht extends Observable { private Double autoHeading; //Mark Rounding - private Integer currentMarkSeqID = 0; + private Integer currentMarkSeqID; private Boolean hasEnteredRoundingZone; private Mark closestCurrentMark; private Boolean hasPassedLine; private Boolean hasPassedThroughGate; - private Boolean finishedRace; public ServerYacht(String boatType, Integer sourceId, String hullID, String shortName, @@ -61,15 +60,15 @@ 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 + this.currentMarkSeqID = 0; this.hasEnteredRoundingZone = false; this.hasPassedLine = false; this.hasPassedThroughGate = false; - this.finishedRace = false; } @@ -322,15 +321,6 @@ public class ServerYacht extends Observable { return boatName; } - - public void setIsFinished(Boolean isFinished) { - finishedRace = isFinished; - } - - public Boolean getFinishedRace() { - return finishedRace; - } - public Double getCurrentVelocity() { return currentVelocity; } 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 837e4145..634eee26 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.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(); @@ -75,8 +77,18 @@ public class GameClient { LobbyController lobbyController = loadLobby(); lobbyController.setPlayerListSource(clientLobbyList); 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; } /** @@ -95,15 +107,27 @@ public class GameClient { socketThread.addStreamObserver(this::parsePackets); LobbyController lobbyController = loadLobby(); lobbyController.setPlayerListSource(clientLobbyList); - lobbyController.setTitle("Hosting Lobby - IP : " + ipAddress + " Port : " + portNumber); + + 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); } @@ -175,13 +199,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())) @@ -210,6 +239,7 @@ public class GameClient { case RACE_START_STATUS: raceState.updateState(StreamParser.extractRaceStartStatus(packet)); + if (lobbyController != null) lobbyController.updateRaceState(raceState); break; case BOAT_LOCATION: @@ -294,7 +324,8 @@ public class GameClient { raceFinished = false; } } - if (raceFinished) { + if (raceFinished == true) { + close(); loadFinishScreenView(); } @@ -340,14 +371,15 @@ public class GameClient { socketThread.sendBoatAction(BoatAction.TACK_GYBE); break; //TODO Allow a zoom in and zoom out methods case Z: // zoom in - System.out.println("Zoom in"); + raceView.getGameView().zoomIn(); break; case X: // zoom out - System.out.println("Zoom out"); + raceView.getGameView().zoomOut(); break; } } + private void keyReleased(KeyEvent e) { switch (e.getCode()) { //TODO 12/07/17 Determine the sail state and send the appropriate packet (eg. if sails are in, send a sail out packet) diff --git a/src/main/java/seng302/visualiser/GameView.java b/src/main/java/seng302/visualiser/GameView.java index 527e8602..5927349c 100644 --- a/src/main/java/seng302/visualiser/GameView.java +++ b/src/main/java/seng302/visualiser/GameView.java @@ -73,6 +73,7 @@ public class GameView extends Pane { private Map boatObjects = new HashMap<>(); private Map annotations = new HashMap<>(); private ObservableList gameObjects; + private BoatObject selectedBoat = null; private Group annotationsGroup = new Group(); private Group wakesGroup = new Group(); private Group boatObjectGroup = new Group(); @@ -96,18 +97,18 @@ public class GameView extends Pane { double scaleFactor = 1; public void zoomOut() { - scaleFactor = 0.95; - for (Node child : getChildren()) { - child.setScaleX(child.getScaleX() * scaleFactor); - child.setScaleY(child.getScaleY() * scaleFactor); + scaleFactor = 0.1; + if (this.getScaleX() > 0.5) { + this.setScaleX(this.getScaleX() - scaleFactor); + this.setScaleY(this.getScaleY() - scaleFactor); } } public void zoomIn() { - scaleFactor = 1.05; - for (Node child : getChildren()) { - child.setScaleX(child.getScaleX() * scaleFactor); - child.setScaleY(child.getScaleY() * scaleFactor); + scaleFactor = 0.10; + if (this.getScaleX() < 2.5) { + this.setScaleX(this.getScaleX() + scaleFactor); + this.setScaleY(this.getScaleY() + scaleFactor); } } @@ -116,14 +117,25 @@ public class GameView extends Pane { VERTICAL } - public GameView() { + + private void trackBoat() { + if (selectedBoat != null) { + double x = selectedBoat.getBoatLayoutX(); + double y = selectedBoat.getBoatLayoutY(); + double displacementX = this.getWidth(); + double displacementY = this.getHeight(); + this.setLayoutX((-x + (displacementX / 2.0)) * this.getScaleX()); + this.setLayoutY((-y + (displacementY / 2.0)) * this.getScaleY()); + } else { + this.setLayoutX(0); + this.setLayoutY(0); + } + } + + public GameView () { gameObjects = this.getChildren(); // create image view for map, bind panel size to image gameObjects.add(mapImage); - fpsDisplay.setLayoutX(5); - fpsDisplay.setLayoutY(20); - fpsDisplay.setStrokeWidth(2); - gameObjects.add(fpsDisplay); gameObjects.add(raceBorder); gameObjects.add(markers); initializeTimer(); @@ -141,6 +153,7 @@ public class GameView extends Pane { @Override public void handle(long now) { + trackBoat(); if (lastTime == 0) { lastTime = now; } else { @@ -164,9 +177,7 @@ public class GameView extends Pane { lastTime = now; } } -// Platform.runLater(() -> - boatObjects.forEach((boat, boatObject) -> boatObject.updateLocation()); -// ); + boatObjects.forEach((boat, boatObject) -> boatObject.updateLocation()); } }; } @@ -449,6 +460,22 @@ public class GameView extends Pane { // drawGoogleMap(); } + private void setSelectedBoat(BoatObject bo, Boolean isSelected) { + if (this.selectedBoat == bo && !isSelected) { + this.selectedBoat = null; + boatObjects.forEach((boat, group) -> + group.setIsSelected(false) + ); + } else if (isSelected) { + this.selectedBoat = bo; + for (BoatObject group : boatObjects.values()) { + if (group != bo) { + group.setIsSelected(false); + } + } + } + } + /** * Draws all the boats. * @param yachts The yachts to set in the race @@ -459,6 +486,7 @@ public class GameView extends Pane { for (ClientYacht yacht : yachts) { Paint colour = Colors.getColor(); newBoat = new BoatObject(); + newBoat.addSelectedBoatListener(this::setSelectedBoat); newBoat.setFill(colour); boatObjects.put(yacht, newBoat); createAndBindAnnotationBox(yacht, colour); @@ -471,9 +499,6 @@ public class GameView extends Pane { BoatObject bo = boatObjects.get(boat); Point2D p2d = findScaledXY(lat, lon); bo.moveTo(p2d.getX(), p2d.getY(), heading, velocity, sailIn, windDir); -// annotations.get(boat).setLayoutX(p2d.getX()); -// annotations.get(boat).setLayoutY(p2d.getY()); -// annotations.get(boat).setLocation(100d, 100d); annotations.get(boat).setLocation(p2d.getX(), p2d.getY()); bo.setTrajectory( heading, @@ -734,7 +759,7 @@ public class GameView extends Pane { public void setBoatAsPlayer (ClientYacht playerYacht) { this.playerYacht = playerYacht; - this.playerYacht.toggleSail(); + playerYacht.toggleSail(); boatObjects.get(playerYacht).setAsPlayer(); CompoundMark currentMark = course.get(playerYacht.getLegNumber()); System.out.println("currentMark = " + currentMark); @@ -812,4 +837,9 @@ public class GameView extends Pane { timeline.setOnFinished(event -> Platform.runLater(() -> gameObjects.remove(circle))); timeline.play(); } + + public void setFrameRateFXText(Text fpsDisplay) { + this.fpsDisplay = null; + this.fpsDisplay = fpsDisplay; + } } diff --git a/src/main/java/seng302/visualiser/controllers/LobbyController.java b/src/main/java/seng302/visualiser/controllers/LobbyController.java index 809bd3b5..fa160e53 100644 --- a/src/main/java/seng302/visualiser/controllers/LobbyController.java +++ b/src/main/java/seng302/visualiser/controllers/LobbyController.java @@ -1,8 +1,7 @@ package seng302.visualiser.controllers; -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; @@ -14,6 +13,8 @@ import javafx.scene.image.ImageView; import javafx.scene.text.Text; import seng302.gameServer.GameStages; import seng302.gameServer.GameState; +import seng302.model.RaceState; +import seng302.visualiser.GameClient; /** * A class describing the actions of the lobby screen @@ -67,9 +68,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