Merge remote-tracking branch 'origin/develop' into develop

This commit is contained in:
Haoming Yin
2017-09-27 22:47:08 +13:00
95 changed files with 4726 additions and 2584 deletions
+51 -24
View File
@@ -10,11 +10,13 @@ import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import seng302.discoveryServer.DiscoveryServer;
import seng302.visualiser.controllers.ViewManager;
public class App extends Application {
private static Logger logger = LoggerFactory.getLogger(App.class);
private static boolean isRunningAsCache = false;
public static void parseArgs(String[] args) throws ParseException {
Options options = new Options();
@@ -25,40 +27,52 @@ public class App extends Application {
.getLogger(Logger.ROOT_LOGGER_NAME);
options.addOption("debugLevel", true, "Set the application debug level");
options.addOption("runAsDiscoveryServer", false, "Run as a discovery server");
options.addOption("discoveryDevMode", false, "Use a local discovery server");
cmd = parser.parse(options, args);
if (cmd.hasOption("runAsDiscoveryServer")){
isRunningAsCache = true;
rootLogger.setLevel(Level.ALL);
return;
}
if (cmd.hasOption("discoveryDevMode")) {
DiscoveryServer.DISCOVERY_SERVER = "localhost";
}
if (cmd.hasOption("debugLevel")) {
switch (cmd.getOptionValue("debugLevel")) {
case "DEBUG":
rootLogger.setLevel(Level.DEBUG);
break;
switch (cmd.getOptionValue("debugLevel")) {
case "DEBUG":
rootLogger.setLevel(Level.DEBUG);
break;
case "ALL":
rootLogger.setLevel(Level.ALL);
break;
case "ALL":
rootLogger.setLevel(Level.ALL);
break;
case "WARNING":
rootLogger.setLevel(Level.WARN);
break;
case "WARNING":
rootLogger.setLevel(Level.WARN);
break;
case "ERROR":
rootLogger.setLevel(Level.ERROR);
break;
case "ERROR":
rootLogger.setLevel(Level.ERROR);
break;
case "INFO":
rootLogger.setLevel(Level.INFO);
case "INFO":
rootLogger.setLevel(Level.INFO);
case "TRACE":
rootLogger.setLevel(Level.TRACE);
case "TRACE":
rootLogger.setLevel(Level.TRACE);
default:
rootLogger.setLevel(Level.ALL);
}
} else {
rootLogger.setLevel(Level.WARN);
default:
rootLogger.setLevel(Level.ALL);
}
} else {
rootLogger.setLevel(Level.WARN);
}
}
@Override
@@ -66,15 +80,28 @@ public class App extends Application {
ViewManager.getInstance().initialiseSplashScreen(primaryStage);
}
private static void runDiscoveryServer(){
try{
new DiscoveryServer();
}
catch (Exception e){
runDiscoveryServer();
}
}
public static void main(String[] args) {
public static void main(String[] args) throws Exception {
try {
parseArgs(args);
} catch (ParseException e) {
logger.error("Could not parse command line arguments");
}
launch(args);
if (!isRunningAsCache){
launch(args);
}
else{
runDiscoveryServer();
}
}
}
@@ -0,0 +1,152 @@
package seng302.discoveryServer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import seng302.gameServer.messages.Message;
import seng302.gameServer.messages.RoomCodeRequest;
import seng302.gameServer.messages.ServerRegistrationMessage;
import seng302.model.stream.packets.PacketType;
import seng302.discoveryServer.util.ServerListing;
import seng302.discoveryServer.util.ServerRepoStreamParser;
import seng302.discoveryServer.util.ServerTable;
import seng302.visualiser.ServerListener;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
public class DiscoveryServer {
public static final String ANSI_GREEN = "\u001B[32m";
public static final String ANSI_YELLOW = "\u001B[33m";
public static final String ANSI_BLUE = "\u001B[34m";
public static final String ANSI_RESET = "\u001B[0m";
private static final int MAX_SERVER_TRIES = 10;
public static String DISCOVERY_SERVER = "party.sydney.srv.michaelrausch.nz";
private ServerTable serverTable;
public static final Integer PORT_NUMBER = 9969;
private final Logger logger = LoggerFactory.getLogger(DiscoveryServer.class);
private void displayHeader(){
String selectedColor = Arrays.asList(ANSI_BLUE, ANSI_GREEN, ANSI_YELLOW).get(new Random().nextInt(2));
System.out.println(selectedColor);
System.out.println(" .ccccc. \n" +
" .cc;'coooxkl;. \n" +
" .:c:::c:,,,,,;c;;,.'. \n" +
" .clc,',:,..:xxocc;'..c; \n" +
" .c:,';:ox:..:c,,,,,,...cd, \n" +
" .c:'.,oxxxxl::l:.,loll;..;ol. \n" +
" ;Oc..:xxxxxxxxx:.,llll,....oc \n" +
" .,;,',:loxxxxxxxxx:.,llll;.,,.'ld, \n" +
" .lo;..:xxxxxxxxxxxx:.'cllc,.:l:'cO; \n" +
" .:;...'cxxxxxxxxxxxxoc;,::,..cdl;;l' \n" +
" .cl;':,'';oxxxxxxdxxxxxx:....,cooc,cO; \n" +
" .,,,::;,lxoc:,,:lxxxxxxxxxxxo:,,;lxxl;'oNc \n" +
" .cdxo;':lxxxxxxc'';cccccoxxxxxxxxxxxxo,.;lc. " + ANSI_YELLOW + "Party-Parrots-At-Sea Discovery Server v0.1 " + selectedColor +"\n" +
" .loc'.'lxxxxxxxxocc;''''';ccoxxxxxxxxx:..oc \n" +
"olc,..',:cccccccccccc:;;;;;;;;:ccccccccc,.'c, \n" +
"Ol;......................................;l' ");
System.out.println(ANSI_RESET);
}
public DiscoveryServer() throws Exception {
displayHeader();
serverTable = new ServerTable();
ServerSocket serverSocket;
try{
serverSocket = new ServerSocket(PORT_NUMBER);
}
catch(java.net.BindException e){
logger.error("FATAL - Could not bind socket, are you sure there isn't already an instance running?");
System.exit(1);
return;
}
logger.info("Started successfully - Now accepting connections");
while (true){
Socket clientSocket = serverSocket.accept();
parseRequest(clientSocket);
clientSocket.close();
}
}
private void parseRequest(Socket clientSocket) throws Exception {
ServerRepoStreamParser parser = new ServerRepoStreamParser(clientSocket.getInputStream());
if (clientSocket.isConnected() && !clientSocket.isClosed()){
PacketType parsePacketResult = parser.parse();
switch (parsePacketResult){
case SERVER_REGISTRATION:
ServerListing listing = parser.getServerListing();
if (!serverTable.getAllServers().contains(listing)){
listing.setRoomCode(serverTable.getNextRoomCode().toString());
}
serverTable.addServer(listing);
Message serverRegMessage = new RoomCodeRequest(listing.getRoomCode());
clientSocket.getOutputStream().write(serverRegMessage.getBuffer());
break;
case ROOM_CODE_REQUEST:
String desiredRoomCode = parser.getRoomCode();
ServerListing serverListing;
if (desiredRoomCode.equals("0000")){
serverListing = getRandomFreeServer();
}
else {
serverListing = serverTable.getServerByRoomCode(desiredRoomCode);
}
Message response;
if (serverListing != null){
response = new ServerRegistrationMessage(serverListing.getServerName(), serverListing.getMapName(), serverListing.getAddress(), serverListing.getPortNumber(), 0, 0, desiredRoomCode);
}
else{
response = ServerRegistrationMessage.getEmptyRegistration();
}
clientSocket.getOutputStream().write(response.getBuffer());
break;
}
}
}
public ServerListing getRandomFreeServer() {
ServerListing serverToJoin;
List<ServerListing> servers = serverTable.getAllServers();
if (servers.size() <= 0){
return null;
}
if (servers.size() == 1){
return servers.get(0);
}
serverToJoin = servers.get(new Random().nextInt(servers.size()));
int tries = 0;
while (serverToJoin != null && serverToJoin.isMaxPlayersReached() && tries < MAX_SERVER_TRIES){
serverToJoin = servers.get(new Random().nextInt(servers.size()));
tries++;
}
return serverToJoin;
}
}
@@ -0,0 +1,195 @@
package seng302.discoveryServer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import seng302.discoveryServer.util.ServerListing;
import seng302.discoveryServer.util.ServerRepoStreamParser;
import seng302.gameServer.messages.Message;
import seng302.gameServer.messages.RoomCodeRequest;
import seng302.gameServer.messages.ServerRegistrationMessage;
import seng302.model.stream.packets.PacketType;
import seng302.visualiser.controllers.ViewManager;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
import java.net.URL;
import java.util.Timer;
import java.util.TimerTask;
public class DiscoveryServerClient {
private final Integer UPDATE_INTERVAL_MS = 5000;
private static String roomCode = null;
private Timer serverListingUpdateTimer;
private Logger logger = LoggerFactory.getLogger(DiscoveryServerClient.class);
private String ip = "";
private Boolean isInInvalidState = false;
public DiscoveryServerClient() {
try {
ip = getInetIpAddr();
} catch (Exception e) {
failError();
}
}
public String getInetIp(){
return ip;
}
private void failError() {
isInInvalidState = true;
ViewManager.getInstance().showErrorSnackBar("You do not appear to be able to connect to the internet. Matchmaking will be unavailable.");
}
public boolean didFail(){
return isInInvalidState;
}
/**
* Register the server with the discovery server
* @param serverListing The listing to register
*/
public void register(ServerListing serverListing){
if (isInInvalidState) return;
if (serverListingUpdateTimer != null){
serverListingUpdateTimer.cancel();
serverListingUpdateTimer = null;
}
serverListingUpdateTimer = new Timer();
serverListingUpdateTimer.schedule(new TimerTask() {
@Override
public void run() {
try {
sendRegistrationUpdate(serverListing);
} catch (Exception e) {
logger.debug("Could not update server listing");
}
}
}, 0, UPDATE_INTERVAL_MS);
}
/**
* Stop updating the server registration updates
*/
public void unregister(){
if (serverListingUpdateTimer != null)
serverListingUpdateTimer.cancel();
}
/**
* Gets the connection information for a server given a room code
*
* @param roomCode The room code to search for
* @return The ServerListing, or null if there was an error
* @throws Exception .
*/
public ServerListing getServerForRoomCode(String roomCode) throws Exception {
Socket socket = new Socket(DiscoveryServer.DISCOVERY_SERVER, DiscoveryServer.PORT_NUMBER);
ServerRepoStreamParser parser = new ServerRepoStreamParser(socket.getInputStream());
Message request = new RoomCodeRequest(roomCode); //roomCode);
socket.getOutputStream().write(request.getBuffer());
PacketType packetType = parser.parse();
if (packetType != PacketType.SERVER_REGISTRATION){
logger.debug("Wrong packet received in response to a room code request");
return null;
}
socket.close();
return parser.getServerListing();
}
public ServerListing getRandomServer() throws Exception {
Socket socket = new Socket(DiscoveryServer.DISCOVERY_SERVER, DiscoveryServer.PORT_NUMBER);
ServerRepoStreamParser parser = new ServerRepoStreamParser(socket.getInputStream());
Message request = new RoomCodeRequest("0000");
socket.getOutputStream().write(request.getBuffer());
PacketType packetType = parser.parse();
if (packetType != PacketType.SERVER_REGISTRATION){
logger.error("Incorrect packet type received");
return null;
}
socket.close();
ServerListing serverListing = parser.getServerListing();
if (serverListing == null || serverListing.equals(ServerRegistrationMessage.getEmptyRegistration())){
return null;
}
return serverListing;
}
/**
* Sends a registration update to the discovery server.
*
* @param serverListing The server listing to send
* @throws Exception IF there was an error sending the update
*/
private void sendRegistrationUpdate(ServerListing serverListing) throws Exception {
Socket socket = new Socket(DiscoveryServer.DISCOVERY_SERVER, DiscoveryServer.PORT_NUMBER);
ServerRepoStreamParser parser = new ServerRepoStreamParser(socket.getInputStream());
Message req = new ServerRegistrationMessage(serverListing);
socket.getOutputStream().write(req.getBuffer());
PacketType packetType = parser.parse();
if (packetType != PacketType.ROOM_CODE_REQUEST){
socket.close();
return;
}
String roomCode = parser.getRoomCode();
if (roomCode.length() != 0){
DiscoveryServerClient.roomCode = roomCode;
}
socket.close();
}
/**
* @return The last room code received by the client
*/
public static String getRoomCode(){
return roomCode;
}
public static String getInetIpAddr() throws Exception {
URL myIp = new URL("http://checkip.amazonaws.com");
BufferedReader in = null;
try {
in = new BufferedReader(new InputStreamReader(
myIp.openStream()));
String ip = in.readLine();
return ip;
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
@@ -0,0 +1,50 @@
package seng302.discoveryServer.util;
import java.io.InputStream;
public class ReadableByteInputStream {
private InputStream is;
public ReadableByteInputStream(InputStream is){
this.is = is;
}
/**
* Get n bytes from the input stream
* @param n number of bytes
* @return the bytes read
* @throws Exception .
*/
public byte[] getBytes(int n) throws Exception {
byte[] bytes = new byte[n];
for (int i = 0; i < n; i++) {
bytes[i] = (byte) readByte();
}
return bytes;
}
/**
* Skip n bytes
* @param n number of bytes to skip
* @throws Exception
*/
public void skipBytes(long n) throws Exception {
for (int i = 0; i < n; i++) {
readByte();
}
}
/**
* Read the next byte from the stream
* @return The byte that was read
* @throws Exception .
*/
public int readByte() throws Exception {
int currentByte = is.read();
if (currentByte == -1) {
throw new Exception();
}
return currentByte;
}
}
@@ -0,0 +1,117 @@
package seng302.discoveryServer.util;
public class ServerListing {
public final static int SERVER_TTL_DEFAULT = 10;
private String serverName = "";
private String mapName = "";
private String address = "";
private int portNumber = 0;
private int capacity = 0;
private int players = 0;
private String roomCode = "";
private int ttl = SERVER_TTL_DEFAULT;
public ServerListing(String serverName, String mapName, String address, int portNumber, int capacity){
this.serverName = serverName;
this.mapName = mapName;
this.address = address;
this.portNumber = portNumber;
this.capacity = capacity;
}
public ServerListing setNumberOfPlayers(int players){
this.players = players;
return this;
}
public ServerListing setRoomCode(String roomCode){
this.roomCode = roomCode;
return this;
}
public void refreshTtl(){
ttl = SERVER_TTL_DEFAULT;
}
public void decrementTtl(){
ttl--;
}
public boolean hasTtlExpired(){
return ttl < 0;
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (!ServerListing.class.isAssignableFrom(obj.getClass())) {
return false;
}
final ServerListing other = (ServerListing) obj;
if (this.getPortNumber() != other.getPortNumber()){
return false;
}
if (!this.getMapName().equals(other.getMapName())){
return false;
}
if (!this.getServerName().equals(other.getServerName())){
return false;
}
if (this.getCapacity() != other.getCapacity()){
return false;
}
if (!this.getAddress().equals(other.getAddress())){
return false;
}
return true;
}
@Override
public int hashCode() {
return this.getServerName().hashCode() +
this.getAddress().hashCode() + this.getMapName().hashCode();
}
public String getRoomCode() {
return roomCode;
}
public int getPortNumber() {
return portNumber;
}
public String getMapName() {
return mapName;
}
public String getServerName() {
return serverName;
}
public int getCapacity() {
return capacity;
}
public String getAddress() {
return address;
}
public void setTtl(Integer ttl){
this.ttl = ttl;
}
public boolean isMaxPlayersReached() {
return players >= capacity;
}
}
@@ -0,0 +1,109 @@
package seng302.discoveryServer.util;
import seng302.gameServer.messages.Message;
import seng302.model.stream.packets.PacketType;
import java.io.InputStream;
import java.util.Arrays;
public class ServerRepoStreamParser {
private ReadableByteInputStream inputStream;
private String roomCode;
private String mapName;
private ServerListing serverListing;
public ServerRepoStreamParser(InputStream is){
inputStream = new ReadableByteInputStream(is);
}
public PacketType parse() throws Exception {
int sync1 = inputStream.readByte();
int sync2 = inputStream.readByte();
PacketType packetType = null;
if (sync1 == 0x47 && sync2 == 0x83) {
int type = inputStream.readByte();
inputStream.skipBytes(10);
long payloadLength = Message.bytesToLong(inputStream.getBytes(2));
byte[] payload = inputStream.getBytes((int) payloadLength);
inputStream.skipBytes(4);
packetType = PacketType.assignPacketType(type, payload);
switch (packetType) {
case ROOM_CODE_REQUEST:
roomCode = parseRoomCodeRequest(payload);
break;
case LOBBY_REQUEST:
mapName = parseLobbyRequest(payload);
case SERVER_REGISTRATION:
serverListing = parseServerRegistration(payload);
break;
}
}
return packetType;
}
private String parseLobbyRequest(byte[] payload) {
int mapNameLength = (int) Message.bytesToLong(Arrays.copyOfRange(payload, 0 ,4));
return new String(Arrays.copyOfRange(payload, 4, 4+mapNameLength));
}
private String parseRoomCodeRequest(byte[] payload) {
int roomCodeLength = (int) Message.bytesToLong(Arrays.copyOfRange(payload, 0 ,6));
return new String(Arrays.copyOfRange(payload, 6, 6+roomCodeLength));
}
public static ServerListing parseServerRegistration(byte[] payload) {
int nameLength = (int) Message.bytesToLong(Arrays.copyOfRange(payload, 0, 6));
int mapNameLength = (int) Message.bytesToLong(Arrays.copyOfRange(payload, 6, 12));
int addressLength = (int) Message.bytesToLong(Arrays.copyOfRange(payload, 12, 18));
int roomCodeLength = (int) Message.bytesToLong(Arrays.copyOfRange(payload, 18, 24));
int portNumber = (int) Message.bytesToLong(Arrays.copyOfRange(payload, 24, 28));
int players = (int) Message.bytesToLong(Arrays.copyOfRange(payload, 28, 32));
int capacity = (int) Message.bytesToLong(Arrays.copyOfRange(payload, 32, 36));
int currentPos = 36;
int nextPos = currentPos + nameLength;
String serverName = new String(Arrays.copyOfRange(payload, currentPos, nextPos));
currentPos = nextPos;
nextPos = currentPos + mapNameLength;
String mapName = new String(Arrays.copyOfRange(payload, currentPos, nextPos));
currentPos = nextPos;
nextPos = currentPos + addressLength;
String address = new String(Arrays.copyOfRange(payload, currentPos, nextPos));
currentPos = nextPos;
nextPos = currentPos + roomCodeLength;
String roomCode = new String(Arrays.copyOfRange(payload, currentPos, nextPos));
ServerListing serverListing = new ServerListing(serverName, mapName, address, portNumber, capacity);
serverListing.setNumberOfPlayers(players);
serverListing.setRoomCode(roomCode);
return serverListing;
}
public String getRoomCode() {
return roomCode;
}
public String getMapName() {
return mapName;
}
public ServerListing getServerListing() {
return serverListing;
}
}
@@ -0,0 +1,97 @@
package seng302.discoveryServer.util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
public class ServerTable {
private List<ServerListing> servers;
private int lastRoomCode = 4020;
private Logger logger = LoggerFactory.getLogger(ServerTable.class);
public ServerTable(){
servers = new ArrayList<>();
new Timer().schedule(new TimerTask() {
@Override
public void run() {
updateServers();
}
}, 0, 1000);
}
/**
* Update the servers TTL values, and then remove expired servers
*/
private void updateServers() {
List<ServerListing> serversToRemove = new ArrayList<>();
for (ServerListing server : servers){
server.decrementTtl();
if (server.hasTtlExpired()){
logger.debug("Removed expired server - " + server.getServerName());
serversToRemove.add(server);
}
}
servers.removeAll(serversToRemove);
}
/**
* Add a server to the table
* @param server The server to add
*/
public void addServer(ServerListing server){
if (servers.contains(server)){
updateTtlForServer(server);
return;
}
logger.debug("Added new server - " + server.getServerName() + " at address: " + server.getAddress() + ":" + server.getPortNumber());
servers.add(server);
}
/**
* Update the TTL for a given server to the default TTL value
* @param server The server to update
*/
private void updateTtlForServer(ServerListing server) {
for (ServerListing serverListing : servers){
if (server.equals(serverListing)){
serverListing.refreshTtl();
}
}
}
/**
* @return All the servers in the table
*/
public List<ServerListing> getAllServers(){
return Collections.unmodifiableList(servers);
}
/**
* Get a server from the table given its room code
* @param roomCode The room code to search for
* @return The ServerListing of the found server, or null
* the server wasn't found
*/
public ServerListing getServerByRoomCode(String roomCode){
for (ServerListing serverListing : servers){
if (serverListing.getRoomCode().equals(roomCode)){
return serverListing;
}
}
return null;
}
/**
* @return The next available room code
*/
public Integer getNextRoomCode(){
lastRoomCode += 1;
return lastRoomCode;
}
}
+38 -47
View File
@@ -1,29 +1,12 @@
package seng302.gameServer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.io.IOException;
import java.util.*;
import javafx.scene.paint.Color;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import seng302.gameServer.messages.BoatAction;
import seng302.gameServer.messages.BoatStatus;
import seng302.gameServer.messages.ChatterMessage;
import seng302.gameServer.messages.CustomizeRequestType;
import seng302.gameServer.messages.MarkRoundingMessage;
import seng302.gameServer.messages.MarkType;
import seng302.gameServer.messages.Message;
import seng302.gameServer.messages.RoundingBoatStatus;
import seng302.gameServer.messages.*;
import seng302.model.GeoPoint;
import seng302.model.Limit;
import seng302.model.Player;
@@ -32,6 +15,7 @@ import seng302.model.ServerYacht;
import seng302.model.mark.CompoundMark;
import seng302.model.mark.Mark;
import seng302.model.mark.MarkOrder;
import seng302.model.stream.xml.parser.RaceXMLData;
import seng302.model.token.Token;
import seng302.model.token.TokenType;
import seng302.utilities.GeoUtility;
@@ -96,8 +80,8 @@ public class GameState implements Runnable {
private static GameStages currentStage;
private static MarkOrder markOrder;
private static long startTime;
private static List<Mark> marks;
private static List<Limit> courseLimit;
private static Set<Mark> marks = new HashSet<>();
private static List<Limit> courseLimit = new ArrayList<>();
private static Integer maxPlayers = 8;
private static List<Token> tokensInPlay;
@@ -106,46 +90,33 @@ public class GameState implements Runnable {
private static List<NewMessageListener> newMessageListeners;
private static Map<Player, String> playerStringMap = new HashMap<>();
private static boolean tokensEnabled = false;
public GameState(String hostIpAddress) {
public GameState() {
windDirection = 180d;
windSpeed = 10000d;
yachts = new HashMap<>();
tokensInPlay = new ArrayList<>();
players = new ArrayList<>();
GameState.hostIpAddress = hostIpAddress;
customizationFlag = false;
playerHasLeftFlag = false;
serverSpeedMultiplier = 1.0;
currentStage = GameStages.LOBBYING;
previousUpdateTime = System.currentTimeMillis();
markOrder = new MarkOrder(); //This could be instantiated at some point with a select map?
newMessageListeners = new ArrayList<>();
marks = new MarkOrder().getAllMarks();
randomSpawn = new RandomSpawn(markOrder.getOrderedUniqueCompoundMarks());
resetStartTime();
setCourseLimit("/server_config/race.xml");
//setCourseLimit("/server_config/race.xml");
new Thread(this, "GameState").start(); //Run the auto updates on the game state
}
private void setCourseLimit(String url) {
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
documentBuilderFactory.setNamespaceAware(true);
DocumentBuilder documentBuilder;
Document document = null;
try {
documentBuilder = documentBuilderFactory.newDocumentBuilder();
document = documentBuilder.parse(new InputSource(getClass().getResourceAsStream(url)));
} catch (Exception e) {
// sorry, we have to catch general one, otherwise we have to catch five different exceptions.
logger.trace("Failed to load course limit for boundary collision detection.", e);
public static void setRace(RaceXMLData raceXMLData) {
markOrder = new MarkOrder(raceXMLData);
for (CompoundMark compoundMark : raceXMLData.getCompoundMarks().values()){
marks.addAll(compoundMark.getMarks());
}
courseLimit = XMLParser.parseRace(document).getCourseLimit();
}
public static String getHostIpAddress() {
return hostIpAddress;
randomSpawn = new RandomSpawn(markOrder.getOrderedUniqueCompoundMarks());
courseLimit = raceXMLData.getCourseLimit();
}
public static List<Player> getPlayers() {
@@ -156,6 +127,10 @@ public class GameState implements Runnable {
return tokensInPlay;
}
public static Set<Mark> getMarks() {
return Collections.unmodifiableSet(marks);
}
public static void addPlayer(Player player) {
players.add(player);
String playerText = player.getYacht().getSourceId() + " " + player.getYacht().getBoatName()
@@ -266,8 +241,10 @@ public class GameState implements Runnable {
timer.schedule(new TimerTask() {
@Override
public void run() {
spawnNewToken();
notifyMessageListeners(MessageFactory.getRaceXML());
if (tokensEnabled) {
spawnNewToken();
notifyMessageListeners(MessageFactory.getRaceXML());
}
}
}, 0, TOKEN_SPAWN_TIME);
}
@@ -353,6 +330,7 @@ public class GameState implements Runnable {
// token.assignType(TokenType.RANDOM);
logger.debug("Spawned token of type " + token.getTokenType());
tokensInPlay.add(token);
MessageFactory.updateTokens(tokensInPlay);
}
/**
@@ -399,6 +377,8 @@ public class GameState implements Runnable {
if (collidedToken != null) {
tokensInPlay.remove(collidedToken);
powerUpYacht(yacht, collidedToken);
MessageFactory.updateTokens(tokensInPlay);
notifyMessageListeners(MessageFactory.getRaceXML());
}
checkPowerUpTimeout(yacht);
@@ -531,6 +511,7 @@ public class GameState implements Runnable {
return true;
}
}
if (GeoUtility.checkCrossedLine(courseLimit.get(courseLimit.size() - 1), courseLimit.get(0),
yacht.getLastLocation(), yacht.getLocation()) != 0) {
return true;
@@ -1043,6 +1024,12 @@ public class GameState implements Runnable {
public static void setMaxPlayers(Integer newMax){
maxPlayers = newMax;
try {
ServerAdvertiser.getInstance().setCapacity(newMax);
} catch (IOException e) {
logger.warn("Couldn't update max players");
}
}
public static void endRace () {
@@ -1053,4 +1040,8 @@ public class GameState implements Runnable {
public static double getServerSpeedMultiplier() {
return serverSpeedMultiplier;
}
public static void setTokensEnabled (boolean tokensEnabled) {
GameState.tokensEnabled = tokensEnabled;
}
}
@@ -1,30 +1,30 @@
package seng302.gameServer;
import java.io.IOException;
import java.io.StringReader;
import java.net.ServerSocket;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.Timer;
import java.util.TimerTask;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import seng302.gameServer.messages.Message;
import seng302.model.GeoPoint;
import seng302.model.Player;
import seng302.model.PolarTable;
import seng302.model.ServerYacht;
import seng302.model.mark.CompoundMark;
import seng302.model.stream.xml.parser.RaceXMLData;
import seng302.model.stream.xml.parser.RegattaXMLData;
import seng302.utilities.GeoUtility;
import seng302.utilities.XMLGenerator;
import seng302.utilities.XMLParser;
import java.io.IOException;
import java.net.ServerSocket;
import java.util.ArrayList;
import java.util.Random;
import java.util.Timer;
import java.util.TimerTask;
/**
* A class describing the overall server, which creates and collects server threads for each client
@@ -42,29 +42,13 @@ public class MainServerThread implements Runnable, ClientConnectionDelegate {
private Thread thread;
private ServerSocket serverSocket = null;
private ArrayList<ServerToClientThread> serverToClientThreads = new ArrayList<>();;
private ArrayList<ServerToClientThread> serverToClientThreads = new ArrayList<>();
private static Integer capacity;
private RaceXMLData raceXMLData;
private RegattaXMLData regattaXMLData;
private boolean serverStarted = false;
private void startAdvertisingServer() {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db;
Document doc;
XMLGenerator generator = new XMLGenerator();
try {
db = dbf.newDocumentBuilder();
String regatta = generator.getRegattaAsXml();
StringReader stringReader = new StringReader(regatta);
InputSource is = new InputSource(stringReader);
doc = db.parse(is);
} catch (ParserConfigurationException | IOException | SAXException e) {
logger.warn("Couldn't load race regatta");
return;
}
RegattaXMLData regattaXMLData = XMLParser.parseRegatta(doc);
Integer capacity = GameState.getCapacity();
Integer numPlayers = GameState.getNumberOfPlayers();
Integer spacesLeft = capacity - numPlayers;
@@ -76,31 +60,38 @@ public class MainServerThread implements Runnable, ClientConnectionDelegate {
// Start advertising server
try {
ServerAdvertiser.getInstance().setMapName(regattaXMLData.getCourseName()).setCapacity(capacity).setNumberOfPlayers(numPlayers);
ServerAdvertiser.getInstance().registerGame(PORT, regattaXMLData.getRegattaName());
ServerAdvertiser.getInstance()
.setMapName(regattaXMLData.getCourseName())
.setCapacity(capacity)
.setNumberOfPlayers(numPlayers - 1)
.registerGame(PORT, regattaXMLData.getRegattaName());
} catch (IOException e) {
logger.warn("Could not register server");
}
}
public MainServerThread() {
new GameState("localhost");
new GameState();
try {
serverSocket = new ServerSocket(PORT);
} catch (IOException e) {
logger.trace("IO error in server thread handler upon trying to make new server socket",
0);
}
startAdvertisingServer();
PolarTable.parsePolarFile(getClass().getResourceAsStream("/config/acc_polars.csv"));
GameState.addMessageEventListener(this::broadcastMessage);
terminated = false;
thread = new Thread(this, "MainServer");
thread.start();
}
private void startServer() {
PolarTable.parsePolarFile(getClass().getResourceAsStream("/server_config/acc_polars.csv"));
MessageFactory.updateXMLGenerator(raceXMLData, regattaXMLData);
GameState.setRace(raceXMLData);
MessageFactory.updateBoats(new ArrayList<>(GameState.getYachts().values()));
startAdvertisingServer();
GameState.addMessageEventListener(this::broadcastMessage);
sendSetupMessages();
}
public void run() {
@@ -128,8 +119,8 @@ public class MainServerThread implements Runnable, ClientConnectionDelegate {
} catch (InterruptedException e) {
logger.trace("Interrupted exception in Main Server Thread thread sleep", 1);
}
if (GameState.getCurrentStage() == GameStages.LOBBYING && GameState
.getCustomizationFlag()) {
if (GameState.getCurrentStage() == GameStages.LOBBYING && GameState.getCustomizationFlag()) {
MessageFactory.updateBoats(new ArrayList<>(GameState.getYachts().values()));
sendSetupMessages();
GameState.resetCustomizationFlag();
}
@@ -171,6 +162,7 @@ public class MainServerThread implements Runnable, ClientConnectionDelegate {
}
private void sendSetupMessages() {
MessageFactory.updateBoats(new ArrayList<>(GameState.getYachts().values()));
broadcastMessage(MessageFactory.getRaceXML());
broadcastMessage(MessageFactory.getRegattaXML());
broadcastMessage(MessageFactory.getBoatXML());
@@ -192,16 +184,43 @@ public class MainServerThread implements Runnable, ClientConnectionDelegate {
logger.debug("Player Connected From " + serverToClientThread.getThread().getName(), 0);
if (serverToClientThreads.size() == 0) { //Sets first client as host.
serverToClientThread.setAsHost();
serverToClientThread.raceXMLProperty().addListener((obs, oldVal, race) -> {
if (race != null) {
raceXMLData = race;
}
if (regattaXMLData != null) {
startServer();
}
});
serverToClientThread.regattaXMLProperty().addListener((obs, oldVal, regatta) -> {
if (regatta != null) {
regattaXMLData = regatta;
}
if (raceXMLData != null) {
startServer();
}
});
} else {
//serverToClientThread.addConnectionListener(this::sendSetupMessages);
}
serverToClientThreads.add(serverToClientThread);
serverToClientThread.addConnectionListener(this::sendSetupMessages);
serverToClientThread.addDisconnectListener(this::clientDisconnected);
try {
ServerAdvertiser.getInstance().setNumberOfPlayers(GameState.getNumberOfPlayers());
} catch (IOException e) {
logger.warn("Couldn't update advertisement");
}
while (regattaXMLData == null && raceXMLData == null){
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
serverToClientThread.addConnectionListener(this::sendSetupMessages);
serverToClientThread.addDisconnectListener(this::clientDisconnected);
}
/**
@@ -222,6 +241,7 @@ public class MainServerThread implements Runnable, ClientConnectionDelegate {
serverToClientThread.sendSetupMessages();
}
}
serverToClientThreads.remove(closedConnection);
try {
@@ -255,9 +275,9 @@ public class MainServerThread implements Runnable, ClientConnectionDelegate {
}, 0, 500);
if (GameState.getCurrentStage() == GameStages.LOBBYING) {
sendSetupMessages();
}
// if (GameState.getCurrentStage() == GameStages.LOBBYING) {
// sendSetupMessages();
// }
}
public void terminate() {
@@ -268,39 +288,166 @@ public class MainServerThread implements Runnable, ClientConnectionDelegate {
* Initialise boats to specific spaced out geopoints behind starting line.
*/
private void initialiseBoatPositions() {
CompoundMark cm = GameState.getMarkOrder().getMarkOrder().get(0);
GeoPoint startMark1 = cm.getSubMark(1);
GeoPoint startMark2 = cm.getSubMark(2);
// CompoundMark cm = GameState.getMarkOrder().getMarkOrder().get(0);
// GeoPoint startMark1 = cm.getSubMark(1);
// GeoPoint startMark2 = cm.getSubMark(2);
//
// // Calculating midpoint
// Double perpendicularAngle = GeoUtility.getBearing(startMark1, startMark2);
// Double length = GeoUtility.getDistance(startMark1, startMark2);
// GeoPoint midpoint = GeoUtility.getGeoCoordinate(startMark1, perpendicularAngle, length / 2);
//
// // Setting each boats position side by side
// final double SEPARATION = 50.0; // distance apart in meters
//
// int boatIndex = 0;
// for (ServerYacht yacht : GameState.getYachts().values()) {
// int distanceApart = boatIndex / 2;
//
// if (boatIndex % 2 == 1 && boatIndex != 0) {
// distanceApart++;
// distanceApart *= -1;
// }
//
// GeoPoint spawnMark = GeoUtility
// .getGeoCoordinate(midpoint, perpendicularAngle, distanceApart * SEPARATION);
//
// if (yacht.getHeading() < perpendicularAngle) {
// spawnMark = GeoUtility
// .getGeoCoordinate(spawnMark, perpendicularAngle + 90, SEPARATION);
// } else {
// spawnMark = GeoUtility
// .getGeoCoordinate(spawnMark, perpendicularAngle + 270, SEPARATION);
// }
//
// yacht.setLocation(spawnMark);
// boatIndex++;
// }
// Calculating midpoint
Double perpendicularAngle = GeoUtility.getBearing(startMark1, startMark2);
Double length = GeoUtility.getDistance(startMark1, startMark2);
GeoPoint midpoint = GeoUtility.getGeoCoordinate(startMark1, perpendicularAngle, length / 2);
// final double SEPARATION = 50.0; // distance apart in meters
//
// //Reverse of the angle from start to first mark
// double angleToFirstMark = 360 - GeoUtility.getBearing(
// GameState.getMarkOrder().getMarkOrder().get(0).getMidPoint(),
// GameState.getMarkOrder().getMarkOrder().get(1).getMidPoint()
// );
//
// //Length of start line
// double startLineLength = GeoUtility.getDistance(
// GameState.getMarkOrder().getMarkOrder().get(0).getSubMark(1),
// GameState.getMarkOrder().getMarkOrder().get(0).getSubMark(2)
// );
//
// //Angle of start line
// double startMarkToMarkAngle = GeoUtility.getBearing(
// GameState.getMarkOrder().getMarkOrder().get(0).getSubMark(1),
// GameState.getMarkOrder().getMarkOrder().get(0).getSubMark(2)
// );
//
// //How many yachts can fit along the start line
// int spacesAlongLine = (int) Math.round(startLineLength / SEPARATION);
// //The free space left by the boats.
// double buffer = (startLineLength % SEPARATION) / 2;
//
// //Randomize starting order.
// List<ServerYacht> serverYachtList = new ArrayList<>(GameState.getYachts().values());
// Collections.shuffle(serverYachtList);
//
// //set the starting point away from start line.
// GeoPoint startingPoint = GeoUtility.getGeoCoordinate(
// GameState.getMarkOrder().getMarkOrder().get(0).getSubMark(1),
// angleToFirstMark, SEPARATION
// );
//
// //Move it along the start line
// startingPoint = GeoUtility.getGeoCoordinate(
// startingPoint, startMarkToMarkAngle, buffer
// );
//
// int yachtCount = 0;
// int repeats = 0;
//
// GeoPoint yachtLocation;
//
// for (ServerYacht serverYacht : serverYachtList) {
//
// //Move away from start line
// yachtLocation = GeoUtility.getGeoCoordinate(
// startingPoint, angleToFirstMark,repeats * SEPARATION
// );
// //Move along start line
// yachtLocation = GeoUtility.getGeoCoordinate(
// yachtLocation, startMarkToMarkAngle, yachtCount * SEPARATION
// );
// serverYacht.setLocation(yachtLocation);
// serverYacht.setHeading(GeoUtility.getBearing(
// yachtLocation, GameState.getMarkOrder().getMarkOrder().get(1).getMidPoint()
// ));
// //Set location for next yacht
// yachtCount++;
// if (yachtCount > spacesAlongLine) {
// yachtCount = 0;
// repeats++;
// }
// }
// Setting each boats position side by side
double DISTANCE_FACTOR = 50.0; // distance apart in meters
int boatIndex = 0;
for (ServerYacht yacht : GameState.getYachts().values()) {
int distanceApart = boatIndex / 2;
final double DISTANCE_TO_START = 75d;
final double YACHT_SEPARATION = 20d;
if (boatIndex % 2 == 1 && boatIndex != 0) {
distanceApart++;
distanceApart *= -1;
//Length of start line
double startLineLength = GeoUtility.getDistance(
GameState.getMarkOrder().getMarkOrder().get(0).getSubMark(1),
GameState.getMarkOrder().getMarkOrder().get(0).getSubMark(2)
);
//How many yachts can fit along the start line
int spacesAlongLine = (int) Math.round(startLineLength / YACHT_SEPARATION);
//Angle of start line
double startMarkToMarkAngle = GeoUtility.getBearing(
GameState.getMarkOrder().getMarkOrder().get(0).getSubMark(1),
GameState.getMarkOrder().getMarkOrder().get(0).getSubMark(2)
);
//angle from first mark to the start
double angleToStart = GeoUtility.getBearing(
GameState.getMarkOrder().getMarkOrder().get(1).getMidPoint(),
GameState.getMarkOrder().getMarkOrder().get(0).getMidPoint()
);
double angleFromStart = GeoUtility.getBearing(
GameState.getMarkOrder().getMarkOrder().get(0).getMidPoint(),
GameState.getMarkOrder().getMarkOrder().get(1).getMidPoint()
);
GeoPoint startingPoint = GeoUtility.getGeoCoordinate(
GameState.getMarkOrder().getMarkOrder().get(0).getMidPoint(),
angleToStart, DISTANCE_TO_START
);
List<ServerYacht> randomisedYachts = new ArrayList<>(GameState.getYachts().values());
Collections.shuffle(randomisedYachts);
while (randomisedYachts.size() > 0) {
int numYachtsInLine = spacesAlongLine > randomisedYachts.size() ? randomisedYachts.size() : spacesAlongLine;
double yachtSpace = numYachtsInLine * YACHT_SEPARATION / 2;
GeoPoint firstYachtPoint = GeoUtility.getGeoCoordinate(
startingPoint, startMarkToMarkAngle + 180, yachtSpace
);
for (int i=0; i<numYachtsInLine; i++){
randomisedYachts.get(0).setHeading(angleFromStart);
randomisedYachts.get(0).setLocation(firstYachtPoint);
firstYachtPoint = GeoUtility.getGeoCoordinate(
firstYachtPoint, startMarkToMarkAngle, yachtSpace
);
randomisedYachts.remove(0);
}
GeoPoint spawnMark = GeoUtility
.getGeoCoordinate(midpoint, perpendicularAngle, distanceApart * DISTANCE_FACTOR);
if (yacht.getHeading() < perpendicularAngle) {
spawnMark = GeoUtility
.getGeoCoordinate(spawnMark, perpendicularAngle + 90, DISTANCE_FACTOR);
} else {
spawnMark = GeoUtility
.getGeoCoordinate(spawnMark, perpendicularAngle + 270, DISTANCE_FACTOR);
}
yacht.setLocation(spawnMark);
boatIndex++;
startingPoint = GeoUtility.getGeoCoordinate(
startingPoint, angleToStart, DISTANCE_TO_START
);
}
}
}
@@ -1,5 +1,6 @@
package seng302.gameServer;
import seng302.gameServer.messages.*;
import java.util.ArrayList;
import java.util.List;
import seng302.gameServer.messages.BoatLocationMessage;
@@ -18,10 +19,15 @@ import seng302.model.Player;
import seng302.model.ServerYacht;
import seng302.model.stream.xml.generator.RaceXMLTemplate;
import seng302.model.stream.xml.generator.RegattaXMLTemplate;
import seng302.model.stream.xml.parser.RaceXMLData;
import seng302.model.stream.xml.parser.RegattaXMLData;
import seng302.model.token.Token;
import seng302.model.token.TokenType;
import seng302.utilities.XMLGenerator;
import java.util.ArrayList;
import java.util.List;
/**
* A Class for interfacing between the data we have in the GameState to the messages we need to send
* through the MainServerThread.
@@ -39,6 +45,51 @@ Ideally this class would be created with an instance of the GameState (I tried i
public class MessageFactory {
private static XMLGenerator xmlGenerator = new XMLGenerator();
private static XMLMessage race;
private static XMLMessage regatta;
private static XMLMessage boats;
public static void updateXMLGenerator(RaceXMLData race, RegattaXMLData regatta) {
xmlGenerator.setRegattaTemplate(
new RegattaXMLTemplate(
regatta.getRegattaName(),
regatta.getCourseName(),
regatta.getCentralLat(),
regatta.getCentralLng()
)
);
xmlGenerator.setRaceTemplate(
new RaceXMLTemplate(
new ArrayList<>(),
new ArrayList<>(),
race.getMarkSequence(),
race.getCourseLimit(),
new ArrayList<>(race.getCompoundMarks().values()),
GameState.getCapacity(), true
)
);
String xmlStr = xmlGenerator.getRaceAsXml();
MessageFactory.race = new XMLMessage(xmlStr, XMLMessageSubType.RACE, xmlStr.length());
xmlStr = xmlGenerator.getRegattaAsXml();
MessageFactory.regatta = new XMLMessage(xmlStr, XMLMessageSubType.REGATTA, xmlStr.length());
xmlStr = xmlGenerator.getBoatsAsXml();
MessageFactory.boats = new XMLMessage(xmlStr, XMLMessageSubType.BOAT, xmlStr.length());
}
public static void updateBoats(List<ServerYacht> yachts) {
// for (ServerYacht serverYacht : yachts) {
// System.out.println(serverYacht);
// }
xmlGenerator.getRace().setBoats(yachts);
String xmlStr = xmlGenerator.getBoatsAsXml();
MessageFactory.boats = new XMLMessage(xmlStr, XMLMessageSubType.BOAT, xmlStr.length());
}
public static void updateTokens(List<Token> tokens) {
xmlGenerator.getRace().setTokens(tokens);
String xmlStr = xmlGenerator.getRaceAsXml();
MessageFactory.race = new XMLMessage(xmlStr, XMLMessageSubType.RACE, xmlStr.length());
}
public static RaceStartStatusMessage getRaceStartStatusMessage() {
@@ -99,38 +150,15 @@ public class MessageFactory {
}
public static XMLMessage getRaceXML() {
List<ServerYacht> yachts = new ArrayList<>(GameState.getYachts().values());
List<Token> tokens = GameState.getTokensInPlay();
RaceXMLTemplate raceXMLTemplate = new RaceXMLTemplate(yachts, tokens);
xmlGenerator.setRaceTemplate(raceXMLTemplate);
XMLMessage raceXMLMessage = new XMLMessage(
xmlGenerator.getRaceAsXml(),
XMLMessageSubType.RACE,
xmlGenerator.getRaceAsXml().length());
return raceXMLMessage;
return race;
}
public static XMLMessage getRegattaXML() {
//@TODO calculate lat/lng values
return new XMLMessage(
xmlGenerator.getRegattaAsXml(),
XMLMessageSubType.REGATTA,
xmlGenerator.getRegattaAsXml().length());
return regatta;
}
public static XMLMessage getBoatXML() {
List<ServerYacht> yachts = new ArrayList<>(GameState.getYachts().values());
List<Token> tokens = GameState.getTokensInPlay();
RaceXMLTemplate raceXMLTemplate = new RaceXMLTemplate(yachts, tokens);
xmlGenerator.setRaceTemplate(raceXMLTemplate);
return new XMLMessage(
xmlGenerator.getBoatsAsXml(),
XMLMessageSubType.BOAT,
xmlGenerator.getBoatsAsXml().length());
return boats;
}
public static YachtEventCodeMessage makeCollisionMessage(ServerYacht serverYacht) {
@@ -1,5 +1,10 @@
package seng302.gameServer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import seng302.discoveryServer.DiscoveryServerClient;
import seng302.discoveryServer.util.ServerListing;
import javax.jmdns.JmDNS;
import javax.jmdns.ServiceInfo;
import java.io.IOException;
@@ -32,12 +37,16 @@ public class ServerAdvertiser {
private static ServerAdvertiser instance = null;
private static JmDNS jmdnsInstance = null;
private ServiceInfo serviceInfo; // Note: Whenever this is changed, our service will be re-registered on the network.
private DiscoveryServerClient repositoryClient;
private Hashtable<String ,String> props;
private Logger logger = LoggerFactory.getLogger(ServerAdvertiser.class);
private ServerAdvertiser() throws IOException{
jmdnsInstance = JmDNS.create(InetAddress.getByName(getLocalHostIp()));
repositoryClient = new DiscoveryServerClient();
props = new Hashtable<>();
props.put("map", "");
props.put("spacesLeft", "0");
@@ -122,10 +131,13 @@ public class ServerAdvertiser {
try {
jmdnsInstance.registerService(serviceInfo);
} catch (IOException e) {
System.out.println("Failed");
logger.warn("Failed to register service info");
}
}
}, 0);
ServerListing serverListing = new ServerListing(serverName, props.get("map"), new DiscoveryServerClient().getInetIp(), portNo, Integer.parseInt(props.get("capacity")));
repositoryClient.register(serverListing);
}
/**
@@ -7,6 +7,10 @@ public class ServerDescription {
private String serverName;
private String mapName;
private Integer numPlayers;
private Long lastUpdated;
private Long lastRefreshed;
private static Long EXPIRY_INTERVAL = 5000L;
public ServerDescription(String serverName, String mapName, Integer numPlayers, Integer capacity, String address, Integer portNum){
this.serverName = serverName;
@@ -15,6 +19,7 @@ public class ServerDescription {
this.address = address;
this.portNum = portNum;
this.capacity = capacity;
lastUpdated = System.currentTimeMillis();
}
@@ -80,4 +85,17 @@ public class ServerDescription {
return this.getName().hashCode() + this.getAddress().hashCode() +
this.portNumber().hashCode() + this.getMapName().hashCode();
}
public Boolean hasExpired(){
return System.currentTimeMillis() - lastUpdated > EXPIRY_INTERVAL;
}
public Boolean serverShouldBeRemoved() {
if (lastRefreshed == null) return false;
return System.currentTimeMillis() - lastRefreshed > EXPIRY_INTERVAL;
}
public void hasBeenRefreshed(){
lastRefreshed = System.currentTimeMillis();
}
}
@@ -1,23 +1,11 @@
package seng302.gameServer;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
import java.util.stream.Collectors;
import java.util.zip.CRC32;
import java.util.zip.Checksum;
import javafx.beans.property.SimpleObjectProperty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import seng302.gameServer.messages.*;
import org.w3c.dom.Document;
import seng302.gameServer.messages.BoatAction;
import seng302.gameServer.messages.ChatterMessage;
import seng302.gameServer.messages.ClientType;
@@ -25,16 +13,29 @@ import seng302.gameServer.messages.CustomizeRequestType;
import seng302.gameServer.messages.Message;
import seng302.gameServer.messages.RegistrationResponseMessage;
import seng302.gameServer.messages.RegistrationResponseStatus;
import seng302.gameServer.messages.XMLMessage;
import seng302.gameServer.messages.XMLMessageSubType;
import seng302.model.Player;
import seng302.model.ServerYacht;
import seng302.model.stream.packets.PacketType;
import seng302.model.stream.packets.StreamPacket;
import seng302.model.stream.xml.generator.RaceXMLTemplate;
import seng302.model.stream.xml.parser.RaceXMLData;
import seng302.model.stream.xml.parser.RegattaXMLData;
import seng302.utilities.StreamParser;
import seng302.utilities.XMLGenerator;
import seng302.utilities.XMLParser;
import seng302.visualiser.fxObjects.assets_3D.BoatMeshType;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.zip.CRC32;
import java.util.zip.Checksum;
/**
* A class describing a single connection to a Client for the purposes of sending and receiving on
* its own thread. All server threads created and owned by the server thread handler which can
@@ -80,6 +81,9 @@ public class ServerToClientThread implements Runnable {
private Player player;
private SimpleObjectProperty<RaceXMLData> raceXMLProperty = new SimpleObjectProperty<>();
private SimpleObjectProperty<RegattaXMLData> regattaXMLProperty = new SimpleObjectProperty<>();
public ServerToClientThread(Socket socket) {
this.socket = socket;
seqNo = 0;
@@ -100,9 +104,8 @@ public class ServerToClientThread implements Runnable {
}
private void setUpPlayer(){
String shortName = "p" + sourceId;
String longName = "player " + sourceId;
String shortName = "P" + sourceId;
String longName = "Player " + sourceId;
ServerYacht yacht = new ServerYacht(
BoatMeshType.DINGHY, sourceId, sourceId.toString(), shortName, longName, "NZ");
@@ -164,37 +167,52 @@ public class ServerToClientThread implements Runnable {
long computedCrc = checksum.getValue();
long packetCrc = Message.bytesToLong(getBytes(4));
if (computedCrc == packetCrc) {
StreamPacket packet = new StreamPacket(type, payloadLength, timeStamp, payload);
switch (PacketType.assignPacketType(type, payload)) {
case BOAT_ACTION:
BoatAction actionType = ServerPacketParser
.extractBoatAction(
new StreamPacket(type, payloadLength, timeStamp, payload));
BoatAction actionType = ServerPacketParser.extractBoatAction(packet);
GameState.updateBoat(sourceId, actionType);
break;
case RACE_REGISTRATION_REQUEST:
ClientType requestedType = ServerPacketParser.extractClientType(
new StreamPacket(type, payloadLength, timeStamp, payload));
ClientType requestedType = ServerPacketParser
.extractClientType(packet);
completeRegistration(requestedType);
break;
case CHATTER_TEXT:
ChatterMessage chatterMessage = ServerPacketParser
.extractChatterText(
new StreamPacket(type, payloadLength, timeStamp, payload));
.extractChatterText(packet);
GameState.processChatter(chatterMessage, isHost);
break;
case RACE_CUSTOMIZATION_REQUEST:
Long sourceID = Message
.bytesToLong(Arrays.copyOfRange(payload, 0, 3));
Long sourceID = Message.bytesToLong(
Arrays.copyOfRange(payload, 0, 3)
);
CustomizeRequestType requestType = ServerPacketParser
.extractCustomizationType(
new StreamPacket(type, payloadLength, timeStamp, payload));
.extractCustomizationType(packet);
GameState.customizePlayer(sourceID, requestType,
Arrays.copyOfRange(payload, 6, payload.length));
Arrays.copyOfRange(payload, 6, payload.length)
);
GameState.setCustomizationFlag();
// TODO: 17/08/2017 ajm412: Send a response packet here, not really necessary until we do shapes.
break;
case RACE_XML:
Document document = StreamParser.extractXmlMessage(packet);
raceXMLProperty.set(
XMLParser.parseRace(document)
);
GameState.setMaxPlayers(XMLParser.getMaxPlayers(document));
GameState.setTokensEnabled(XMLParser.tokensEnabled(document));
break;
case REGATTA_XML:
regattaXMLProperty.set(
XMLParser.parseRegatta(
StreamParser.extractXmlMessage(packet)
)
);
break;
}
} else {
logger.warn("Packet has been dropped", 1);
@@ -211,23 +229,9 @@ public class ServerToClientThread implements Runnable {
}
public void sendSetupMessages() {
xmlGenerator = new XMLGenerator();
RaceXMLTemplate race = new RaceXMLTemplate(new ArrayList<>(GameState.getYachts().values()), new ArrayList<>());
xmlGenerator.setRaceTemplate(race);
XMLMessage xmlMessage;
xmlMessage = new XMLMessage(xmlGenerator.getRegattaAsXml(), XMLMessageSubType.REGATTA,
xmlGenerator.getRegattaAsXml().length());
sendMessage(xmlMessage);
xmlMessage = new XMLMessage(xmlGenerator.getBoatsAsXml(), XMLMessageSubType.BOAT,
xmlGenerator.getBoatsAsXml().length());
sendMessage(xmlMessage);
xmlMessage = new XMLMessage(xmlGenerator.getRaceAsXml(), XMLMessageSubType.RACE,
xmlGenerator.getRaceAsXml().length());
sendMessage(xmlMessage);
sendMessage(MessageFactory.getRegattaXML());
sendMessage(MessageFactory.getBoatXML());
sendMessage(MessageFactory.getRaceXML());
}
private void closeSocket() {
@@ -319,4 +323,12 @@ public class ServerToClientThread implements Runnable {
public void setAsHost() {
isHost = true;
}
public SimpleObjectProperty<RaceXMLData> raceXMLProperty() {
return raceXMLProperty;
}
public SimpleObjectProperty<RegattaXMLData> regattaXMLProperty() {
return regattaXMLProperty;
}
}
@@ -21,7 +21,10 @@ public enum MessageType {
REGISTRATION_REQUEST(101),
REGISTRATION_RESPONSE(102),
CUSTOMIZATION_REQUEST(103),
CUSTOMIZATION_RESPONSE(104);
CUSTOMIZATION_RESPONSE(104),
REPO_REGISTRATION_REQUEST(201),
ROOM_CODE_REQUEST(202),
LOBBY_REQUEST(203);
private int code;
@@ -0,0 +1,24 @@
package seng302.gameServer.messages;
public class RoomCodeRequest extends Message{
private int size = 0;
@Override
public int getSize() {
return size;
}
public RoomCodeRequest(String roomCode){
size = roomCode.length() + 6;
setHeader(new Header(MessageType.ROOM_CODE_REQUEST, 0x01, (short)getSize()));
allocateBuffer();
writeHeaderToBuffer();
putInt(roomCode.length(), 6);
putBytes(roomCode.getBytes());
writeCRC();
rewind();
}
}
@@ -0,0 +1,64 @@
package seng302.gameServer.messages;
import seng302.discoveryServer.util.ServerListing;
public class ServerRegistrationMessage extends Message {
private int size;
public ServerRegistrationMessage(ServerListing serverListing) {
String serverName = serverListing.getServerName();
String mapName = serverListing.getMapName();
String address = serverListing.getAddress();
int port = serverListing.getPortNumber();
int players = serverListing.getPortNumber();
int capacity = serverListing.getCapacity();
String roomCode = serverListing.getRoomCode();
createMessage(serverName, mapName, address, port, players, capacity, roomCode);
}
public static ServerRegistrationMessage getEmptyRegistration() {
return new ServerRegistrationMessage("","","",0,0,0,"");
}
@Override
public int getSize() {
return size;
}
public ServerRegistrationMessage(String serverName, String mapName, String address, int port, int players, int capacity, String roomCode){
createMessage(serverName, mapName, address, port, players, capacity, roomCode);
}
private void createMessage(String serverName, String mapName, String address, int port, int players, int capacity, String roomCode){
size = serverName.getBytes().length + mapName.length() + address.length() + roomCode.length() + 36;
setHeader(new Header(MessageType.REPO_REGISTRATION_REQUEST, 0x01, (short) getSize()));
allocateBuffer();
writeHeaderToBuffer();
int nameLength = serverName.length();
int mapNameLength = mapName.length();
int addressLength = address.length();
int roomCodeLength = roomCode.length();
// Put fields here
putInt(nameLength, 6);
putInt(mapNameLength, 6);
putInt(addressLength, 6);
putInt(roomCodeLength, 6);
putInt(port, 4);
putInt(players, 4);
putInt(capacity, 4);
putBytes(serverName.getBytes());
putBytes(mapName.getBytes());
putBytes(address.getBytes());
putBytes(roomCode.getBytes());
writeCRC();
rewind();
}
}
@@ -21,6 +21,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import seng302.model.token.TokenType;
import seng302.visualiser.fxObjects.assets_3D.BoatMeshType;
import seng302.model.token.TokenType;
import seng302.visualiser.fxObjects.assets_3D.BoatObject;
/**
+5
View File
@@ -15,4 +15,9 @@ public class Limit extends GeoPoint {
public Integer getSeqID() {
return seqID;
}
@Override
public String toString(){
return "Limit = {seqID=" + seqID + ", lat=" + getLat() + ", lng=" + getLng() + "}";
}
}
@@ -0,0 +1,122 @@
package seng302.model;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import javafx.geometry.Point2D;
import seng302.utilities.GeoUtility;
/**
* Contains information on a scaled lat lon point for use with mapping geographical elements to a 2d plane.
*/
public class ScaledPoint extends GeoPoint {
public enum ScaleDirection {
HORIZONTAL,
VERTICAL
}
private double x, y, scaleFactor;
private ScaleDirection scaleDirection;
private ScaledPoint(double lat, double lng, double x, double y, double scaleFactor, ScaleDirection direction) {
super(lat, lng);
this.x = x;
this.y = y;
this.scaleFactor = scaleFactor;
this.scaleDirection = direction;
}
public double getX() {
return x;
}
public double getY() {
return y;
}
public double getScaleFactor() {
return scaleFactor;
}
public ScaleDirection getScaleDirection() {
return scaleDirection;
}
public Point2D findScaledXY(GeoPoint unscaled) {
return findScaledXY(unscaled.getLat(), unscaled.getLng());
}
public Point2D findScaledXY(double unscaledLat, double unscaledLon) {
double distanceFromReference;
double angleFromReference;
double xReference = this.getX();
double yReference = this.getY();
angleFromReference = GeoUtility.getBearingRad(
this, new GeoPoint(unscaledLat, unscaledLon)
);
distanceFromReference = GeoUtility.getDistance(
this, new GeoPoint(unscaledLat, unscaledLon)
);
if (angleFromReference >= 0 && angleFromReference <= Math.PI / 2) {
xReference += scaleFactor * Math.sin(angleFromReference) * distanceFromReference;
yReference -= scaleFactor * Math.cos(angleFromReference) * distanceFromReference;
} else if (angleFromReference >= 0) {
angleFromReference = angleFromReference - Math.PI / 2;
xReference += scaleFactor * Math.cos(angleFromReference) * distanceFromReference;
yReference += scaleFactor * Math.sin(angleFromReference) * distanceFromReference;
} else if (angleFromReference < 0 && angleFromReference >= -Math.PI / 2) {
angleFromReference = Math.abs(angleFromReference);
xReference -= scaleFactor * Math.sin(angleFromReference) * distanceFromReference;
yReference -= scaleFactor * Math.cos(angleFromReference) * distanceFromReference;
} else {
angleFromReference = Math.abs(angleFromReference) - Math.PI / 2;
xReference -= scaleFactor * Math.cos(angleFromReference) * distanceFromReference;
yReference += scaleFactor * Math.sin(angleFromReference) * distanceFromReference;
}
return new Point2D(xReference, yReference);
}
public static ScaledPoint makeScaledPoint(double width, double height,
List<? extends GeoPoint> points, boolean centered) {
double referencePointX, referencePointY, scaleFactor, lat, lng;
ScaleDirection scaleDirection;
points = new ArrayList<>(points);
points.sort(Comparator.comparingDouble(GeoPoint::getLat));
GeoPoint minLatPoint = points.get(0);
GeoPoint maxLatPoint = points.get(points.size() - 1);
points.sort(Comparator.comparingDouble(GeoPoint::getLng));
GeoPoint minLonPoint = points.get(0);
GeoPoint maxLonPoint = points.get(points.size() - 1);
referencePointX = centered ? 0 : width / 2;
referencePointY = centered ? 0 : height / 2;
lat = (maxLatPoint.getLat() - minLatPoint.getLat()) / 2 + minLatPoint.getLat();
lng = (maxLonPoint.getLng() - minLonPoint.getLng()) / 2 + minLonPoint.getLng();
GeoPoint ref = new GeoPoint(lat, lng);
double vertDistance = GeoUtility.getDistance(
ref, new GeoPoint(ref.getLat(), maxLonPoint.getLng())
) * 2.1;
double horiDistance = GeoUtility.getDistance(
ref, new GeoPoint(maxLatPoint.getLat(), ref.getLng())
) * 2.1;
double vertScale = height / vertDistance;
if (horiDistance * vertScale > width) {
scaleFactor = width / horiDistance;
scaleDirection = ScaleDirection.HORIZONTAL;
} else {
scaleFactor = vertScale;
scaleDirection = ScaleDirection.VERTICAL;
}
return new ScaledPoint(lat, lng, referencePointX, referencePointY, scaleFactor, scaleDirection);
}
}
@@ -1,6 +1,6 @@
package seng302.model.mark;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import seng302.gameServer.messages.RoundingSide;
import seng302.model.GeoPoint;
@@ -10,13 +10,13 @@ public class CompoundMark {
private int compoundMarkId;
private String name;
private List<Mark> marks = new ArrayList<>();
private List<Mark> marks;
private GeoPoint midPoint;
public CompoundMark(int markID, String name, List<Mark> marks) {
this.compoundMarkId = markID;
this.name = name;
this.marks.addAll(marks);
this.marks = Collections.unmodifiableList(marks);
if (marks.size() > 1) {
this.midPoint = GeoUtility.getDirtyMidPoint(marks.get(0), marks.get(1));
} else {
@@ -32,4 +32,10 @@ public class Corner {
public Integer getZoneSize() {
return zoneSize;
}
@Override
public String toString() {
return "Corner = {seqID=" + seqID + ", compoundMarkID=" + compoundMarkID + ", rounding="
+ rounding +", zoneSize=" + zoneSize + "}";
}
}
+14 -84
View File
@@ -1,23 +1,12 @@
package seng302.model.mark;
import java.io.IOException;
import java.io.StringReader;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import seng302.gameServer.messages.RoundingSide;
import seng302.model.ServerYacht;
import seng302.model.stream.xml.generator.RaceXMLTemplate;
import seng302.model.stream.xml.parser.RaceXMLData;
import seng302.model.token.Token;
import seng302.utilities.XMLGenerator;
import seng302.utilities.XMLParser;
import java.util.*;
/**
* Class to hold the order of the marks in the race.
@@ -28,8 +17,17 @@ public class MarkOrder {
private Logger logger = LoggerFactory.getLogger(MarkOrder.class);
private List<Mark> allMarks;
public MarkOrder(){
loadRaceProperties();
public MarkOrder(RaceXMLData raceXMLData){
raceMarkOrder = new ArrayList<>();
for (Corner corner : raceXMLData.getMarkSequence()){
CompoundMark compoundMark = raceXMLData.getCompoundMarks().get(corner.getCompoundMarkID());
compoundMark.setRoundingSide(
RoundingSide.getRoundingSide(corner.getRounding())
);
raceMarkOrder.add(compoundMark);
}
orderedUniqueCompoundMarks = new ArrayList<>(raceXMLData.getCompoundMarks().values());
}
/**
@@ -41,7 +39,6 @@ public class MarkOrder {
logger.warn("Race order accessed but not instantiated");
return null;
}
return Collections.unmodifiableList(raceMarkOrder);
}
@@ -79,71 +76,4 @@ public class MarkOrder {
public CompoundMark getNextMark(Integer currentSeqID) throws IndexOutOfBoundsException {
return raceMarkOrder.get(currentSeqID + 1);
}
public List<Mark> getAllMarks() {
return Collections.unmodifiableList(allMarks);
}
/**
* Loads the race order from an XML string
* @param xml An AC35 RaceXML
* @return An ordered list of marks in the race
*/
private List<CompoundMark> loadRaceOrderFromXML(String xml) {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db;
Document doc;
allMarks = new ArrayList<>();
try {
db = dbf.newDocumentBuilder();
doc = db.parse(new InputSource(new StringReader(xml)));
} catch (ParserConfigurationException | IOException | SAXException e) {
logger.error("Failed to read generated race XML");
return null;
}
RaceXMLData data = XMLParser.parseRace(doc);
if (data != null){
logger.debug("Loaded RaceXML for mark order");
List<Corner> corners = data.getMarkSequence();
Map<Integer, CompoundMark> marks = data.getCompoundMarks();
orderedUniqueCompoundMarks = new ArrayList<>(marks.values());
List<CompoundMark> course = new ArrayList<>();
for (Corner corner : corners){
CompoundMark compoundMark = marks.get(corner.getCompoundMarkID());
compoundMark.setRoundingSide(
RoundingSide.getRoundingSide(corner.getRounding())
);
course.add(compoundMark);
allMarks.addAll(compoundMark.getMarks());
}
return course;
}
return null;
}
/**
* Load the raceXML and mark order
*/
private void loadRaceProperties(){
XMLGenerator generator = new XMLGenerator();
// TODO: 29/08/17 wmu16 - This is terrible, having to make a template just to receive constant data
generator.setRaceTemplate(new RaceXMLTemplate(
new ArrayList<>(),
new ArrayList<>()));
String raceXML = generator.getRaceAsXml();
if (raceXML == null){
logger.error("Failed to generate raceXML (for race properties)");
return;
}
raceMarkOrder = loadRaceOrderFromXML(raceXML);
}
}
@@ -20,7 +20,9 @@ public enum PacketType {
RACE_REGISTRATION_REQUEST,
RACE_REGISTRATION_RESPONSE,
RACE_CUSTOMIZATION_REQUEST,
RACE_CUSTOMIZATION_RESPONSE;
RACE_CUSTOMIZATION_RESPONSE,
SERVER_REGISTRATION, ROOM_CODE_REQUEST, LOBBY_REQUEST;
public static PacketType assignPacketType(int packetType, byte[] payload){
switch(packetType){
@@ -65,6 +67,10 @@ public enum PacketType {
return RACE_CUSTOMIZATION_REQUEST;
case 104:
return RACE_CUSTOMIZATION_RESPONSE;
case 201:
return SERVER_REGISTRATION;
case 202:
return ROOM_CODE_REQUEST;
default:
}
return OTHER;
@@ -1,10 +1,12 @@
package seng302.model.stream.xml.generator;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import seng302.model.Limit;
import seng302.model.ServerYacht;
import seng302.model.mark.CompoundMark;
import seng302.model.mark.Corner;
import seng302.model.token.Token;
/**
@@ -15,11 +17,22 @@ public class RaceXMLTemplate {
private List<ServerYacht> yachts;
private LocalDateTime startTime;
private List<Token> tokens;
private List<Corner> roundings;
private List<Limit> courseLimit;
private List<CompoundMark> course;
private Integer maxPlayers;
private Boolean tokensEnabled;
public RaceXMLTemplate(List<ServerYacht> yachts, List<Token> tokens) {
public RaceXMLTemplate(List<ServerYacht> yachts, List<Token> tokens, List<Corner> roundings,
List<Limit> limit, List<CompoundMark> course, Integer maxPlayers, Boolean tokensEnabled) {
this.yachts = yachts;
this.tokens = tokens;
this.roundings = roundings;
this.courseLimit = limit;
this.course = course;
startTime = LocalDateTime.now();
this.maxPlayers = maxPlayers;
this.tokensEnabled = tokensEnabled;
}
/**
@@ -39,6 +52,18 @@ public class RaceXMLTemplate {
return Collections.unmodifiableList(tokens);
}
public List<CompoundMark> getCompoundMarks() {
return Collections.unmodifiableList(course);
}
public List<Limit> getCourseLimit() {
return Collections.unmodifiableList(courseLimit);
}
public List<Corner> getRoundings() {
return Collections.unmodifiableList(roundings);
}
/**
* Set the time until the race starts
* @param seconds The time in seconds until the race starts
@@ -54,4 +79,20 @@ public class RaceXMLTemplate {
public String getRaceStartTime(){
return startTime.toString();
}
public void setBoats(List<ServerYacht> boats) {
yachts = boats;
}
public void setTokens(List<Token> tokens) {
this.tokens = tokens;
}
public String getTokensEnabled() {
return tokensEnabled.toString();
}
public String getMaxPlayers() {
return maxPlayers.toString();
}
}
@@ -136,7 +136,6 @@ public class StreamParser {
long messageLength = bytesToLong(Arrays.copyOfRange(payload, 12, 14));
String xmlMessage = new String(
(Arrays.copyOfRange(payload, 14, (int) (14 + messageLength)))).trim();
//Create XML document Object
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db;
@@ -179,4 +179,8 @@ public class XMLGenerator {
public RegattaXMLTemplate getRegatta() {
return regatta;
}
public RaceXMLTemplate getRace() {
return race;
}
}
+209 -13
View File
@@ -1,21 +1,31 @@
package seng302.utilities;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import javafx.scene.paint.Color;
import javafx.util.Pair;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import seng302.model.ClientYacht;
import seng302.model.Colors;
import seng302.model.Limit;
import seng302.model.mark.CompoundMark;
import seng302.model.mark.Corner;
import seng302.model.mark.Mark;
import seng302.model.stream.xml.generator.RaceXMLTemplate;
import seng302.model.stream.xml.generator.RegattaXMLTemplate;
import seng302.model.stream.xml.parser.RaceXMLData;
import seng302.model.stream.xml.parser.RegattaXMLData;
import seng302.model.token.Token;
@@ -27,6 +37,8 @@ import seng302.visualiser.fxObjects.assets_3D.BoatMeshType;
*/
public class XMLParser {
private static final int MAX_PLAYERS = 8;
/**
* Returns the text content of a given child element tag, assuming it exists, as an Integer.
*
@@ -37,7 +49,7 @@ public class XMLParser {
private static Integer getElementInt(Element ele, String tag) {
NodeList tagList = ele.getElementsByTagName(tag);
if (tagList.getLength() > 0) {
return Integer.parseInt(tagList.item(0).getTextContent());
return Integer.parseInt(tagList.item(0).getTextContent().replaceAll("\\s+",""));
} else {
return null;
}
@@ -69,7 +81,7 @@ public class XMLParser {
private static Double getElementDouble(Element ele, String tag) {
NodeList tagList = ele.getElementsByTagName(tag);
if (tagList.getLength() > 0) {
return Double.parseDouble(tagList.item(0).getTextContent());
return Double.parseDouble(tagList.item(0).getTextContent().replaceAll("\\s+",""));
} else {
return null;
}
@@ -187,6 +199,36 @@ public class XMLParser {
);
}
public static Boolean tokensEnabled(Document doc) {
Element docEle = doc.getDocumentElement();
try {
NamedNodeMap namedNodeMap = docEle.getElementsByTagName("Tokens").item(0).getAttributes();
Node node = namedNodeMap.getNamedItem("Enabled");
if (node != null) {
return Boolean.parseBoolean(node.getNodeValue());
}
} catch (NullPointerException npe) {
npe.printStackTrace();
return false;
}
return false;
}
public static Integer getMaxPlayers(Document doc) {
Element docEle = doc.getDocumentElement();
try {
NamedNodeMap namedNodeMap = docEle.getElementsByTagName("Participants").item(0).getAttributes();
Node node = namedNodeMap.getNamedItem("MaxPlayers");
if (node != null) {
return Integer.parseInt(node.getNodeValue());
}
} catch (NullPointerException npe) {
npe.printStackTrace();
return MAX_PLAYERS;
}
return MAX_PLAYERS;
}
/**
* Returns an object containing the data extracted from the given xml formatted document
*
@@ -196,7 +238,7 @@ public class XMLParser {
public static RaceXMLData parseRace(Document doc) {
Element docEle = doc.getDocumentElement();
return new RaceXMLData(
extractParticpantIDs(docEle),
extractParticipantIDs(docEle),
extractTokens(docEle),
extractCompoundMarks(docEle),
extractMarkOrder(docEle),
@@ -235,13 +277,11 @@ public class XMLParser {
for (int i = 0; i < limitList.getLength(); i++) {
Node limitNode = limitList.item(i);
if (limitNode.getNodeName().equals("Limit")) {
courseLimit.add(
new Limit(
XMLParser.getNodeAttributeInt(limitNode, "SeqID"),
XMLParser.getNodeAttributeDouble(limitNode, "Lat"),
XMLParser.getNodeAttributeDouble(limitNode, "Lon")
)
);
courseLimit.add(new Limit(
XMLParser.getNodeAttributeInt(limitNode, "SeqID"),
XMLParser.getNodeAttributeDouble(limitNode, "Lat"),
XMLParser.getNodeAttributeDouble(limitNode, "Lon")
));
}
}
return courseLimit;
@@ -273,7 +313,7 @@ public class XMLParser {
/**
* Extracts course participants data
*/
private static List<Integer> extractParticpantIDs (Element docEle) {
private static List<Integer> extractParticipantIDs(Element docEle) {
List<Integer> boatIDs = new ArrayList<>();
NodeList pList = docEle.getElementsByTagName("Participants").item(0).getChildNodes();
for (int i = 0; i < pList.getLength(); i++) {
@@ -295,10 +335,11 @@ public class XMLParser {
for (int i = 0; i < cMarkList.getLength(); i++) {
Node cMarkNode = cMarkList.item(i);
if (cMarkNode.getNodeName().equals("CompoundMark")) {
String name = XMLParser.getNodeAttributeString(cMarkNode, "Name");
name = (name == null || name.equals("")) ? "Mark " + i+1: name;
cMark = new CompoundMark(
XMLParser.getNodeAttributeInt(cMarkNode, "CompoundMarkID"),
XMLParser.getNodeAttributeString(cMarkNode, "Name"),
createMarks(cMarkNode)
name, createMarks(cMarkNode)
);
allMarks.add(cMark);
}
@@ -319,14 +360,169 @@ public class XMLParser {
Node markNode = childMarks.item(i);
if (markNode.getNodeName().equals("Mark")) {
Integer seqID = XMLParser.getNodeAttributeInt(markNode, "SeqID");
seqID = (seqID == null) ? i+1 : seqID;
Integer sourceID = XMLParser.getNodeAttributeInt(markNode, "SourceID");
sourceID = (sourceID == null) ? i+1 : sourceID;
String markName = XMLParser.getNodeAttributeString(markNode, "Name");
markName = (markName == null || markName.equals("")) ? cMarkName + " " + i+1: markName;
Double targetLat = XMLParser.getNodeAttributeDouble(markNode, "TargetLat");
Double targetLng = XMLParser.getNodeAttributeDouble(markNode, "TargetLng");
Mark mark = new Mark(markName, seqID, targetLat, targetLng, sourceID);
subMarks.add(mark);
}
}
return subMarks;
}
/**
* This ungodly combination of existing methods and code blocks parses a race definition file.
* Look upon it and despair.
* @param url The input file path
* @param serverName the name of the server
* @param repetitions the repetitions of a segment of the race def file.
* @param maxPlayers max number of players. uses the default race max if null or greater than the actual max.
* @param tokensEnabled if tokens are enabled
* @return a pair which contains regatta string, race string as key, value pair.
*/
public static Pair<RegattaXMLTemplate, RaceXMLTemplate> parseRaceDef(
String url, String serverName, Integer repetitions, Integer maxPlayers, Boolean tokensEnabled
) {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db;
Document doc = null;
try {
db = dbf.newDocumentBuilder();
doc = db.parse(XMLParser.class.getResourceAsStream(url));
} catch (ParserConfigurationException | IOException | SAXException e) {
e.printStackTrace();
}
Element docEle = doc.getDocumentElement();
RegattaXMLTemplate regattaXMLTemplate = new RegattaXMLTemplate(
serverName, XMLParser.getElementString(docEle, "CourseName"),
XMLParser.getElementDouble(docEle, "CentralLat"),
XMLParser.getElementDouble(docEle, "CentralLng")
);
XMLGenerator xmlGenerator = new XMLGenerator();
xmlGenerator.setRegattaTemplate(regattaXMLTemplate);
if (maxPlayers == null) {
maxPlayers = XMLParser.getElementInt(docEle, "MaxPlayers");
} else if (maxPlayers > XMLParser.getElementInt(docEle, "MaxPlayers")) {
maxPlayers = XMLParser.getElementInt(docEle, "MaxPlayers");
}
RaceXMLTemplate raceXMLTemplate = new RaceXMLTemplate(
new ArrayList<>(), new ArrayList<>(),
XMLParser.extractMarkOrderRaceDef(docEle, repetitions),
XMLParser.extractCourseLimitRaceDef(docEle),
XMLParser.extractCompoundMarksRaceDef(docEle),
maxPlayers, tokensEnabled
);
xmlGenerator.setRaceTemplate(raceXMLTemplate);
return new Pair<>(regattaXMLTemplate, raceXMLTemplate);
}
private static List<Corner> extractMarkOrderRaceDef(Element docEle, int repitions){
List<Corner> compoundMarkSequence = new ArrayList<>();
NodeList cornerList = docEle.getElementsByTagName("Course").item(0).getChildNodes();
int seqId = 1;
final int zoneSize = 3;
for (int i=0; i<cornerList.getLength(); i++) {
Node segment = cornerList.item(i);
if (segment.getNodeName().equals("OpeningSegment") ||
segment.getNodeName().equals("ClosingSegment")) {
seqId = parseCourseSegment(segment, seqId, compoundMarkSequence);
} else if (segment.getNodeName().equals("RepeatingSegment")) {
for (int k = 0; k < repitions; k++) {
seqId = parseCourseSegment(segment, seqId, compoundMarkSequence);
}
}
}
return compoundMarkSequence;
}
/**
* Parses a segment of the course adding new Corners to the given list.
* @param segment Segment to parse
* @param seqID initial sequence ID
* @param course course to add corners to
* @return the last sequence id.
*/
private static int parseCourseSegment(Node segment, int seqID, List<Corner> course) {
NodeList segmentList = segment.getChildNodes();
for (int j = 0; j < segmentList.getLength(); j++) {
Node corner = segmentList.item(j);
if (corner.getNodeName().equals("Corner")) {
String rounding = XMLParser.getNodeAttributeString(corner, "Rounding");
rounding = //Converting "P" to "Port" and "S" to "Stbd"
rounding.equals("P") ? "Port" :
rounding.equals("S") ? "Stbd" : rounding;
course.add(new Corner(
seqID++, XMLParser.getNodeAttributeInt(corner, "CompoundMarkID"),
rounding, 3
));
}
}
return seqID;
}
private static List<Limit> extractCourseLimitRaceDef(Element docEle) {
List<Limit> courseLimit = new ArrayList<>();
NodeList limitList = docEle.getElementsByTagName("CourseLimit").item(0).getChildNodes();
int seqId = 1;
for (int i = 0; i < limitList.getLength(); i++) {
Node limitNode = limitList.item(i);
if (limitNode.getNodeName().equals("Limit")) {
courseLimit.add(new Limit(
seqId++, XMLParser.getNodeAttributeDouble(limitNode, "Lat"),
XMLParser.getNodeAttributeDouble(limitNode, "Lng")
));
}
}
return courseLimit;
}
private static List<CompoundMark> extractCompoundMarksRaceDef(Element docEle){
List<CompoundMark> allMarks = new ArrayList<>();
NodeList cMarkList = docEle.getElementsByTagName("Marks").item(0).getChildNodes();
CompoundMark cMark;
int markCount = 200;
for (int i = 0; i < cMarkList.getLength(); i++) {
Node cMarkNode = cMarkList.item(i);
if (cMarkNode.getNodeName().equals("CompoundMark")) {
Integer id = XMLParser.getNodeAttributeInt(cMarkNode, "CompoundMarkID");
List<Mark> subMarks = createMarksRaceDef(cMarkNode, markCount,"Mark " + id);
markCount += subMarks.size();
allMarks.add(new CompoundMark(id, "Mark " + id, subMarks));
}
}
return allMarks;
}
private static List<Mark> createMarksRaceDef(Node compoundMark, int markCount, String markName) {
List<Mark> subMarks = new ArrayList<>();
NodeList childMarks = compoundMark.getChildNodes();
int seqID = 1;
for (int i = 0; i < childMarks.getLength(); i++) {
Node markNode = childMarks.item(i);
if (markNode.getNodeName().equals("Mark")) {
Double targetLat = XMLParser.getNodeAttributeDouble(markNode, "Lat");
Double targetLng = XMLParser.getNodeAttributeDouble(markNode, "Lng");
Mark mark = new Mark(markName + " subMark " + seqID, seqID, targetLat, targetLng, markCount++);
subMarks.add(mark);
seqID += 1;
}
}
return subMarks;
}
}
@@ -1,19 +1,23 @@
package seng302.visualiser;
import javafx.util.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import seng302.gameServer.messages.*;
import seng302.model.stream.packets.PacketType;
import seng302.model.stream.packets.StreamPacket;
import seng302.utilities.XMLParser;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Queue;
import java.util.Timer;
import java.util.TimerTask;
import java.util.*;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.zip.CRC32;
import java.util.zip.Checksum;
import javafx.util.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import seng302.gameServer.messages.BoatAction;
@@ -25,8 +29,15 @@ import seng302.gameServer.messages.CustomizeRequestType;
import seng302.gameServer.messages.Message;
import seng302.gameServer.messages.RegistrationRequestMessage;
import seng302.gameServer.messages.RegistrationResponseStatus;
import seng302.gameServer.messages.XMLMessage;
import seng302.gameServer.messages.XMLMessageSubType;
import seng302.model.stream.packets.PacketType;
import seng302.model.stream.packets.StreamPacket;
import seng302.model.stream.xml.generator.RaceXMLTemplate;
import seng302.model.stream.xml.generator.RegattaXMLTemplate;
import seng302.utilities.XMLGenerator;
import seng302.utilities.XMLParser;
import seng302.visualiser.controllers.ViewManager;
/**
* A class describing a single connection to a Server for the purposes of sending and receiving on
@@ -44,7 +55,12 @@ public class ClientToServerThread implements Runnable {
@FunctionalInterface
public interface DisconnectedFromHostListener {
void notifYDisconnection (String message);
void notifyDisconnection(String message);
}
@FunctionalInterface
public interface ConnectionErrorListener {
void notifyConnectionError(String message);
}
private class ByteReadException extends Exception {
@@ -56,6 +72,7 @@ public class ClientToServerThread implements Runnable {
private Queue<StreamPacket> streamPackets = new ConcurrentLinkedQueue<>();
private List<ClientSocketListener> listeners = new ArrayList<>();
private List<DisconnectedFromHostListener> disconnectionListeners = new ArrayList<>();
private ConnectionErrorListener connectionErrorListener = null;
private Thread thread;
private Socket socket;
@@ -68,7 +85,7 @@ public class ClientToServerThread implements Runnable {
private Timer upWindPacketTimer = new Timer();
private Timer downWindPacketTimer = new Timer();
private boolean upwindTimerFlag = false, downwindTimerFlag = false;
static public final int PACKET_SENDING_INTERVAL_MS = 100;
public static final int PACKET_SENDING_INTERVAL_MS = 100;
private int clientId = -1;
@@ -133,8 +150,10 @@ public class ClientToServerThread implements Runnable {
else {
if (clientId == -1) continue; // Do not continue if not registered
streamPackets.add(new StreamPacket(type, payloadLength, timeStamp, payload));
for (ClientSocketListener csl : listeners)
csl.newPacket();
synchronized (this) {
for (ClientSocketListener csl : listeners)
csl.newPacket();
}
}
}
} else {
@@ -150,6 +169,9 @@ public class ClientToServerThread implements Runnable {
logger.warn("Closed connection to server", 1);
notifyDisconnectListeners("Connection to server was terminated");
closeSocket();
ViewManager.getInstance().goToStartView();
ViewManager.getInstance().showErrorSnackBar("Server rejected connection.");
}
public void sendCustomizationRequest(CustomizeRequestType reqType, byte[] payload) {
@@ -166,11 +188,23 @@ public class ClientToServerThread implements Runnable {
private void notifyDisconnectListeners (String message) {
if (socketOpen) {
for (DisconnectedFromHostListener listener : disconnectionListeners) {
listener.notifYDisconnection(message);
listener.notifyDisconnection(message);
}
}
}
private void handleConnectionError(String message){
if (connectionErrorListener != null){
connectionErrorListener.notifyConnectionError(message);
}
try {
this.socket.close();
} catch (IOException e) {
logger.error("Couldn't close socket");
}
}
/**
* Sends a request to the server asking for a source ID
*/
@@ -191,7 +225,7 @@ public class ClientToServerThread implements Runnable {
* @param packet The registration requests packet
*/
private void processRegistrationResponse(StreamPacket packet){
int sourceId = (int) Message.bytesToLong(Arrays.copyOfRange(packet.getPayload(), 0, 3));
int sourceId = (int) Message.bytesToLong(Arrays.copyOfRange(packet.getPayload(), 0, 4));
int statusCode = (int) Message.bytesToLong(Arrays.copyOfRange(packet.getPayload(), 4,5));
RegistrationResponseStatus status = RegistrationResponseStatus.getResponseStatus(statusCode);
@@ -210,8 +244,10 @@ public class ClientToServerThread implements Runnable {
else{
alertErrorText = "Could not connect to server";
}
handleConnectionError("Server no longer available.");
notifyDisconnectListeners(alertErrorText);
closeSocket();
System.out.println();
}
/**
@@ -318,7 +354,9 @@ public class ClientToServerThread implements Runnable {
}
public void addStreamObserver (ClientSocketListener streamListener) {
listeners.add(streamListener);
synchronized (this){
listeners.add(streamListener);
}
}
public void removeStreamObserver (ClientSocketListener streamListener) {
@@ -326,11 +364,21 @@ public class ClientToServerThread implements Runnable {
}
public void addDisconnectionListener (DisconnectedFromHostListener listener) {
disconnectionListeners.add(listener);
synchronized (this){
disconnectionListeners.add(listener);
}
}
public void removeDisconnectionListener (DisconnectedFromHostListener listener) {
disconnectionListeners.remove(listener);
synchronized (this){
disconnectionListeners.remove(listener);
}
}
public void setConnectionErrorListener(ConnectionErrorListener listener){
synchronized (this){
connectionErrorListener = listener;
}
}
private int readByte() throws ByteReadException {
@@ -347,6 +395,7 @@ public class ClientToServerThread implements Runnable {
notifyDisconnectListeners("Cannot read from server.");
closeSocket();
logger.warn("InputStream reach end of stream", 1);
handleConnectionError("Could not connect to server. Server is no longer available.");
}
return currentByte;
}
@@ -368,4 +417,25 @@ public class ClientToServerThread implements Runnable {
public int getClientId () {
return clientId;
}
public void sendXML(String path, String serverName, Integer legRepeats, Integer maxPlayers, Boolean tokensEnabled) {
Pair<RegattaXMLTemplate, RaceXMLTemplate> regattaRace = XMLParser.parseRaceDef(
path, serverName, legRepeats, maxPlayers, tokensEnabled
);
XMLGenerator xmlGenerator = new XMLGenerator();
xmlGenerator.setRegattaTemplate(regattaRace.getKey());
xmlGenerator.setRaceTemplate(regattaRace.getValue());
String regatta = xmlGenerator.getRegattaAsXml();
String race = xmlGenerator.getRaceAsXml();
sendByteBuffer(
new XMLMessage(
regatta, XMLMessageSubType.REGATTA, regatta.length()
).getBuffer()
);
sendByteBuffer(
new XMLMessage(
race, XMLMessageSubType.RACE, race.length()
).getBuffer()
);
}
}
@@ -49,6 +49,12 @@ import seng302.visualiser.controllers.RaceViewController;
import seng302.visualiser.controllers.ViewManager;
import seng302.visualiser.controllers.dialogs.PopupDialogController;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.util.*;
/**
* This class is a client side instance of a yacht racing game in JavaFX. The game is instantiated
* with a JavaFX Pane to insert itself into.
@@ -111,21 +117,33 @@ public class GameClient {
ViewManager.getInstance().setProperty("serverName", regattaData.getRegattaName());
ViewManager.getInstance().setProperty("mapName", regattaData.getCourseName());
getServerThread().setConnectionErrorListener((eMessage) -> {
ViewManager.getInstance().showErrorSnackBar(eMessage);
destroyClientToServerThread();
});
this.lobbyController = ViewManager.getInstance().goToLobby(true);
} catch (IOException ioe) {
showConnectionError("Unable to find server");
ViewManager.getInstance().showErrorSnackBar("There are no servers currently available.");
}
}
private void destroyClientToServerThread() {
socketThread.closeSocket();
socketThread = null;
}
/**
* Connect to a game as the host at the given address and starts the visualiser.
* @param ipAddress IP to connect to.
* @param portNumber Port to connect to.
*/
public ServerDescription runAsHost(String ipAddress, Integer portNumber, String serverName, Integer maxPlayers) {
public ServerDescription runAsHost(
String ipAddress, Integer portNumber, String serverName, Integer maxPlayers, String race,
Integer numLegs, Boolean tokensEnabled
) {
XMLGenerator.setDefaultRaceName(serverName);
GameState.setMaxPlayers(maxPlayers);
server = new MainServerThread();
@@ -134,7 +152,7 @@ public class GameClient {
} catch (IOException e) {
showConnectionError("Cannot connect to server as host");
}
socketThread.sendXML(race, serverName, numLegs, maxPlayers, tokensEnabled);
while (regattaData == null){
try {
Thread.sleep(100);
+19 -431
View File
@@ -1,443 +1,31 @@
package seng302.visualiser;
import javafx.application.Platform;
import javafx.collections.ObservableList;
import javafx.geometry.Point2D;
import javafx.scene.*;
import javafx.scene.image.ImageView;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.scene.shape.Polygon;
import seng302.gameServer.messages.RoundingSide;
import seng302.model.GeoPoint;
import java.util.ArrayList;
import java.util.List;
import javafx.scene.Group;
import javafx.scene.Node;
import seng302.model.Limit;
import seng302.model.ScaledPoint;
import seng302.model.mark.CompoundMark;
import seng302.model.mark.Corner;
import seng302.model.mark.Mark;
import seng302.utilities.GeoUtility;
import seng302.visualiser.fxObjects.MarkArrowFactory;
import seng302.visualiser.fxObjects.assets_2D.*;
import java.util.*;
/**
* Created by cir27 on 20/07/17.
* Abstract class for keeping functionality common between race visualisation.
*/
public class GameView extends Pane {
public abstract class GameView {
private double bufferSize = 50;
private double horizontalBuffer = 0;
double canvasWidth, canvasHeight;
ScaledPoint scaledPoint;
private double canvasWidth = 1100;
private double canvasHeight = 920;
private boolean horizontalInversion = false;
List<Limit> borderPoints;
Group gameObjects = new Group();
Group markers = new Group();
Group tokens = new Group();
List<CompoundMark> course = new ArrayList<>();
List<CompoundMark> compoundMarks = new ArrayList<>();
List<Corner> courseOrder = new ArrayList<>();
private double distanceScaleFactor;
private ScaleDirection scaleDirection;
private GeoPoint minLatPoint, minLonPoint, maxLatPoint, maxLonPoint;
private double referencePointX, referencePointY;
private Polygon raceBorder = new CourseBoundary();
/* Note that if either of these is null then values for it have not been added and the other
should be used as the limits of the map. */
private List<Limit> borderPoints;
private Map<Mark, Marker2D> markerObjects;
private ObservableList<Node> gameObjects;
private Group markers = new Group();
private Group tokens = new Group();
private List<CompoundMark> course = new ArrayList<>();
private ImageView mapImage = new ImageView();
private enum ScaleDirection {
HORIZONTAL,
VERTICAL
}
public GameView () {
gameObjects = this.getChildren();
gameObjects.addAll(mapImage, raceBorder, markers, tokens);
}
/**
* Adds a course to the GameView. The view is scaled accordingly unless a border is set in which
* case the course is added relative ot the border.
*
* @param newCourse the mark objects that make up the course.
* @param sequence The sequence the marks travel through
*/
public void updateCourse(List<CompoundMark> newCourse, List<Corner> sequence) {
markerObjects = new HashMap<>();
for (Corner corner : sequence) { //Makes course out of all compound marks.
for (CompoundMark compoundMark : newCourse) {
if (corner.getCompoundMarkID() == compoundMark.getId()) {
course.add(compoundMark);
}
}
}
// TODO: 16/08/17 Updating mark roundings here. It should not happen here. Nor should it be done this way.
for (Corner corner : sequence){
CompoundMark compoundMark = course.get(corner.getSeqID() - 1);
compoundMark.setRoundingSide(
RoundingSide.getRoundingSide(corner.getRounding())
);
}
final List<Gate> gates = new ArrayList<>();
Paint colour = Color.BLACK;
//Creates new markers
for (CompoundMark cMark : newCourse) {
//Set start and end colour
if (cMark.getId() == sequence.get(0).getCompoundMarkID()) {
colour = Color.GREEN;
} else if (cMark.getId() == sequence.get(sequence.size() - 1).getCompoundMarkID()) {
colour = Color.RED;
}
//Create mark dots
for (Mark mark : cMark.getMarks()) {
makeAndBindMarker(mark, colour);
}
//Create gate line
if (cMark.isGate()) {
for (int i = 1; i < cMark.getMarks().size(); i++) {
gates.add(
makeAndBindGate(
markerObjects.get(cMark.getSubMark(i)),
markerObjects.get(cMark.getSubMark(i + 1)),
colour
)
);
}
}
colour = Color.BLACK;
}
createMarkArrows(sequence);
//Scale race to markers if there is no border.
if (borderPoints == null) {
rescaleRace(new ArrayList<>(markerObjects.keySet()));
}
//Move the Markers to initial position.
markerObjects.forEach(((mark, marker2D) -> {
Point2D p2d = findScaledXY(mark.getLat(), mark.getLng());
marker2D.setLayoutX(p2d.getX());
marker2D.setLayoutY(p2d.getY());
}));
Platform.runLater(() -> {
markers.getChildren().clear();
markers.getChildren().addAll(gates);
markers.getChildren().addAll(markerObjects.values());
});
}
/**
* Calculates all the data needed for to create mark arrows. Requires that a course has been
* added to the gameview.
* @param sequence The order in which marks are traversed.
*/
private void createMarkArrows (List<Corner> sequence) {
for (int i=1; i < sequence.size()-1; i++) { //General case.
double averageLat = 0;
double averageLng = 0;
int numMarks = course.get(i-1).getMarks().size();
for (Mark mark : course.get(i-1).getMarks()) {
averageLat += mark.getLat();
averageLng += mark.getLng();
}
GeoPoint lastMarkAv = new GeoPoint(averageLat / numMarks, averageLng / numMarks);
numMarks = course.get(i+1).getMarks().size();
averageLat = 0;
averageLng = 0;
for (Mark mark : course.get(i+1).getMarks()) {
averageLat += mark.getLat();
averageLng += mark.getLng();
}
GeoPoint nextMarkAv = new GeoPoint(averageLat / numMarks, averageLng / numMarks);
// TODO: 16/08/17 This comparison doesn't need to exist but the alternative is to user server enum client side.
for (Mark mark : course.get(i).getMarks()) {
markerObjects.get(mark).addArrows(
mark.getRoundingSide() == RoundingSide.STARBOARD ? MarkArrowFactory.RoundingSide.STARBOARD : MarkArrowFactory.RoundingSide.PORT,
GeoUtility.getBearing(lastMarkAv, mark),
GeoUtility.getBearing(mark, nextMarkAv)
);
}
}
createStartLineArrows();
createFinishLineArrows();
}
private void createStartLineArrows () {
double averageLat = 0;
double averageLng = 0;
int numMarks = 0;
for (Mark mark : course.get(1).getMarks()) {
numMarks += 1;
averageLat += mark.getLat();
averageLng += mark.getLng();
}
GeoPoint firstMarkAv = new GeoPoint(averageLat / numMarks, averageLng / numMarks);
for (Mark mark : course.get(0).getMarks()) {
markerObjects.get(mark).addArrows(
mark.getRoundingSide() == RoundingSide.STARBOARD ? MarkArrowFactory.RoundingSide.STARBOARD : MarkArrowFactory.RoundingSide.PORT,
0d, //90
GeoUtility.getBearing(mark, firstMarkAv)
);
}
}
private void createFinishLineArrows () {
double numMarks = 0;
double averageLat = 0;
double averageLng = 0;
for (Mark mark : course.get(course.size()-2).getMarks()) {
numMarks += 1;
averageLat += mark.getLat();
averageLng += mark.getLng();
}
GeoPoint secondToLastMarkAv = new GeoPoint(averageLat / numMarks, averageLng / numMarks);
for (Mark mark : course.get(course.size()-1).getMarks()) {
markerObjects.get(mark).addArrows(
mark.getRoundingSide() == RoundingSide.STARBOARD ? MarkArrowFactory.RoundingSide.STARBOARD : MarkArrowFactory.RoundingSide.PORT,
GeoUtility.getBearing(secondToLastMarkAv, mark),
GeoUtility.getBearing(mark, mark)
);
}
}
/**
* Creates a new Marker and binds it's position to the given Mark.
*
* @param observableMark The mark to bind the marker to.
* @param colour The desired colour of the mark
*/
private void makeAndBindMarker(Mark observableMark, Paint colour) {
Marker2D marker2D = new Marker2D(colour);
// marker.addArrows(MarkArrowFactory.RoundingSide.PORT, ThreadLocalRandom.current().nextDouble(91, 180), ThreadLocalRandom.current().nextDouble(1, 90));
markerObjects.put(observableMark, marker2D);
observableMark.addPositionListener((mark, lat, lon) -> {
Point2D p2d = findScaledXY(lat, lon);
markerObjects.get(mark).setLayoutX(p2d.getX());
markerObjects.get(mark).setLayoutY(p2d.getY());
});
}
/**
* Creates a new gate connecting the given marks.
*
* @param m1 The first Mark of the gate.
* @param m2 The second Mark of the gate.
* @param colour The desired colour of the gate.
* @return the new gate.
*/
private Gate makeAndBindGate(Marker2D m1, Marker2D m2, Paint colour) {
Gate gate = new Gate(colour);
gate.startXProperty().bind(
m1.layoutXProperty()
);
gate.startYProperty().bind(
m1.layoutYProperty()
);
gate.endXProperty().bind(
m2.layoutXProperty()
);
gate.endYProperty().bind(
m2.layoutYProperty()
);
return gate;
}
/**
* Adds a border to the GameView and rescales to the size of the border, does not rescale if a
* border already exists. Assumes the border is larger than the course.
*
* @param border the race border to be drawn.
*/
public void updateBorder(List<Limit> border) {
if (borderPoints == null) {
borderPoints = border;
rescaleRace(new ArrayList<>(borderPoints));
}
rescaleRace(new ArrayList<>(border));
List<Double> boundaryPoints = new ArrayList<>();
for (Limit limit : border) {
Point2D location = findScaledXY(limit.getLat(), limit.getLng());
boundaryPoints.add(location.getX());
boundaryPoints.add(location.getY());
}
raceBorder.getPoints().setAll(boundaryPoints);
}
/**
* Rescales the race to the size of the window.
*
* @param limitingCoordinates the set of geo points that contains the extremities of the race.
*/
public void rescaleRace(List<GeoPoint> limitingCoordinates) {
//Check is called once to avoid unnecessarily change the course limits once the race is running
findMinMaxPoint(limitingCoordinates);
double minLonToMaxLon = scaleRaceExtremities();
calculateReferencePointLocation(minLonToMaxLon);
}
/**
* Sets the class variables minLatPoint, maxLatPoint, minLonPoint, maxLonPoint to the point with
* the leftmost point, rightmost point, southern most point and northern most point
* respectively.
*/
private void findMinMaxPoint(List<GeoPoint> points) {
List<GeoPoint> sortedPoints = new ArrayList<>(points);
sortedPoints.sort(Comparator.comparingDouble(GeoPoint::getLat));
minLatPoint = new GeoPoint(sortedPoints.get(0).getLat(), sortedPoints.get(0).getLng());
GeoPoint maxLat = sortedPoints.get(sortedPoints.size() - 1);
maxLatPoint = new GeoPoint(maxLat.getLat(), maxLat.getLng());
sortedPoints.sort(Comparator.comparingDouble(GeoPoint::getLng));
minLonPoint = new GeoPoint(sortedPoints.get(0).getLat(), sortedPoints.get(0).getLng());
GeoPoint maxLon = sortedPoints.get(sortedPoints.size() - 1);
maxLonPoint = new GeoPoint(maxLon.getLat(), maxLon.getLng());
if (maxLonPoint.getLng() - minLonPoint.getLng() > 180) {
horizontalInversion = true;
}
}
/**
* Calculates the location of a reference point, this is always the point with minimum latitude,
* in relation to the canvas.
*
* @param minLonToMaxLon The horizontal distance between the point of minimum longitude to
* maximum longitude.
*/
private void calculateReferencePointLocation(double minLonToMaxLon) {
GeoPoint referencePoint = minLatPoint;
double referenceAngle;
if (scaleDirection == ScaleDirection.HORIZONTAL) {
referenceAngle = Math.abs(
GeoUtility.getBearingRad(referencePoint, minLonPoint)
);
referencePointX =
bufferSize + distanceScaleFactor * Math.sin(referenceAngle) * GeoUtility
.getDistance(referencePoint, minLonPoint);
referenceAngle = Math.abs(GeoUtility.getDistance(referencePoint, maxLatPoint));
referencePointY = canvasHeight - (bufferSize + bufferSize);
referencePointY -= distanceScaleFactor * Math.cos(referenceAngle) * GeoUtility
.getDistance(referencePoint, maxLatPoint);
referencePointY = referencePointY / 2;
referencePointY += bufferSize;
referencePointY += distanceScaleFactor * Math.cos(referenceAngle) * GeoUtility
.getDistance(referencePoint, maxLatPoint);
} else {
referencePointY = canvasHeight - bufferSize;
referenceAngle = Math.abs(
Math.toRadians(
GeoUtility.getDistance(referencePoint, minLonPoint)
)
);
referencePointX = bufferSize;
referencePointX += distanceScaleFactor * Math.sin(referenceAngle) * GeoUtility
.getDistance(referencePoint, minLonPoint);
referencePointX +=
((canvasWidth - (bufferSize + bufferSize)) - (minLonToMaxLon * distanceScaleFactor))
/ 2;
referencePointX += horizontalBuffer;
}
if (horizontalInversion) {
referencePointX = canvasWidth - bufferSize - (referencePointX - bufferSize);
}
}
/**
* Finds the scale factor necessary to fit all race markers within the onscreen map and assigns
* it to distanceScaleFactor Returns the max horizontal distance of the map.
*/
private double scaleRaceExtremities() {
double vertAngle = Math.abs(
GeoUtility.getBearingRad(minLatPoint, maxLatPoint)
);
double vertDistance =
Math.cos(vertAngle) * GeoUtility.getDistance(minLatPoint, maxLatPoint);
double horiAngle = Math.abs(
GeoUtility.getBearingRad(minLonPoint, maxLonPoint)
);
if (horiAngle <= (Math.PI / 2)) {
horiAngle = (Math.PI / 2) - horiAngle;
} else {
horiAngle = horiAngle - (Math.PI / 2);
}
double horiDistance =
Math.cos(horiAngle) * GeoUtility.getDistance(minLonPoint, maxLonPoint);
double vertScale = (canvasHeight - (bufferSize + bufferSize)) / vertDistance;
if ((horiDistance * vertScale) > (canvasWidth - (bufferSize + bufferSize))) {
distanceScaleFactor = (canvasWidth - (bufferSize + bufferSize)) / horiDistance;
scaleDirection = ScaleDirection.HORIZONTAL;
} else {
distanceScaleFactor = vertScale;
scaleDirection = ScaleDirection.VERTICAL;
}
return horiDistance;
}
private Point2D findScaledXY(double unscaledLat, double unscaledLon) {
double distanceFromReference;
double angleFromReference;
double xAxisLocation = referencePointX;
double yAxisLocation = referencePointY;
angleFromReference = GeoUtility.getBearingRad(
minLatPoint, new GeoPoint(unscaledLat, unscaledLon)
);
distanceFromReference = GeoUtility.getDistance(
minLatPoint, new GeoPoint(unscaledLat, unscaledLon)
);
if (angleFromReference >= 0 && angleFromReference <= Math.PI / 2) {
xAxisLocation += Math
.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference);
yAxisLocation -= Math
.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference);
} else if (angleFromReference >= 0) {
angleFromReference = angleFromReference - Math.PI / 2;
xAxisLocation += Math
.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference);
yAxisLocation += Math
.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference);
} else if (angleFromReference < 0 && angleFromReference >= -Math.PI / 2) {
angleFromReference = Math.abs(angleFromReference);
xAxisLocation -= Math
.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference);
yAxisLocation -= Math
.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference);
} else {
angleFromReference = Math.abs(angleFromReference) - Math.PI / 2;
xAxisLocation -= Math
.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference);
yAxisLocation += Math
.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference);
}
if (horizontalInversion) {
xAxisLocation = canvasWidth - bufferSize - (xAxisLocation - bufferSize);
}
return new Point2D(xAxisLocation, yAxisLocation);
}
public void setSize(Double width, Double height){
this.canvasWidth = width;
this.canvasHeight = height;
}
public void setHorizontalBuffer(Double buff){
this.horizontalBuffer = buff;
}
public abstract Node getAssets();
public abstract void updateCourse(List<CompoundMark> newCourse, List<Corner> sequence);
public abstract void updateBorder(List<Limit> border);
}
+43 -203
View File
@@ -16,22 +16,19 @@ import javafx.scene.Node;
import javafx.scene.PerspectiveCamera;
import javafx.scene.SceneAntialiasing;
import javafx.scene.SubScene;
import javafx.scene.image.Image;
import javafx.scene.input.KeyEvent;
import javafx.scene.paint.Color;
import javafx.scene.transform.Rotate;
import javafx.scene.transform.Scale;
import javafx.scene.transform.Translate;
import org.fxyz3d.scene.Skybox;
import seng302.gameServer.messages.RoundingSide;
import seng302.model.ClientYacht;
import seng302.model.GameKeyBind;
import seng302.model.GeoPoint;
import seng302.model.KeyAction;
import seng302.model.Limit;
import seng302.model.*;
import seng302.model.mark.CompoundMark;
import seng302.model.mark.Corner;
import seng302.model.mark.Mark;
import seng302.model.token.Token;
import seng302.model.token.TokenType;
import seng302.utilities.GeoUtility;
import seng302.utilities.Sounds;
import seng302.visualiser.cameras.ChaseCamera;
@@ -42,6 +39,7 @@ import seng302.visualiser.controllers.ViewManager;
import seng302.visualiser.fxObjects.MarkArrowFactory;
import seng302.visualiser.fxObjects.assets_3D.BoatObject;
import seng302.visualiser.fxObjects.assets_3D.Marker3D;
import seng302.visualiser.fxObjects.assets_3D.Model;
import seng302.visualiser.fxObjects.assets_3D.ModelFactory;
import seng302.visualiser.fxObjects.assets_3D.ModelType;
@@ -49,61 +47,43 @@ import seng302.visualiser.fxObjects.assets_3D.ModelType;
* Collection of animated3D assets that displays a race.
*/
public class GameView3D {
public class GameView3D extends GameView{
private final double FOV = 60;
private final double DEFAULT_CAMERA_X = 0;
private final double DEFAULT_CAMERA_Y = 155;
private final double DEFAULT_CAMERA_Y = 100;
private Group root3D;
private SubScene view;
private Group gameObjects;
private Group raceBorder = new Group();
// Cameras
private PerspectiveCamera isometricCam;
private PerspectiveCamera topDownCam;
private PerspectiveCamera chaseCam;
private double bufferSize = 0;
private double canvasWidth = 200;
private double canvasHeight = 200;
private boolean horizontalInversion = false;
private double distanceScaleFactor;
private ScaleDirection scaleDirection;
private GeoPoint minLatPoint, minLonPoint, maxLatPoint, maxLonPoint;
private double referencePointX, referencePointY;
private Group raceBorder = new Group();
/* Note that if either of these is null then values for it have not been added and the other
should be used as the limits of the map. */
private List<Limit> borderPoints;
private Map<Mark, Marker3D> markerObjects;
private Map<ClientYacht, BoatObject> boatObjects = new HashMap<>();
private BoatObject selectedBoat = null;
private Group wakesGroup = new Group();
private Group boatObjectGroup = new Group();
private Group markers = new Group();
private Group tokens = new Group();
private List<CompoundMark> course = new ArrayList<>();
private List<Node> mapTokens;
private AnimationTimer playerBoatAnimationTimer;
private Group trail = new Group();
private Double windDir;
private enum ScaleDirection {
HORIZONTAL,
VERTICAL
}
private Skybox skybox;
public GameView3D () {
isometricCam = new IsometricCamera(DEFAULT_CAMERA_X, DEFAULT_CAMERA_Y);
topDownCam = new TopDownCamera();
chaseCam = new ChaseCamera();
canvasWidth = canvasHeight = 300;
for (PerspectiveCamera pc : Arrays.asList(isometricCam, topDownCam, chaseCam)) {
pc.setFarClip(600);
pc.setFarClip(100000);
pc.setNearClip(0.1);
pc.setFieldOfView(FOV);
}
@@ -111,14 +91,21 @@ public class GameView3D {
gameObjects = new Group();
root3D = new Group(isometricCam, gameObjects);
view = new SubScene(
root3D, 1000, 1000, true, SceneAntialiasing.BALANCED
root3D, 5000, 3000, true, SceneAntialiasing.BALANCED
);
view.setCamera(isometricCam);
skybox = new Skybox(new Image(getClass().getResourceAsStream("/images/skybox.jpg")), 100000, isometricCam);
skybox.getTransforms().addAll(new Rotate(90, Rotate.X_AXIS));
Model land = ModelFactory.importModel(ModelType.LAND_SMOOTH);
land.getAssets().getTransforms().add(new Rotate(90, Rotate.X_AXIS));
gameObjects.getChildren().addAll(
ModelFactory.importModel(ModelType.OCEAN).getAssets(),
raceBorder, trail, markers, tokens
raceBorder, trail, markers, tokens, skybox, land.getAssets()
);
view.sceneProperty().addListener((obs, old, scene) -> {
if (scene != null) {
scene.addEventHandler(KeyEvent.KEY_PRESSED, this::cameraMovement);
@@ -126,8 +113,10 @@ public class GameView3D {
});
}
@Override
public void updateCourse(List<CompoundMark> newCourse, List<Corner> sequence) {
markerObjects = new HashMap<>();
compoundMarks = newCourse;
for (Corner corner : sequence) { //Makes course out of all compound marks.
for (CompoundMark compoundMark : newCourse) {
@@ -178,11 +167,13 @@ public class GameView3D {
//Scale race to markers if there is no border.
if (borderPoints == null) {
rescaleRace(new ArrayList<>(markerObjects.keySet()));
scaledPoint = ScaledPoint.makeScaledPoint(
canvasWidth, canvasHeight, new ArrayList<>(markerObjects.keySet()), true
);
}
//Move the Markers to initial position.
markerObjects.forEach(((mark, marker) -> {
Point2D p2d = findScaledXY(mark.getLat(), mark.getLng());
Point2D p2d = scaledPoint.findScaledXY(mark.getLat(), mark.getLng());
marker.setLayoutX(p2d.getX());
marker.setLayoutY(p2d.getY());
}));
@@ -202,7 +193,7 @@ public class GameView3D {
private void makeAndBindMarker(Mark observableMark, ModelType markerType) {
markerObjects.put(observableMark, new Marker3D(markerType));
observableMark.addPositionListener((mark, lat, lon) -> {
Point2D p2d = findScaledXY(lat, lon);
Point2D p2d = scaledPoint.findScaledXY(lat, lon);
markerObjects.get(mark).setLayoutX(p2d.getX());
markerObjects.get(mark).setLayoutY(p2d.getY());
});
@@ -217,8 +208,8 @@ public class GameView3D {
* @return the new gate.
*/
private Group makeGate(Mark m1, Mark m2, ModelType gateType) {
Point2D m1Location = findScaledXY(m1);
Point2D m2Location = findScaledXY(m2);
Point2D m1Location = scaledPoint.findScaledXY(m1);
Point2D m2Location = scaledPoint.findScaledXY(m2);
Group barrier = ModelFactory.importModel(gateType).getAssets();
barrier.getTransforms().addAll(
@@ -276,144 +267,6 @@ public class GameView3D {
}
}
/**
* Sets the class variables minLatPoint, maxLatPoint, minLonPoint, maxLonPoint to the point with
* the leftmost point, rightmost point, southern most point and northern most point
* respectively.
*/
private void findMinMaxPoint(List<GeoPoint> points) {
List<GeoPoint> sortedPoints = new ArrayList<>(points);
sortedPoints.sort(Comparator.comparingDouble(GeoPoint::getLat));
minLatPoint = new GeoPoint(sortedPoints.get(0).getLat(), sortedPoints.get(0).getLng());
GeoPoint maxLat = sortedPoints.get(sortedPoints.size() - 1);
maxLatPoint = new GeoPoint(maxLat.getLat(), maxLat.getLng());
sortedPoints.sort(Comparator.comparingDouble(GeoPoint::getLng));
minLonPoint = new GeoPoint(sortedPoints.get(0).getLat(), sortedPoints.get(0).getLng());
GeoPoint maxLon = sortedPoints.get(sortedPoints.size() - 1);
maxLonPoint = new GeoPoint(maxLon.getLat(), maxLon.getLng());
if (maxLonPoint.getLng() - minLonPoint.getLng() > 180) {
horizontalInversion = true;
}
}
/**
* Calculates the location of a reference point, this is always the point with minimum latitude,
* in relation to the canvas.
*
* @param minLonToMaxLon The horizontal distance between the point of minimum longitude to
* maximum longitude.
*/
private void calculateReferencePointLocation(double minLonToMaxLon) {
GeoPoint referencePoint = minLatPoint;
double referenceAngle;
if (scaleDirection == ScaleDirection.HORIZONTAL) {
referenceAngle = Math.abs(
GeoUtility.getBearingRad(referencePoint, minLonPoint)
);
referencePointX =
-100 + distanceScaleFactor * Math.sin(referenceAngle) * GeoUtility
.getDistance(referencePoint, minLonPoint);
referenceAngle = Math.abs(GeoUtility.getDistance(referencePoint, maxLatPoint));
referencePointY = -100 + canvasHeight - (bufferSize + bufferSize);
referencePointY -= distanceScaleFactor * Math.cos(referenceAngle) * GeoUtility
.getDistance(referencePoint, maxLatPoint);
referencePointY = referencePointY / 2;
referencePointY += bufferSize;
referencePointY += distanceScaleFactor * Math.cos(referenceAngle) * GeoUtility
.getDistance(referencePoint, maxLatPoint);
} else {
referencePointY = -100 + canvasHeight - bufferSize;
referenceAngle = Math.abs(
Math.toRadians(
GeoUtility.getDistance(referencePoint, minLonPoint)
)
);
referencePointX = -100 + bufferSize;
referencePointX += distanceScaleFactor * Math.sin(referenceAngle) * GeoUtility
.getDistance(referencePoint, minLonPoint);
referencePointX +=
((canvasWidth - (bufferSize + bufferSize)) - (minLonToMaxLon * distanceScaleFactor))
/ 2;
}
if (horizontalInversion) {
referencePointX = -100 + canvasWidth - bufferSize - (referencePointX - bufferSize);
}
}
/**
* Finds the scale factor necessary to fit all race markers within the onscreen map and assigns
* it to distanceScaleFactor Returns the max horizontal distance of the map.
*/
private double scaleRaceExtremities() {
double vertAngle = Math.abs(
GeoUtility.getBearingRad(minLatPoint, maxLatPoint)
);
double vertDistance =
Math.cos(vertAngle) * GeoUtility.getDistance(minLatPoint, maxLatPoint);
double horiAngle = Math.abs(
GeoUtility.getBearingRad(minLonPoint, maxLonPoint)
);
if (horiAngle <= (Math.PI / 2)) {
horiAngle = (Math.PI / 2) - horiAngle;
} else {
horiAngle = horiAngle - (Math.PI / 2);
}
double horiDistance =
Math.cos(horiAngle) * GeoUtility.getDistance(minLonPoint, maxLonPoint);
double vertScale = (canvasHeight - (bufferSize + bufferSize)) / vertDistance;
if ((horiDistance * vertScale) > (canvasWidth - (bufferSize + bufferSize))) {
distanceScaleFactor = (canvasWidth - (bufferSize + bufferSize)) / horiDistance;
scaleDirection = ScaleDirection.HORIZONTAL;
} else {
distanceScaleFactor = vertScale;
scaleDirection = ScaleDirection.VERTICAL;
}
return horiDistance;
}
private Point2D findScaledXY(GeoPoint unscaled) {
return findScaledXY(unscaled.getLat(), unscaled.getLng());
}
private Point2D findScaledXY(double unscaledLat, double unscaledLon) {
double distanceFromReference;
double angleFromReference;
double xAxisLocation = referencePointX;
double yAxisLocation = referencePointY;
angleFromReference = GeoUtility.getBearingRad(
minLatPoint, new GeoPoint(unscaledLat, unscaledLon)
);
distanceFromReference = GeoUtility.getDistance(
minLatPoint, new GeoPoint(unscaledLat, unscaledLon)
);
if (angleFromReference >= 0 && angleFromReference <= Math.PI / 2) {
xAxisLocation += distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference;
yAxisLocation -= distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference;
} else if (angleFromReference >= 0) {
angleFromReference = angleFromReference - Math.PI / 2;
xAxisLocation += distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference;
yAxisLocation += distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference;
} else if (angleFromReference < 0 && angleFromReference >= -Math.PI / 2) {
angleFromReference = Math.abs(angleFromReference);
xAxisLocation -= distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference;
yAxisLocation -= distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference;
} else {
angleFromReference = Math.abs(angleFromReference) - Math.PI / 2;
xAxisLocation -= distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference;
yAxisLocation += distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference;
}
if (horizontalInversion) {
xAxisLocation = canvasWidth - bufferSize - (xAxisLocation - bufferSize);
}
return new Point2D(xAxisLocation, yAxisLocation);
}
public void cameraMovement(KeyEvent event) {
GameKeyBind keyBinds = GameKeyBind.getInstance();
KeyAction keyPressed = keyBinds.getKeyAction(event.getCode());
@@ -456,19 +309,6 @@ public class GameView3D {
}
}
/**
* Rescales the race to the size of the window.
*
* @param limitingCoordinates the set of geo points that contains the extremities of the race.
*/
private void rescaleRace(List<GeoPoint> limitingCoordinates) {
//Check is called once to avoid unnecessarily change the course limits once the race is running
findMinMaxPoint(limitingCoordinates);
double minLonToMaxLon = scaleRaceExtremities();
calculateReferencePointLocation(minLonToMaxLon);
// drawGoogleMap();
}
/**
* Draws all the boats.
* @param yachts The yachts to set in the race
@@ -516,7 +356,7 @@ public class GameView3D {
private void updateBoatLocation(ClientYacht boat, Double lat, Double lon, Double heading,
Boolean sailIn, Double velocity) {
BoatObject bo = boatObjects.get(boat);
Point2D p2d = findScaledXY(lat, lon);
Point2D p2d = scaledPoint.findScaledXY(lat, lon);
bo.moveTo(p2d.getX(), p2d.getY(), heading, velocity, sailIn, windDir);
}
@@ -529,18 +369,20 @@ public class GameView3D {
public void updateBorder(List<Limit> border) {
if (borderPoints == null) {
borderPoints = border;
rescaleRace(new ArrayList<>(borderPoints));
scaledPoint = ScaledPoint.makeScaledPoint(
canvasWidth, canvasHeight, new ArrayList<>(borderPoints), true
);
}
List<Node> boundaryAssets = new ArrayList<>();
Point2D lastLocation = findScaledXY(border.get(0).getLat(), border.get(0).getLng());
Point2D lastLocation = scaledPoint.findScaledXY(border.get(0).getLat(), border.get(0).getLng());
Group pylon = ModelFactory.importModel(ModelType.BORDER_PYLON).getAssets();
pylon.setLayoutX(lastLocation.getX());
pylon.setLayoutY(lastLocation.getY());
boundaryAssets.add(pylon);
for (int i=1; i<border.size(); i++) {
Point2D location = findScaledXY(border.get(i).getLat(), border.get(i).getLng());
Point2D location = scaledPoint.findScaledXY(border.get(i).getLat(), border.get(i).getLng());
pylon = ModelFactory.importModel(ModelType.BORDER_PYLON).getAssets();
pylon.setLayoutX(location.getX());
pylon.setLayoutY(location.getY());
@@ -566,7 +408,7 @@ public class GameView3D {
boundaryAssets.add(pylon);
}
Point2D firstLocation = findScaledXY(border.get(0).getLat(), border.get(0).getLng());
Point2D firstLocation = scaledPoint.findScaledXY(border.get(0).getLat(), border.get(0).getLng());
Group barrier = ModelFactory.importModel(ModelType.BORDER_BARRIER).getAssets();
barrier.getTransforms().addAll(
new Rotate(
@@ -594,7 +436,7 @@ public class GameView3D {
public void updateTokens(List<Token> newTokens) {
mapTokens = new ArrayList<>();
for (Token token : newTokens) {
Point2D location = findScaledXY(token.getLat(), token.getLng());
Point2D location = scaledPoint.findScaledXY(token.getLat(), token.getLng());
ModelType modelType = null;
switch (token.getTokenType()) {
@@ -630,19 +472,17 @@ public class GameView3D {
playerYacht.toggleSail();
playerBoatAnimationTimer = new AnimationTimer() {
double count = 60;
Point2D lastLocation = findScaledXY(playerYacht.getLocation());
Point2D lastLocation = scaledPoint.findScaledXY(playerYacht.getLocation());
@Override
public void handle(long now) {
if (--count == 0) {
count = 60;
Point2D location = scaledPoint.findScaledXY(playerYacht.getLocation());
if (Math.abs(lastLocation.distance(location)) > 2) {
Node segment = ModelFactory.importModel(ModelType.TRAIL_SEGMENT).getAssets();
Point2D location = findScaledXY(playerYacht.getLocation());
location = scaledPoint.findScaledXY(playerYacht.getLocation());
segment.getTransforms().addAll(
new Translate(location.getX(), location.getY(), 0),
new Rotate(playerYacht.getHeading(), new Point3D(0,0,1)),
new Scale(1, lastLocation.distance(location) / 5, 1)
new Rotate(playerYacht.getHeading(), new Point3D(0,0,1))
);
trail.getChildren().add(segment);
if (trail.getChildren().size() > 50) {
@@ -0,0 +1,126 @@
package seng302.visualiser;
import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javafx.scene.Node;
import javafx.util.Pair;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import seng302.model.stream.xml.generator.RaceXMLTemplate;
import seng302.model.stream.xml.generator.RegattaXMLTemplate;
import seng302.model.stream.xml.parser.RaceXMLData;
import seng302.model.stream.xml.parser.RegattaXMLData;
import seng302.utilities.XMLGenerator;
import seng302.utilities.XMLParser;
/**
* Makes maps from map definition xml files.
*/
public class MapMaker {
private List<MapPreview> mapPreviews = new ArrayList<>();
private List<RaceXMLData> races = new ArrayList<>();
private List<RegattaXMLData> regattas = new ArrayList<>();
private List<String> filePaths = new ArrayList<>();
private List<Integer> maxPlayers = new ArrayList<>();
private static MapMaker instance;
private int index = 0;
private XMLGenerator xmlGenerator = new XMLGenerator();
private List<String> maps = new ArrayList<>(Arrays.asList("default.xml", "horseshoe.xml"));
public static MapMaker getInstance() {
if (instance == null) {
instance = new MapMaker();
}
return instance;
}
private MapMaker() {
for (String mapPath : maps){
String path = ("/maps/" + mapPath);
Pair<RegattaXMLTemplate, RaceXMLTemplate> regattaRace = XMLParser.parseRaceDef(
path, "", 1, null, false
);
RegattaXMLTemplate regattaTemplate = regattaRace.getKey();
filePaths.add(path);
regattas.add(new RegattaXMLData(
regattaTemplate.getRegattaId(),
regattaTemplate.getName(),
regattaTemplate.getCourseName(),
regattaTemplate.getLatitude(),
regattaTemplate.getLongitude(),
regattaTemplate.getUtcOffset()
));
RaceXMLTemplate raceTemplate = regattaRace.getValue();
xmlGenerator.setRaceTemplate(raceTemplate);
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db;
Document doc = null;
try {
db = dbf.newDocumentBuilder();
doc = db.parse(new InputSource(new StringReader(xmlGenerator.getRaceAsXml())));
} catch (ParserConfigurationException | IOException | SAXException e) {
e.printStackTrace();
}
RaceXMLData race = XMLParser.parseRace(doc);
maxPlayers.add(XMLParser.getMaxPlayers(doc));
mapPreviews.add(new MapPreview(
new ArrayList<>(race.getCompoundMarks().values()),
race.getMarkSequence(), race.getCourseLimit()
));
races.add(race);
}
}
public void next() {
index += 1;
if (index >= mapPreviews.size()) {
index = 0;
}
}
public void previous() {
index -= 1;
if (index < 0) {
index = mapPreviews.size() - 1;
}
}
public Node getCurrentGameView() {
return mapPreviews.get(index).getAssets();
}
public RaceXMLData getCurrentRace() {
return races.get(index);
}
public RegattaXMLData getCurrentRegatta() {
return regattas.get(index);
}
public String getCurrentRacePath() {
return filePaths.get(index);
}
public Integer getMaxPlayers() {
return maxPlayers.get(index);
}
}
@@ -0,0 +1,283 @@
package seng302.visualiser;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javafx.application.Platform;
import javafx.geometry.Point2D;
import javafx.scene.Node;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.scene.shape.Polygon;
import seng302.gameServer.messages.RoundingSide;
import seng302.model.GeoPoint;
import seng302.model.Limit;
import seng302.model.ScaledPoint;
import seng302.model.mark.CompoundMark;
import seng302.model.mark.Corner;
import seng302.model.mark.Mark;
import seng302.utilities.GeoUtility;
import seng302.visualiser.fxObjects.MarkArrowFactory;
import seng302.visualiser.fxObjects.assets_2D.CourseBoundary;
import seng302.visualiser.fxObjects.assets_2D.Gate;
import seng302.visualiser.fxObjects.assets_2D.Marker2D;
/**
* Created by cir27 on 20/07/17.
*/
public class MapPreview extends GameView {
private Polygon raceBorder = new CourseBoundary();
private Map<Mark, Marker2D> markerObjects;
public MapPreview(List<CompoundMark> marks, List<Corner> course, List<Limit> border) {
this.compoundMarks = marks;
this.courseOrder = course;
this.borderPoints = border;
gameObjects.getChildren().addAll(raceBorder, markers, tokens);
gameObjects.parentProperty().addListener((obs, old, parent) -> {
if (parent != null) {
canvasWidth = parent.prefWidth(1);
canvasHeight = parent.prefHeight(1);
updateBorder(borderPoints);
updateCourse(compoundMarks, courseOrder);
}
});
}
@Override
public Node getAssets() {
return gameObjects;
}
public void setSize(double width, double height) {
canvasHeight = height;
canvasWidth = width;
updateBorder(borderPoints);
updateCourse(compoundMarks, courseOrder);
}
/**
* Adds a course to the GameView. The view is scaled accordingly unless a border is set in which
* case the course is added relative ot the border.
*
* @param newCourse the mark objects that make up the course.
* @param sequence The sequence the marks travel through
*/
@Override
public void updateCourse(List<CompoundMark> newCourse, List<Corner> sequence) {
if (newCourse.size() == 0) {
return;
}
compoundMarks = newCourse;
markerObjects = new HashMap<>();
courseOrder = sequence;
for (Corner corner : courseOrder) { //Makes course out of all compound marks.
for (CompoundMark compoundMark : newCourse) {
if (corner.getCompoundMarkID() == compoundMark.getId()) {
course.add(compoundMark);
}
}
}
// TODO: 16/08/17 Updating mark roundings here. It should not happen here. Nor should it be done this way.
for (Corner corner : sequence){
CompoundMark compoundMark = course.get(corner.getSeqID() - 1);
compoundMark.setRoundingSide(
RoundingSide.getRoundingSide(corner.getRounding())
);
}
final List<Gate> gates = new ArrayList<>();
Paint colour = Color.BLACK;
//Creates new markers
for (CompoundMark cMark : newCourse) {
//Set start and end colour
if (cMark.getId() == sequence.get(0).getCompoundMarkID()) {
colour = Color.GREEN;
} else if (cMark.getId() == sequence.get(sequence.size() - 1).getCompoundMarkID()) {
colour = Color.RED;
}
//Create mark dots
for (Mark mark : cMark.getMarks()) {
makeAndBindMarker(mark, colour);
}
//Create gate line
if (cMark.isGate()) {
for (int i = 1; i < cMark.getMarks().size(); i++) {
gates.add(
makeAndBindGate(
markerObjects.get(cMark.getSubMark(i)),
markerObjects.get(cMark.getSubMark(i + 1)),
colour
)
);
}
}
colour = Color.BLACK;
}
createMarkArrows(sequence);
//Scale race to markers if there is no border.
if (borderPoints == null) {
scaledPoint = ScaledPoint.makeScaledPoint(
canvasWidth, canvasHeight, new ArrayList<>(markerObjects.keySet()), false
);
}
//Move the Markers to initial position.
markerObjects.forEach(((mark, marker2D) -> {
Point2D p2d = scaledPoint.findScaledXY(mark.getLat(), mark.getLng());
marker2D.setLayoutX(p2d.getX());
marker2D.setLayoutY(p2d.getY());
}));
Platform.runLater(() -> {
markers.getChildren().clear();
markers.getChildren().addAll(gates);
markers.getChildren().addAll(markerObjects.values());
});
}
/**
* Calculates all the data needed for to create mark arrows. Requires that a course has been
* added to the gameview.
* @param sequence The order in which marks are traversed.
*/
private void createMarkArrows (List<Corner> sequence) {
for (int i=1; i < sequence.size()-1; i++) { //General case.
double averageLat = 0;
double averageLng = 0;
int numMarks = course.get(i-1).getMarks().size();
for (Mark mark : course.get(i-1).getMarks()) {
averageLat += mark.getLat();
averageLng += mark.getLng();
}
GeoPoint lastMarkAv = new GeoPoint(averageLat / numMarks, averageLng / numMarks);
numMarks = course.get(i+1).getMarks().size();
averageLat = 0;
averageLng = 0;
for (Mark mark : course.get(i+1).getMarks()) {
averageLat += mark.getLat();
averageLng += mark.getLng();
}
GeoPoint nextMarkAv = new GeoPoint(averageLat / numMarks, averageLng / numMarks);
// TODO: 16/08/17 This comparison doesn't need to exist but the alternative is to user server enum client side.
for (Mark mark : course.get(i).getMarks()) {
markerObjects.get(mark).addArrows(
mark.getRoundingSide() == RoundingSide.STARBOARD ? MarkArrowFactory.RoundingSide.STARBOARD : MarkArrowFactory.RoundingSide.PORT,
GeoUtility.getBearing(lastMarkAv, mark),
GeoUtility.getBearing(mark, nextMarkAv)
);
}
}
createStartLineArrows();
createFinishLineArrows();
}
private void createStartLineArrows () {
double averageLat = 0;
double averageLng = 0;
int numMarks = 0;
for (Mark mark : course.get(1).getMarks()) {
numMarks += 1;
averageLat += mark.getLat();
averageLng += mark.getLng();
}
GeoPoint firstMarkAv = new GeoPoint(averageLat / numMarks, averageLng / numMarks);
for (Mark mark : course.get(0).getMarks()) {
markerObjects.get(mark).addArrows(
mark.getRoundingSide() == RoundingSide.STARBOARD ? MarkArrowFactory.RoundingSide.STARBOARD : MarkArrowFactory.RoundingSide.PORT,
0d, //90
GeoUtility.getBearing(mark, firstMarkAv)
);
}
}
private void createFinishLineArrows () {
double numMarks = 0;
double averageLat = 0;
double averageLng = 0;
for (Mark mark : course.get(course.size()-2).getMarks()) {
numMarks += 1;
averageLat += mark.getLat();
averageLng += mark.getLng();
}
GeoPoint secondToLastMarkAv = new GeoPoint(averageLat / numMarks, averageLng / numMarks);
for (Mark mark : course.get(course.size()-1).getMarks()) {
markerObjects.get(mark).addArrows(
mark.getRoundingSide() == RoundingSide.STARBOARD ? MarkArrowFactory.RoundingSide.STARBOARD : MarkArrowFactory.RoundingSide.PORT,
GeoUtility.getBearing(secondToLastMarkAv, mark),
GeoUtility.getBearing(mark, mark)
);
}
}
/**
* Creates a new Marker and binds it's position to the given Mark.
*
* @param observableMark The mark to bind the marker to.
* @param colour The desired colour of the mark
*/
private void makeAndBindMarker(Mark observableMark, Paint colour) {
Marker2D marker2D = new Marker2D(colour);
// marker.addArrows(MarkArrowFactory.RoundingSide.PORT, ThreadLocalRandom.current().nextDouble(91, 180), ThreadLocalRandom.current().nextDouble(1, 90));
markerObjects.put(observableMark, marker2D);
observableMark.addPositionListener((mark, lat, lon) -> {
Point2D p2d = scaledPoint.findScaledXY(lat, lon);
markerObjects.get(mark).setLayoutX(p2d.getX());
markerObjects.get(mark).setLayoutY(p2d.getY());
});
}
/**
* Creates a new gate connecting the given marks.
*
* @param m1 The first Mark of the gate.
* @param m2 The second Mark of the gate.
* @param colour The desired colour of the gate.
* @return the new gate.
*/
private Gate makeAndBindGate(Marker2D m1, Marker2D m2, Paint colour) {
Gate gate = new Gate(colour);
gate.startXProperty().bind(
m1.layoutXProperty()
);
gate.startYProperty().bind(
m1.layoutYProperty()
);
gate.endXProperty().bind(
m2.layoutXProperty()
);
gate.endYProperty().bind(
m2.layoutYProperty()
);
return gate;
}
/**
* Adds a border to the GameView and rescales to the size of the border, does not rescale if a
* border already exists. Assumes the border is larger than the course.
*
* @param border the race border to be drawn.
*/
@Override
public void updateBorder(List<Limit> border) {
if (border.size() == 0) {
return;
}
borderPoints = border;
scaledPoint = ScaledPoint.makeScaledPoint(canvasWidth, canvasHeight, border, false);
List<Double> boundaryPoints = new ArrayList<>();
for (Limit limit : border) {
Point2D location = scaledPoint.findScaledXY(limit.getLat(), limit.getLng());
boundaryPoints.add(location.getX());
boundaryPoints.add(location.getY());
}
raceBorder.getPoints().setAll(boundaryPoints);
}
}
@@ -6,11 +6,10 @@ import seng302.gameServer.ServerDescription;
import javax.jmdns.JmDNS;
import javax.jmdns.ServiceEvent;
import javax.jmdns.ServiceListener;
import javax.jmdns.impl.JmDNSImpl;
import java.io.IOException;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
import java.util.*;
import static seng302.gameServer.ServerAdvertiser.getLocalHostIp;
@@ -18,6 +17,7 @@ import static seng302.gameServer.ServerAdvertiser.getLocalHostIp;
* Listens for servers on the local network
*/
public class ServerListener{
private static Integer SERVICE_REFRESH_INTERVAL = 5 * 1000;
private static ServerListener instance;
private ServerListenerDelegate delegate;
private JmDNS jmdns = null;
@@ -91,8 +91,16 @@ public class ServerListener{
private ServerListener() throws IOException {
jmdns = JmDNS.create(InetAddress.getByName(getLocalHostIp()));
listener = new GameServeMonitor();
jmdns.addServiceListener(ServerAdvertiser.SERVICE_TYPE, listener);
/*new Timer().schedule(new TimerTask() {
@Override
public void run() {
refresh();
}
}, 50, SERVICE_REFRESH_INTERVAL);*/
}
public static ServerListener getInstance() throws IOException {
@@ -110,4 +118,25 @@ public class ServerListener{
public void setDelegate(ServerListenerDelegate delegate){
this.delegate = delegate;
}
public void refresh(){
ArrayList<ServerDescription> servers = new ArrayList<>(listener.servers);
for (ServerDescription serverDescription : servers){
if (serverDescription.hasExpired()){
jmdns.requestServiceInfo(ServerAdvertiser.SERVICE_TYPE, serverDescription.getName());
}
else{
serverDescription.hasBeenRefreshed();
}
}
for (ServerDescription server : servers){
if (server.serverShouldBeRemoved()){
listener.servers.remove(server);
delegate.serverRemoved(new ArrayList<ServerDescription>(listener.servers));
}
}
}
}
@@ -9,10 +9,11 @@ import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import seng302.discoveryServer.DiscoveryServerClient;
import seng302.gameServer.GameStages;
import seng302.gameServer.GameState;
import seng302.model.ClientYacht;
@@ -23,7 +24,7 @@ import seng302.model.mark.CompoundMark;
import seng302.model.mark.Corner;
import seng302.model.stream.xml.parser.RaceXMLData;
import seng302.utilities.Sounds;
import seng302.visualiser.GameView;
import seng302.visualiser.MapPreview;
import seng302.visualiser.controllers.cells.PlayerCell;
import seng302.visualiser.controllers.dialogs.BoatCustomizeController;
@@ -33,10 +34,12 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
import seng302.visualiser.fxObjects.assets_3D.BoatMeshType;
public class LobbyController implements Initializable {
private final double INITIAL_MAP_HEIGHT = 770d;
private final double INITIAL_MAP_WIDTH = 574d;
//--------FXML BEGIN--------//
@FXML
private VBox playerListVBox;
@@ -51,19 +54,21 @@ public class LobbyController implements Initializable {
@FXML
private Label mapName;
@FXML
private Pane serverMap;
private AnchorPane serverMap;
@FXML
private Label roomLabel;
//---------FXML END---------//
private RaceState raceState;
private JFXDialog customizationDialog;
public Color playersColor;
private Map<Integer, ClientYacht> playerBoats;
private Double mapWidth, mapHeight;
private GameView gameView;
private Double mapWidth = INITIAL_MAP_WIDTH, mapHeight = INITIAL_MAP_HEIGHT;
private MapPreview mapPreview;
@Override
public void initialize(URL location, ResourceBundle resources) {
roomLabel.setText("");
this.playerBoats = ViewManager.getInstance().getGameClient().getAllBoatsMap();
if (this.playersColor == null) {
@@ -86,6 +91,21 @@ public class LobbyController implements Initializable {
serverName.setText(ViewManager.getInstance().getProperty("serverName"));
mapName.setText(ViewManager.getInstance().getProperty("mapName"));
int tries = 0;
while (DiscoveryServerClient.getRoomCode() == null && tries <= 10){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
tries ++;
}
if (DiscoveryServerClient.getRoomCode() != null){
setRoomCode(DiscoveryServerClient.getRoomCode());
}
ViewManager.getInstance().getPlayerList().addListener((ListChangeListener<String>) c -> Platform.runLater(this::refreshPlayerList));
ViewManager.getInstance().getPlayerList().setAll(ViewManager.getInstance().getPlayerList().sorted());
@@ -112,13 +132,13 @@ public class LobbyController implements Initializable {
private JFXDialog createCustomizeDialog() {
FXMLLoader dialog = new FXMLLoader(
getClass().getResource("/views/dialogs/BoatCustomizeDialog.fxml"));
getClass().getResource("/views/dialogs/BoatCustomizeDialog.fxml"));
JFXDialog customizationDialog = null;
try {
customizationDialog = new JFXDialog(serverListMainStackPane, dialog.load(),
JFXDialog.DialogTransition.CENTER);
JFXDialog.DialogTransition.CENTER);
} catch (IOException e) {
e.printStackTrace();
}
@@ -136,44 +156,30 @@ public class LobbyController implements Initializable {
return customizationDialog;
}
/**
*
*/
private void refreshMapView(){
RaceXMLData raceData = ViewManager.getInstance().getGameClient().getCourseData();
List<Limit> border = raceData.getCourseLimit();
List<CompoundMark> marks = new ArrayList<CompoundMark>(raceData.getCompoundMarks().values());
List<Corner> corners = raceData.getMarkSequence();
gameView.setSize(mapWidth, mapHeight);
// Update game view
gameView.updateBorder(border);
gameView.updateCourse(marks, corners);
}
/**
* Initializes a top down preview of the race course map.
*/
private void initMapPreview() {
gameView = new GameView();
gameView.setHorizontalBuffer(330d);
RaceXMLData raceData = ViewManager.getInstance().getGameClient().getCourseData();
List<Limit> border = raceData.getCourseLimit();
List<CompoundMark> marks = new ArrayList<>(raceData.getCompoundMarks().values());
List<Corner> corners = raceData.getMarkSequence();
mapWidth = 770d;
mapHeight = 574d;
// Add game view
mapPreview = new MapPreview(marks, corners, border);
serverMap.getChildren().clear();
serverMap.getChildren().add(gameView);
serverMap.getChildren().add(mapPreview.getAssets());
mapPreview.setSize(mapWidth, mapHeight);
serverMap.widthProperty().addListener((observable, oldValue, newValue) -> {
mapWidth = newValue.doubleValue();
refreshMapView();
mapPreview.setSize(mapWidth, mapHeight);
});
//
serverMap.heightProperty().addListener((observable, oldValue, newValue) -> {
mapHeight = newValue.doubleValue();
refreshMapView();
mapPreview.setSize(mapWidth, mapHeight);
});
}
@@ -245,4 +251,8 @@ public class LobbyController implements Initializable {
public void closeCustomizationDialog() {
customizationDialog.close();
}
public void setRoomCode(String roomCode) {
roomLabel.setText("Room: " + roomCode);
}
}
@@ -13,8 +13,6 @@ import javafx.animation.RotateTransition;
import javafx.animation.Timeline;
import javafx.application.Platform;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
@@ -25,8 +23,6 @@ import javafx.scene.Scene;
import javafx.scene.SubScene;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.chart.XYChart.Data;
import javafx.scene.chart.XYChart.Series;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
@@ -168,32 +164,6 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
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.visibleProperty().setValue(false);
//raceSparkLine.getYAxis().setAutoRanging(false);
//sparklineYAxis.setTickMarkVisible(false);
//positionVbox.getStylesheets().add(getClass().getResource("/css/master.css").toString());
// raceSparkLine.visibleProperty().setValue(false);
// raceSparkLine.getYAxis().setAutoRanging(false);
// sparklineYAxis.setTickMarkVisible(false);
// positionVbox.getStylesheets().add(getClass().getResource("/css/master.css").toString());
//selectAnnotationBtn.setOnAction(event -> loadSelectAnnotationView());
// rvAnchorPane.prefWidthProperty().bind(ViewManager.getInstance().getDecorator().widthProperty());
// rvAnchorPane.prefHeightProperty().bind(ViewManager.getInstance().getDecorator().heightProperty());
// selectAnnotationBtn.setOnAction(event -> loadSelectAnnotationView());
// windArrowHolder.getChildren().addAll(windArrow);
// windArrow.setLayoutX(windArrowHolder.getWidth() / 2);
// windArrow.setLayoutY(windArrowHolder.getHeight() / 2);
// selectAnnotationBtn.setOnAction(event -> loadSelectAnnotationView());
chatInput.lengthProperty().addListener((obs, oldLen, newLen) -> {
if (newLen.intValue() > CHAT_LIMIT) {
chatInput.setText(chatInput.getText().substring(0, CHAT_LIMIT));
@@ -207,26 +177,17 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
chatHistory.prefHeightProperty().bind(
chatHistoryHolder.heightProperty()
);
// chatHistory.setFitToWidth(true);
// chatHistory.setFitToHeight(true);
// chatHistory.textProperty().addListener((obs, oldValue, newValue) -> {
// chatHistory.setScrollTop(Double.MAX_VALUE);
// });
contentStackPane.setOnMouseClicked(event -> {
contentStackPane.requestFocus();
});
Platform.runLater(contentStackPane::requestFocus);
//Makes the chat history non transparent when clicked on
chatInput.focusedProperty().addListener(new ChangeListener<Boolean>() {
@Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue,
Boolean newValue) {
if (newValue) {
chatHistory.increaseOpacity();
} else {
chatHistory.decreaseOpacity();
}
chatInput.focusedProperty().addListener((observable, oldValue, newValue) -> {
if (newValue) {
chatHistory.increaseOpacity();
} else {
chatHistory.decreaseOpacity();
}
});
}
@@ -283,7 +244,6 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
while (c.next()) {
if (c.wasPermutated()) {
updateOrder(raceState.getPlayerPositions());
updateSparkLine();
}
}
});
@@ -293,7 +253,6 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
updateOrder(raceState.getPlayerPositions());
gameView = new GameView3D();
// gameView.setFrameRateFXText(fpsDisplay);
Platform.runLater(() -> {
contentStackPane.getChildren().add(0, gameView.getAssets());
((SubScene) gameView.getAssets()).widthProperty()
@@ -307,11 +266,10 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
gameView.updateCourse(
new ArrayList<>(raceData.getCompoundMarks().values()), raceData.getMarkSequence()
);
// gameView.enableZoom();
gameView.setBoatAsPlayer(player);
// gameView.startRace();
// raceState.addCollisionListener(gameView::drawCollision);
raceState.windDirectionProperty().addListener((obs, oldDirection, newDirection) -> {
gameView.setWindDir(newDirection.doubleValue());
Platform.runLater(() -> updateWindDirection(newDirection.doubleValue()));
@@ -324,9 +282,7 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
updateWindSpeed(raceState.getWindSpeed());
});
gameView.setWindDir(raceState.windDirectionProperty().doubleValue());
Platform.runLater(() -> {
initializeUpdateTimer();
});
Platform.runLater(this::initializeUpdateTimer);
}
/**
@@ -429,137 +385,6 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
}
}
private void initialiseFPSCheckBox() {
// toggleFps.selectedProperty().addListener((obs, oldVal, newVal) ->
// gameView.setFPSVisibility(toggleFps.isSelected())
// );
}
private void initialiseAnnotationSlider() {
// annotationSlider.setLabelFormatter(new StringConverter<Double>() {
// @Override
// public String toString(Double n) {
// if (n == 0) {
// return "None";
// }
// if (n == 1) {
// return "Important";
// }
// if (n == 2) {
// return "All";
// }
// return "All";
// }
//
// @Override
// public Double fromString(String s) {
// switch (s) {
// case "None":
// return 0d;
// case "Important":
// return 1d;
// case "All":
// return 2d;
//
// default:
// return 2d;
// }
// }
// });
// annotationSlider.setValue(2);
// annotationSlider.valueProperty().addListener((obs, oldVal, newVal) ->
// setAnnotations((int) annotationSlider.getValue())
// );
}
/**
* Used to add any new yachts into the race that may have started late or not have had data received yet
*/
private void updateSparkLine(){
// // TODO: 2/08/17 there is about 0 chance of this working. Once we are keeping track of boat positions it can be fixed.
// // Collect the racing yachts that aren't already in the chart
// sparkLineData.clear();
// List<ClientYacht> sparkLineCandidates = new ArrayList<>(participants.values());
// // Create a new data series for new yachts
// sparkLineCandidates
// .stream()
// .filter(yacht -> yacht.getPosition() != null)
// .forEach(yacht -> {
// Series<String, Double> yachtData = new Series<>();
// yachtData.setName(yacht.getSourceId().toString());
// yachtData.getData().add(
// new Data<>(
// Integer.toString(yacht.getLegNumber()),
// 1.0 + participants.size() - yacht.getPosition()
// )
// );
// sparkLineData.add(yachtData);
// });
//
// // Lambda function to sort the series in order of leg (later legs shown more to the right)
// sparkLineData.sort((o1, o2) -> {
// Integer leg1 = Integer.parseInt(o1.getData().get(o1.getData().size()-1).getXValue());
// Integer leg2 = Integer.parseInt(o2.getData().get(o2.getData().size()-1).getXValue());
// if (leg2 < leg1){
// return 1;
// } else {
// return -1;
// }
// });
//
// // Adds the new data series to the sparkline (and set the colour of the series)
// Platform.runLater(() -> {
// sparkLineData
// .stream()
// .filter(spark -> !raceSparkLine.getData().contains(spark))
// .forEach(spark -> {
// raceSparkLine.getData().add(spark);
// spark.getNode().lookup(".chart-series-line").setStyle("-fx-stroke:" + getBoatColorAsRGB(spark.getName()));
// });
// });
}
private void initialiseSparkLine() {
// sparklineYAxis.setUpperBound(participants.size() + 1);
// raceSparkLine.setCreateSymbols(false);
}
/**
* Updates the yachts sparkline of the desired yacht and using the new leg number
* @param yacht The yacht to be updated on the sparkline
* @param legNumber the leg number that the position will be assigned to
*/
void updateYachtPositionSparkline(ClientYacht yacht, Integer legNumber){
for (XYChart.Series<String, Double> positionData : sparkLineData) {
positionData.getData().add(
new Data<>(
Integer.toString(legNumber),
1.0 + participants.size() - yacht.getPlacing()
)
);
}
}
/**
* gets the rgb string of the yachts colour to use for the chart via css
* @param yachtId id of yacht passed in to get the yachts colour
* @return the colour as an rgb string
*/
private String getBoatColorAsRGB(String yachtId){
Color color = participants.get(Integer.valueOf(yachtId)).getColour();
if (color == null){
return String.format("#%02X%02X%02X",255,255,255);
}
return String.format( "#%02X%02X%02X",
(int)( color.getRed() * 255 ),
(int)( color.getGreen() * 255 ),
(int)( color.getBlue() * 255 )
);
}
/**
* Initialises a timer which updates elements of the RaceView such as wind direction, yacht
* orderings etc.. which are dependent on the info from the stream parser constantly.
@@ -23,11 +23,21 @@ import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import seng302.discoveryServer.DiscoveryServerClient;
import seng302.discoveryServer.util.ServerListing;
import seng302.gameServer.ServerDescription;
import seng302.gameServer.messages.ServerRegistrationMessage;
import seng302.utilities.Sounds;
import seng302.visualiser.ServerListener;
import seng302.visualiser.ServerListenerDelegate;
import seng302.visualiser.controllers.cells.ServerCell;
import seng302.visualiser.controllers.dialogs.DirectConnectController;
import java.io.IOException;
import java.net.URL;
import java.util.Arrays;
import java.util.List;
import java.util.ResourceBundle;
import seng302.visualiser.controllers.dialogs.ServerCreationController;
import seng302.visualiser.validators.HostNameFieldValidator;
import seng302.visualiser.validators.NumberRangeValidator;
@@ -48,15 +58,21 @@ public class ServerListController implements Initializable, ServerListenerDelega
private JFXButton serverListHostButton;
//Direct Connect
@FXML
private JFXButton connectButton;
@FXML
private JFXTextField serverHostName;
private JFXButton directConnectButton;
@FXML
private JFXTextField serverPortNumber;
@FXML
private JFXButton roomConnectButton;
@FXML
private JFXTextField roomNumber;
@FXML
private JFXButton autoSelectGame;
//---------FXML END---------//
private Label noServersFound;
private Logger logger = LoggerFactory.getLogger(ServerListController.class);
private JFXDialog directConnectDialog;
private JFXDialog serverCreationDialog;
private List<ServerCreationDialogListener> serverCreationDialogListeners = new ArrayList<>();
@@ -72,13 +88,25 @@ public class ServerListController implements Initializable, ServerListenerDelega
serverListVBox.minWidthProperty().bind(serverListScrollPane.widthProperty());
// Set Event Bindings
connectButton.setOnMouseEntered(event -> Sounds.playHoverSound());
directConnectButton.setOnMouseEntered(event -> Sounds.playHoverSound());
serverListHostButton.setOnMouseEntered(event -> Sounds.playHoverSound());
connectButton.setOnMouseReleased(event -> {
attemptToDirectConnect();
roomNumber.setOnKeyPressed(event -> {
if (event.getCode().equals(KeyCode.ENTER)) {
connectToRoomCode(roomNumber.getText());
}
});
directConnectButton.setOnMouseReleased(event -> {
directConnectDialog.show();
Sounds.playButtonClick();
});
for (JFXTextField textField : Arrays.asList(serverHostName, serverPortNumber)) {
directConnectDialog = createDirectConnectDialog();
for (JFXTextField textField : Arrays.asList(roomNumber)) {
// Event for pressing enter to submit direct connection
textField.setOnKeyPressed(event -> {
if (event.getCode().equals(KeyCode.ENTER)) {
@@ -92,15 +120,41 @@ public class ServerListController implements Initializable, ServerListenerDelega
textField.getValidators().add(validator);
}
autoSelectGame.setOnMouseReleased(e -> {
ServerListing listing;
DiscoveryServerClient client = new DiscoveryServerClient();
try {
listing = client.getRandomServer();
} catch (Exception e1) {
ViewManager.getInstance().showErrorSnackBar("Unable to connect to matchmaking server. Are you connected to the internet?");
return;
}
if (client.didFail()){
return;
}
if (listing == null || listing.equals(ServerRegistrationMessage.getEmptyRegistration())) {
ViewManager.getInstance().showErrorSnackBar("There are currently no servers available for you to connect to.");
return;
}
ViewManager.getInstance().getGameClient().runAsClient(listing.getAddress(), listing.getPortNumber());
});
/*
// Validating the hostname
HostNameFieldValidator hostNameValidator = new HostNameFieldValidator();
hostNameValidator.setMessage("Host name incorrect");
serverHostName.getValidators().add(hostNameValidator);
roomCodeInput.getValidators().add(hostNameValidator);
// Validating the port number
NumberRangeValidator portNumberValidator = new NumberRangeValidator(1025, 65536);
portNumberValidator.setMessage("Port number incorrect");
serverPortNumber.getValidators().add(portNumberValidator);
TODO later
*/
// Start listening for servers on network
try {
@@ -121,6 +175,11 @@ public class ServerListController implements Initializable, ServerListenerDelega
);
serverListVBox.getChildren().add(noServersFound);
roomConnectButton.setOnMouseReleased(e -> {
String roomCode = roomNumber.getText();
connectToRoomCode(roomCode);
});
// Set up dialog for server creation
serverListHostButton.setOnAction(action -> {
showServerCreationDialog();
@@ -144,11 +203,30 @@ public class ServerListController implements Initializable, ServerListenerDelega
serverCreationDialog.show();
Sounds.playButtonClick();
} catch (IOException e) {
e.printStackTrace();
logger.warn("Could not create Server Creation Dialog.");
}
});
}
private JFXDialog createDirectConnectDialog() {
FXMLLoader dialog = new FXMLLoader(
getClass().getResource("/views/dialogs/DirectConnect.fxml"));
JFXDialog dcDialog = null;
try {
dcDialog = new JFXDialog(serverListMainStackPane, dialog.load(),
JFXDialog.DialogTransition.CENTER);
} catch (IOException e) {
e.printStackTrace();
}
DirectConnectController controller = dialog.getController();
return dcDialog;
}
private void closeServerCreationDialog() {
serverCreationDialog.close();
}
@@ -157,9 +235,9 @@ public class ServerListController implements Initializable, ServerListenerDelega
* Validates the connection and attempts to connect to a given hostname and port number.
*/
private void attemptToDirectConnect() {
if (validateDirectConnection(serverHostName.getText(), serverPortNumber.getText())) {
/*if (validateDirectConnection(serverHostName.getText(), serverPortNumber.getText())) {
DirectConnect();
}
}*/
}
/**
@@ -169,10 +247,40 @@ public class ServerListController implements Initializable, ServerListenerDelega
* @return boolean value if host and port number are valid values
*/
private Boolean validateDirectConnection(String hostName, String portNumber) {
Boolean hostNameValid = ValidationTools.validateTextField(serverHostName);
/*Boolean hostNameValid = ValidationTools.validateTextField(serverHostName);
*
Boolean portNumberValid = ValidationTools.validateTextField(serverPortNumber);
return hostNameValid && portNumberValid;
return hostNameValid && portNumberValid;*/
return true;
}
private void connectToRoomCode(String roomCode){
DiscoveryServerClient client = new DiscoveryServerClient();
ServerListing serverListing;
if (client.didFail()){
return;
}
try {
serverListing = client.getServerForRoomCode(roomCode);
} catch (Exception e) {
ViewManager.getInstance().showErrorSnackBar("Error connecting to matchmaking server. Please try again later.");
return;
}
if (serverListing == null || serverListing.equals(new ServerListing("","","", 0, 0))){
ViewManager.getInstance().showErrorSnackBar("No servers could be found with that room code.");
return;
}
try {
ViewManager.getInstance().getGameClient().runAsClient(serverListing.getAddress(), serverListing.getPortNumber());
}
catch (Exception e) {
ViewManager.getInstance().showErrorSnackBar("Error connecting to matchmaking service.");
}
}
/**
@@ -180,7 +288,7 @@ public class ServerListController implements Initializable, ServerListenerDelega
*/
private void DirectConnect() {
Sounds.playButtonClick();
ViewManager.getInstance().getGameClient().runAsClient(serverHostName.getText(), Integer.parseInt(serverPortNumber.getText()));
// ViewManager.getInstance().getGameClient().runAsClient(serverHostName.getText(), Integer.parseInt(serverPortNumber.getText()));
}
/**
@@ -59,7 +59,7 @@ public class StartScreenController implements Initializable{
/**
* Changes the view to the Server Browser.
*/
private void goToServerBrowser() {
public void goToServerBrowser() {
try {
ViewManager.getInstance().setScene(serverList);
} catch (Exception e) {
@@ -6,16 +6,10 @@ import com.jfoenix.controls.JFXDialog;
import com.jfoenix.controls.JFXDialog.DialogTransition;
import com.jfoenix.controls.JFXSnackbar;
import com.jfoenix.svg.SVGGlyph;
import java.io.IOException;
import java.util.HashMap;
import javafx.application.Platform;
import javafx.collections.ObservableList;
import javafx.fxml.FXMLLoader;
import javafx.scene.Cursor;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.SceneAntialiasing;
import javafx.scene.*;
import javafx.scene.image.Image;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
@@ -25,12 +19,14 @@ import javafx.stage.StageStyle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import seng302.gameServer.ServerAdvertiser;
import seng302.utilities.BonjourInstallChecker;
import seng302.utilities.Sounds;
import seng302.visualiser.GameClient;
import seng302.visualiser.controllers.dialogs.KeyBindingDialogController;
import seng302.visualiser.controllers.dialogs.PopupDialogController;
import java.io.IOException;
import java.util.HashMap;
public class ViewManager {
private static ViewManager instance;
@@ -277,16 +273,9 @@ public class ViewManager {
jfxSnackbar.show(snackbarText, 1500);
}
/**
* Determines if a PC has compatibility with the bonjour protocol for server detection.
*/
private void checkCompatibility() {
if (BonjourInstallChecker.isBonjourSupported()) {
BonjourInstallChecker.openInstallUrl();
}
}
private void closeAll() {
if (stage!= null) stage.close();
try {
ServerAdvertiser.getInstance().unregister();
} catch (IOException e1) {
@@ -352,8 +341,9 @@ public class ViewManager {
logger.error("Could not load lobby view");
}
LobbyController lobbyController = loader.getController();
if (disableReadyButton) {
LobbyController lobbyController = loader.getController();
lobbyController.disableReadyButton();
}
@@ -365,7 +355,6 @@ public class ViewManager {
*
* @return A RaceViewController for the race view screen.
*/
public RaceViewController loadRaceView() {
FXMLLoader loader = loadFxml("/views/RaceView.fxml");
// have to create a new stage and set the race view maximized as JFoenix decorator has
@@ -410,6 +399,14 @@ public class ViewManager {
return loader.getController();
}
public void showErrorSnackBar(String msg){
decorator.getStylesheets()
.add(getClass().getResource("/css/dialogs/Snackbar.css").toExternalForm());
JFXSnackbar bar = new JFXSnackbar(decorator);
bar.enqueue(new JFXSnackbar.SnackbarEvent(msg));
}
public Stage getStage() {
return stage;
}
@@ -0,0 +1,70 @@
package seng302.visualiser.controllers.dialogs;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXSlider;
import com.jfoenix.controls.JFXTextField;
import com.jfoenix.validation.RequiredFieldValidator;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.application.Platform;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
import javafx.scene.input.MouseEvent;
import seng302.gameServer.ServerDescription;
import seng302.utilities.Sounds;
import seng302.visualiser.controllers.ViewManager;
import seng302.visualiser.validators.FieldLengthValidator;
import seng302.visualiser.validators.ValidationTools;
public class DirectConnectController implements Initializable {
//--------FXML BEGIN--------//
@FXML
private JFXTextField serverAddress;
@FXML
private JFXTextField portNumber;
@FXML
private JFXButton submitBtn;
//---------FXML END---------//
public void initialize(URL location, ResourceBundle resources) {
FieldLengthValidator fieldLengthValidator = new FieldLengthValidator(40);
fieldLengthValidator.setMessage("Too long.");
RequiredFieldValidator fieldRequiredValidator = new RequiredFieldValidator();
fieldRequiredValidator.setMessage("Required.");
serverAddress.setValidators(fieldLengthValidator, fieldRequiredValidator);
portNumber.setValidators(fieldLengthValidator, fieldRequiredValidator);
submitBtn.setOnMouseReleased(event -> {
Sounds.playButtonClick();
connectToServer();
});
}
/**
* connects to the server
*/
private void connectToServer() {
//TODO fix port number validation
try{
Integer.parseInt(portNumber.getText());
}
catch (NumberFormatException e){
ViewManager.getInstance().showErrorSnackBar("You need to enter a valid port number");
return;
}
ViewManager.getInstance().getGameClient()
.runAsClient(serverAddress.getText(), Integer.parseInt(portNumber.getText()));
}
public void playButtonHoverSound(MouseEvent mouseEvent) {
Sounds.playHoverSound();
}
}
@@ -1,6 +1,7 @@
package seng302.visualiser.controllers.dialogs;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXCheckBox;
import com.jfoenix.controls.JFXSlider;
import com.jfoenix.controls.JFXTextField;
import com.jfoenix.validation.RequiredFieldValidator;
@@ -10,9 +11,10 @@ import java.util.ResourceBundle;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.AnchorPane;
import seng302.gameServer.ServerDescription;
import seng302.utilities.Sounds;
import seng302.visualiser.MapMaker;
import seng302.visualiser.controllers.ServerListController.ServerCreationDialogListener;
import seng302.visualiser.controllers.ViewManager;
import seng302.visualiser.validators.FieldLengthValidator;
@@ -31,15 +33,42 @@ public class ServerCreationController implements Initializable {
private JFXButton submitBtn;
@FXML
private Label closeLabel;
@FXML
private JFXButton nextMapButton;
@FXML
private JFXButton lastMapButton;
@FXML
private Label mapNameLabel;
@FXML
private JFXSlider legsSlider;
@FXML
private Label legsSliderLabel;
@FXML
private JFXCheckBox pickupsCheckBox;
@FXML
private AnchorPane mapHolder;
private MapMaker mapMaker = MapMaker.getInstance();
//---------FXML END---------//
private List<ServerCreationDialogListener> serverCreationDialogListeners;
public void initialize(URL location, ResourceBundle resources) {
maxPlayersSlider.valueProperty().addListener(
(observable, oldValue, newValue) -> updateMaxPlayerLabel()
);
maxPlayersSlider.setMax(mapMaker.getMaxPlayers());
maxPlayersSlider.setValue(mapMaker.getMaxPlayers());
legsSlider.valueProperty().addListener(
(obs, oldVal, newVal) -> updateLegSliderLabel()
);
legsSlider.setMax(10);
updateMaxPlayerLabel();
maxPlayersSlider.valueProperty().addListener((observable, oldValue, newValue) -> {
updateMaxPlayerLabel();
});
updateLegSliderLabel();
FieldLengthValidator fieldLengthValidator = new FieldLengthValidator(40);
fieldLengthValidator.setMessage("Server name too long.");
@@ -54,7 +83,20 @@ public class ServerCreationController implements Initializable {
validateServerSettings();
});
closeLabel.setOnMouseClicked(event -> notifyListeners());
nextMapButton.setOnMouseReleased(event -> {
Sounds.playButtonClick();
nextMap();
});
lastMapButton.setOnMouseReleased(event -> {
Sounds.playButtonClick();
lastMap();
});
mapHolder.getChildren().setAll(mapMaker.getCurrentGameView());
mapNameLabel.setText(mapMaker.getCurrentRegatta().getCourseName());
pickupsCheckBox.setSelected(true);
//closeLabel.setOnMouseClicked(event -> notifyListeners());
}
/**
@@ -75,7 +117,7 @@ public class ServerCreationController implements Initializable {
private void createServer() {
ServerDescription serverDescription = ViewManager.getInstance().getGameClient()
.runAsHost("localhost", 4941, serverName.getText(), (int) maxPlayersSlider
.getValue());
.getValue(), mapMaker.getCurrentRacePath(), (int) legsSlider.getValue(), pickupsCheckBox.isSelected());
ViewManager.getInstance().setProperty("serverName", serverDescription.getName());
ViewManager.getInstance().setProperty("mapName", serverDescription.getMapName());
@@ -86,13 +128,38 @@ public class ServerCreationController implements Initializable {
*/
private void updateMaxPlayerLabel() {
maxPlayersSlider.setValue(Math.floor(maxPlayersSlider.getValue()));
maxPlayersLabel.setText(String.format("YOU SELECTED: %.0f", maxPlayersSlider.getValue()));
maxPlayersLabel.setText(String.format("Max players: %.0f", maxPlayersSlider.getValue()));
}
public void playButtonHoverSound(MouseEvent mouseEvent) {
private void updateLegSliderLabel() {
legsSlider.setValue(Math.floor(legsSlider.getValue()));
legsSliderLabel.setText(
String.format("A section of the race will repeat %.0f times", legsSlider.getValue())
);
}
public void playButtonHoverSound() {
Sounds.playHoverSound();
}
private void nextMap() {
mapMaker.next();
updateMap();
}
private void lastMap() {
mapMaker.previous();
updateMap();
}
private void updateMap() {
mapHolder.getChildren().setAll(mapMaker.getCurrentGameView());
mapNameLabel.setText(mapMaker.getCurrentRegatta().getCourseName());
maxPlayersSlider.setMax(mapMaker.getMaxPlayers());
maxPlayersSlider.setValue(mapMaker.getMaxPlayers());
}
public void setListener(List<ServerCreationDialogListener> serverCreationDialogListeners) {
this.serverCreationDialogListeners = serverCreationDialogListeners;
}
@@ -26,7 +26,9 @@ public enum ModelType {
PLAYER_IDENTIFIER ("player_identifier.dae"),
PLAIN_ARROW ("arrow.dae"),
START_ARROW ("start_arrow.dae"),
FINISH_ARROW ("finish_arrow.dae");
FINISH_ARROW ("finish_arrow.dae"),
LAND("land.dae"),
LAND_SMOOTH("land_smooth.dae");
final String filename;