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..86193c57 100644 --- a/src/main/java/seng302/gameServer/GameState.java +++ b/src/main/java/seng302/gameServer/GameState.java @@ -1,6 +1,7 @@ package seng302.gameServer; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -15,6 +16,7 @@ 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; @@ -39,9 +41,9 @@ import seng302.utilities.XMLParser; */ public class GameState implements Runnable { + @FunctionalInterface interface NewMessageListener { - void notify(Message message); } @@ -59,6 +61,7 @@ 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. @@ -72,19 +75,9 @@ public class GameState implements Runnable { private static Set marks; private static List courseLimit; - private static List markListeners; + private static List messageListeners; 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; @@ -94,13 +87,13 @@ public class GameState implements Runnable { 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<>(); + messageListeners = new ArrayList<>(); resetStartTime(); @@ -366,7 +359,7 @@ public class GameState implements Runnable { 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; // TODO: 15/08/17 remove magic numbers from these equations. if (yacht.getSailIn()) { if (velocity < maxBoatSpeed - 500) { @@ -671,8 +664,8 @@ public class GameState implements Runnable { } private static void notifyMessageListeners(Message message) { - for (NewMessageListener mpl : markListeners) { - mpl.notify(message); + for (NewMessageListener ml : messageListeners) { + ml.notify(message); } } @@ -684,8 +677,38 @@ 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 addMarkPassListener(NewMessageListener listener) { - markListeners.add(listener); + messageListeners.add(listener); } public static void setCustomizationFlag() { @@ -699,4 +722,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..45a88185 100644 --- a/src/main/java/seng302/gameServer/MainServerThread.java +++ b/src/main/java/seng302/gameServer/MainServerThread.java @@ -86,17 +86,20 @@ public class MainServerThread implements Runnable, ClientConnectionDelegate { //FINISHED else if (GameState.getCurrentStage() == GameStages.FINISHED) { - terminate(); + broadcastMessage(makeRaceStatusMessage()); + try { + Thread.sleep(1000); //Hackish fix to make sure all threads have sent closing RaceStatus + terminate(); + } catch (InterruptedException ie) { + serverLog("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"); } @@ -169,6 +172,9 @@ public class MainServerThread implements Runnable, ClientConnectionDelegate { @Override public void clientConnected(ServerToClientThread serverToClientThread) { serverLog("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) { @@ -257,6 +263,8 @@ public class MainServerThread implements Runnable, ClientConnectionDelegate { if (timeTillStart > PREPATORY_TIME) { raceStatus = RaceStatus.PREPARATORY; } + } else if (GameState.getCurrentStage() == GameStages.FINISHED) { + raceStatus = RaceStatus.TERMINATED; } else { raceStatus = RaceStatus.STARTED; } 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..e1c5dc3a 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,23 +31,6 @@ 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; @@ -91,6 +75,7 @@ public class ServerToClientThread implements Runnable, Observer { private ClientType clientType; private Boolean isRegistered = false; + private Boolean isHost = false; private XMLGenerator xml; @@ -225,7 +210,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)); @@ -386,4 +376,41 @@ public class ServerToClientThread implements Runnable, Observer { public void addDisconnectListener(DisconnectListener disconnectListener) { this.disconnectListener = disconnectListener; } + + public void setAsHost() { + isHost = true; + } + + private void checkChatterForCommands(ChatterMessage chatterMessage) { + +// String chatterText = new String( +// Arrays.copyOfRange(chatterPayload, 3, 3 + chatterPayload.length) +// ); +// String[] words = chatterText.split("\\s+"); +// if (words.length > 2 && isHost) { +// switch (words[2].trim()) { +// case ">speed": +// try { +// GameState.setSpeedMultiplier(Double.valueOf(words[3])); +// GameState.broadcastChatter(new ChatterMessage( +// Byte.toUnsignedInt(chatterPayload[1]), +// "SERVER: Speed modifier set to x" + words[3] +// )); +// } catch (Exception e) { +// logger.error("cannot parse >speed value"); +// } +// return; +// case ">finish": +// GameState.broadcastChatter(new ChatterMessage( +// chatterPayload[1], +// "SERVER: Game will now finish" +// )); +// GameState.endRace(); +// return; +// } +// } +// GameState.broadcastChatter( +// ServerPacketParser.extractChatterText(chatterPayload) +// ); + } } 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/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..9fe962b0 --- /dev/null +++ b/src/main/java/seng302/utilities/Sounds.java @@ -0,0 +1,163 @@ +package seng302.utilities; + +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 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.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.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.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.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.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 playHoverSound() { + if (!soundEffectsMuted) { + Media hoverSound = new Media(Sounds.class.getClassLoader().getResource("sounds/sound-over.wav").toString()); + soundPlayer = new MediaPlayer(hoverSound); + soundPlayer.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 57256760..f84fc421 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; @@ -283,9 +284,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 +303,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..4d2effb2 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,8 +15,10 @@ 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; @@ -28,6 +33,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 +59,8 @@ public class GameClient { private RaceState raceState = new RaceState(); private LobbyController lobbyController; + private ArrayList finishedBoats = new ArrayList<>(); + private ObservableList clientLobbyList = FXCollections.observableArrayList(); /** @@ -142,11 +150,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 +202,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()); @@ -280,6 +297,14 @@ public class GameClient { case YACHT_EVENT_CODE: showCollisionAlert(StreamParser.extractYachtEventCode(packet)); break; + + case CHATTER_TEXT: + Pair playerIdMessagePair = StreamParser + .extractChatterText(packet); + raceView.updateChatHistory( + allBoatsMap.get(playerIdMessagePair.getKey()).getColour(), + playerIdMessagePair.getValue() + ); } } } @@ -335,6 +360,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 +378,7 @@ public class GameClient { } if (raceFinished) { + Sounds.playFinishSound(); close(); loadFinishScreenView(); } @@ -373,6 +402,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 +416,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 @@ -409,6 +448,7 @@ public class GameClient { private void showCollisionAlert(YachtEventData yachtEventData) { // 33 is the agreed code to show collision if (yachtEventData.getEventId() == 33) { + Sounds.playCrashSound(); raceState.storeCollision( allBoatsMap.get( yachtEventData.getSubjectId().intValue() @@ -416,4 +456,19 @@ public class GameClient { ); } } + + 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 99a49021..61441b72 100644 --- a/src/main/java/seng302/visualiser/GameView.java +++ b/src/main/java/seng302/visualiser/GameView.java @@ -36,6 +36,7 @@ import seng302.model.mark.CompoundMark; import seng302.model.mark.Corner; import seng302.model.mark.Mark; 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 +65,7 @@ public class GameView extends Pane { private double metersPerPixelX, metersPerPixelY; final double SCALE_DELTA = 1.1; + private boolean isZoom = false; private Text fpsDisplay = new Text(); private Polygon raceBorder = new CourseBoundary(); @@ -101,7 +103,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); } @@ -109,7 +111,7 @@ public class GameView extends Pane { private void zoomIn() { scaleFactor = 0.10; - if (this.getScaleX() < 2.5) { + if (this.isZoom && this.getScaleX() < 2.5) { this.setScaleX(this.getScaleX() + scaleFactor); this.setScaleY(this.getScaleY() + scaleFactor); } @@ -142,6 +144,13 @@ public class GameView extends Pane { gameObjects.add(raceBorder); gameObjects.add(markers); initializeTimer(); + this.sceneProperty().addListener(((observable, oldValue, scene) -> { + if (scene != null) { + setupZoom(); + } else { + disableZoom(); + } + })); } private void initializeTimer() { @@ -439,17 +448,25 @@ public class GameView extends Pane { /** * Enables zoom. Has to be called after this is added to a scene. */ - 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 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. * @@ -785,12 +802,14 @@ public class GameView extends Pane { private void updateMarkArrows (ClientYacht yacht, CompoundMark compoundMark, int legNumber) { //Only show arrows for this and next leg. if (compoundMark != null) { + Sounds.playMarkRoundingSound(); 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(); 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 a7cd9718..7103fc93 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,7 @@ 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.Pane; import javafx.scene.layout.VBox; @@ -41,18 +43,30 @@ 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.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,26 +99,51 @@ 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(); //Formatting the y axis of the sparkline -// raceSparkLine.getYAxis().setRotate(180); -// raceSparkLine.getYAxis().setTickLabelRotation(180); -// raceSparkLine.getYAxis().setTranslateX(-5); + raceSparkLine.getYAxis().setRotate(180); + raceSparkLine.getYAxis().setTickLabelRotation(180); + raceSparkLine.getYAxis().setTranslateX(-5); 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 +155,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()) { @@ -140,7 +173,6 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel gameView.updateCourse( new ArrayList<>(raceData.getCompoundMarks().values()), raceData.getMarkSequence() ); - gameView.enableZoom(); gameView.setBoatAsPlayer(player); gameView.startRace(); @@ -155,6 +187,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 +351,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 +572,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); @@ -622,4 +659,24 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel this.courseData = raceData; gameView.updateBorder(raceData.getCourseLimit()); } + + 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/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 @@ -