Merge branch 'develop' into 1250_SendingGameObjects

# Conflicts:
#	src/main/java/seng302/gameServer/GameState.java
#	src/main/java/seng302/gameServer/MainServerThread.java
#	src/main/java/seng302/visualiser/GameView.java
#	src/main/java/seng302/visualiser/controllers/RaceViewController.java
This commit is contained in:
William Muir
2017-09-09 14:02:12 +12:00
42 changed files with 1036 additions and 279 deletions
@@ -17,6 +17,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;
@@ -45,7 +46,6 @@ public class GameState implements Runnable {
@FunctionalInterface
interface NewMessageListener {
void notify(Message message);
}
@@ -70,6 +70,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.
@@ -99,7 +100,7 @@ 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
@@ -431,7 +432,7 @@ 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);
Double maxBoatSpeed = GeoUtility.knotsToMMS(boatSpeedInKnots) * speedMultiplier;
if (yacht.getPowerUp() != null) {
if (yacht.getPowerUp().equals(TokenType.BOOST)) {
maxBoatSpeed *= 2;
@@ -756,6 +757,35 @@ 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);
}
@@ -772,4 +802,16 @@ public class GameState implements Runnable {
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;
}
}
@@ -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++;
}
/**
@@ -85,17 +85,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");
}
@@ -199,6 +202,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);
@@ -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))
);
}
}
@@ -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;
@@ -75,6 +76,7 @@ public class ServerToClientThread implements Runnable {
private ClientType clientType;
private Boolean isRegistered = false;
private Boolean isHost = false;
private XMLGenerator xmlGenerator;
@@ -199,7 +201,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));
@@ -313,4 +320,41 @@ public class ServerToClientThread implements Runnable {
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)
// );
}
}
@@ -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;
}
}
@@ -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<ClientYacht> playerPositions;
private List<ClientYacht> 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);
}
}
@@ -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<long[]> getBoatData () {
return boatData;
+163
View File
@@ -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();
}
}
}
@@ -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<Integer, String> 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
*
@@ -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;
@@ -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<ClientYacht> finishedBoats = new ArrayList<>();
private ObservableList<String> clientLobbyList = FXCollections.observableArrayList();
/**
@@ -74,7 +82,6 @@ public class GameClient {
startClientToServerThread(ipAddress, portNumber);
socketThread.addDisconnectionListener((cause) -> {
showConnectionError(cause);
tearDownConnection();
Platform.runLater(this::loadStartScreen);
});
socketThread.addStreamObserver(this::parsePackets);
@@ -92,10 +99,7 @@ public class GameClient {
lobbyController.setCourseName("");
}
lobbyController.addCloseListener((exitCause) -> {
this.tearDownConnection();
this.loadStartScreen();
});
lobbyController.addCloseListener((exitCause) -> this.loadStartScreen());
this.lobbyController = lobbyController;
} catch (IOException ioe) {
showConnectionError("Unable to find server");
@@ -113,7 +117,6 @@ public class GameClient {
try {
startClientToServerThread(ipAddress, portNumber);
socketThread.addDisconnectionListener((cause) -> {
this.tearDownConnection();
Platform.runLater(this::loadStartScreen);
});
LobbyController lobbyController = loadLobby();
@@ -134,7 +137,8 @@ public class GameClient {
lobbyController.disableReadyButton();
server.startGame();
} else if (exitCause == CloseStatus.LEAVE) {
tearDownConnection();
server.terminate();
server = null;
loadStartScreen();
}
});
@@ -145,20 +149,11 @@ public class GameClient {
}
}
private void tearDownConnection() {
socketThread.setSocketToClose();
if (server != null) {
server.terminate();
server = null;
}
}
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 {
@@ -207,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());
@@ -293,6 +298,14 @@ public class GameClient {
case YACHT_EVENT_CODE:
showCollisionAlert(StreamParser.extractYachtEventCode(packet));
break;
case CHATTER_TEXT:
Pair<Integer, String> playerIdMessagePair = StreamParser
.extractChatterText(packet);
raceView.updateChatHistory(
allBoatsMap.get(playerIdMessagePair.getKey()).getColour(),
playerIdMessagePair.getValue()
);
}
}
}
@@ -347,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();
}
}
@@ -362,6 +378,7 @@ public class GameClient {
}
if (raceFinished) {
Sounds.playFinishSound();
close();
loadFinishScreenView();
}
@@ -385,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;
@@ -393,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
@@ -421,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()
@@ -428,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;
}
}
+30 -12
View File
@@ -35,6 +35,7 @@ import seng302.model.mark.Corner;
import seng302.model.mark.Mark;
import seng302.model.token.Token;
import seng302.utilities.GeoUtility;
import seng302.utilities.Sounds;
import seng302.visualiser.fxObjects.AnnotationBox;
import seng302.visualiser.fxObjects.BoatObject;
import seng302.visualiser.fxObjects.CourseBoundary;
@@ -63,6 +64,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);
}
@@ -143,6 +145,13 @@ public class GameView extends Pane {
gameObjects.add(markers);
gameObjects.add(tokens);
initializeTimer();
this.sceneProperty().addListener(((observable, oldValue, scene) -> {
if (scene != null) {
setupZoom();
} else {
disableZoom();
}
}));
}
private void initializeTimer() {
@@ -462,17 +471,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.
*
@@ -809,6 +826,7 @@ public class GameView extends Pane {
//Only show arrows for this and next leg.
CompoundMark nextMark = null;
if (legNumber < course.size() - 1) {
Sounds.playMarkRoundingSound();
nextMark = course.get(legNumber);
for (Mark mark : nextMark.getMarks()) {
markerObjects.get(mark).showNextEnterArrow();
@@ -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();
@@ -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();
}
}
@@ -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
@@ -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;
@@ -42,18 +44,30 @@ import seng302.model.mark.CompoundMark;
import seng302.model.mark.Mark;
import seng302.model.stream.xml.parser.RaceXMLData;
import seng302.model.token.Token;
import seng302.utilities.Sounds;
import seng302.visualiser.GameView;
import seng302.visualiser.controllers.annotations.Annotation;
import seng302.visualiser.controllers.annotations.ImportantAnnotationController;
import seng302.visualiser.controllers.annotations.ImportantAnnotationDelegate;
import seng302.visualiser.controllers.annotations.ImportantAnnotationsState;
import seng302.visualiser.fxObjects.BoatObject;
import seng302.visualiser.fxObjects.ChatHistory;
/**
* Controller class that manages the display of a race
*/
public class RaceViewController extends Thread implements ImportantAnnotationDelegate {
private final int CHAT_LIMIT = 128;
@FXML
private Pane basePane;
@FXML
private Button chatSend;
@FXML
private Pane chatHistoryHolder;
@FXML
private TextField chatInput;
@FXML
private LineChart<String, Double> raceSparkLine;
@FXML
@@ -86,26 +100,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<Series<String, Double>> sparkLineData = new ArrayList<>();
private ImportantAnnotationsState importantAnnotations;
private ObservableList<ClientYacht> 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 (
@@ -117,12 +156,6 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
this.markers = raceData.getCompoundMarks();
this.raceState = raceState;
initializeUpdateTimer();
initialiseFPSCheckBox();
initialiseAnnotationSlider();
initialiseBoatSelectionComboBox();
initialiseSparkLine();
raceState.getPlayerPositions().addListener((ListChangeListener<ClientYacht>) c -> {
while (c.next()) {
if (c.wasPermutated()) {
@@ -138,11 +171,10 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
Platform.runLater(() -> contentAnchorPane.getChildren().add(0, gameView));
gameView.setBoats(new ArrayList<>(participants.values()));
gameView.updateBorder(raceData.getCourseLimit());
gameView.updateTokens(raceData.getTokens());
gameView.updateTokens(raceData.getTokens());
gameView.updateCourse(
new ArrayList<>(raceData.getCompoundMarks().values()), raceData.getMarkSequence()
);
gameView.enableZoom();
);
gameView.setBoatAsPlayer(player);
gameView.startRace();
@@ -157,6 +189,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();
});
}
/**
@@ -307,13 +353,6 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
)
);
}
// XYChart.Series<String, Double> positionData = sparkLineData.get(yacht.getSourceID());
// positionData.getData().add(
// new XYChart.Data<>(
// Integer.toString(legNumber),
// 1.0 + participants.size() - yacht.getPlacing()
// )
// );
}
@@ -535,10 +574,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);
@@ -625,4 +662,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));
}
}
@@ -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();
}
}
@@ -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<Node>) 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);
}
}
}