diff --git a/src/main/java/seng302/App.java b/src/main/java/seng302/App.java index 8c6f85ac..16470fb1 100644 --- a/src/main/java/seng302/App.java +++ b/src/main/java/seng302/App.java @@ -68,7 +68,7 @@ public class App extends Application { @Override public void start(Stage primaryStage) throws Exception { Parent root = FXMLLoader.load(getClass().getResource("/views/StartScreenView.fxml")); - primaryStage.setTitle("RaceVision"); + primaryStage.setTitle("Party Parrots at Sea"); Scene scene = new Scene(root, 1530, 960); scene.getStylesheets().add(getClass().getResource("/css/master.css").toString()); primaryStage.setScene(scene); diff --git a/src/main/java/seng302/gameServer/GameState.java b/src/main/java/seng302/gameServer/GameState.java index 0b36ab16..0bfab598 100644 --- a/src/main/java/seng302/gameServer/GameState.java +++ b/src/main/java/seng302/gameServer/GameState.java @@ -1,10 +1,12 @@ package seng302.gameServer; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Random; import java.util.Set; import javafx.scene.paint.Color; import javax.xml.parsers.DocumentBuilder; @@ -15,12 +17,14 @@ import org.w3c.dom.Document; import org.xml.sax.InputSource; import seng302.gameServer.messages.BoatAction; import seng302.gameServer.messages.BoatStatus; +import seng302.gameServer.messages.ChatterMessage; import seng302.gameServer.messages.CustomizeRequestType; 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.messages.YachtEventType; import seng302.model.GeoPoint; import seng302.model.Limit; import seng302.model.Player; @@ -29,6 +33,8 @@ import seng302.model.ServerYacht; import seng302.model.mark.CompoundMark; import seng302.model.mark.Mark; import seng302.model.mark.MarkOrder; +import seng302.model.token.Token; +import seng302.model.token.TokenType; import seng302.utilities.GeoUtility; import seng302.utilities.XMLParser; @@ -41,24 +47,31 @@ public class GameState implements Runnable { @FunctionalInterface interface NewMessageListener { - void notify(Message message); } - private Logger logger = LoggerFactory.getLogger(GameState.class); + private static Logger logger = LoggerFactory.getLogger(GameState.class); + + + static final int WARNING_TIME = 10 * -1000; + static final int PREPATORY_TIME = 5 * -1000; + private static final int TIME_TILL_START = 10 * 1000; + static Integer MAX_PLAYERS = 8; + + private static final Long POWERUP_TIMEOUT_MS = 10_000L; private static final Integer STATE_UPDATES_PER_SECOND = 60; - public static Integer MAX_PLAYERS = 8; - public static Double ROUNDING_DISTANCE = 50d; // TODO: 14/08/17 wmu16 - Look into this value further - public static final Double MARK_COLLISION_DISTANCE = 15d; + private static Double ROUNDING_DISTANCE = 50d; // TODO: 14/08/17 wmu16 - Look into this value further + private static final Double MARK_COLLISION_DISTANCE = 15d; public static final Double YACHT_COLLISION_DISTANCE = 25.0; - public static final Double BOUNCE_DISTANCE_MARK = 20.0; + private static final Double BOUNCE_DISTANCE_MARK = 20.0; public static final Double BOUNCE_DISTANCE_YACHT = 30.0; - public static final Double COLLISION_VELOCITY_PENALTY = 0.3; + private static final Double COLLISION_VELOCITY_PENALTY = 0.3; private static Long previousUpdateTime; public static Double windDirection; private static Double windSpeed; + private static Double speedMultiplier = 1d; private static Boolean customizationFlag; // dirty flag to tell if a player has customized their boat. @@ -72,35 +85,30 @@ public class GameState implements Runnable { private static Set marks; private static List courseLimit; - private static List markListeners; + private static List allTokens; + private static List tokensInPlay; + + private static List newMessageListeners; private static Map playerStringMap = new HashMap<>(); - /* - Ideally I would like to make this class an object instantiated by the server and given to - it's created threads if necessary. Outside of that I think the dependencies on it - (atm only Yacht & GameClient) can be removed from most other classes. The observable list of - players could be pulled directly from the server by the GameClient since it instantiates it - and it is reasonable for it to pull data. The current setup of publicly available statics is - pretty meh IMO because anything can change it making it unreliable and like people did with - the old ServerParser class everything that needs shared just gets thrown in the static - collections and things become a real mess. - */ public GameState(String hostIpAddress) { windDirection = 180d; windSpeed = 10000d; - this.hostIpAddress = hostIpAddress; yachts = new HashMap<>(); + tokensInPlay = new ArrayList<>(); + players = new ArrayList<>(); GameState.hostIpAddress = hostIpAddress; customizationFlag = false; - + speedMultiplier = 1.0; currentStage = GameStages.LOBBYING; isRaceStarted = false; //set this when game stage changes to prerace previousUpdateTime = System.currentTimeMillis(); markOrder = new MarkOrder(); //This could be instantiated at some point with a select map? - markListeners = new ArrayList<>(); + newMessageListeners = new ArrayList<>(); + allTokens = makeTokens(); resetStartTime(); @@ -125,6 +133,21 @@ public class GameState implements Runnable { courseLimit = XMLParser.parseRace(document).getCourseLimit(); } + + /** + * Make a pre defined set of tokensInPlay. //TODO wmu16 - Should read from some file for each + * race ideally + * + * @return A list of possible tokensInPlay for this race + */ + private ArrayList makeTokens() { + Token token1 = new Token(TokenType.BOOST, 57.66946, 11.83154); + Token token2 = new Token(TokenType.BOOST, 57.66877, 11.83382); + Token token3 = new Token(TokenType.BOOST, 57.66914, 11.83965); + Token token4 = new Token(TokenType.BOOST, 57.66684, 11.83214); + return new ArrayList<>(Arrays.asList(token1, token2, token3, token4)); + } + public static String getHostIpAddress() { return hostIpAddress; } @@ -137,6 +160,10 @@ public class GameState implements Runnable { return players; } + public static List getTokensInPlay() { + return tokensInPlay; + } + public static void addPlayer(Player player) { players.add(player); String playerText = player.getYacht().getSourceId() + " " + player.getYacht().getBoatName() @@ -178,7 +205,7 @@ public class GameState implements Runnable { } public static void resetStartTime(){ - startTime = System.currentTimeMillis() + MainServerThread.TIME_TILL_START; + startTime = System.currentTimeMillis() + TIME_TILL_START; } public static Double getWindDirection() { @@ -264,7 +291,23 @@ public class GameState implements Runnable { } /** - * Called periodically in this GameState thread to update the GameState values + * Randomly select a subset of tokensInPlay from a pre defined superset + * Broadasts a new race status message to show this update + */ + public static void spawnNewToken() { + Random random = new Random(); + tokensInPlay.clear(); + tokensInPlay.add(allTokens.get(random.nextInt(allTokens.size()))); + } + + /** + * Called periodically in this GameState thread to update the GameState values. + * -Updates yachts velocity + * -Updates locations + * -Checks for collisions + * -Checks for progression + * + * -Also checks things like the end of the race and race start time etc */ public void update() { Boolean raceFinished = true; @@ -276,6 +319,7 @@ public class GameState implements Runnable { } for (ServerYacht yacht : yachts.values()) { updateVelocity(yacht); + checkPowerUpTimeout(yacht); yacht.runAutoPilot(); yacht.updateLocation(timeInterval); if (yacht.getBoatStatus() != BoatStatus.FINISHED) { @@ -283,8 +327,6 @@ public class GameState implements Runnable { checkForLegProgression(yacht); raceFinished = false; } - - } if (raceFinished) { @@ -292,6 +334,17 @@ public class GameState implements Runnable { } } + + private void checkPowerUpTimeout(ServerYacht yacht) { + if (yacht.getPowerUp() != null) { + if (System.currentTimeMillis() - yacht.getPowerUpStartTime() > POWERUP_TIMEOUT_MS) { + yacht.powerDown(); + logger.debug("Yacht: " + yacht.getShortName() + " powered down!"); + } + } + } + + /** * Check if the yacht has crossed the course limit * @@ -312,13 +365,45 @@ public class GameState implements Runnable { return false; } + /** + * Checks all tokensInPlay to see if a yacht has picked one up + * @return Token which was collided with + * @param serverYacht The yacht to check for collision with a token + */ + private static Token checkTokenPickUp(ServerYacht serverYacht) { + for (Token token : tokensInPlay) { + Double distance = GeoUtility.getDistance(token, serverYacht.getLocation()); + if (distance < YACHT_COLLISION_DISTANCE) { + return token; + } + } + + return null; + } + + + /** + * Checks for collision with other in game objects for the given serverYacht. To be called each + * update. If there is a collision, Notifies the server to send the appropriate messages out. + * Checks for these items in turn: + * - Other yachts + * - Marks + * - Boundary + * - Tokens + * + * @param serverYacht The server yacht to check collisions with + */ public static void checkCollision(ServerYacht serverYacht) { + //Yacht Collision ServerYacht collidedYacht = checkYachtCollision(serverYacht); + Mark collidedMark = checkMarkCollision(serverYacht); + if (collidedYacht != null) { GeoPoint originalLocation = serverYacht.getLocation(); serverYacht.setLocation( calculateBounceBack(serverYacht, originalLocation, BOUNCE_DISTANCE_YACHT) ); + System.out.println("DID BOUNCE BACK"); serverYacht.setCurrentVelocity( serverYacht.getCurrentVelocity() * COLLISION_VELOCITY_PENALTY ); @@ -329,59 +414,83 @@ public class GameState implements Runnable { collidedYacht.getCurrentVelocity() * COLLISION_VELOCITY_PENALTY ); notifyMessageListeners( - new YachtEventCodeMessage(serverYacht.getSourceId()) + new YachtEventCodeMessage(serverYacht.getSourceId(), YachtEventType.COLLISION) ); - } else { - Mark collidedMark = checkMarkCollision(serverYacht); - if (collidedMark != null) { - serverYacht.setLocation( - calculateBounceBack(serverYacht, collidedMark, BOUNCE_DISTANCE_MARK) - ); - serverYacht.setCurrentVelocity( - serverYacht.getCurrentVelocity() * COLLISION_VELOCITY_PENALTY - ); - 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()) - ); - } - } } + + //Mark Collision + else if (collidedMark != null) { + serverYacht.setLocation( + calculateBounceBack(serverYacht, collidedMark, BOUNCE_DISTANCE_MARK) + ); + + System.out.println("DID BOUNCE BACK2"); + serverYacht.setCurrentVelocity( + serverYacht.getCurrentVelocity() * COLLISION_VELOCITY_PENALTY + ); + notifyMessageListeners( + new YachtEventCodeMessage(serverYacht.getSourceId(), YachtEventType.COLLISION) + ); + } + + //Boundary Collision + else if (checkBoundaryCollision(serverYacht)) { + serverYacht.setLocation( + calculateBounceBack(serverYacht, serverYacht.getLocation(), + BOUNCE_DISTANCE_YACHT) + ); + + System.out.println("DID BOUNCE BACK3"); + serverYacht.setCurrentVelocity( + serverYacht.getCurrentVelocity() * COLLISION_VELOCITY_PENALTY + ); + notifyMessageListeners( + new YachtEventCodeMessage(serverYacht.getSourceId(), YachtEventType.COLLISION) + ); + } + + //Token Collision + Token collidedToken = checkTokenPickUp(serverYacht); + if (collidedToken != null) { + tokensInPlay.remove(collidedToken); + serverYacht.powerUp(collidedToken.getTokenType()); + logger.debug("Yacht: " + serverYacht.getShortName() + " got powerup " + collidedToken + .getTokenType()); + notifyMessageListeners(MessageFactory.getRaceXML()); + notifyMessageListeners( + new YachtEventCodeMessage(serverYacht.getSourceId(), YachtEventType.TOKEN)); + } + + } private void updateVelocity(ServerYacht yacht) { - Double velocity = yacht.getCurrentVelocity(); Double trueWindAngle = Math.abs(windDirection - yacht.getHeading()); Double boatSpeedInKnots = PolarTable.getBoatSpeed(getWindSpeedKnots(), trueWindAngle); - Double maxBoatSpeed = GeoUtility.knotsToMMS(boatSpeedInKnots); + Double maxBoatSpeed = GeoUtility.knotsToMMS(boatSpeedInKnots) * speedMultiplier; + if (yacht.getPowerUp() != null) { + if (yacht.getPowerUp().equals(TokenType.BOOST)) { + maxBoatSpeed *= 2; + } + } + + Double currentVelocity = yacht.getCurrentVelocity(); // TODO: 15/08/17 remove magic numbers from these equations. if (yacht.getSailIn()) { - if (velocity < maxBoatSpeed - 500) { + if (currentVelocity < maxBoatSpeed - 500) { yacht.changeVelocity(maxBoatSpeed / 100); - } else if (velocity > maxBoatSpeed + 500) { - yacht.changeVelocity(-velocity / 200); + } else if (currentVelocity > maxBoatSpeed + 500) { + yacht.changeVelocity(-currentVelocity / 200); } else { yacht.setCurrentVelocity(maxBoatSpeed); } } else { - if (velocity > 3000) { - yacht.changeVelocity(-velocity / 200); - } else if (velocity > 100) { - yacht.changeVelocity(-velocity / 50); - } else if (velocity <= 100) { + if (currentVelocity > 3000) { + yacht.changeVelocity(-currentVelocity / 200); + } else if (currentVelocity > 100) { + yacht.changeVelocity(-currentVelocity / 50); + } else if (currentVelocity <= 100) { yacht.setCurrentVelocity(0d); } } @@ -671,8 +780,8 @@ public class GameState implements Runnable { } private static void notifyMessageListeners(Message message) { - for (NewMessageListener mpl : markListeners) { - mpl.notify(message); + for (NewMessageListener ml : newMessageListeners) { + ml.notify(message); } } @@ -684,8 +793,37 @@ public class GameState implements Runnable { } - public static void addMarkPassListener(NewMessageListener listener) { - markListeners.add(listener); + public static void processChatter(ChatterMessage chatterMessage, boolean isHost) { + String chatterText = chatterMessage.getMessage(); + String[] words = chatterText.split("\\s+"); + if (words.length > 2 && isHost) { + switch (words[2].trim()) { + case ">speed": + try { + setSpeedMultiplier(Double.valueOf(words[3])); + notifyMessageListeners(new ChatterMessage( + chatterMessage.getMessage_type(), + "SERVER: Speed modifier set to x" + words[3] + )); + } catch (Exception e) { + Logger logger = LoggerFactory.getLogger(GameState.class); + logger.error("cannot parse >speed value"); + } + return; + case ">finish": + notifyMessageListeners(new ChatterMessage( + chatterMessage.getMessage_type(), + "SERVER: Game will now finish" + )); + endRace(); + return; + } + } + notifyMessageListeners(chatterMessage); + } + + public static void addMessageEventListener(NewMessageListener listener) { + newMessageListeners.add(listener); } public static void setCustomizationFlag() { @@ -699,4 +837,17 @@ public class GameState implements Runnable { public static void resetCustomizationFlag() { customizationFlag = false; } + + public static void endRace () { + yachts.forEach((id, yacht) -> yacht.setBoatStatus(BoatStatus.FINISHED)); + currentStage = GameStages.FINISHED; + } + + public static void setSpeedMultiplier (double multiplier) { + speedMultiplier = multiplier; + } + + public static double getSpeedMultiplier () { + return speedMultiplier; + } } diff --git a/src/main/java/seng302/gameServer/HeartbeatThread.java b/src/main/java/seng302/gameServer/HeartbeatThread.java index b9367134..ba7d4360 100644 --- a/src/main/java/seng302/gameServer/HeartbeatThread.java +++ b/src/main/java/seng302/gameServer/HeartbeatThread.java @@ -4,6 +4,8 @@ import java.io.IOException; import java.util.Stack; import java.util.Timer; import java.util.TimerTask; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import seng302.model.Player; import seng302.gameServer.messages.Heartbeat; import seng302.gameServer.messages.Message; @@ -14,6 +16,9 @@ import seng302.gameServer.messages.Message; * cannot be sent to a player */ public class HeartbeatThread implements Runnable { + + private Logger logger = LoggerFactory.getLogger(HeartbeatThread.class); + private final int HEARTBEAT_PERIOD = 200; private ClientConnectionDelegate delegate; private Integer seqNum; @@ -44,20 +49,23 @@ public class HeartbeatThread implements Runnable { * The delegate is notified if a player has disconnected */ private void sendHeartbeatToAllPlayers(){ - Message heartbeat = new Heartbeat(seqNum); - for (Player player : GameState.getPlayers()){ - if (!player.getSocket().isConnected()) { - playerLostConnection(player); - } - - try { - player.getSocket().getOutputStream().write(heartbeat.getBuffer()); - } catch (IOException e) { - playerLostConnection(player); + try { + Message heartbeat = new Heartbeat(seqNum); + for (Player player : GameState.getPlayers()) { + if (!player.getSocket().isConnected()) { + playerLostConnection(player); + } + try { + player.getSocket().getOutputStream().write(heartbeat.getBuffer()); + } catch (IOException e) { + playerLostConnection(player); + } } + updateDelegate(); + seqNum++; + } catch (NullPointerException ne) { + logger.debug("Socket closed between checking for connection and sending heartbeat"); } - updateDelegate(); - seqNum++; } /** diff --git a/src/main/java/seng302/gameServer/MainServerThread.java b/src/main/java/seng302/gameServer/MainServerThread.java index 83fb304c..3c305f90 100644 --- a/src/main/java/seng302/gameServer/MainServerThread.java +++ b/src/main/java/seng302/gameServer/MainServerThread.java @@ -1,16 +1,19 @@ package seng302.gameServer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; 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.token.Token; +import seng302.model.token.TokenType; import seng302.utilities.GeoUtility; import java.io.IOException; import java.net.ServerSocket; -import java.time.LocalDateTime; import java.util.*; /** @@ -19,18 +22,14 @@ import java.util.*; */ public class MainServerThread implements Runnable, ClientConnectionDelegate { + private Logger logger = LoggerFactory.getLogger(MainServerThread.class); + 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; @@ -43,13 +42,15 @@ public class MainServerThread implements Runnable, ClientConnectionDelegate { try { serverSocket = new ServerSocket(PORT); } catch (IOException e) { - serverLog("IO error in server thread handler upon trying to make new server socket", 0); + logger.trace("IO error in server thread handler upon trying to make new server socket", + 0); } PolarTable.parsePolarFile(getClass().getResourceAsStream("/config/acc_polars.csv")); - GameState.addMarkPassListener(this::broadcastMessage); + GameState.addMessageEventListener(this::broadcastMessage); terminated = false; thread = new Thread(this, "MainServer"); startUpdatingWind(); + startSpawningTokens(); thread.start(); } @@ -64,50 +65,57 @@ public class MainServerThread implements Runnable, ClientConnectionDelegate { try { Thread.sleep(1000 / CLIENT_UPDATES_PER_SECOND); } catch (InterruptedException e) { - serverLog("Interrupted exception in Main Server Thread thread sleep", 1); + logger.trace("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(); - } + sendSetupMessages(); GameState.resetCustomizationFlag(); } if (GameState.getCurrentStage() == GameStages.PRE_RACE) { - updateClients(); + sendBoatLocations(); } //RACING if (GameState.getCurrentStage() == GameStages.RACING) { - updateClients(); + sendBoatLocations(); } //FINISHED else if (GameState.getCurrentStage() == GameStages.FINISHED) { - terminate(); + broadcastMessage(MessageFactory.getRaceStatusMessage()); + try { + Thread.sleep(1000); //Hackish fix to make sure all threads have sent closing RaceStatus + terminate(); + } catch (InterruptedException ie) { + logger.trace("Thread interrupted while waiting to terminate clients", 1); + } } } - - // 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 sendBoatLocations() { + for (ServerYacht serverYacht : GameState.getYachts().values()) { + broadcastMessage(MessageFactory.getBoatLocationMessage(serverYacht)); } } + private void sendSetupMessages() { + broadcastMessage(MessageFactory.getRaceXML()); + broadcastMessage(MessageFactory.getRegattaXML()); + broadcastMessage(MessageFactory.getBoatXML()); + } + private void broadcastMessage(Message message) { for (ServerToClientThread serverToClientThread : serverToClientThreads) { serverToClientThread.sendMessage(message); @@ -143,6 +151,25 @@ public class MainServerThread implements Runnable, ClientConnectionDelegate { GameState.setWindDirection(direction.doubleValue()); } + // TODO: 29/08/17 wmu16 - This should not be in one function (init and a scheduling update) + public void startGame() { + initialiseBoatPositions(); + Timer t = new Timer(); + + t.schedule(new TimerTask() { + @Override + public void run() { + broadcastMessage(MessageFactory.getRaceStatusMessage()); + if (GameState.getCurrentStage() == GameStages.PRE_RACE + || GameState.getCurrentStage() == GameStages.LOBBYING) { + broadcastMessage(MessageFactory.getRaceStartStatusMessage()); + } + } + }, 0, 500); + } + + + // TODO: 29/08/17 wmu16 - This sort of update should be in game state private static void startUpdatingWind(){ Timer timer = new Timer(); timer.schedule(new TimerTask() { @@ -153,12 +180,18 @@ public class MainServerThread implements Runnable, ClientConnectionDelegate { }, 0, 500); } - - static void serverLog(String message, int logLevel) { - if (logLevel <= LOG_LEVEL) { - System.out.println( - "[SERVER " + LocalDateTime.now().toLocalTime().toString() + "] " + message); - } + /** + * Start spawning coins every 60s after the first minute + */ + private void startSpawningTokens() { + Timer timer = new Timer(); + timer.schedule(new TimerTask() { + @Override + public void run() { + GameState.spawnNewToken(); + broadcastMessage(MessageFactory.getRaceXML()); + } + }, 0, 60000); } /** @@ -168,13 +201,12 @@ public class MainServerThread implements Runnable, ClientConnectionDelegate { */ @Override public void clientConnected(ServerToClientThread serverToClientThread) { - serverLog("Player Connected From " + serverToClientThread.getThread().getName(), 0); + logger.debug("Player Connected From " + serverToClientThread.getThread().getName(), 0); + if (serverToClientThreads.size() == 0) { //Sets first client as host. + serverToClientThread.setAsHost(); + } serverToClientThreads.add(serverToClientThread); - serverToClientThread.addConnectionListener(() -> { - for (ServerToClientThread thread : serverToClientThreads) { - thread.sendSetupMessages(); - } - }); + serverToClientThread.addConnectionListener(this::sendSetupMessages); serverToClientThread.addDisconnectListener(this::clientDisconnected); } @@ -185,86 +217,22 @@ public class MainServerThread implements Runnable, ClientConnectionDelegate { */ @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); + logger.debug("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); closedConnection.terminate(); - } - - public void startGame() { - 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 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; + sendSetupMessages(); } - - return new RaceStatusMessage(1, raceStatus, GameState.getStartTime(), - GameState.getWindDirection(), - GameState.getWindSpeedMMS().longValue(), GameState.getPlayers().size(), - RaceType.MATCH_RACE, 1, boatSubMessages); } public void terminate() { @@ -275,10 +243,6 @@ public class MainServerThread implements Runnable, ClientConnectionDelegate { * 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); diff --git a/src/main/java/seng302/gameServer/MessageFactory.java b/src/main/java/seng302/gameServer/MessageFactory.java new file mode 100644 index 00000000..62b5ea0a --- /dev/null +++ b/src/main/java/seng302/gameServer/MessageFactory.java @@ -0,0 +1,136 @@ +package seng302.gameServer; + +import java.util.ArrayList; +import java.util.List; +import seng302.gameServer.messages.BoatLocationMessage; +import seng302.gameServer.messages.BoatSubMessage; +import seng302.gameServer.messages.RaceStartNotificationType; +import seng302.gameServer.messages.RaceStartStatusMessage; +import seng302.gameServer.messages.RaceStatus; +import seng302.gameServer.messages.RaceStatusMessage; +import seng302.gameServer.messages.RaceType; +import seng302.gameServer.messages.XMLMessage; +import seng302.gameServer.messages.XMLMessageSubType; +import seng302.model.Player; +import seng302.model.ServerYacht; +import seng302.model.stream.xml.generator.RaceXMLTemplate; +import seng302.model.stream.xml.generator.RegattaXMLTemplate; +import seng302.model.token.Token; +import seng302.utilities.XMLGenerator; + +/** + * A Class for interfacing between the data we have in the GameState to the messages we need to send + * through the MainServerThread. + * + * WARNING DO NOT USE THIS CLASS IF GAMESTATE HAS NOT BEEN INSTANTIATED. (Main Server has not started) + * // TODO: 29/08/17 wmu16 - Make GameState non static to fix this ¯\_(ツ)_/¯ + * Created by wmu16 on 29/08/17. + */ + +/* +Ideally this class would be created with an instance of the GameState (I tried implementing this for + a bit) but it was too difficult to properly make GameState non static without doing some proper + re working. To do later. + */ +public class MessageFactory { + + private static XMLGenerator xmlGenerator = new XMLGenerator(); + + + public static RaceStartStatusMessage getRaceStartStatusMessage() { + return new RaceStartStatusMessage( + 1, + GameState.getStartTime(), + 1, + RaceStartNotificationType.SET_RACE_START_TIME); + } + + public static RaceStatusMessage getRaceStatusMessage() { + // 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(), + 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 > GameState.WARNING_TIME) { + raceStatus = RaceStatus.WARNING; + } + + if (timeTillStart > GameState.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 static BoatLocationMessage getBoatLocationMessage(ServerYacht yacht) { + return new BoatLocationMessage( + yacht.getSourceId(), + 0, // TODO: 29/08/17 wmu16 - Work out what to do with seqNo. Currently not used + yacht.getLocation().getLat(), + yacht.getLocation().getLng(), + yacht.getHeading(), + yacht.getCurrentVelocity().longValue()); + } + + public static XMLMessage getRaceXML() { + List yachts = new ArrayList<>(GameState.getYachts().values()); + List tokens = GameState.getTokensInPlay(); + RaceXMLTemplate raceXMLTemplate = new RaceXMLTemplate(yachts, tokens); + xmlGenerator.setRaceTemplate(raceXMLTemplate); + + XMLMessage raceXMLMessage = new XMLMessage( + xmlGenerator.getRaceAsXml(), + XMLMessageSubType.RACE, + xmlGenerator.getRaceAsXml().length()); + + return raceXMLMessage; + } + + public static XMLMessage getRegattaXML() { + //@TODO calculate lat/lng values + xmlGenerator.setRegattaTemplate( + new RegattaXMLTemplate( + "Party Parrot Test Server", "Bermuda Test Course", + 57.6679590, 11.8503233) + ); + + return new XMLMessage( + xmlGenerator.getRegattaAsXml(), + XMLMessageSubType.REGATTA, + xmlGenerator.getRegattaAsXml().length()); + } + + public static XMLMessage getBoatXML() { + List yachts = new ArrayList<>(GameState.getYachts().values()); + List tokens = GameState.getTokensInPlay(); + RaceXMLTemplate raceXMLTemplate = new RaceXMLTemplate(yachts, tokens); + xmlGenerator.setRaceTemplate(raceXMLTemplate); + + return new XMLMessage( + xmlGenerator.getBoatsAsXml(), + XMLMessageSubType.BOAT, + xmlGenerator.getBoatsAsXml().length()); + } +} diff --git a/src/main/java/seng302/gameServer/ServerPacketParser.java b/src/main/java/seng302/gameServer/ServerPacketParser.java index 62d971ea..cb8ac036 100644 --- a/src/main/java/seng302/gameServer/ServerPacketParser.java +++ b/src/main/java/seng302/gameServer/ServerPacketParser.java @@ -2,6 +2,7 @@ package seng302.gameServer; import java.util.Arrays; import seng302.gameServer.messages.BoatAction; +import seng302.gameServer.messages.ChatterMessage; import seng302.gameServer.messages.ClientType; import seng302.gameServer.messages.CustomizeRequestType; import seng302.gameServer.messages.Message; @@ -28,5 +29,18 @@ public class ServerPacketParser { long type = Message.bytesToLong(Arrays.copyOfRange(payload, 4, 5)); return CustomizeRequestType.getRequestType((int) type); } + + public static ChatterMessage extractChatterText(byte[] payload) { + return new ChatterMessage( + payload[1], new String(Arrays.copyOfRange(payload, 3, payload.length)) + ); + } + + public static ChatterMessage extractChatterText(StreamPacket packet) { + byte[] payload = packet.getPayload(); + return new ChatterMessage( + payload[1], new String(Arrays.copyOfRange(payload, 3, payload.length)) + ); + } } diff --git a/src/main/java/seng302/gameServer/ServerToClientThread.java b/src/main/java/seng302/gameServer/ServerToClientThread.java index 3a9b4057..965b9577 100644 --- a/src/main/java/seng302/gameServer/ServerToClientThread.java +++ b/src/main/java/seng302/gameServer/ServerToClientThread.java @@ -22,6 +22,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import seng302.gameServer.messages.BoatAction; import seng302.gameServer.messages.BoatLocationMessage; +import seng302.gameServer.messages.ChatterMessage; import seng302.gameServer.messages.ClientType; import seng302.gameServer.messages.CustomizeRequestType; import seng302.gameServer.messages.Message; @@ -30,29 +31,13 @@ import seng302.gameServer.messages.RegistrationResponseStatus; import seng302.gameServer.messages.XMLMessage; import seng302.gameServer.messages.XMLMessageSubType; import seng302.gameServer.messages.YachtEventCodeMessage; -import seng302.gameServer.messages.YachtEventCodeMessage; import seng302.model.Player; import seng302.model.ServerYacht; import seng302.model.stream.packets.PacketType; import seng302.model.stream.packets.StreamPacket; -import seng302.model.stream.xml.generator.Race; -import seng302.model.stream.xml.generator.Regatta; -import seng302.utilities.XMLGenerator; -import seng302.gameServer.messages.BoatAction; -import seng302.gameServer.messages.BoatLocationMessage; -import seng302.gameServer.messages.ClientType; -import seng302.gameServer.messages.Message; -import seng302.gameServer.messages.RegistrationResponseMessage; -import seng302.gameServer.messages.RegistrationResponseStatus; -import seng302.gameServer.messages.XMLMessage; -import seng302.gameServer.messages.XMLMessageSubType; -import seng302.gameServer.messages.YachtEventCodeMessage; -import seng302.model.Player; -import seng302.model.ServerYacht; -import seng302.model.stream.packets.PacketType; -import seng302.model.stream.packets.StreamPacket; -import seng302.model.stream.xml.generator.Race; -import seng302.model.stream.xml.generator.Regatta; +import seng302.model.stream.xml.generator.RaceXMLTemplate; +import seng302.model.stream.xml.generator.RegattaXMLTemplate; +import seng302.model.token.Token; import seng302.utilities.XMLGenerator; /** @@ -60,7 +45,7 @@ import seng302.utilities.XMLGenerator; * its own thread. All server threads created and owned by the server thread handler which can * trigger client updates on its threads Created by wmu16 on 13/07/17. */ -public class ServerToClientThread implements Runnable, Observer { +public class ServerToClientThread implements Runnable { /** * Called to notify listeners when this thread receives a connection correctly. @@ -91,8 +76,9 @@ public class ServerToClientThread implements Runnable, Observer { private ClientType clientType; private Boolean isRegistered = false; + private Boolean isHost = false; - private XMLGenerator xml; + private XMLGenerator xmlGenerator; private List connectionListeners = new ArrayList<>(); private DisconnectListener disconnectListener; @@ -144,21 +130,11 @@ public class ServerToClientThread implements Runnable, Observer { "Yacht", sourceId, sourceId.toString(), fName, fName + " " + lName, "NZ" ); - yacht.addObserver(this); // TODO: yacht can notify mark rounding message hyi25 13/8/17 player = new Player(socket, yacht); GameState.addYacht(sourceId, yacht); GameState.addPlayer(player); } - @Override - public void update(Observable o, Object arg) { - if (arg != null) { - sendMessage((Message) arg); - } else { - sendSetupMessages(); - } - } - private void completeRegistration(ClientType clientType) throws IOException { // Fail if not a player if (!clientType.equals(ClientType.PLAYER)){ @@ -225,7 +201,12 @@ public class ServerToClientThread implements Runnable, Observer { completeRegistration(requestedType); break; - + case CHATTER_TEXT: + ChatterMessage chatterMessage = ServerPacketParser + .extractChatterText( + new StreamPacket(type, payloadLength, timeStamp, payload)); + GameState.processChatter(chatterMessage, isHost); + break; case RACE_CUSTOMIZATION_REQUEST: Long sourceID = Message .bytesToLong(Arrays.copyOfRange(payload, 0, 3)); @@ -250,36 +231,6 @@ public class ServerToClientThread implements Runnable, Observer { logger.warn("Closed serverToClientThread" + thread, 1); } - public void sendSetupMessages() { - xml = new XMLGenerator(); - Race race = new Race(); - - for (ServerYacht yacht : GameState.getYachts().values()) { - race.addBoat(yacht); - } - - //@TODO calculate lat/lng values - xml.setRegatta( - new Regatta( - "Party Parrot Test Server", "Bermuda Test Course", - 57.6679590, 11.8503233) - ); - xml.setRace(race); - - XMLMessage xmlMessage; - xmlMessage = new XMLMessage(xml.getRegattaAsXml(), XMLMessageSubType.REGATTA, - xml.getRegattaAsXml().length()); - sendMessage(xmlMessage); - - xmlMessage = new XMLMessage(xml.getBoatsAsXml(), XMLMessageSubType.BOAT, - xml.getBoatsAsXml().length()); - sendMessage(xmlMessage); - - xmlMessage = new XMLMessage(xml.getRaceAsXml(), XMLMessageSubType.RACE, - xml.getRaceAsXml().length()); - sendMessage(xmlMessage); - } - private void closeSocket() { try { socket.close(); @@ -334,23 +285,6 @@ public class ServerToClientThread implements Runnable, Observer { return seqNo; } - - public void sendBoatLocationPackets() { - ArrayList yachts = new ArrayList<>(GameState.getYachts().values()); - for (ServerYacht yacht : yachts) { - BoatLocationMessage boatLocationMessage = - new BoatLocationMessage( - yacht.getSourceId(), - getSeqNo(), - yacht.getLocation().getLat(), - yacht.getLocation().getLng(), - yacht.getHeading(), - yacht.getCurrentVelocity().longValue()); - - sendMessage(boatLocationMessage); - } - } - public Thread getThread() { return thread; } @@ -363,10 +297,6 @@ public class ServerToClientThread implements Runnable, Observer { return yacht; } - public void sendCollisionMessage(Integer yachtId) { - sendMessage(new YachtEventCodeMessage(yachtId)); - } - public void addConnectionListener(ConnectionListener listener) { connectionListeners.add(listener); } @@ -386,4 +316,8 @@ public class ServerToClientThread implements Runnable, Observer { public void addDisconnectListener(DisconnectListener disconnectListener) { this.disconnectListener = disconnectListener; } + + public void setAsHost() { + isHost = true; + } } diff --git a/src/main/java/seng302/gameServer/messages/ChatterMessage.java b/src/main/java/seng302/gameServer/messages/ChatterMessage.java index f312109f..266fca62 100644 --- a/src/main/java/seng302/gameServer/messages/ChatterMessage.java +++ b/src/main/java/seng302/gameServer/messages/ChatterMessage.java @@ -11,9 +11,11 @@ public class ChatterMessage extends Message { private int message_size = 21; private String message; - public ChatterMessage(int message_type, int message_size, String message) { + public ChatterMessage(int message_type, String message) { + byte[] byteMessage = message.getBytes(); + this.message_type = message_type; - this.message_size = message_size; + this.message_size = byteMessage.length; this.message = message; setHeader(new Header(MessageType.CHATTER_TEXT, 1, (short) getSize())); @@ -23,7 +25,7 @@ public class ChatterMessage extends Message { putByte((byte) MESSAGE_VERSION_NUMBER); putInt(message_type, 1); putInt(message_size, 1); - putBytes(message.getBytes()); + putBytes(byteMessage); writeCRC(); rewind(); @@ -34,5 +36,11 @@ public class ChatterMessage extends Message { return MESSAGE_SIZE + message_size; } + public String getMessage() { + return message; + } + public int getMessage_type() { + return message_type; + } } diff --git a/src/main/java/seng302/gameServer/messages/YachtEventCodeMessage.java b/src/main/java/seng302/gameServer/messages/YachtEventCodeMessage.java index eb9f557e..e26dad6b 100644 --- a/src/main/java/seng302/gameServer/messages/YachtEventCodeMessage.java +++ b/src/main/java/seng302/gameServer/messages/YachtEventCodeMessage.java @@ -18,13 +18,13 @@ public class YachtEventCodeMessage extends Message { private int eventId; - public YachtEventCodeMessage(Integer subjectId) { + public YachtEventCodeMessage(Integer subjectId, YachtEventType yachtEventType) { timeStamp = System.currentTimeMillis() / 1000L; ack = 0; raceId = 1; destSourceId = subjectId; // collision boat source id incidentId = 0; - eventId = 33; + eventId = yachtEventType.getCode(); setHeader(new Header(MESSAGE_TYPE, 0x01, (short) getSize())); allocateBuffer(); diff --git a/src/main/java/seng302/gameServer/messages/YachtEventType.java b/src/main/java/seng302/gameServer/messages/YachtEventType.java new file mode 100644 index 00000000..b31d45a1 --- /dev/null +++ b/src/main/java/seng302/gameServer/messages/YachtEventType.java @@ -0,0 +1,19 @@ +package seng302.gameServer.messages; + +/** + * Created by wmu16 on 11/09/17. + */ +public enum YachtEventType { + COLLISION(33), + TOKEN(34); + + private int code; + + YachtEventType(int code) { + this.code = code; + } + + public int getCode() { + return code; + } +} diff --git a/src/main/java/seng302/model/ClientYacht.java b/src/main/java/seng302/model/ClientYacht.java index 5eccc3c5..531df8cd 100644 --- a/src/main/java/seng302/model/ClientYacht.java +++ b/src/main/java/seng302/model/ClientYacht.java @@ -15,7 +15,6 @@ import javafx.beans.property.ReadOnlyLongWrapper; import javafx.scene.paint.Color; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import seng302.model.mark.CompoundMark; /** * Yacht class for the racing boat.

Class created to store more variables (eg. boat statuses) @@ -32,7 +31,7 @@ public class ClientYacht extends Observable { @FunctionalInterface public interface MarkRoundingListener { - void notifyRounding(ClientYacht yacht, CompoundMark markPassed, int legNumber); + void notifyRounding(ClientYacht yacht, int legNumber); } private Logger logger = LoggerFactory.getLogger(ClientYacht.class); @@ -63,7 +62,6 @@ public class ClientYacht extends Observable { private ReadOnlyLongWrapper timeTillNextProperty = new ReadOnlyLongWrapper(); private ReadOnlyLongWrapper timeSinceLastMarkProperty = new ReadOnlyLongWrapper(); private ReadOnlyIntegerWrapper placingProperty = new ReadOnlyIntegerWrapper(); - private CompoundMark lastMarkRounded; private Color colour; public ClientYacht(String boatType, Integer sourceId, String hullID, String shortName, @@ -189,14 +187,6 @@ public class ClientYacht extends Observable { return markRoundTime; } - public CompoundMark getLastMarkRounded() { - return lastMarkRounded; - } - - public void setLastMarkRounded(CompoundMark lastMarkRounded) { - this.lastMarkRounded = lastMarkRounded; - } - public GeoPoint getLocation() { return location; } @@ -286,13 +276,12 @@ public class ClientYacht extends Observable { return sailIn; } - public void roundMark(CompoundMark mark, long markRoundTime, long timeSinceLastMark) { + public void roundMark(long markRoundTime, long timeSinceLastMark) { this.markRoundTime = markRoundTime; timeSinceLastMarkProperty.set(timeSinceLastMark); - lastMarkRounded = mark; legNumber++; for (MarkRoundingListener listener : markRoundingListeners) { - listener.notifyRounding(this, lastMarkRounded, legNumber); + listener.notifyRounding(this, legNumber); } } } diff --git a/src/main/java/seng302/model/RaceState.java b/src/main/java/seng302/model/RaceState.java index 501a3417..bab854b6 100644 --- a/src/main/java/seng302/model/RaceState.java +++ b/src/main/java/seng302/model/RaceState.java @@ -15,6 +15,7 @@ import javafx.collections.FXCollections; import javafx.collections.ObservableList; import seng302.model.stream.parser.RaceStartData; import seng302.model.stream.parser.RaceStatusData; +import seng302.utilities.Sounds; /** * Class for storing race data that does not relate to specific vessels or marks such as time or wind. @@ -34,6 +35,7 @@ public class RaceState { private long serverSystemTime; private long expectedStartTime; private boolean isRaceStarted = false; + private boolean gunFired = false; long timeTillStart; private ObservableList playerPositions; private List collisions = new ArrayList<>(); @@ -64,6 +66,10 @@ public class RaceState { if (raceTime < 0) { return "-" + DATE_TIME_FORMAT.format(-1 * (raceTime - 1000)); } else { + if (!gunFired) { + gunFired = true; + Sounds.playCapGunSound(); + } return DATE_TIME_FORMAT.format(serverSystemTime - expectedStartTime); } } diff --git a/src/main/java/seng302/model/ServerYacht.java b/src/main/java/seng302/model/ServerYacht.java index 64143023..a24e2ed5 100644 --- a/src/main/java/seng302/model/ServerYacht.java +++ b/src/main/java/seng302/model/ServerYacht.java @@ -9,6 +9,7 @@ import org.slf4j.LoggerFactory; import seng302.gameServer.GameState; import seng302.gameServer.messages.BoatStatus; import seng302.model.mark.Mark; +import seng302.model.token.TokenType; import seng302.utilities.GeoUtility; /** @@ -16,7 +17,7 @@ import seng302.utilities.GeoUtility; * compared to the XMLParser boat class, also done outside Boat class because some old variables are * not used anymore. */ -public class ServerYacht extends Observable { +public class ServerYacht { private Logger logger = LoggerFactory.getLogger(ClientYacht.class); @@ -30,10 +31,8 @@ public class ServerYacht extends Observable { private String boatName; private String country; private BoatStatus boatStatus; - private Color boatColor; - //Location private Double lastHeading; private Boolean sailIn; @@ -52,6 +51,10 @@ public class ServerYacht extends Observable { private Boolean hasPassedLine; private Boolean hasPassedThroughGate; + //PowerUp + private TokenType powerUp; + private Long powerUpStartTime; + public ServerYacht(String boatType, Integer sourceId, String hullID, String shortName, String boatName, String country) { @@ -71,6 +74,7 @@ public class ServerYacht extends Observable { this.currentMarkSeqID = 0; this.legNumber = 0; this.boatColor = Colors.getColor(sourceId - 1); + this.powerUp = null; this.hasEnteredRoundingZone = false; this.hasPassedLine = false; @@ -101,13 +105,21 @@ public class ServerYacht extends Observable { location = geoPoint; } - /** - * Add ServerToClientThread as the observer, this observer pattern mainly server for the boat - * rounding package. - */ - @Override - public void addObserver(Observer o) { - super.addObserver(o); + public void powerUp(TokenType powerUp) { + this.powerUp = powerUp; + powerUpStartTime = System.currentTimeMillis(); + } + + public void powerDown() { + this.powerUp = null; + } + + public Long getPowerUpStartTime() { + return powerUpStartTime; + } + + public TokenType getPowerUp() { + return powerUp; } /** diff --git a/src/main/java/seng302/model/mark/MarkOrder.java b/src/main/java/seng302/model/mark/MarkOrder.java index ab3a1848..0e250e99 100644 --- a/src/main/java/seng302/model/mark/MarkOrder.java +++ b/src/main/java/seng302/model/mark/MarkOrder.java @@ -11,8 +11,10 @@ import org.w3c.dom.Document; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import seng302.gameServer.messages.RoundingSide; -import seng302.model.stream.xml.generator.Race; +import seng302.model.ServerYacht; +import seng302.model.stream.xml.generator.RaceXMLTemplate; import seng302.model.stream.xml.parser.RaceXMLData; +import seng302.model.token.Token; import seng302.utilities.XMLGenerator; import seng302.utilities.XMLParser; import java.util.*; @@ -125,7 +127,10 @@ public class MarkOrder { private void loadRaceProperties(){ XMLGenerator generator = new XMLGenerator(); - generator.setRace(new Race()); + // TODO: 29/08/17 wmu16 - This is terrible, having to make a template just to receive constant data + generator.setRaceTemplate(new RaceXMLTemplate( + new ArrayList<>(), + new ArrayList<>())); String raceXML = generator.getRaceAsXml(); diff --git a/src/main/java/seng302/model/stream/parser/RaceStatusData.java b/src/main/java/seng302/model/stream/parser/RaceStatusData.java index ba836442..867ff282 100644 --- a/src/main/java/seng302/model/stream/parser/RaceStatusData.java +++ b/src/main/java/seng302/model/stream/parser/RaceStatusData.java @@ -57,7 +57,7 @@ public class RaceStatusData { * Returns the data for boats collected form race status packets. * * @return A list of boat data. Boat data is in the form - * [boatID, estTimeToNextMark, estTimeToFinish, legNumber]. + * [boatID, estTimeToNextMark, estTimeToFinish, legNumber, status]. */ public List getBoatData () { return boatData; diff --git a/src/main/java/seng302/model/stream/xml/generator/Race.java b/src/main/java/seng302/model/stream/xml/generator/RaceXMLTemplate.java similarity index 71% rename from src/main/java/seng302/model/stream/xml/generator/Race.java rename to src/main/java/seng302/model/stream/xml/generator/RaceXMLTemplate.java index cf42cb2c..8ce7d5ad 100644 --- a/src/main/java/seng302/model/stream/xml/generator/Race.java +++ b/src/main/java/seng302/model/stream/xml/generator/RaceXMLTemplate.java @@ -5,28 +5,23 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; import seng302.model.ServerYacht; +import seng302.model.token.Token; /** * A Race object that can be parsed into XML */ -public class Race { +public class RaceXMLTemplate { private List yachts; private LocalDateTime startTime; + private List tokens; - public Race(){ - yachts = new ArrayList<>(); + public RaceXMLTemplate(List yachts, List tokens) { + this.yachts = yachts; + this.tokens = tokens; startTime = LocalDateTime.now(); } - /** - * Add a boat to the race - * @param yacht The boat to add - */ - public void addBoat(ServerYacht yacht) { - yachts.add(yacht); - } - /** * Get a list of boats in the race * @return A List of boats @@ -35,6 +30,15 @@ public class Race { return Collections.unmodifiableList(yachts); } + /** + * Get a list of tokens in the race + * + * @return A list of tokens + */ + public List getTokens() { + return Collections.unmodifiableList(tokens); + } + /** * Set the time until the race starts * @param seconds The time in seconds until the race starts diff --git a/src/main/java/seng302/model/stream/xml/generator/Regatta.java b/src/main/java/seng302/model/stream/xml/generator/RegattaXMLTemplate.java similarity index 91% rename from src/main/java/seng302/model/stream/xml/generator/Regatta.java rename to src/main/java/seng302/model/stream/xml/generator/RegattaXMLTemplate.java index fa802e01..8bd344ba 100644 --- a/src/main/java/seng302/model/stream/xml/generator/Regatta.java +++ b/src/main/java/seng302/model/stream/xml/generator/RegattaXMLTemplate.java @@ -3,7 +3,7 @@ package seng302.model.stream.xml.generator; /** * A Race regatta that can be parsed into XML */ -public class Regatta { +public class RegattaXMLTemplate { private final Double DEFAULT_ALTITUDE = 0d; private final Integer DEFAULT_REGATTA_ID = 0; @@ -18,7 +18,7 @@ public class Regatta { private Integer utcOffset; private Double magneticVariation; - public Regatta(String name, String courseName, Double latitude, Double longitude) { + public RegattaXMLTemplate(String name, String courseName, Double latitude, Double longitude) { this.name = name; this.id = DEFAULT_REGATTA_ID; this.courseName = courseName; diff --git a/src/main/java/seng302/model/stream/xml/parser/RaceXMLData.java b/src/main/java/seng302/model/stream/xml/parser/RaceXMLData.java index 13a0bdbc..5f1935df 100644 --- a/src/main/java/seng302/model/stream/xml/parser/RaceXMLData.java +++ b/src/main/java/seng302/model/stream/xml/parser/RaceXMLData.java @@ -6,6 +6,7 @@ import java.util.Map; import seng302.model.Limit; import seng302.model.mark.CompoundMark; import seng302.model.mark.Corner; +import seng302.model.token.Token; /** * Process a Document object containing race data in XML format and stores the data. @@ -13,13 +14,16 @@ import seng302.model.mark.Corner; public class RaceXMLData { private List participants; + private List tokens; private Map compoundMarks; private List markSequence; private List courseLimit; - public RaceXMLData(List participants, List compoundMarks, + public RaceXMLData(List participants, List tokens, + List compoundMarks, List markSequence, List courseLimit) { this.participants = participants; + this.tokens = tokens; this.markSequence = markSequence; this.courseLimit = courseLimit; this.compoundMarks = new HashMap<>(); @@ -32,6 +36,10 @@ public class RaceXMLData { return participants; } + public List getTokens() { + return tokens; + } + public Map getCompoundMarks() { return compoundMarks; } diff --git a/src/main/java/seng302/model/token/Token.java b/src/main/java/seng302/model/token/Token.java new file mode 100644 index 00000000..9f8ca619 --- /dev/null +++ b/src/main/java/seng302/model/token/Token.java @@ -0,0 +1,21 @@ +package seng302.model.token; + +import seng302.model.GeoPoint; + +/** + * A class describing a game token + * Created by wmu16 on 28/08/17. + */ +public class Token extends GeoPoint { + + private TokenType tokenType; + + public Token(TokenType tokenType, double lat, double lng) { + super(lat, lng); + this.tokenType = tokenType; + } + + public TokenType getTokenType() { + return tokenType; + } +} diff --git a/src/main/java/seng302/model/token/TokenType.java b/src/main/java/seng302/model/token/TokenType.java new file mode 100644 index 00000000..d5c2df01 --- /dev/null +++ b/src/main/java/seng302/model/token/TokenType.java @@ -0,0 +1,31 @@ +package seng302.model.token; + +/** + * An enum describing the different types of game objects + * Created by wmu16 on 28/08/17. + */ +public enum TokenType { + BOOST(0), + HANDLING(1); + + private int value; + + TokenType(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + + public static TokenType getToken(int value) { + switch (value) { + case 0: + return BOOST; + case 1: + return HANDLING; + default: + return BOOST; + } + } +} diff --git a/src/main/java/seng302/utilities/Sounds.java b/src/main/java/seng302/utilities/Sounds.java new file mode 100644 index 00000000..0678369c --- /dev/null +++ b/src/main/java/seng302/utilities/Sounds.java @@ -0,0 +1,177 @@ +package seng302.utilities; + +import javafx.scene.media.AudioClip; +import javafx.scene.media.Media; +import javafx.scene.media.MediaPlayer; + +/** + * Static class for playing sounds throughout the program + * + * Created by kre39 on 28/08/17. + */ +public class Sounds { + + private static MediaPlayer musicPlayer; + private static MediaPlayer soundEffect; + private static MediaPlayer soundPlayer; + private static AudioClip hoverSoundPlayer = new AudioClip(Sounds.class.getClassLoader().getResource("sounds/sound-over.wav").toExternalForm());; + + private static boolean musicMuted = false; + private static boolean soundEffectsMuted = false; + + + public static void stopMusic() { + if (musicPlayer != null) { + musicPlayer.stop(); + } + } + + public static void setMutes() { + if (soundPlayer != null) { + soundPlayer.setMute(soundEffectsMuted); + } + if (soundEffect != null) { + soundEffect.setMute(soundEffectsMuted); + } + if (musicPlayer != null) { + musicPlayer.setMute(musicMuted); + } + } + + public static void stopSoundEffects() { + if (soundEffect != null) { + soundEffect.stop(); + } + } + + public static void toggleMuteMusic() { + musicMuted = !musicMuted; + if (musicPlayer != null) { + musicPlayer.setMute(musicMuted); + } + } + + public static void toggleMuteEffects() { + soundEffectsMuted = !soundEffectsMuted; + if (soundPlayer != null) { + soundPlayer.setMute(soundEffectsMuted); + } + if (soundEffect != null) { + soundEffect.setMute(soundEffectsMuted); + } + } + + public static boolean isMusicMuted() { + return musicMuted; + } + + public static boolean isSoundEffectsMuted() { + return soundEffectsMuted; + } + + public static void playRaceMusic() { +// Media menuMusic = new Media(Sounds.class.getClassLoader().getResource("sounds/Chill-house-music-loop-116-bpm.wav").toString()); + Media raceMusic = new Media(Sounds.class.getClassLoader().getResource("sounds/Music-loop-120-bpm.mp3").toString()); + musicPlayer = new MediaPlayer(raceMusic); + musicPlayer.setCycleCount(MediaPlayer.INDEFINITE); + musicPlayer.setVolume(0.3); + musicPlayer.play(); + raceMusic = new Media(Sounds.class.getClassLoader().getResource("sounds/Sounds-of-the-ocean.mp3").toString()); + soundEffect = new MediaPlayer(raceMusic); + soundEffect.setCycleCount(MediaPlayer.INDEFINITE); + soundEffect.setVolume(0.3); + soundEffect.play(); + musicPlayer.setMute(musicMuted); + soundEffect.setMute(soundEffectsMuted); + } + + public static void playMenuMusic() { + Media menuMusic = new Media( + Sounds.class.getClassLoader().getResource("sounds/Elevator-music.mp3").toString()); + musicPlayer = new MediaPlayer(menuMusic); + musicPlayer.setCycleCount(MediaPlayer.INDEFINITE); + musicPlayer.setVolume(0.3); + musicPlayer.play(); + } + + + public static void playFinishMusic() { + Media finishMusic = new Media(Sounds.class.getClassLoader().getResource("sounds/Happy-birthday-song.mp3").toString()); + musicPlayer = new MediaPlayer(finishMusic); + musicPlayer.setCycleCount(MediaPlayer.INDEFINITE); + musicPlayer.setVolume(0.3); + musicPlayer.play(); + musicPlayer.setMute(musicMuted); + } + + public static void playButtonClick() { + if (!soundEffectsMuted) { + Media buttonClick = new Media( + Sounds.class.getClassLoader().getResource("sounds/Button-click-sound.mp3") + .toString()); + soundPlayer = new MediaPlayer(buttonClick); + soundPlayer.setVolume(0.5); + soundPlayer.play(); + soundPlayer.setMute(soundEffectsMuted); + } + } + + public static void playFinishSound() { + if (!soundEffectsMuted) { + Media finishSound = new Media( + Sounds.class.getClassLoader().getResource("sounds/Sms-notification.mp3") + .toString()); + soundPlayer = new MediaPlayer(finishSound); + soundPlayer.setVolume(0.5); + soundPlayer.play(); + } + } + + + public static void playMarkRoundingSound() { + if (!soundEffectsMuted) { + Media markRoundingSound = new Media( + Sounds.class.getClassLoader().getResource("sounds/sms-tone.mp3").toString()); + soundPlayer = new MediaPlayer(markRoundingSound); + soundPlayer.play(); + } + } + + public static void playCapGunSound() { + if (!soundEffectsMuted) { + Media gunSound = new Media( + Sounds.class.getClassLoader().getResource("sounds/Gunshot-sound.mp3").toString()); + soundPlayer = new MediaPlayer(gunSound); + soundPlayer.play(); + } + } + + public static void playCrashSound() { + if (!soundEffectsMuted) { + Media crashSound = new Media( + Sounds.class.getClassLoader().getResource("sounds/Large-metal-door-slam.mp3") + .toString()); + soundPlayer = new MediaPlayer(crashSound); + soundPlayer.play(); + } + } + + public static void playTokenPickupSound() { + if (!soundEffectsMuted) { + Media pickupSound = new Media( + Sounds.class.getClassLoader().getResource("sounds/Coin-pick-up-sound-effect.mp3") + .toString()); + soundPlayer = new MediaPlayer(pickupSound); + soundPlayer.play(); + } + } + + public static void playHoverSound() { + if (!soundEffectsMuted) { + hoverSoundPlayer.setVolume(2.5); + hoverSoundPlayer.play(); + } + } + + +} diff --git a/src/main/java/seng302/utilities/StreamParser.java b/src/main/java/seng302/utilities/StreamParser.java index 1f90eac8..748337df 100644 --- a/src/main/java/seng302/utilities/StreamParser.java +++ b/src/main/java/seng302/utilities/StreamParser.java @@ -2,9 +2,11 @@ package seng302.utilities; import java.io.IOException; import java.io.StringReader; +import java.lang.reflect.Array; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import javafx.util.Pair; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; @@ -62,31 +64,10 @@ public class StreamParser { long windDir = bytesToLong(Arrays.copyOfRange(payload, 18, 20)); long rawWindSpeed = bytesToLong(Arrays.copyOfRange(payload, 20, 22)); -// DateFormat format = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss"); -// currentTime = format.format((new Date(currentTime))) - RaceStatusData data = new RaceStatusData( windDir, rawWindSpeed, raceStatus, currentTime, expectedStartTime ); -// long timeTillStart = -// ((new Date(expectedStartTime)).getTime() - (new Date(currentTime)).getTime()) / 1000; -// -// if (timeTillStart > 0) { -// timeSinceStart = timeTillStart; -// } else { -// if (raceStatus == 4 || raceStatus == 8) { -// raceFinished = true; -// raceStarted = false; -// } else if (!raceStarted) { -// raceStarted = true; -// raceFinished = false; -// } -// timeSinceStart = timeTillStart; -// } -// - -// int noBoats = payload[22]; int raceType = payload[23]; long boatID, estTimeAtNextMark, estTimeAtFinish; @@ -106,24 +87,6 @@ public class StreamParser { return data; } -// private static void setBoatLegPosition(Yacht updatingBoat, Integer leg){ -// Integer placing = 1; -// if (leg != updatingBoat.getLegNumber() && (raceStarted || raceFinished)) { -// for (Yacht boat : boats.values()) { -// if (boat.getLegNumber() != null && leg <= boat.getLegNumber()){ -// placing += 1; -// } -// } -// updatingBoat.setPlacing(placing.toString()); -// updatingBoat.setLegNumber(leg); -// boatsPos.putIfAbsent(placing, updatingBoat); -// boatsPos.replace(placing, updatingBoat); -// } else if(updatingBoat.getLegNumber() == null){ -// updatingBoat.setPlacing("1"); -// updatingBoat.setLegNumber(leg); -// } -// } - /** * Parses and returns the text from a StreamPacket containing text data for display. * @@ -255,15 +218,15 @@ public class StreamParser { * @return Chatter text message as a string. Returns null if the packet is not of type * CHATTER_TEXT. */ - public static String extractChatterText(StreamPacket packet) { + public static Pair extractChatterText(StreamPacket packet) { if (packet.getType() != PacketType.CHATTER_TEXT) { return null; } byte[] payload = packet.getPayload(); int messageVersionNo = payload[0]; int messageType = payload[1]; - int length = payload[2]; - return new String(Arrays.copyOfRange(payload, 3, 3 + length)); + int length = (int) bytesToLong(new byte[]{payload[2]}); + return new Pair<>(messageType, new String(Arrays.copyOfRange(payload, 3, 3 + length))); } /** @@ -392,26 +355,6 @@ public class StreamParser { }; } - - public static void extractBoatAction(StreamPacket packet) { - byte[] payload = packet.getPayload(); - int messageVersionNo = payload[0]; - long actionType = bytesToLong(Arrays.copyOfRange(payload, 0, 1)); - if (actionType == 1) { - System.out.println("VMG"); - } else if (actionType == 2) { - System.out.println("SAILS IN"); - } else if (actionType == 3) { - System.out.println("SAILS OUT"); - } else if (actionType == 4) { - System.out.println("TACK/GYBE"); - } else if (actionType == 5) { - System.out.println("UPWIND"); - } else if (actionType == 6) { - System.out.println("DOWNWIND"); - } - } - /** * takes an array of up to 7 bytes and returns a positive long constructed from the input bytes * diff --git a/src/main/java/seng302/utilities/XMLGenerator.java b/src/main/java/seng302/utilities/XMLGenerator.java index 7fcc8efd..4915bea1 100644 --- a/src/main/java/seng302/utilities/XMLGenerator.java +++ b/src/main/java/seng302/utilities/XMLGenerator.java @@ -7,8 +7,8 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.UnsupportedEncodingException; -import seng302.model.stream.xml.generator.Race; -import seng302.model.stream.xml.generator.Regatta; +import seng302.model.stream.xml.generator.RaceXMLTemplate; +import seng302.model.stream.xml.generator.RegattaXMLTemplate; import seng302.gameServer.messages.XMLMessageSubType; /** @@ -20,8 +20,8 @@ public class XMLGenerator { private static final String BOATS_TEMPLATE_NAME = "boats.ftlh"; private static final String RACE_TEMPLATE_NAME = "race.ftlh"; private Configuration configuration; - private Regatta regatta; - private Race race; + private RegattaXMLTemplate regatta; + private RaceXMLTemplate race; /** * Set up a configuration instance for Apache Freemake @@ -48,7 +48,7 @@ public class XMLGenerator { * Note: This must be set before a regatta message can be generated * @param regatta The race regatta */ - public void setRegatta(Regatta regatta){ + public void setRegattaTemplate(RegattaXMLTemplate regatta) { this.regatta = regatta; } @@ -57,7 +57,7 @@ public class XMLGenerator { * Note: This must be set before a boat or race message can be generated * @param race The race */ - public void setRace(Race race){ + public void setRaceTemplate(RaceXMLTemplate race) { this.race = race; } diff --git a/src/main/java/seng302/utilities/XMLParser.java b/src/main/java/seng302/utilities/XMLParser.java index d7556234..a903daaf 100644 --- a/src/main/java/seng302/utilities/XMLParser.java +++ b/src/main/java/seng302/utilities/XMLParser.java @@ -16,6 +16,8 @@ import seng302.model.mark.Corner; import seng302.model.mark.Mark; import seng302.model.stream.xml.parser.RaceXMLData; import seng302.model.stream.xml.parser.RegattaXMLData; +import seng302.model.token.Token; +import seng302.model.token.TokenType; /** * Utilities for parsing XML documents @@ -182,12 +184,32 @@ public class XMLParser { Element docEle = doc.getDocumentElement(); return new RaceXMLData( extractParticpantIDs(docEle), + extractTokens(docEle), extractCompoundMarks(docEle), extractMarkOrder(docEle), extractCourseLimit(docEle) ); } + /** + * Extracts token data + */ + private static List extractTokens(Element docEle) { + List tokens = new ArrayList<>(); + NodeList tokenList = docEle.getElementsByTagName("Tokens").item(0).getChildNodes(); + for (int i = 0; i < tokenList.getLength(); i++) { + Node tokenNode = tokenList.item(i); + if (tokenNode.getNodeName().equals("Token")) { + String tokenType = getNodeAttributeString(tokenNode, "TokenType"); + Double lat = getNodeAttributeDouble(tokenNode, "TargetLat"); + Double lng = getNodeAttributeDouble(tokenNode, "TargetLng"); + tokens.add(new Token(TokenType.valueOf(tokenType), lat, lng)); + } + } + + return tokens; + } + /** * Extracts course limit data */ diff --git a/src/main/java/seng302/visualiser/ClientToServerThread.java b/src/main/java/seng302/visualiser/ClientToServerThread.java index 57256760..5f09af10 100644 --- a/src/main/java/seng302/visualiser/ClientToServerThread.java +++ b/src/main/java/seng302/visualiser/ClientToServerThread.java @@ -18,6 +18,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import seng302.gameServer.messages.BoatAction; import seng302.gameServer.messages.BoatActionMessage; +import seng302.gameServer.messages.ChatterMessage; import seng302.gameServer.messages.ClientType; import seng302.gameServer.messages.CustomizeRequestMessage; import seng302.gameServer.messages.CustomizeRequestType; @@ -33,8 +34,6 @@ import seng302.model.stream.packets.StreamPacket; */ public class ClientToServerThread implements Runnable { - - /** * Functional interface for receiving packets from client socket. */ @@ -95,7 +94,7 @@ public class ClientToServerThread implements Runnable { sendRegistrationRequest(); - thread = new Thread(this); + thread = new Thread(this, "ClientToServer"); thread.start(); } @@ -283,9 +282,17 @@ public class ClientToServerThread implements Runnable { * @param message The given message type. */ private void sendBoatActionMessage(BoatActionMessage message) { + sendByteBuffer(message.getBuffer()); + } + + public void sendChatterMessage(String message) { + sendByteBuffer(new ChatterMessage(clientId, message).getBuffer()); + } + + private void sendByteBuffer(byte[] bytes) { if (clientId != -1) { try { - os.write(message.getBuffer()); + os.write(bytes); } catch (IOException e) { logger.warn("IOException on attempting to sendBoatAction from Client"); notifyDisconnectListeners("Cannot communicate with server"); @@ -294,7 +301,7 @@ public class ClientToServerThread implements Runnable { } } - private void closeSocket() { + public void closeSocket() { try { socket.close(); socketOpen = false; diff --git a/src/main/java/seng302/visualiser/GameClient.java b/src/main/java/seng302/visualiser/GameClient.java index 968d9a92..6914906e 100644 --- a/src/main/java/seng302/visualiser/GameClient.java +++ b/src/main/java/seng302/visualiser/GameClient.java @@ -1,8 +1,11 @@ package seng302.visualiser; import java.io.IOException; +import java.text.SimpleDateFormat; import java.time.ZoneId; import java.time.ZoneOffset; +import java.util.ArrayList; +import java.util.Date; import java.util.Map; import java.util.TimeZone; import javafx.application.Platform; @@ -12,12 +15,15 @@ import javafx.fxml.FXMLLoader; import javafx.scene.Node; import javafx.scene.control.Alert; import javafx.scene.control.Alert.AlertType; +import javafx.scene.input.KeyCode; import javafx.scene.input.KeyEvent; import javafx.scene.layout.Pane; +import javafx.util.Pair; import seng302.gameServer.GameState; import seng302.gameServer.MainServerThread; import seng302.gameServer.messages.BoatAction; import seng302.gameServer.messages.BoatStatus; +import seng302.gameServer.messages.YachtEventType; import seng302.model.ClientYacht; import seng302.model.RaceState; import seng302.model.stream.packets.StreamPacket; @@ -28,6 +34,7 @@ import seng302.model.stream.parser.RaceStatusData; import seng302.model.stream.parser.YachtEventData; import seng302.model.stream.xml.parser.RaceXMLData; import seng302.model.stream.xml.parser.RegattaXMLData; +import seng302.utilities.Sounds; import seng302.utilities.StreamParser; import seng302.utilities.XMLParser; import seng302.visualiser.controllers.FinishScreenViewController; @@ -53,6 +60,8 @@ public class GameClient { private RaceState raceState = new RaceState(); private LobbyController lobbyController; + private ArrayList finishedBoats = new ArrayList<>(); + private ObservableList clientLobbyList = FXCollections.observableArrayList(); /** @@ -142,11 +151,10 @@ public class GameClient { } private void loadStartScreen() { - socketThread.setSocketToClose(); - if (server != null) { - server.terminate(); - server = null; + if (socketThread != null) { + socketThread.setSocketToClose(); } + FXMLLoader fxmlLoader = new FXMLLoader( getClass().getResource("/views/StartScreenView.fxml")); try { @@ -195,9 +203,19 @@ public class GameClient { raceView = fxmlLoader.getController(); ClientYacht player = allBoatsMap.get(socketThread.getClientId()); raceView.loadRace(allBoatsMap, courseData, raceState, player); + raceView.getSendPressedProperty().addListener((obs, old, isPressed) -> { + if (isPressed) { + formatAndSendChatMessage(raceView.readChatInput()); + } + }); } + + private void loadFinishScreenView() { + Sounds.stopMusic(); + Sounds.stopSoundEffects(); + Sounds.playFinishMusic(); FXMLLoader fxmlLoader = loadFXMLToHolder("/views/FinishScreenView.fxml"); FinishScreenViewController controller = fxmlLoader.getController(); controller.setFinishers(raceState.getPlayerPositions()); @@ -248,6 +266,7 @@ public class GameClient { courseData = XMLParser.parseRace( StreamParser.extractXmlMessage(packet) ); + if (raceView != null) { raceView.updateRaceData(courseData); } @@ -278,8 +297,21 @@ public class GameClient { break; case YACHT_EVENT_CODE: - showCollisionAlert(StreamParser.extractYachtEventCode(packet)); + YachtEventData yachtEventData = StreamParser.extractYachtEventCode(packet); + if (yachtEventData.getEventId() == YachtEventType.COLLISION.getCode()) { + showCollisionAlert(StreamParser.extractYachtEventCode(packet)); + } else if (yachtEventData.getEventId() == YachtEventType.TOKEN.getCode()) { + showPickUp(); + } break; + + case CHATTER_TEXT: + Pair playerIdMessagePair = StreamParser + .extractChatterText(packet); + raceView.updateChatHistory( + allBoatsMap.get(playerIdMessagePair.getKey()).getColour(), + playerIdMessagePair.getValue() + ); } } } @@ -320,7 +352,6 @@ public class GameClient { if (allXMLReceived()) { ClientYacht clientYacht = allBoatsMap.get(roundingData.getBoatId()); clientYacht.roundMark( - courseData.getCompoundMarks().get(roundingData.getMarkId()), roundingData.getTimeStamp(), raceState.getRaceTime() - roundingData.getTimeStamp() ); @@ -335,6 +366,9 @@ public class GameClient { for (ClientYacht yacht : allBoatsMap.values()) { if (yacht.getBoatStatus() != BoatStatus.FINISHED.getCode()) { raceFinished = false; + } else if (!finishedBoats.contains(yacht)) { + finishedBoats.add(yacht); + Sounds.playFinishSound(); } } @@ -350,6 +384,7 @@ public class GameClient { } if (raceFinished) { + Sounds.playFinishSound(); close(); loadFinishScreenView(); } @@ -373,6 +408,12 @@ public class GameClient { * @param e The key event triggering this call */ private void keyPressed(KeyEvent e) { + if (raceView.isChatInputFocused()) { + if (e.getCode() == KeyCode.ENTER) { + formatAndSendChatMessage(raceView.readChatInput()); + } + return; + } switch (e.getCode()) { case SPACE: // align with vmg socketThread.sendBoatAction(BoatAction.VMG); break; @@ -381,12 +422,16 @@ public class GameClient { case PAGE_DOWN: // downwind socketThread.sendBoatAction(BoatAction.DOWNWIND); break; case ENTER: // tack/gybe + // if chat box is active take whatever is in there and send it to server socketThread.sendBoatAction(BoatAction.TACK_GYBE); break; } } private void keyReleased(KeyEvent e) { + if (raceView.isChatInputFocused()) { + return; + } 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) case SHIFT: // sails in/sails out @@ -407,13 +452,31 @@ public class GameClient { * Tells race view to show a collision animation. */ private void showCollisionAlert(YachtEventData yachtEventData) { - // 33 is the agreed code to show collision - if (yachtEventData.getEventId() == 33) { - raceState.storeCollision( - allBoatsMap.get( - yachtEventData.getSubjectId().intValue() - ) + Sounds.playCrashSound(); + raceState.storeCollision( + allBoatsMap.get( + yachtEventData.getSubjectId().intValue() + ) + ); + } + + // TODO: 11/09/17 wmu16 - Add in functionality to viually indicate a pickup to a user + private void showPickUp() { + Sounds.playTokenPickupSound(); + } + + private void formatAndSendChatMessage(String rawChat) { + if (rawChat.length() > 0) { + socketThread.sendChatterMessage( + new SimpleDateFormat("[HH:mm:ss] ").format(new Date()) + + allBoatsMap.get(socketThread.getClientId()).getShortName() + ": " + rawChat ); } } + + + public ClientToServerThread getSocketThread() { + return socketThread; + } + } diff --git a/src/main/java/seng302/visualiser/GameView.java b/src/main/java/seng302/visualiser/GameView.java index 885d5619..9465aae6 100644 --- a/src/main/java/seng302/visualiser/GameView.java +++ b/src/main/java/seng302/visualiser/GameView.java @@ -36,7 +36,9 @@ import seng302.model.Limit; import seng302.model.mark.CompoundMark; import seng302.model.mark.Corner; import seng302.model.mark.Mark; +import seng302.model.token.Token; import seng302.utilities.GeoUtility; +import seng302.utilities.Sounds; import seng302.visualiser.fxObjects.AnnotationBox; import seng302.visualiser.fxObjects.BoatObject; import seng302.visualiser.fxObjects.CourseBoundary; @@ -64,6 +66,8 @@ public class GameView extends Pane { private double referencePointX, referencePointY; private double metersPerPixelX, metersPerPixelY; + private boolean isZoom = false; + private Text fpsDisplay = new Text(); private Polygon raceBorder = new CourseBoundary(); @@ -81,6 +85,7 @@ public class GameView extends Pane { private Group boatObjectGroup = new Group(); private Group trails = new Group(); private Group markers = new Group(); + private Group tokens = new Group(); private List course = new ArrayList<>(); private ImageView mapImage = new ImageView(); @@ -100,7 +105,7 @@ public class GameView extends Pane { private void zoomOut() { scaleFactor = 0.1; - if (this.getScaleX() > 0.5) { + if (this.isZoom && this.getScaleX() > 0.5) { this.setScaleX(this.getScaleX() - scaleFactor); this.setScaleY(this.getScaleY() - scaleFactor); } @@ -108,7 +113,7 @@ public class GameView extends Pane { private void zoomIn() { scaleFactor = 0.1; - if (this.getScaleX() < 2.5) { + if (this.isZoom && this.getScaleX() < 2.5) { this.setScaleX(this.getScaleX() + scaleFactor); this.setScaleY(this.getScaleY() + scaleFactor); } @@ -140,7 +145,15 @@ public class GameView extends Pane { gameObjects.add(mapImage); gameObjects.add(raceBorder); gameObjects.add(markers); + gameObjects.add(tokens); initializeTimer(); + this.sceneProperty().addListener(((observable, oldValue, scene) -> { + if (scene != null) { + setupZoom(); + } else { + disableZoom(); + } + })); this.widthProperty().addListener(new ChangeListener() { @Override @@ -468,23 +481,51 @@ public class GameView extends Pane { raceBorder.getPoints().setAll(boundaryPoints); } + /** + * Replaces all tokens in the course with those passed in + * + * @param newTokens the tokens to be put on the course. + */ + public void updateTokens(List newTokens) { + + List mapTokens = new ArrayList<>(); + + for (Token token : newTokens) { + Point2D location = findScaledXY(token.getLat(), token.getLng()); + Marker thisMarker = new Marker(Color.YELLOW); + thisMarker.setLayoutX(location.getX()); + thisMarker.setLayoutY(location.getY()); + mapTokens.add(thisMarker); + } + Platform.runLater(() -> { + tokens.getChildren().clear(); + tokens.getChildren().addAll(mapTokens); + }); + } + // TODO: 16/08/17 initialize zooming internal to GameView only /** * Enables zoom. Has to be called after this is added to a scene. */ - public void enableZoom () { - System.out.println("enable zoom"); - if (this.getScene() != null) { - System.out.println("can zoom"); - this.getScene().addEventHandler(KeyEvent.KEY_PRESSED, (event) -> { - if (event.getCode() == KeyCode.Z) { - zoomIn(); - } else if (event.getCode() == KeyCode.X) { - zoomOut(); - } - }); - } + private void setupZoom() { + this.getScene().addEventHandler(KeyEvent.KEY_PRESSED, (event) -> { + if (event.getCode() == KeyCode.Z) { + zoomIn(); + } else if (event.getCode() == KeyCode.X) { + zoomOut(); + } + }); + enableZoom(); } + + public void enableZoom() { + isZoom = true; + } + + public void disableZoom() { + isZoom = false; + } + /** * Rescales the race to the size of the window. * @@ -817,15 +858,11 @@ public class GameView extends Pane { playerYacht.addMarkRoundingListener(this::updateMarkArrows); } - private void updateMarkArrows (ClientYacht yacht, CompoundMark compoundMark, int legNumber) { + private void updateMarkArrows (ClientYacht yacht, int legNumber) { //Only show arrows for this and next leg. - if (compoundMark != null) { - for (Mark mark : compoundMark.getMarks()) { - markerObjects.get(mark).showNextExitArrow(); - } - } CompoundMark nextMark = null; if (legNumber < course.size() - 1) { + Sounds.playMarkRoundingSound(); nextMark = course.get(legNumber); for (Mark mark : nextMark.getMarks()) { markerObjects.get(mark).showNextEnterArrow(); @@ -839,6 +876,14 @@ public class GameView extends Pane { } } } + if (legNumber - 1 >= 0) { + CompoundMark thisMark = course.get(Math.max(0, legNumber - 1)); + if (thisMark != nextMark) { + for (Mark mark : thisMark.getMarks()) { + markerObjects.get(mark).showNextExitArrow(); + } + } + } } /** diff --git a/src/main/java/seng302/visualiser/controllers/CustomizationController.java b/src/main/java/seng302/visualiser/controllers/CustomizationController.java index 1a5b5e4d..7a81bb8f 100644 --- a/src/main/java/seng302/visualiser/controllers/CustomizationController.java +++ b/src/main/java/seng302/visualiser/controllers/CustomizationController.java @@ -7,6 +7,7 @@ import javafx.scene.control.TextField; import javafx.scene.paint.Color; import javafx.stage.Stage; import seng302.gameServer.messages.CustomizeRequestType; +import seng302.utilities.Sounds; import seng302.visualiser.ClientToServerThread; public class CustomizationController { @@ -34,7 +35,8 @@ public class CustomizationController { @FXML public void submitCustomization() { - System.out.println("Attempting to send"); + Sounds.playButtonClick(); +// System.out.println("Attempting to send"); socketThread.sendCustomizationRequest(CustomizeRequestType.NAME, nameField.getText().getBytes()); // TODO: 16/08/17 ajm412: Turn colors into byte array. Color color = boatColorPicker.getValue(); diff --git a/src/main/java/seng302/visualiser/controllers/FinishScreenViewController.java b/src/main/java/seng302/visualiser/controllers/FinishScreenViewController.java index b2e49f1a..32c1a4d3 100644 --- a/src/main/java/seng302/visualiser/controllers/FinishScreenViewController.java +++ b/src/main/java/seng302/visualiser/controllers/FinishScreenViewController.java @@ -15,10 +15,12 @@ import javafx.fxml.Initializable; import javafx.scene.control.TableColumn; import javafx.scene.control.TableView; import javafx.scene.control.cell.PropertyValueFactory; +import javafx.scene.input.MouseEvent; import javafx.scene.layout.AnchorPane; import javafx.scene.layout.GridPane; import javafx.scene.layout.Pane; import seng302.model.ClientYacht; +import seng302.utilities.Sounds; public class FinishScreenViewController implements Initializable { @@ -85,6 +87,11 @@ public class FinishScreenViewController implements Initializable { } public void switchToStartScreenView() { + Sounds.playButtonClick(); setContentPane("/views/StartScreenView.fxml"); } + + public void playButtonHoverSound(MouseEvent mouseEvent) { + Sounds.playHoverSound(); + } } diff --git a/src/main/java/seng302/visualiser/controllers/LobbyController.java b/src/main/java/seng302/visualiser/controllers/LobbyController.java index 4dc5293f..00c7febd 100644 --- a/src/main/java/seng302/visualiser/controllers/LobbyController.java +++ b/src/main/java/seng302/visualiser/controllers/LobbyController.java @@ -16,6 +16,7 @@ import javafx.scene.control.Button; import javafx.scene.control.TextArea; import javafx.scene.image.Image; import javafx.scene.image.ImageView; +import javafx.scene.input.MouseEvent; import javafx.scene.paint.Color; import javafx.scene.text.Text; import javafx.stage.Stage; @@ -23,6 +24,7 @@ import seng302.gameServer.GameStages; import seng302.gameServer.GameState; import seng302.model.Colors; import seng302.model.RaceState; +import seng302.utilities.Sounds; import seng302.visualiser.ClientToServerThread; /** @@ -31,6 +33,10 @@ import seng302.visualiser.ClientToServerThread; */ public class LobbyController { + public void playButtonHoverSound(MouseEvent mouseEvent) { + Sounds.playHoverSound(); + } + public enum CloseStatus { LEAVE, READY @@ -153,6 +159,7 @@ public class LobbyController { @FXML public void customize() { + Sounds.playButtonClick(); Parent root; try { FXMLLoader fxmlLoader = new FXMLLoader(LobbyController.class.getResource("/views/customizeView.fxml")); @@ -184,6 +191,7 @@ public class LobbyController { @FXML public void leaveLobbyButtonPressed() { + Sounds.playButtonClick(); // TODO: 10/07/17 wmu16 - Finish function! GameState.setCurrentStage(GameStages.CANCELLED); // TODO: 20/07/17 wmu16 - Implement some way of terminating the game @@ -193,6 +201,7 @@ public class LobbyController { @FXML public void readyButtonPressed() { + Sounds.playButtonClick(); GameState.setCurrentStage(GameStages.PRE_RACE); // Do countdown logic here diff --git a/src/main/java/seng302/visualiser/controllers/RaceViewController.java b/src/main/java/seng302/visualiser/controllers/RaceViewController.java index 74686a92..7f6a14b6 100644 --- a/src/main/java/seng302/visualiser/controllers/RaceViewController.java +++ b/src/main/java/seng302/visualiser/controllers/RaceViewController.java @@ -9,6 +9,7 @@ import java.util.TimerTask; import java.util.concurrent.TimeUnit; import javafx.animation.Timeline; import javafx.application.Platform; +import javafx.beans.property.ReadOnlyBooleanProperty; import javafx.collections.FXCollections; import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; @@ -25,6 +26,8 @@ import javafx.scene.control.Button; import javafx.scene.control.CheckBox; import javafx.scene.control.ComboBox; import javafx.scene.control.Slider; +import javafx.scene.control.TextField; +import javafx.scene.layout.AnchorPane; import javafx.scene.layout.GridPane; import javafx.scene.layout.Pane; import javafx.scene.layout.VBox; @@ -41,18 +44,31 @@ import seng302.model.RaceState; import seng302.model.mark.CompoundMark; import seng302.model.mark.Mark; import seng302.model.stream.xml.parser.RaceXMLData; +import seng302.model.token.Token; +import seng302.utilities.Sounds; import seng302.visualiser.GameView; import seng302.visualiser.controllers.annotations.Annotation; import seng302.visualiser.controllers.annotations.ImportantAnnotationController; import seng302.visualiser.controllers.annotations.ImportantAnnotationDelegate; import seng302.visualiser.controllers.annotations.ImportantAnnotationsState; import seng302.visualiser.fxObjects.BoatObject; +import seng302.visualiser.fxObjects.ChatHistory; /** * Controller class that manages the display of a race */ public class RaceViewController extends Thread implements ImportantAnnotationDelegate { + private final int CHAT_LIMIT = 128; + + @FXML + private Pane basePane; + @FXML + private Button chatSend; + @FXML + private Pane chatHistoryHolder; + @FXML + private TextField chatInput; @FXML private LineChart raceSparkLine; @FXML @@ -85,12 +101,17 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel private GameView gameView; private RaceState raceState; + private ChatHistory chatHistory; + private Timeline timerTimeline; private Timer timer = new Timer(); private List> sparkLineData = new ArrayList<>(); private ImportantAnnotationsState importantAnnotations; + private ObservableList selectionComboBoxList = FXCollections.observableArrayList(); public void initialize() { + Sounds.stopMusic(); + Sounds.playRaceMusic(); // Load a default important annotation state importantAnnotations = new ImportantAnnotationsState(); @@ -101,10 +122,30 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel raceSparkLine.visibleProperty().setValue(false); raceSparkLine.getYAxis().setAutoRanging(false); sparklineYAxis.setTickMarkVisible(false); - positionVbox.getStylesheets().add(getClass().getResource("/css/master.css").toString()); selectAnnotationBtn.setOnAction(event -> loadSelectAnnotationView()); + chatInput.lengthProperty().addListener((obs, oldLen, newLen) -> { + if (newLen.intValue() > CHAT_LIMIT) { + chatInput.setText(chatInput.getText().substring(0, CHAT_LIMIT)); + } + }); + chatHistory = new ChatHistory(); + chatHistoryHolder.getChildren().addAll(chatHistory); + chatHistory.prefWidthProperty().bind( + chatHistoryHolder.widthProperty() + ); + chatHistory.prefHeightProperty().bind( + chatHistoryHolder.heightProperty() + ); +// chatHistory.setFitToWidth(true); +// chatHistory.setFitToHeight(true); +// chatHistory.textProperty().addListener((obs, oldValue, newValue) -> { +// chatHistory.setScrollTop(Double.MAX_VALUE); +// }); + contentAnchorPane.setOnMouseClicked((event) -> + contentAnchorPane.requestFocus() + ); } public void loadRace ( @@ -116,12 +157,6 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel this.markers = raceData.getCompoundMarks(); this.raceState = raceState; - initializeUpdateTimer(); - initialiseFPSCheckBox(); - initialiseAnnotationSlider(); - initialiseBoatSelectionComboBox(); - initialiseSparkLine(); - raceState.getPlayerPositions().addListener((ListChangeListener) c -> { while (c.next()) { if (c.wasPermutated()) { @@ -137,10 +172,10 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel Platform.runLater(() -> contentAnchorPane.getChildren().add(0, gameView)); gameView.setBoats(new ArrayList<>(participants.values())); gameView.updateBorder(raceData.getCourseLimit()); + gameView.updateTokens(raceData.getTokens()); gameView.updateCourse( new ArrayList<>(raceData.getCompoundMarks().values()), raceData.getMarkSequence() ); - gameView.enableZoom(); gameView.setBoatAsPlayer(player); gameView.startRace(); @@ -155,6 +190,20 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel updateWindDirection(raceState.windDirectionProperty().doubleValue()); updateWindSpeed(raceState.getWindSpeed()); gameView.setWindDir(raceState.windDirectionProperty().doubleValue()); + chatInput.focusedProperty().addListener((obs, oldValue, newValue) -> { + if (newValue) { + gameView.disableZoom(); + } else { + gameView.enableZoom(); + } + }); + Platform.runLater(() -> { + initializeUpdateTimer(); + initialiseFPSCheckBox(); + initialiseAnnotationSlider(); + initialiseBoatSelectionComboBox(); + initialiseSparkLine(); + }); } /** @@ -305,13 +354,6 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel ) ); } -// XYChart.Series positionData = sparkLineData.get(yacht.getSourceID()); -// positionData.getData().add( -// new XYChart.Data<>( -// Integer.toString(legNumber), -// 1.0 + participants.size() - yacht.getPlacing() -// ) -// ); } @@ -533,10 +575,8 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel * for the combobox to take action upon selection */ private void initialiseBoatSelectionComboBox() { - yachtSelectionComboBox.setItems( - FXCollections.observableArrayList(participants.values()) - ); - //Null check is if the listener is fired but nothing selected + selectionComboBoxList.setAll(participants.values()); + yachtSelectionComboBox.setItems(selectionComboBoxList); yachtSelectionComboBox.valueProperty().addListener((obs, lastSelection, selectedBoat) -> { if (selectedBoat != null) { gameView.selectBoat(selectedBoat); @@ -621,5 +661,26 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel public void updateRaceData (RaceXMLData raceData) { this.courseData = raceData; gameView.updateBorder(raceData.getCourseLimit()); + gameView.updateTokens(raceData.getTokens()); } + + public ReadOnlyBooleanProperty getSendPressedProperty() { + return chatSend.pressedProperty(); + } + + public boolean isChatInputFocused() { + return chatInput.focusedProperty().getValue(); + } + + public String readChatInput() { + String chat = chatInput.getText(); + chatInput.clear(); + basePane.requestFocus(); + return chat; + } + + public void updateChatHistory(Paint playerColour, String newMessage) { + Platform.runLater(() -> chatHistory.addMessage(playerColour, newMessage)); + } + } \ No newline at end of file diff --git a/src/main/java/seng302/visualiser/controllers/StartScreenController.java b/src/main/java/seng302/visualiser/controllers/StartScreenController.java index 1a9db1a1..37d9ef62 100644 --- a/src/main/java/seng302/visualiser/controllers/StartScreenController.java +++ b/src/main/java/seng302/visualiser/controllers/StartScreenController.java @@ -6,12 +6,16 @@ import java.net.NetworkInterface; import java.net.URL; import java.util.Enumeration; import java.util.ResourceBundle; +import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.fxml.Initializable; import javafx.scene.control.TextField; +import javafx.scene.control.ToggleButton; +import javafx.scene.input.MouseEvent; import javafx.scene.layout.AnchorPane; import javafx.scene.layout.GridPane; import seng302.gameServer.GameState; +import seng302.utilities.Sounds; import seng302.visualiser.GameClient; /** @@ -20,114 +24,56 @@ import seng302.visualiser.GameClient; */ public class StartScreenController implements Initializable { + @FXML + private ToggleButton muteMusicButton; + @FXML + private ToggleButton muteSoundsButton; @FXML private TextField ipTextField; @FXML - private TextField portTextField; - @FXML - private GridPane startScreen2; - @FXML private AnchorPane holder; - GameClient gameClient; + private GameClient gameClient; public void initialize(URL url, ResourceBundle resourceBundle) { + Sounds.stopMusic(); + Sounds.stopSoundEffects(); + Sounds.playMenuMusic(); + if (Sounds.isMusicMuted()) { + muteMusicButton.setText("UnMute Music"); + } else { + muteMusicButton.setText("Mute Music"); + } + if (Sounds.isSoundEffectsMuted()) { + muteSoundsButton.setText("UnMute Sounds"); + } else { + muteSoundsButton.setText("Mute Sounds"); + } + Sounds.setMutes(); // gameClient = new GameClient(holder); } -// -// /** -// * Loads the fxml content into the parent pane -// * @param jfxUrl -// * @return the controller of the fxml -// */ -// private Object setContentPane(String jfxUrl) { -// try { -// AnchorPane contentPane = (AnchorPane) startScreen2.getParent(); -// contentPane.getChildren().removeAll(); -// contentPane.getChildren().clear(); -// contentPane.getStylesheets().add(getClass().getResource("/css/master.css").toString()); -// FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(jfxUrl)); -// contentPane.getChildren().addAll((Pane) fxmlLoader.load()); -// -// return fxmlLoader.getController(); -// } catch (IOException e) { -// e.printStackTrace(); -// } -// return null; -// } - /** - * ATTEMPTS TO: - * Sets up a new game state with your IP address as designated as the host. - * Starts a thread to listen for incoming connections. - * Starts a client to server thread and connects to own ip. - * Switches to the lobby screen + * Creates an instance of GameClient and runs it as a host. */ @FXML public void hostButtonPressed() { -// new GameState(getLocalHostIp()); + Sounds.playButtonClick(); gameClient = new GameClient(holder); gameClient.runAsHost(getLocalHostIp(), 4942); -// try { -//// String ipAddress = InetAddress.getLocalHost().getHostAddress(); -//// new GameState(ipAddress); -//// new MainServerThread(); -//// ClientToServerThread clientToServerThread = new ClientToServerThread("localhost", 4950); -//// controller.setClientToServerThread(clientToServerThread); -// // get the lobby controller so that we can pass the game server thread to it -// new GameState(getLocalHostIp()); -// MainServerThread mainServerThread = new MainServerThread(); -//// ClientState.setHost(true); -// // host will connect and handshake to itself after setting up the server -// // TODO: 24/07/17 wmu16 - Make port number some static global type constant? -//// ClientToServerThread clientToServerThread = new ClientToServerThread(ClientState.getHostIp(), 4942); -//// ClientState.setConnectedToHost(true); -//// controller.setClientToServerThread(clientToServerThread); -// LobbyController lobbyController = (LobbyController) setContentPane("/views/LobbyView.fxml"); -// lobbyController.setMainServerThread(mainServerThread); -// } catch (Exception e) { -// Alert alert = new Alert(AlertType.ERROR); -// alert.setHeaderText("Cannot host"); -// alert.setContentText("Oops, failed to host, try to restart."); -// alert.showAndWait(); -// e.printStackTrace(); -// } } /** - * ATTEMPTS TO: - * Connect to an ip address and port using the ip and port specified on start screen. - * Starts a Client To Server Thread to maintain connection to host. - * Switch view to lobby view. + * Creates an instance of GameClient and runs it has a client. */ @FXML public void connectButtonPressed() { // TODO: 10/07/17 wmu16 - Finish function + Sounds.playButtonClick(); gameClient = new GameClient(holder); gameClient.runAsClient(ipTextField.getText().trim().toLowerCase(), 4942); - -// try { -// String ipAddress = ipTextField.getText().trim().toLowerCase(); -// Integer port = Integer.valueOf(portTextField.getText().trim()); -// -//// ClientToServerThread clientToServerThread = new ClientToServerThread(ipAddress, port); -//// ClientState.setHost(false); -//// ClientState.setConnectedToHost(true); -// -//// controller.setClientToServerThread(clientToServerThread); -//// setContentPane("/views/LobbyView.fxml"); -// } catch (Exception e) { -// Alert alert = new Alert(AlertType.ERROR); -// alert.setHeaderText("Cannot reach the host"); -// alert.setContentText("Please check your host IP address."); -// alert.showAndWait(); -// } } -// public void setController(Controller controller) { -// this.controller = controller; -// } /** * Gets the local host ip address and sets this ip to ClientState. @@ -162,7 +108,30 @@ public class StartScreenController implements Initializable { if (ipAddress == null) { System.out.println("[HOST] Cannot obtain local host ip address."); } -// ClientState.setHostIp(ipAddress); return ipAddress; } + + public void toggleMusic(ActionEvent actionEvent) { + Sounds.toggleMuteMusic(); + Sounds.playButtonClick(); + if (Sounds.isMusicMuted()) { + muteMusicButton.setText("UnMute Music"); + } else { + muteMusicButton.setText("Mute Music"); + } + } + + public void toggleSounds(ActionEvent actionEvent) { + Sounds.toggleMuteEffects(); + Sounds.playButtonClick(); + if (Sounds.isSoundEffectsMuted()) { + muteSoundsButton.setText("UnMute Sounds"); + } else { + muteSoundsButton.setText("Mute Sounds"); + } + } + + public void playButtonHoverSound(MouseEvent mouseEvent) { + Sounds.playHoverSound(); + } } diff --git a/src/main/java/seng302/visualiser/fxObjects/ChatHistory.java b/src/main/java/seng302/visualiser/fxObjects/ChatHistory.java new file mode 100644 index 00000000..93f5a58b --- /dev/null +++ b/src/main/java/seng302/visualiser/fxObjects/ChatHistory.java @@ -0,0 +1,75 @@ +package seng302.visualiser.fxObjects; + +import java.util.Arrays; +import javafx.collections.ListChangeListener; +import javafx.scene.Node; +import javafx.scene.control.ScrollPane; +import javafx.scene.layout.Background; +import javafx.scene.paint.Color; +import javafx.scene.paint.Paint; +import javafx.scene.text.Text; +import javafx.scene.text.TextFlow; + +/** + * Extension of a ScrollPane that contains a TextFlow. Has an addMessage() function to parse and + * display chatter text. + */ +public class ChatHistory extends ScrollPane { + + private TextFlow textFlow = new TextFlow(); + + public ChatHistory() { + this.setContent(textFlow); + this.setFitToWidth(true); + this.setFitToHeight(true); + this.setMaxHeight(Double.MAX_VALUE); + this.setMaxWidth(Double.MAX_VALUE); + this.setVbarPolicy(ScrollBarPolicy.AS_NEEDED); + this.setHbarPolicy(ScrollBarPolicy.NEVER); + this.lookup(".scroll-pane").setStyle("-fx-background: rgba(255, 255, 255, 0.1); -fx-background-color: rgba(255, 255, 255, 0.1);"); + + this.textFlow.setStyle( + "-fx-background: rgba(255, 255, 255, 0.1); -fx-background-color: rgba(255, 255, 255, 0.1);" + ); + //This makes the window auto scroll. + textFlow.getChildren().addListener((ListChangeListener) c -> + this.setVvalue(1.0) + ); + //This just makes it so that the ChatHistory is on focus it passes it off to the parent. + this.parentProperty().addListener((obs, old, parent) -> + this.focusedProperty().addListener((obsVal, oldVal, onFocus) -> { + if (onFocus) { + parent.requestFocus(); + } + }) + ); + } + + /** + * Adds a message to chat history. Messages should be either of the form: + * "[HH:MM:ss] player_name: message_text" or + * "SERVER: message_text" + * @param colour The colour of the user sending the message + * @param Text The chatter text message to be displayed + */ + public void addMessage (Paint colour, String Text) { + String[] words = Text.split(":"); + if (words[0].trim().equals("SERVER")) { + Text text = new Text(Text + "\n\n"); + text.setStyle("-fx-font-weight: bolder"); + textFlow.getChildren().add(text); + } else { + Text timePlayer = new Text( + String.join(":", Arrays.copyOfRange(words, 0, 3)) + ":" + ); + timePlayer.setStyle("-fx-font-weight: bold"); + timePlayer.setFill(colour); + Text message = new Text( + String.join(":", Arrays.copyOfRange(words, 3, words.length)) + "\n\n" + ); + message.wrappingWidthProperty().bind(this.widthProperty()); + textFlow.getChildren().addAll(timePlayer, message); + } + + } +} diff --git a/src/main/resources/icons/muteIcon.png b/src/main/resources/icons/muteIcon.png new file mode 100644 index 00000000..47975cb8 Binary files /dev/null and b/src/main/resources/icons/muteIcon.png differ diff --git a/src/main/resources/icons/unMuteIcon.png b/src/main/resources/icons/unMuteIcon.png new file mode 100644 index 00000000..e5d1445e Binary files /dev/null and b/src/main/resources/icons/unMuteIcon.png differ diff --git a/src/main/resources/server_config/race.xml b/src/main/resources/server_config/race.xml index b2379fca..eb6354d7 100644 --- a/src/main/resources/server_config/race.xml +++ b/src/main/resources/server_config/race.xml @@ -12,6 +12,8 @@ + + diff --git a/src/main/resources/server_config/xml_templates/race.ftlh b/src/main/resources/server_config/xml_templates/race.ftlh index 8fa95216..88d2e22a 100644 --- a/src/main/resources/server_config/xml_templates/race.ftlh +++ b/src/main/resources/server_config/xml_templates/race.ftlh @@ -4,13 +4,16 @@ 15082901 Fleet - <#list boats as boat> - + + <#list tokens as token> + + + diff --git a/src/main/resources/sounds/Button-click-sound.mp3 b/src/main/resources/sounds/Button-click-sound.mp3 new file mode 100644 index 00000000..70bacd67 Binary files /dev/null and b/src/main/resources/sounds/Button-click-sound.mp3 differ diff --git a/src/main/resources/sounds/Chill-house-music-loop-116-bpm.wav b/src/main/resources/sounds/Chill-house-music-loop-116-bpm.wav new file mode 100644 index 00000000..4119e341 Binary files /dev/null and b/src/main/resources/sounds/Chill-house-music-loop-116-bpm.wav differ diff --git a/src/main/resources/sounds/Crash-sound-effect.mp3 b/src/main/resources/sounds/Crash-sound-effect.mp3 new file mode 100644 index 00000000..672540b9 Binary files /dev/null and b/src/main/resources/sounds/Crash-sound-effect.mp3 differ diff --git a/src/main/resources/sounds/Elevator-music.mp3 b/src/main/resources/sounds/Elevator-music.mp3 new file mode 100644 index 00000000..d637c4c4 Binary files /dev/null and b/src/main/resources/sounds/Elevator-music.mp3 differ diff --git a/src/main/resources/sounds/Gunshot-sound.mp3 b/src/main/resources/sounds/Gunshot-sound.mp3 new file mode 100644 index 00000000..4e81c60b Binary files /dev/null and b/src/main/resources/sounds/Gunshot-sound.mp3 differ diff --git a/src/main/resources/sounds/Happy-birthday-song.mp3 b/src/main/resources/sounds/Happy-birthday-song.mp3 new file mode 100644 index 00000000..aee030a7 Binary files /dev/null and b/src/main/resources/sounds/Happy-birthday-song.mp3 differ diff --git a/src/main/resources/sounds/Large-metal-door-slam.mp3 b/src/main/resources/sounds/Large-metal-door-slam.mp3 new file mode 100644 index 00000000..7844d974 Binary files /dev/null and b/src/main/resources/sounds/Large-metal-door-slam.mp3 differ diff --git a/src/main/resources/sounds/Music-loop-120-bpm.mp3 b/src/main/resources/sounds/Music-loop-120-bpm.mp3 new file mode 100644 index 00000000..68a1d4da Binary files /dev/null and b/src/main/resources/sounds/Music-loop-120-bpm.mp3 differ diff --git a/src/main/resources/sounds/Sms-notification.mp3 b/src/main/resources/sounds/Sms-notification.mp3 new file mode 100644 index 00000000..6965d388 Binary files /dev/null and b/src/main/resources/sounds/Sms-notification.mp3 differ diff --git a/src/main/resources/sounds/Sounds-of-the-ocean.mp3 b/src/main/resources/sounds/Sounds-of-the-ocean.mp3 new file mode 100644 index 00000000..ed040f3f Binary files /dev/null and b/src/main/resources/sounds/Sounds-of-the-ocean.mp3 differ diff --git a/src/main/resources/sounds/sms-tone.mp3 b/src/main/resources/sounds/sms-tone.mp3 new file mode 100644 index 00000000..eeb3bcf7 Binary files /dev/null and b/src/main/resources/sounds/sms-tone.mp3 differ diff --git a/src/main/resources/sounds/sound-over.wav b/src/main/resources/sounds/sound-over.wav new file mode 100644 index 00000000..c3e06199 Binary files /dev/null and b/src/main/resources/sounds/sound-over.wav differ diff --git a/src/main/resources/views/FinishScreenView.fxml b/src/main/resources/views/FinishScreenView.fxml index ab1e5835..0ebdf261 100644 --- a/src/main/resources/views/FinishScreenView.fxml +++ b/src/main/resources/views/FinishScreenView.fxml @@ -1,5 +1,10 @@ + + + + + @@ -10,6 +15,7 @@ + @@ -42,6 +48,6 @@ -