diff --git a/src/main/java/seng302/gameServer/GameState.java b/src/main/java/seng302/gameServer/GameState.java index 27f6855f..b41ec834 100644 --- a/src/main/java/seng302/gameServer/GameState.java +++ b/src/main/java/seng302/gameServer/GameState.java @@ -1,5 +1,13 @@ 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 org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -7,6 +15,21 @@ import org.w3c.dom.Document; import org.xml.sax.InputSource; import seng302.gameServer.messages.*; import seng302.model.*; +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; +import seng302.model.PolarTable; +import seng302.model.ServerYacht; import seng302.model.mark.CompoundMark; import seng302.model.mark.Mark; import seng302.model.mark.MarkOrder; @@ -28,11 +51,10 @@ 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; @@ -52,13 +74,13 @@ public class GameState implements Runnable { 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. private static String hostIpAddress; private static List players; private static Map yachts; - private static List tokens; private static Boolean isRaceStarted; private static GameStages currentStage; private static MarkOrder markOrder; @@ -68,36 +90,30 @@ public class GameState implements Runnable { private static Integer maxPlayers = 8; - 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<>(); - tokens = new ArrayList<>(); + 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(); @@ -122,6 +138,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; } @@ -134,16 +165,8 @@ public class GameState implements Runnable { return players; } - public static void addToken(Token token) { - tokens.add(token); - } - - public static List getTokens() { - return tokens; - } - - public static void clearTokens() { - tokens.clear(); + public static List getTokensInPlay() { + return tokensInPlay; } public static void addPlayer(Player player) { @@ -273,7 +296,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; @@ -288,9 +327,8 @@ public class GameState implements Runnable { checkPowerUpTimeout(yacht); yacht.runAutoPilot(); yacht.updateLocation(timeInterval); + checkCollision(yacht); if (yacht.getBoatStatus() != BoatStatus.FINISHED) { - checkCollision(yacht); - checkTokenPickUp(yacht); checkForLegProgression(yacht); raceFinished = false; } @@ -333,27 +371,38 @@ public class GameState implements Runnable { } /** - * Checks all tokens to see if a yacht has picked one up - * - * @param serverYacht The yacht to check for + * 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 void checkTokenPickUp(ServerYacht serverYacht) { - for (Token token : tokens) { + private static Token checkTokenPickUp(ServerYacht serverYacht) { + for (Token token : tokensInPlay) { Double distance = GeoUtility.getDistance(token, serverYacht.getLocation()); if (distance < YACHT_COLLISION_DISTANCE) { - tokens.remove(token); - serverYacht.powerUp(token.getTokenType()); - logger.debug("Yacht: " + serverYacht.getShortName() + " got powerup " + token - .getTokenType()); - notifyMessageListeners(MessageFactory.getRaceXML()); - break; + 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( @@ -369,35 +418,49 @@ 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) + ); + + 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) + ); + + 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)); } } @@ -405,9 +468,10 @@ public class GameState implements Runnable { private void updateVelocity(ServerYacht yacht) { Double trueWindAngle = Math.abs(windDirection - yacht.getHeading()); Double boatSpeedInKnots = PolarTable.getBoatSpeed(getWindSpeedKnots(), trueWindAngle); - Double maxBoatSpeed = GeoUtility.knotsToMMS(boatSpeedInKnots) * 4; + Double maxBoatSpeed = GeoUtility.knotsToMMS(boatSpeedInKnots) * speedMultiplier; if (yacht.getPowerUp() != null) { if (yacht.getPowerUp().equals(TokenType.BOOST)) { + // TODO: 11/09/17 wmu16 CHANGE THIS TO MAGIC NUMBER maxBoatSpeed *= 2; } } @@ -711,15 +775,14 @@ public class GameState implements Runnable { // TODO: 13/8/17 figure out the rounding side, rounded mark source ID and boat status. Message markRoundingMessage = new MarkRoundingMessage(0, 0, sourceID, RoundingBoatStatus.RACING, roundingMark.getRoundingSide(), markType, - currentMark.getId()); -// currentMarkSeqID + 1); + currentMarkSeqID + 1); notifyMessageListeners(markRoundingMessage); } private static void notifyMessageListeners(Message message) { - for (NewMessageListener mpl : markListeners) { - mpl.notify(message); + for (NewMessageListener ml : newMessageListeners) { + ml.notify(message); } } @@ -731,8 +794,37 @@ public class GameState implements Runnable { } + 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) { - markListeners.add(listener); + newMessageListeners.add(listener); } public static void setCustomizationFlag() { @@ -767,4 +859,16 @@ public class GameState implements Runnable { maxPlayers = newMax; } + 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 40d2d4cd..b577d59a 100644 --- a/src/main/java/seng302/gameServer/MainServerThread.java +++ b/src/main/java/seng302/gameServer/MainServerThread.java @@ -31,6 +31,9 @@ import java.util.*; * Created by wmu16 on 13/07/17. */ 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; @@ -135,29 +138,32 @@ public class MainServerThread implements Runnable, ClientConnectionDelegate { //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 sendBoatLocations() { + private void sendBoatLocations() { for (ServerYacht serverYacht : GameState.getYachts().values()) { broadcastMessage(MessageFactory.getBoatLocationMessage(serverYacht)); } } - public void sendSetupMessages() { + private void sendSetupMessages() { broadcastMessage(MessageFactory.getRaceXML()); broadcastMessage(MessageFactory.getRegattaXML()); broadcastMessage(MessageFactory.getBoatXML()); @@ -220,34 +226,10 @@ public class MainServerThread implements Runnable, ClientConnectionDelegate { timer.schedule(new TimerTask() { @Override public void run() { - spawnNewCoins(); + GameState.spawnNewToken(); broadcastMessage(MessageFactory.getRaceXML()); } - }, 0, 60000); - } - - /** - * Randomly select a subset of tokens from a pre defined superset - * Broadasts a new race status message to show this update - */ - private void spawnNewCoins() { - - List allTokens = new ArrayList<>(); - 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); - allTokens.add(token1); - allTokens.add(token2); - allTokens.add(token3); - allTokens.add(token4); - - GameState.clearTokens(); - Random random = new Random(); - Collections.shuffle(allTokens); - for (int i = 0; i < random.nextInt(allTokens.size()); i++) { - GameState.addToken(allTokens.get(i)); - } + }, 10000, 60000); } /** @@ -258,6 +240,9 @@ public class MainServerThread implements Runnable, ClientConnectionDelegate { @Override public void clientConnected(ServerToClientThread serverToClientThread) { 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(this::sendSetupMessages); serverToClientThread.addDisconnectListener(this::clientDisconnected); @@ -320,7 +305,7 @@ public class MainServerThread implements Runnable, ClientConnectionDelegate { }, 0, 500); - if (GameState.getCurrentStage() != GameStages.RACING) { + if (GameState.getCurrentStage() == GameStages.LOBBYING) { sendSetupMessages(); } } @@ -333,10 +318,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 index c71e6028..62b5ea0a 100644 --- a/src/main/java/seng302/gameServer/MessageFactory.java +++ b/src/main/java/seng302/gameServer/MessageFactory.java @@ -96,7 +96,7 @@ public class MessageFactory { public static XMLMessage getRaceXML() { List yachts = new ArrayList<>(GameState.getYachts().values()); - List tokens = GameState.getTokens(); + List tokens = GameState.getTokensInPlay(); RaceXMLTemplate raceXMLTemplate = new RaceXMLTemplate(yachts, tokens); xmlGenerator.setRaceTemplate(raceXMLTemplate); @@ -124,7 +124,7 @@ public class MessageFactory { public static XMLMessage getBoatXML() { List yachts = new ArrayList<>(GameState.getYachts().values()); - List tokens = GameState.getTokens(); + List tokens = GameState.getTokensInPlay(); RaceXMLTemplate raceXMLTemplate = new RaceXMLTemplate(yachts, tokens); xmlGenerator.setRaceTemplate(raceXMLTemplate); 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 df5d3141..f0984dd4 100644 --- a/src/main/java/seng302/gameServer/ServerToClientThread.java +++ b/src/main/java/seng302/gameServer/ServerToClientThread.java @@ -21,6 +21,27 @@ import java.util.concurrent.ThreadLocalRandom; import java.util.stream.Collectors; import java.util.zip.CRC32; import java.util.zip.Checksum; +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; +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.RaceXMLTemplate; +import seng302.model.stream.xml.generator.RegattaXMLTemplate; +import seng302.model.token.Token; +import seng302.utilities.XMLGenerator; /** * A class describing a single connection to a Client for the purposes of sending and receiving on @@ -58,6 +79,7 @@ public class ServerToClientThread implements Runnable { private ClientType clientType; private Boolean isRegistered = false; + private Boolean isHost = false; private XMLGenerator xmlGenerator; @@ -182,7 +204,12 @@ public class ServerToClientThread implements Runnable { 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)); @@ -293,10 +320,6 @@ public class ServerToClientThread implements Runnable { return yacht; } - public void sendCollisionMessage(Integer yachtId) { - sendMessage(new YachtEventCodeMessage(yachtId)); - } - public void addConnectionListener(ConnectionListener listener) { connectionListeners.add(listener); } @@ -316,4 +339,8 @@ public class ServerToClientThread implements Runnable { 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/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/utilities/Sounds.java b/src/main/java/seng302/utilities/Sounds.java new file mode 100644 index 00000000..b929205d --- /dev/null +++ b/src/main/java/seng302/utilities/Sounds.java @@ -0,0 +1,188 @@ +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 MediaPlayer hoverSoundPlayer; + + private static boolean hoverInitialized = false; + 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) { + if (!hoverInitialized) { + Media crashSound = new Media( + Sounds.class.getClassLoader().getResource("sounds/Error-sound-effect.mp3") + .toString()); + hoverSoundPlayer = new MediaPlayer(crashSound); + hoverInitialized = true; + } + hoverSoundPlayer.setVolume(0.5); + if (hoverSoundPlayer != null) { + hoverSoundPlayer.stop(); + } + 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/visualiser/ClientToServerThread.java b/src/main/java/seng302/visualiser/ClientToServerThread.java index def7957c..e53aed22 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; @@ -281,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"); @@ -292,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 0b58de39..e4dd8a74 100644 --- a/src/main/java/seng302/visualiser/GameClient.java +++ b/src/main/java/seng302/visualiser/GameClient.java @@ -1,10 +1,13 @@ package seng302.visualiser; import java.io.IOException; +import java.text.SimpleDateFormat; import java.time.ZoneId; import java.time.ZoneOffset; import java.util.Collections; import java.util.List; +import java.util.ArrayList; +import java.util.Date; import java.util.Map; import java.util.TimeZone; import javafx.application.Platform; @@ -14,14 +17,17 @@ 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 seng302.gameServer.GameStages; +import javafx.util.Pair; import seng302.gameServer.GameState; import seng302.gameServer.MainServerThread; import seng302.gameServer.ServerDescription; 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; @@ -32,6 +38,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.XMLGenerator; import seng302.utilities.XMLParser; @@ -56,6 +63,8 @@ public class GameClient { private RaceState raceState = new RaceState(); private LobbyController lobbyController; + private ArrayList finishedBoats = new ArrayList<>(); + private ObservableList clientLobbyList = FXCollections.observableArrayList(); /** @@ -75,12 +84,11 @@ public class GameClient { public void runAsClient(String ipAddress, Integer portNumber) { try { startClientToServerThread(ipAddress, portNumber); - -// socketThread.addDisconnectionListener((cause) -> { -// showConnectionError(cause); -// Platform.runLater(this::loadStartScreen); -// }); - + socketThread.addDisconnectionListener((cause) -> { + showConnectionError(cause); + tearDownConnection(); + Platform.runLater(this::loadStartScreen); + }); socketThread.addStreamObserver(this::parsePackets); ViewManager.getInstance().setPlayerList(clientLobbyList); @@ -203,9 +211,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()); @@ -233,6 +251,7 @@ public class GameClient { switch (packet.getType()) { case RACE_STATUS: processRaceStatusUpdate(StreamParser.extractRaceStatus(packet)); + if (raceState.getTimeTillStart() <= 5000) { startRaceIfAllDataReceived(); } @@ -288,8 +307,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() + ); } } } @@ -333,7 +365,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() ); @@ -348,6 +379,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(); } } @@ -363,6 +397,7 @@ public class GameClient { } if (raceFinished) { + Sounds.playFinishSound(); close(); loadFinishScreenView(); } @@ -385,7 +420,13 @@ public class GameClient { * Handle the key-pressed event from the text field. * @param e The key event triggering this call */ - public void keyPressed(KeyEvent e) { + 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; @@ -394,12 +435,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; } } - public void keyReleased(KeyEvent e) { + 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 @@ -420,31 +465,43 @@ 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 void startGame(){ - server.startGame(); - } - - public ClientToServerThread getServerThread() { - return socketThread; - } - - public List getPlayerNames(){ - return Collections.unmodifiableList(clientLobbyList.sorted()); - } - - public void stopGame() { - GameState.setCurrentStage(GameStages.CANCELLED); - if (server != null) server.terminate(); - if (socketThread != null) socketThread.setSocketToClose(); - } +// public void startGame(){ +// server.startGame(); +// } +// +// public ClientToServerThread getServerThread() { +// return socketThread; +// } +// +// public List getPlayerNames(){ +// return Collections.unmodifiableList(clientLobbyList.sorted()); +// } +// +// public void stopGame() { +// GameState.setCurrentStage(GameStages.CANCELLED); +// if (server != null) server.terminate(); +// if (socketThread != null) socketThread.setSocketToClose(); +// } } diff --git a/src/main/java/seng302/visualiser/GameView.java b/src/main/java/seng302/visualiser/GameView.java index 08948f21..fb3e76ec 100644 --- a/src/main/java/seng302/visualiser/GameView.java +++ b/src/main/java/seng302/visualiser/GameView.java @@ -11,6 +11,8 @@ import javafx.animation.KeyFrame; import javafx.animation.KeyValue; import javafx.animation.Timeline; import javafx.application.Platform; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; import javafx.collections.ObservableList; import javafx.geometry.Point2D; import javafx.scene.*; @@ -24,6 +26,7 @@ import javafx.scene.paint.Paint; import javafx.scene.shape.Circle; import javafx.scene.shape.Polygon; import javafx.scene.text.Text; +import javafx.scene.transform.Scale; import javafx.util.Duration; import seng302.gameServer.messages.RoundingSide; import seng302.model.ClientYacht; @@ -42,6 +45,13 @@ import seng302.visualiser.fxObjects.assets_2D.MarkArrowFactory; import seng302.visualiser.fxObjects.assets_2D.Marker; import seng302.visualiser.fxObjects.assets_3D.ModelFactory; import seng302.visualiser.fxObjects.assets_3D.ModelType; +import seng302.utilities.Sounds; +import seng302.visualiser.fxObjects.AnnotationBox; +import seng302.visualiser.fxObjects.BoatObject; +import seng302.visualiser.fxObjects.CourseBoundary; +import seng302.visualiser.fxObjects.Gate; +import seng302.visualiser.fxObjects.MarkArrowFactory; +import seng302.visualiser.fxObjects.Marker; import seng302.visualiser.map.Boundary; import seng302.visualiser.map.CanvasMap; @@ -51,7 +61,7 @@ import seng302.visualiser.map.CanvasMap; public class GameView extends Pane { private double bufferSize = 50; - private double panelWidth = 1260; // it should be 1280 but, minors 40 to cancel the bias. + private double panelWidth = 1280; private double panelHeight = 960; private double canvasWidth = 1100; private double canvasHeight = 920; @@ -63,7 +73,7 @@ public class GameView extends Pane { private double referencePointX, referencePointY; private double metersPerPixelX, metersPerPixelY; - final double SCALE_DELTA = 1.1; + private boolean isZoom = false; private Text fpsDisplay = new Text(); private Polygon raceBorder = new CourseBoundary(); @@ -165,6 +175,48 @@ public class GameView extends Pane { }); initializeTimer(); gameObjects.addAll(mapImage, raceBorder, markers, tokens, pl); + // TODO: 11/09/17 ajm412: do you even zoom bro? +// this.sceneProperty().addListener(((observable, oldValue, scene) -> { +// if (scene != null) { +// setupZoom(); +// } else { +// disableZoom(); +// } +// })); +// +// this.widthProperty().addListener(new ChangeListener() { +// @Override +// public void changed(ObservableValue observable, Number oldValue, +// Number newValue) { +// scaleFactor = getWidth() / panelWidth; +// +// if (panelHeight * scaleFactor < getHeight()) { +// Scale scale = new Scale(scaleFactor, scaleFactor, 0, 0); +// getTransforms().remove(0, getTransforms().size()); +// getTransforms().add(scale); +// +// setPrefWidth(getWidth() / scaleFactor); +// setPrefHeight(getHeight() / scaleFactor); +// } +// } +// }); +// +// this.heightProperty().addListener(new ChangeListener() { +// @Override +// public void changed(ObservableValue observable, Number oldValue, +// Number newValue) { +// scaleFactor = getHeight() / panelHeight; +// +// if (panelWidth * scaleFactor < getWidth()) { +// Scale scale = new Scale(scaleFactor, scaleFactor, 0, 0); +// getTransforms().remove(0, getTransforms().size()); +// getTransforms().add(scale); +// +// setPrefWidth(getWidth() / scaleFactor); +// setPrefHeight(getHeight() / scaleFactor); +// } +// } +// }); } private void initializeTimer() { @@ -456,18 +508,18 @@ public class GameView extends Pane { raceBorder.getPoints().setAll(boundaryPoints); } - /** - * Rescales the race to the size of the window. - * - * @param limitingCoordinates the set of geo points that contains the extremities of the race. - */ - private void rescaleRace(List limitingCoordinates) { - //Check is called once to avoid unnecessarily change the course limits once the race is running - findMinMaxPoint(limitingCoordinates); - double minLonToMaxLon = scaleRaceExtremities(); - calculateReferencePointLocation(minLonToMaxLon); -// drawGoogleMap(); - } +// /** +// * Rescales the race to the size of the window. +// * +// * @param limitingCoordinates the set of geo points that contains the extremities of the race. +// */ +// private void rescaleRace(List limitingCoordinates) { +// //Check is called once to avoid unnecessarily change the course limits once the race is running +// findMinMaxPoint(limitingCoordinates); +// double minLonToMaxLon = scaleRaceExtremities(); +// calculateReferencePointLocation(minLonToMaxLon); +//// drawGoogleMap(); +// } /** * Replaces all tokens in the course with those passed in @@ -483,27 +535,46 @@ public class GameView extends Pane { tokenObject.setLayoutY(location.getY()); mapTokens.add(tokenObject); } - Platform.runLater(() -> { tokens.getChildren().clear(); tokens.getChildren().addAll(mapTokens); }); } - // TODO: 16/08/17 initialize zooming internal to GameView only +// // TODO: 16/08/17 initialize zooming internal to GameView only +// /** +// * Enables zoom. Has to be called after this is added to a scene. +// */ +// 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; +// } + /** - * Enables zoom. Has to be called after this is added to a scene. + * Rescales the race to the size of the window. + * + * @param limitingCoordinates the set of geo points that contains the extremities of the race. */ - public void enableZoom () { - if (this.getScene() != null) { - this.getScene().addEventHandler(KeyEvent.KEY_PRESSED, (event) -> { - if (event.getCode() == KeyCode.Z) { - zoomIn(); - } else if (event.getCode() == KeyCode.X) { - zoomOut(); - } - }); - } + private void rescaleRace(List limitingCoordinates) { + //Check is called once to avoid unnecessarily change the course limits once the race is running + findMinMaxPoint(limitingCoordinates); + double minLonToMaxLon = scaleRaceExtremities(); + calculateReferencePointLocation(minLonToMaxLon); +// drawGoogleMap(); } private void setSelectedBoat(BoatObject bo, Boolean isSelected) { @@ -825,17 +896,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. -// System.out.println(markerObjects); - if (compoundMark != null) { - for (Mark mark : compoundMark.getMarks()) { -// System.out.println("markerObjects.get(mark) = " + markerObjects.get(mark)); - 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(); @@ -849,6 +914,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_old.java b/src/main/java/seng302/visualiser/controllers/CustomizationController_old.java index de3090c1..b8f0b146 100644 --- a/src/main/java/seng302/visualiser/controllers/CustomizationController_old.java +++ b/src/main/java/seng302/visualiser/controllers/CustomizationController_old.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_old { @@ -34,7 +35,8 @@ public class CustomizationController_old { @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 7a0fe2f2..74da890c 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,12 @@ public class FinishScreenViewController implements Initializable { } public void switchToStartScreenView() { - setContentPane("/views/StartScreenView_old.fxml"); + Sounds.playButtonClick(); + //TODO merge fix + setContentPane("/views/StartScreenView.fxml"); + } + + public void playButtonHoverSound(MouseEvent mouseEvent) { + Sounds.playHoverSound(); } } diff --git a/src/main/java/seng302/visualiser/controllers/RaceViewController.java b/src/main/java/seng302/visualiser/controllers/RaceViewController.java index d8b28e81..a673a5f4 100644 --- a/src/main/java/seng302/visualiser/controllers/RaceViewController.java +++ b/src/main/java/seng302/visualiser/controllers/RaceViewController.java @@ -2,6 +2,8 @@ package seng302.visualiser.controllers; 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; import javafx.fxml.FXML; @@ -17,7 +19,8 @@ import javafx.scene.control.Button; import javafx.scene.control.CheckBox; import javafx.scene.control.ComboBox; import javafx.scene.control.Slider; -import javafx.scene.layout.AnchorPane; +import javafx.scene.control.TextField; +import javafx.scene.layout.GridPane; import javafx.scene.layout.Pane; import javafx.scene.layout.StackPane; import javafx.scene.layout.VBox; @@ -32,10 +35,15 @@ import seng302.model.RaceState; import seng302.model.mark.CompoundMark; import seng302.model.mark.Mark; import seng302.model.stream.xml.parser.RaceXMLData; +import seng302.utilities.Sounds; +import seng302.visualiser.GameView; +import seng302.visualiser.controllers.annotations.Annotation; import seng302.visualiser.GameView3D; 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; import seng302.visualiser.fxObjects.assets_2D.BoatObject; import seng302.visualiser.fxObjects.assets_2D.WindArrow; @@ -48,6 +56,16 @@ import java.util.concurrent.TimeUnit; */ 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 @@ -60,6 +78,7 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel private Text timerLabel; @FXML private StackPane contentAnchorPane; + private GridPane contentGridPane; @FXML private AnchorPane rvAnchorPane; @FXML @@ -84,13 +103,18 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel private GameView3D gameView; private RaceState raceState; + private ChatHistory chatHistory; + private Timeline timerTimeline; private Timer timer = new Timer(); private List> sparkLineData = new ArrayList<>(); private ImportantAnnotationsState importantAnnotations; private Polyline windArrow = new WindArrow(Color.LIGHTGRAY); + private ObservableList selectionComboBoxList = FXCollections.observableArrayList(); public void initialize() { + Sounds.stopMusic(); + Sounds.playRaceMusic(); // Load a default important annotation state //importantAnnotations = new ImportantAnnotationsState(); @@ -103,6 +127,10 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel //sparklineYAxis.setTickMarkVisible(false); //positionVbox.getStylesheets().add(getClass().getResource("/css/master.css").toString()); + raceSparkLine.visibleProperty().setValue(false); + raceSparkLine.getYAxis().setAutoRanging(false); + sparklineYAxis.setTickMarkVisible(false); + positionVbox.getStylesheets().add(getClass().getResource("/css/master.css").toString()); //selectAnnotationBtn.setOnAction(event -> loadSelectAnnotationView()); // rvAnchorPane.prefWidthProperty().bind(ViewManager.getInstance().getDecorator().widthProperty()); @@ -112,6 +140,28 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel // windArrow.setLayoutX(windArrowHolder.getWidth() / 2); // windArrow.setLayoutY(windArrowHolder.getHeight() / 2); + 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); +// }); + contentGridPane.setOnMouseClicked((event) -> + contentGridPane.requestFocus() + ); } public void loadRace ( @@ -123,12 +173,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()) { @@ -165,6 +209,33 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel // updateWindSpeed(raceState.getWindSpeed()); // }); // gameView.setWindDir(raceState.windDirectionProperty().doubleValue()); + + //TODO extract chat stuff +// raceState.addCollisionListener(gameView::drawCollision); +// raceState.windDirectionProperty().addListener((obs, oldDirection, newDirection) -> { +// gameView.setWindDir(newDirection.doubleValue()); +// updateWindDirection(newDirection.doubleValue()); +// }); +// raceState.windSpeedProperty().addListener((obs, oldSpeed, newSpeed) -> { +// updateWindSpeed(newSpeed.doubleValue()); +// }); +// 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(); +// }); } /** @@ -315,13 +386,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() -// ) -// ); } @@ -551,6 +615,15 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel // if (selectedBoat != null) { // gameView.selectBoat(selectedBoat); // } +// }); + + //TODO uncomment out +// selectionComboBoxList.setAll(participants.values()); +// yachtSelectionComboBox.setItems(selectionComboBoxList); +// yachtSelectionComboBox.valueProperty().addListener((obs, lastSelection, selectedBoat) -> { +// if (selectedBoat != null) { +// gameView.selectBoat(selectedBoat); +// } // }); } @@ -561,9 +634,9 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel FXMLLoader loader = new FXMLLoader(getClass().getResource("/views/FinishView.fxml")); try { - contentAnchorPane.getChildren().removeAll(); - contentAnchorPane.getChildren().clear(); - contentAnchorPane.getChildren().addAll((Pane) loader.load()); + contentGridPane.getChildren().removeAll(); + contentGridPane.getChildren().clear(); + contentGridPane.getChildren().addAll((Pane) loader.load()); } catch (javafx.fxml.LoadException e) { System.err.println(e.getCause().toString()); @@ -632,4 +705,24 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel 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 c9190226..7d406210 100644 --- a/src/main/java/seng302/visualiser/controllers/StartScreenController.java +++ b/src/main/java/seng302/visualiser/controllers/StartScreenController.java @@ -6,6 +6,7 @@ import java.io.IOException; import java.net.URL; import java.util.ResourceBundle; import javafx.application.Platform; +import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; import javafx.fxml.Initializable; @@ -19,10 +20,14 @@ import javafx.scene.control.Alert; import javafx.scene.control.Button; import javafx.scene.control.ListView; 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.ServerAdvertiser; import seng302.gameServer.ServerDescription; +import seng302.gameServer.GameState; +import seng302.utilities.Sounds; import seng302.visualiser.GameClient; import seng302.visualiser.ServerListener; import seng302.visualiser.ServerListenerDelegate; @@ -49,6 +54,23 @@ public class StartScreenController implements Initializable{ private Logger logger = LoggerFactory.getLogger(StartScreenController.class); private List servers; + 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); + } private void setInitialDropShadow(){ DropShadow dropShadow = new DropShadow(); @@ -57,7 +79,25 @@ public class StartScreenController implements Initializable{ dropShadow.setOffsetY(4.0); dropShadow.setColor(Color.color(0, 0, 0, 0.5)); headText.setEffect(dropShadow); + /** + * Creates an instance of GameClient and runs it as a host. + */ + @FXML + public void hostButtonPressed() { + Sounds.playButtonClick(); + gameClient = new GameClient(holder); + gameClient.runAsHost(getLocalHostIp(), 4942); + } + /** + * 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); } private void preloadServerListView(){ @@ -88,4 +128,27 @@ public class StartScreenController implements Initializable{ } + 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/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/Coin-pick-up-sound-effect.mp3 b/src/main/resources/sounds/Coin-pick-up-sound-effect.mp3 new file mode 100644 index 00000000..0846c286 Binary files /dev/null and b/src/main/resources/sounds/Coin-pick-up-sound-effect.mp3 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/Error-sound-effect.mp3 b/src/main/resources/sounds/Error-sound-effect.mp3 new file mode 100644 index 00000000..e27176ff Binary files /dev/null and b/src/main/resources/sounds/Error-sound-effect.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/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 @@ -