Compare commits
155 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 057af2799a | |||
| 45669c333a | |||
| f7fd5494ef | |||
| a1d468c689 | |||
| 6fafb02a8f | |||
| 4f80640718 | |||
| d436d2a6e4 | |||
| 27379ae96d | |||
| ce3e08abfc | |||
| 923c381797 | |||
| ece45ff967 | |||
| 02dc1dbd3d | |||
| d9b9c2f808 | |||
| 4e3de02a93 | |||
| 261f68f143 | |||
| ed9b7acc62 | |||
| 74c1219e0d | |||
| 83218ae0a0 | |||
| 0231c43a2c | |||
| a05a41d5ec | |||
| 06bc3644bc | |||
| 7620f0023e | |||
| 77ee1ebbc0 | |||
| 6d0835b0cf | |||
| 54410efa12 | |||
| 74241ee819 | |||
| a9ce8901f5 | |||
| 8810554ce9 | |||
| 7e3ed872ed | |||
| 21ce34dda2 | |||
| 2bf318a122 | |||
| e56d284792 | |||
| 80c26a9e4a | |||
| e1ebbc71c1 | |||
| c4a3df32c8 | |||
| 1e19dd5ab6 | |||
| c98297ea79 | |||
| 1aedcaddf5 | |||
| d7fc339ad5 | |||
| a5eea10c87 | |||
| 51078c82a0 | |||
| 22fbb529ef | |||
| 02aabc3162 | |||
| d2a05de25a | |||
| 597fbe935f | |||
| 7cfad28d6b | |||
| 3345734efd | |||
| 9795083d4d | |||
| f02208ba3a | |||
| d3e8a21d2f | |||
| b9a2d60115 | |||
| 076c4c9a40 | |||
| 8d74c3b756 | |||
| 3f666fa092 | |||
| 55d82298b6 | |||
| 909407fe63 | |||
| cb4e47f71a | |||
| 499acb9733 | |||
| 5688e10e6f | |||
| c72c4929ff | |||
| 265b20ad61 | |||
| 5cbd729214 | |||
| ddf5a96e0f | |||
| 81b2d285e9 | |||
| 5d7f307260 | |||
| 852575c9e7 | |||
| 4ec23a1785 | |||
| 37e4fe4ce7 | |||
| 567e351c7f | |||
| e87931a8fc | |||
| d1edbc4b8a | |||
| 275a2cbab7 | |||
| 705669ad07 | |||
| 71f6b9accb | |||
| caf04e1e99 | |||
| b9cb6fa5b4 | |||
| 72fe8c4881 | |||
| 0b8e2499a7 | |||
| 9075d2a909 | |||
| 00e2af9c14 | |||
| ba768deabc | |||
| 73861b2ef3 | |||
| 4018dd783e | |||
| 8b7407bf89 | |||
| 00ddf117b2 | |||
| a266779709 | |||
| df24fe072a | |||
| a1a34b9bd7 | |||
| 42d490d6fd | |||
| cd4a2f8da3 | |||
| 287cfd77d0 | |||
| a08a38c985 | |||
| 810dde6a21 | |||
| 7a76efa2ce | |||
| ff4c2cd5b6 | |||
| c5e6302f86 | |||
| cd199767ae | |||
| 5cc4898ab5 | |||
| 22e1e57c24 | |||
| c5d2016733 | |||
| efc71f2003 | |||
| be72062c8e | |||
| 0d212a4a1d | |||
| 0e9b818071 | |||
| 452e83c1c3 | |||
| aded794a67 | |||
| 2b3a972ed5 | |||
| 2a523a5664 | |||
| 270326ea77 | |||
| 67f3124cfb | |||
| 7db387bdec | |||
| 2ceca2fd42 | |||
| 00ff771fc3 | |||
| d963785679 | |||
| 78f64557c3 | |||
| 982fac38a0 | |||
| edbfb2f84f | |||
| c01111038f | |||
| fd53fd52a4 | |||
| 6f62efcc93 | |||
| daf3867433 | |||
| c0bd498f1b | |||
| b18d9e8573 | |||
| e9881bb24a | |||
| ab5ad58237 | |||
| df7264cc1f | |||
| 671efcaf08 | |||
| 98abe64f00 | |||
| 870d7a6e82 | |||
| 735699dc85 | |||
| 81e791bd1a | |||
| 06e5f4ae00 | |||
| cd2b4cb93c | |||
| dde1c82dbb | |||
| d9c832168b | |||
| 9cfb3b9e5d | |||
| e990c68d40 | |||
| 83871a0336 | |||
| 6cba024d64 | |||
| 51747e2d13 | |||
| b3981b19e0 | |||
| 3d7a64068f | |||
| c12f7408ad | |||
| 35b50d1436 | |||
| d0844e861d | |||
| 5d32d76d9d | |||
| 9ca39d1a7c | |||
| 30dad8509e | |||
| ca320f7fb8 | |||
| 9b00ba654a | |||
| 5e3ae40d03 | |||
| 95ad7a4840 | |||
| 6ca75b2cac | |||
| 40a7f9bc5b | |||
| e17e9749d8 |
@@ -109,6 +109,12 @@
|
||||
<version>3.4.2</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.fxyz3d</groupId>
|
||||
<artifactId>fxyz3d</artifactId>
|
||||
<version>0.1.1</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
@@ -10,11 +10,16 @@ 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;
|
||||
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
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,56 +30,93 @@ 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
|
||||
public void start(Stage primaryStage) throws Exception {
|
||||
ViewManager.getInstance().initialStartView(primaryStage);
|
||||
ViewManager.getInstance().initialiseSplashScreen(primaryStage);
|
||||
}
|
||||
|
||||
private static void runDiscoveryServer() throws Exception {
|
||||
while (true){
|
||||
try {
|
||||
new DiscoveryServer();
|
||||
}
|
||||
catch (Exception ignored){
|
||||
;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
/*
|
||||
* Do not trust Java to do garbage collection
|
||||
*/
|
||||
new Timer().schedule(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
System.gc();
|
||||
}
|
||||
}, 0, 1_000);
|
||||
|
||||
public static void main(String[] args) {
|
||||
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,170 @@
|
||||
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.io.IOException;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.Timer;
|
||||
|
||||
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 ServerSocket serverSocket;
|
||||
|
||||
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 v1.0.0 (Release) " + 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();
|
||||
|
||||
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");
|
||||
|
||||
try{
|
||||
while (true){
|
||||
Socket clientSocket = serverSocket.accept();
|
||||
|
||||
parseRequest(clientSocket);
|
||||
|
||||
clientSocket.close();
|
||||
}
|
||||
}
|
||||
catch (Exception e){
|
||||
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++;
|
||||
}
|
||||
|
||||
if (serverToJoin != null && serverToJoin.isMaxPlayersReached()){
|
||||
return null;
|
||||
}
|
||||
|
||||
return serverToJoin;
|
||||
}
|
||||
|
||||
public void close(){
|
||||
try {
|
||||
serverSocket.close();
|
||||
} catch (IOException ignored) {
|
||||
;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 = 1000;
|
||||
|
||||
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 = 5;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package seng302.gameServer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
@@ -11,13 +11,10 @@ import java.util.Random;
|
||||
import java.util.Set;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import javafx.beans.property.ReadOnlyDoubleWrapper;
|
||||
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;
|
||||
@@ -26,8 +23,6 @@ import seng302.gameServer.messages.MarkRoundingMessage;
|
||||
import seng302.gameServer.messages.MarkType;
|
||||
import seng302.gameServer.messages.Message;
|
||||
import seng302.gameServer.messages.RoundingBoatStatus;
|
||||
import seng302.gameServer.messages.YachtEventCodeMessage;
|
||||
import seng302.gameServer.messages.YachtEventType;
|
||||
import seng302.model.GeoPoint;
|
||||
import seng302.model.Limit;
|
||||
import seng302.model.Player;
|
||||
@@ -36,11 +31,11 @@ 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;
|
||||
import seng302.utilities.RandomSpawn;
|
||||
import seng302.utilities.XMLParser;
|
||||
import seng302.visualiser.fxObjects.assets_3D.BoatMeshType;
|
||||
|
||||
/**
|
||||
@@ -74,7 +69,7 @@ public class GameState implements Runnable {
|
||||
|
||||
//Collision constants
|
||||
private static final Double MARK_COLLISION_DISTANCE = 15d;
|
||||
public static final Double YACHT_COLLISION_DISTANCE = 25.0;
|
||||
public static final Double YACHT_COLLISION_DISTANCE = 15.0;
|
||||
private static final Double BOUNCE_DISTANCE_MARK = 20.0;
|
||||
public static final Double BOUNCE_DISTANCE_YACHT = 30.0;
|
||||
private static final Double COLLISION_VELOCITY_PENALTY = 0.3;
|
||||
@@ -88,6 +83,7 @@ public class GameState implements Runnable {
|
||||
|
||||
private static Long previousUpdateTime;
|
||||
public static Double windDirection;
|
||||
public static ReadOnlyDoubleWrapper windDirectionProperty = new ReadOnlyDoubleWrapper();
|
||||
private static Double windSpeed;
|
||||
private static Double serverSpeedMultiplier;
|
||||
|
||||
@@ -97,12 +93,11 @@ public class GameState implements Runnable {
|
||||
private static String hostIpAddress;
|
||||
private static List<Player> players;
|
||||
private static Map<Integer, ServerYacht> yachts;
|
||||
private static Boolean isRaceStarted;
|
||||
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;
|
||||
@@ -111,47 +106,34 @@ 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;
|
||||
windDirectionProperty.set(windDirection);
|
||||
windSpeed = 10000d;
|
||||
yachts = new HashMap<>();
|
||||
tokensInPlay = new ArrayList<>();
|
||||
players = new ArrayList<>();
|
||||
GameState.hostIpAddress = hostIpAddress;
|
||||
customizationFlag = false;
|
||||
playerHasLeftFlag = false;
|
||||
serverSpeedMultiplier = 1.0;
|
||||
currentStage = GameStages.LOBBYING;
|
||||
isRaceStarted = false;
|
||||
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() {
|
||||
@@ -162,6 +144,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()
|
||||
@@ -182,10 +168,6 @@ public class GameState implements Runnable {
|
||||
yachts.remove(yachtId);
|
||||
}
|
||||
|
||||
public static Boolean getIsRaceStarted() {
|
||||
return isRaceStarted;
|
||||
}
|
||||
|
||||
public static GameStages getCurrentStage() {
|
||||
return currentStage;
|
||||
}
|
||||
@@ -212,6 +194,7 @@ public class GameState implements Runnable {
|
||||
|
||||
public static void setWindDirection(Double newWindDirection) {
|
||||
windDirection = newWindDirection;
|
||||
windDirectionProperty.set(newWindDirection);
|
||||
}
|
||||
|
||||
public static void setWindSpeed(Double newWindSpeed) {
|
||||
@@ -259,7 +242,7 @@ public class GameState implements Runnable {
|
||||
if (System.currentTimeMillis() > startTime) {
|
||||
startSpawningTokens();
|
||||
startUpdatingWind();
|
||||
GameState.setCurrentStage(GameStages.RACING);
|
||||
GameState.currentStage = GameStages.RACING;
|
||||
}
|
||||
}
|
||||
if (currentStage == GameStages.RACING) {
|
||||
@@ -276,8 +259,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);
|
||||
}
|
||||
@@ -318,8 +303,8 @@ public class GameState implements Runnable {
|
||||
windSpeed += random.nextInt(500);
|
||||
}
|
||||
|
||||
GameState.setWindSpeed(Double.valueOf(windSpeed));
|
||||
GameState.setWindDirection(direction.doubleValue());
|
||||
GameState.windSpeed = Double.valueOf(windSpeed);
|
||||
GameState.windDirection = direction.doubleValue();
|
||||
}
|
||||
|
||||
|
||||
@@ -359,10 +344,11 @@ public class GameState implements Runnable {
|
||||
*/
|
||||
private void spawnNewToken() {
|
||||
tokensInPlay.clear();
|
||||
Token token = randomSpawn.getRandomTokenLocation();
|
||||
Token token = randomSpawn.getRandomToken();
|
||||
// token.assignType(TokenType.WIND_WALKER);
|
||||
logger.debug("Spawned token of type " + token.getTokenType());
|
||||
tokensInPlay.add(token);
|
||||
MessageFactory.updateTokens(tokensInPlay);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -409,6 +395,8 @@ public class GameState implements Runnable {
|
||||
if (collidedToken != null) {
|
||||
tokensInPlay.remove(collidedToken);
|
||||
powerUpYacht(yacht, collidedToken);
|
||||
MessageFactory.updateTokens(tokensInPlay);
|
||||
notifyMessageListeners(MessageFactory.getRaceXML());
|
||||
}
|
||||
|
||||
checkPowerUpTimeout(yacht);
|
||||
@@ -525,6 +513,7 @@ public class GameState implements Runnable {
|
||||
Double optimalAngle = PolarTable.getOptimalAngle();
|
||||
Double heading = yacht.getHeading();
|
||||
windDirection = (double) Math.floorMod(Math.round(heading + optimalAngle), 360L);
|
||||
windDirectionProperty.set(windDirection);
|
||||
}
|
||||
|
||||
|
||||
@@ -541,6 +530,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;
|
||||
@@ -740,6 +730,10 @@ public class GameState implements Runnable {
|
||||
* @param yacht The current yacht to check for
|
||||
*/
|
||||
private Boolean checkStartLineCrossing(ServerYacht yacht) {
|
||||
long timeTillStart = System.currentTimeMillis() - this.getStartTime();
|
||||
if (timeTillStart < 0){
|
||||
return false;
|
||||
}
|
||||
Integer currentMarkSeqID = yacht.getCurrentMarkSeqID();
|
||||
CompoundMark currentMark = markOrder.getCurrentMark(currentMarkSeqID);
|
||||
GeoPoint lastLocation = yacht.getLastLocation();
|
||||
@@ -1049,6 +1043,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 () {
|
||||
@@ -1059,4 +1059,12 @@ public class GameState implements Runnable {
|
||||
public static double getServerSpeedMultiplier() {
|
||||
return serverSpeedMultiplier;
|
||||
}
|
||||
|
||||
public static void setTokensEnabled (boolean tokensEnabled) {
|
||||
GameState.tokensEnabled = tokensEnabled;
|
||||
}
|
||||
|
||||
public static ReadOnlyDoubleWrapper getWindDirectionProperty() {
|
||||
return windDirectionProperty;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,30 +1,23 @@
|
||||
package seng302.gameServer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
import java.net.ServerSocket;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Random;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
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;
|
||||
|
||||
|
||||
/**
|
||||
* A class describing the overall server, which creates and collects server threads for each client
|
||||
@@ -32,39 +25,34 @@ import seng302.utilities.XMLParser;
|
||||
*/
|
||||
public class MainServerThread implements Runnable, ClientConnectionDelegate {
|
||||
|
||||
private Logger logger = LoggerFactory.getLogger(MainServerThread.class);
|
||||
|
||||
private static final int PORT = 4942;
|
||||
private static int selectedPort = PORT;
|
||||
private static final Integer CLIENT_UPDATES_PER_SECOND = 60;
|
||||
|
||||
private Logger logger = LoggerFactory.getLogger(MainServerThread.class);
|
||||
private boolean terminated;
|
||||
|
||||
private Thread thread;
|
||||
private boolean hasStarted = false;
|
||||
|
||||
private ServerSocket serverSocket = null;
|
||||
private ArrayList<ServerToClientThread> serverToClientThreads = new ArrayList<>();;
|
||||
private static Integer capacity;
|
||||
private ArrayList<ServerToClientThread> serverToClientThreads = new ArrayList<>();
|
||||
private RaceXMLData raceXMLData;
|
||||
private RegattaXMLData regattaXMLData;
|
||||
|
||||
public MainServerThread() {
|
||||
new GameState();
|
||||
try {
|
||||
serverSocket = new ServerSocket(0);
|
||||
selectedPort = serverSocket.getLocalPort();
|
||||
} catch (IOException e) {
|
||||
logger.trace("IO error in server thread handler upon trying to make new server socket",
|
||||
0);
|
||||
}
|
||||
terminated = false;
|
||||
Thread thread = new Thread(this, "MainServer");
|
||||
thread.start();
|
||||
}
|
||||
|
||||
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,37 +64,33 @@ 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(selectedPort, regattaXMLData.getRegattaName());
|
||||
} catch (IOException e) {
|
||||
logger.warn("Could not register server");
|
||||
}
|
||||
}
|
||||
|
||||
public MainServerThread() {
|
||||
new GameState("localhost");
|
||||
try {
|
||||
serverSocket = new ServerSocket(PORT);
|
||||
} catch (IOException e) {
|
||||
logger.trace("IO error in server thread handler upon trying to make new server socket",
|
||||
0);
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
PolarTable.parsePolarFile(getClass().getResourceAsStream("/config/acc_polars.csv"));
|
||||
GameState.addMessageEventListener(this::broadcastMessage);
|
||||
terminated = false;
|
||||
thread = new Thread(this, "MainServer");
|
||||
thread.start();
|
||||
sendSetupMessages();
|
||||
}
|
||||
|
||||
|
||||
public void run() {
|
||||
|
||||
new HeartbeatThread(this);
|
||||
new ServerListenThread(serverSocket, this);
|
||||
|
||||
hasStarted = true;
|
||||
|
||||
//You should handle interrupts in some way, so that the thread won't keep on forever if you exit the app.
|
||||
while (!terminated) {
|
||||
if (GameState.getPlayerHasLeftFlag()) {
|
||||
@@ -130,6 +114,7 @@ public class MainServerThread implements Runnable, ClientConnectionDelegate {
|
||||
}
|
||||
if (GameState.getCurrentStage() == GameStages.LOBBYING && GameState
|
||||
.getCustomizationFlag()) {
|
||||
MessageFactory.updateBoats(new ArrayList<>(GameState.getYachts().values()));
|
||||
sendSetupMessages();
|
||||
GameState.resetCustomizationFlag();
|
||||
}
|
||||
@@ -147,7 +132,8 @@ public class MainServerThread implements Runnable, ClientConnectionDelegate {
|
||||
else if (GameState.getCurrentStage() == GameStages.FINISHED) {
|
||||
broadcastMessage(MessageFactory.getRaceStatusMessage());
|
||||
try {
|
||||
Thread.sleep(1000); //Hackish fix to make sure all threads have sent closing RaceStatus
|
||||
Thread.sleep(
|
||||
1000); //Hackish fix to make sure all threads have sent closing RaceStatus
|
||||
terminate();
|
||||
} catch (InterruptedException ie) {
|
||||
logger.trace("Thread interrupted while waiting to terminate clients", 1);
|
||||
@@ -155,8 +141,10 @@ public class MainServerThread implements Runnable, ClientConnectionDelegate {
|
||||
}
|
||||
}
|
||||
try {
|
||||
for (ServerToClientThread serverToClientThread : serverToClientThreads) {
|
||||
serverToClientThread.terminate();
|
||||
synchronized (this) {
|
||||
for (ServerToClientThread serverToClientThread : serverToClientThreads) {
|
||||
serverToClientThread.terminate();
|
||||
}
|
||||
}
|
||||
serverSocket.close();
|
||||
} catch (IOException e) {
|
||||
@@ -171,6 +159,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 +181,42 @@ 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();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -218,10 +233,11 @@ public class MainServerThread implements Runnable, ClientConnectionDelegate {
|
||||
for (ServerToClientThread serverToClientThread : serverToClientThreads) {
|
||||
if (serverToClientThread.getSocket() == player.getSocket()) {
|
||||
closedConnection = serverToClientThread;
|
||||
} else if (GameState.getCurrentStage() != GameStages.RACING){
|
||||
} else if (GameState.getCurrentStage() != GameStages.RACING) {
|
||||
serverToClientThread.sendSetupMessages();
|
||||
}
|
||||
}
|
||||
|
||||
serverToClientThreads.remove(closedConnection);
|
||||
|
||||
try {
|
||||
@@ -230,7 +246,9 @@ public class MainServerThread implements Runnable, ClientConnectionDelegate {
|
||||
logger.warn("Couldn't update advertisement");
|
||||
}
|
||||
|
||||
closedConnection.terminate();
|
||||
if (closedConnection != null) {
|
||||
closedConnection.terminate();
|
||||
}
|
||||
}
|
||||
|
||||
public void startGame() {
|
||||
@@ -248,16 +266,12 @@ public class MainServerThread implements Runnable, ClientConnectionDelegate {
|
||||
public void run() {
|
||||
broadcastMessage(MessageFactory.getRaceStatusMessage());
|
||||
if (GameState.getCurrentStage() == GameStages.PRE_RACE
|
||||
|| GameState.getCurrentStage() == GameStages.LOBBYING) {
|
||||
|| GameState.getCurrentStage() == GameStages.LOBBYING) {
|
||||
broadcastMessage(MessageFactory.getRaceStartStatusMessage());
|
||||
}
|
||||
}
|
||||
}, 0, 500);
|
||||
|
||||
|
||||
if (GameState.getCurrentStage() == GameStages.LOBBYING) {
|
||||
sendSetupMessages();
|
||||
}
|
||||
}
|
||||
|
||||
public void terminate() {
|
||||
@@ -268,39 +282,74 @@ 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);
|
||||
|
||||
// Calculating midpoint
|
||||
Double perpendicularAngle = GeoUtility.getBearing(startMark1, startMark2);
|
||||
Double length = GeoUtility.getDistance(startMark1, startMark2);
|
||||
GeoPoint midpoint = GeoUtility.getGeoCoordinate(startMark1, perpendicularAngle, length / 2);
|
||||
final double DISTANCE_TO_START = 75d;
|
||||
final double YACHT_SEPARATION = 20d;
|
||||
|
||||
// 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;
|
||||
//Length of start line
|
||||
double startLineLength = GeoUtility.getDistance(
|
||||
GameState.getMarkOrder().getMarkOrder().get(0).getSubMark(1),
|
||||
GameState.getMarkOrder().getMarkOrder().get(0).getSubMark(2)
|
||||
);
|
||||
|
||||
if (boatIndex % 2 == 1 && boatIndex != 0) {
|
||||
distanceApart++;
|
||||
distanceApart *= -1;
|
||||
//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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasStarted() {
|
||||
return hasStarted;
|
||||
}
|
||||
|
||||
public int getPortNumber() {
|
||||
return selectedPort;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@ 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;
|
||||
@@ -39,6 +41,48 @@ 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) {
|
||||
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 +143,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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -134,6 +146,8 @@ public class ServerAdvertiser {
|
||||
public void unregister(){
|
||||
if (serviceInfo != null)
|
||||
jmdnsInstance.unregisterService(serviceInfo);
|
||||
|
||||
repositoryClient.unregister();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,7 +158,13 @@ public abstract class Message {
|
||||
* @return The current buffer as a byte array
|
||||
*/
|
||||
public byte[] getBuffer(){
|
||||
return buffer.array();
|
||||
byte[] bytes = buffer.array();
|
||||
|
||||
// buffer.reset();
|
||||
// buffer.clear();
|
||||
// buffer = null;
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -13,10 +13,7 @@ import javafx.beans.property.ReadOnlyIntegerProperty;
|
||||
import javafx.beans.property.ReadOnlyIntegerWrapper;
|
||||
import javafx.beans.property.ReadOnlyLongProperty;
|
||||
import javafx.beans.property.ReadOnlyLongWrapper;
|
||||
import javafx.beans.value.ObservableObjectValue;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.scene.paint.Color;
|
||||
import jdk.nashorn.internal.objects.annotations.Function;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import seng302.model.token.TokenType;
|
||||
@@ -285,6 +282,7 @@ public class ClientYacht extends Observable {
|
||||
|
||||
public void setHeading(Double heading) {
|
||||
this.heading = heading;
|
||||
System.out.println(heading);
|
||||
setHeadingProperty();
|
||||
}
|
||||
|
||||
|
||||
@@ -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() + "}";
|
||||
}
|
||||
}
|
||||
@@ -134,4 +134,8 @@ public class RaceState {
|
||||
public Boolean getRaceFinished() {
|
||||
return raceFinished;
|
||||
}
|
||||
|
||||
public ReadOnlyDoubleWrapper getWindDirection() {
|
||||
return windDirection;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
@@ -72,7 +72,6 @@ public class CompoundMark {
|
||||
getSubMark(1).setRoundingSide(RoundingSide.STARBOARD);
|
||||
break;
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 + "}";
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ public class RandomSpawn {
|
||||
private static final Integer DEGREES_IN_CIRCLE = 360;
|
||||
|
||||
private HashMap<GeoPoint, Double> spawnRadii;
|
||||
private Object[] spawnCentres;
|
||||
private Random random;
|
||||
|
||||
/**
|
||||
@@ -27,6 +28,7 @@ public class RandomSpawn {
|
||||
random = new Random();
|
||||
|
||||
spawnRadii = generateSpawnRadii(markOrder);
|
||||
spawnCentres = spawnRadii.keySet().toArray();
|
||||
}
|
||||
|
||||
private HashMap<GeoPoint, Double> generateSpawnRadii(List<CompoundMark> markOrder) {
|
||||
@@ -48,9 +50,8 @@ public class RandomSpawn {
|
||||
* @return A random token type at a random location in a random radii of the set of possible
|
||||
* radii
|
||||
*/
|
||||
public Token getRandomTokenLocation() {
|
||||
Object[] keys = spawnRadii.keySet().toArray();
|
||||
GeoPoint randomSpawnCentre = (GeoPoint) keys[random.nextInt(keys.length)];
|
||||
public Token getRandomToken() {
|
||||
GeoPoint randomSpawnCentre = (GeoPoint) spawnCentres[random.nextInt(spawnCentres.length)];
|
||||
Double spawnRadius = spawnRadii.get(randomSpawnCentre);
|
||||
Double randomDistance = spawnRadius * random.nextDouble();
|
||||
Double randomAngle = random.nextDouble() * DEGREES_IN_CIRCLE;
|
||||
|
||||
@@ -101,6 +101,10 @@ public class Sounds {
|
||||
musicPlayer.setCycleCount(MediaPlayer.INDEFINITE);
|
||||
musicPlayer.setVolume(0.3);
|
||||
musicPlayer.play();
|
||||
musicPlayer.setMute(musicMuted);
|
||||
if (soundEffect != null) {
|
||||
soundEffect.stop();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@ import java.util.TimerTask;
|
||||
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 +26,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;
|
||||
|
||||
|
||||
/**
|
||||
* A class describing a single connection to a Server for the purposes of sending and receiving on
|
||||
@@ -34,6 +42,8 @@ import seng302.model.stream.packets.StreamPacket;
|
||||
*/
|
||||
public class ClientToServerThread implements Runnable {
|
||||
|
||||
private boolean isStarted = false;
|
||||
|
||||
/**
|
||||
* Functional interface for receiving packets from client socket.
|
||||
*/
|
||||
@@ -44,7 +54,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 +71,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 +84,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;
|
||||
|
||||
@@ -103,6 +119,8 @@ public class ClientToServerThread implements Runnable {
|
||||
* variable is false.
|
||||
*/
|
||||
public void run() {
|
||||
isStarted = true;
|
||||
|
||||
int sync1;
|
||||
int sync2;
|
||||
// TODO: 14/07/17 wmu16 - Work out how to fix this while loop
|
||||
@@ -133,8 +151,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 +170,13 @@ public class ClientToServerThread implements Runnable {
|
||||
logger.warn("Closed connection to server", 1);
|
||||
notifyDisconnectListeners("Connection to server was terminated");
|
||||
closeSocket();
|
||||
|
||||
//thread.interrupt();
|
||||
|
||||
// Platform.runLater(() -> {
|
||||
// ViewManager.getInstance().showErrorSnackBar("Server rejected connection.");
|
||||
// ViewManager.getInstance().goToStartView();
|
||||
// });
|
||||
}
|
||||
|
||||
public void sendCustomizationRequest(CustomizeRequestType reqType, byte[] payload) {
|
||||
@@ -166,11 +193,17 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a request to the server asking for a source ID
|
||||
*/
|
||||
@@ -191,7 +224,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 +243,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 +353,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 +363,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 {
|
||||
@@ -345,8 +392,9 @@ public class ClientToServerThread implements Runnable {
|
||||
}
|
||||
if (currentByte == -1) {
|
||||
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.");
|
||||
closeSocket();
|
||||
}
|
||||
return currentByte;
|
||||
}
|
||||
@@ -368,4 +416,29 @@ 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()
|
||||
);
|
||||
}
|
||||
|
||||
public boolean hasStarted() {
|
||||
return isStarted;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,9 +5,7 @@ import java.text.SimpleDateFormat;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TimeZone;
|
||||
import java.util.Timer;
|
||||
@@ -15,10 +13,8 @@ import java.util.TimerTask;
|
||||
import javafx.application.Platform;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.util.Pair;
|
||||
import seng302.gameServer.GameStages;
|
||||
@@ -56,7 +52,6 @@ import seng302.visualiser.controllers.dialogs.PopupDialogController;
|
||||
*/
|
||||
public class GameClient {
|
||||
|
||||
private Pane holderPane;
|
||||
private ClientToServerThread socketThread;
|
||||
private MainServerThread server;
|
||||
|
||||
@@ -78,10 +73,8 @@ public class GameClient {
|
||||
/**
|
||||
* Create an instance of the game client. Does not do anything until run with runAsClient()
|
||||
* runAsHost().
|
||||
* @param holder The JavaFX Pane that the visual elements for the race will be inserted into.
|
||||
*/
|
||||
public GameClient(Pane holder) {
|
||||
this.holderPane = holder;
|
||||
public GameClient() {
|
||||
this.gameKeyBind = GameKeyBind.getInstance();
|
||||
}
|
||||
|
||||
@@ -90,65 +83,105 @@ public class GameClient {
|
||||
* @param ipAddress IP to connect to.
|
||||
* @param portNumber Port to connect to.
|
||||
*/
|
||||
public void runAsClient(String ipAddress, Integer portNumber) {
|
||||
public boolean runAsClient(String ipAddress, Integer portNumber) {
|
||||
try {
|
||||
startClientToServerThread(ipAddress, portNumber);
|
||||
socketThread.addDisconnectionListener((cause) -> {
|
||||
showConnectionError(cause);
|
||||
tearDownConnection();
|
||||
Platform.runLater(this::loadStartScreen);
|
||||
});
|
||||
socketThread.addStreamObserver(this::parsePackets);
|
||||
|
||||
ViewManager.getInstance().setPlayerList(clientLobbyList);
|
||||
|
||||
while (regattaData == null){
|
||||
int triesLeft = 10;
|
||||
|
||||
while (regattaData == null && triesLeft >= 0){
|
||||
try {
|
||||
Thread.sleep(100);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
} catch (InterruptedException ignored) {
|
||||
;
|
||||
}
|
||||
triesLeft--;
|
||||
}
|
||||
|
||||
if (triesLeft < 1){
|
||||
return false;
|
||||
}
|
||||
|
||||
ViewManager.getInstance().setProperty("serverName", regattaData.getRegattaName());
|
||||
ViewManager.getInstance().setProperty("mapName", regattaData.getCourseName());
|
||||
|
||||
getServerThread().setConnectionErrorListener((eMessage) -> ViewManager.getInstance().showErrorSnackBar(eMessage));
|
||||
|
||||
this.lobbyController = ViewManager.getInstance().goToLobby(true);
|
||||
|
||||
} catch (IOException ioe) {
|
||||
showConnectionError("Unable to find server");
|
||||
ViewManager.getInstance().showErrorSnackBar("There are no servers currently available.");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 serverName, Integer maxPlayers, String race,
|
||||
Integer numLegs, Boolean tokensEnabled
|
||||
) {
|
||||
XMLGenerator.setDefaultRaceName(serverName);
|
||||
GameState.setMaxPlayers(maxPlayers);
|
||||
|
||||
server = new MainServerThread();
|
||||
|
||||
try {
|
||||
startClientToServerThread(ipAddress, 4942);
|
||||
} catch (IOException e) {
|
||||
showConnectionError("Cannot connect to server as host");
|
||||
}
|
||||
|
||||
while (regattaData == null){
|
||||
while (!server.hasStarted()){
|
||||
try {
|
||||
Thread.sleep(100);
|
||||
Thread.sleep(10);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
startClientToServerThread("localhost", server.getPortNumber());
|
||||
} catch (IOException e) {
|
||||
showConnectionError("Cannot connect to server as host");
|
||||
}
|
||||
|
||||
// Wait for C2S thread
|
||||
while (!socketThread.hasStarted()){
|
||||
try {
|
||||
Thread.sleep(10);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
socketThread.sendXML(race, serverName, numLegs, maxPlayers, tokensEnabled);
|
||||
|
||||
int triesLeft = 15;
|
||||
|
||||
while (regattaData == null && triesLeft > 0){
|
||||
try {
|
||||
Thread.sleep(100);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
triesLeft--;
|
||||
}
|
||||
|
||||
if (triesLeft <= 0){
|
||||
showConnectionError("Could not launch server");
|
||||
return null;
|
||||
}
|
||||
|
||||
this.lobbyController = ViewManager.getInstance().goToLobby(false);
|
||||
|
||||
lobbyController.setPortNumber(""+server.getPortNumber());
|
||||
|
||||
ViewManager.getInstance().setPlayerList(clientLobbyList);
|
||||
return new ServerDescription(serverName, regattaData.getCourseName(), GameState.getNumberOfPlayers(), GameState.getCapacity(), ipAddress, 4942);
|
||||
return new ServerDescription(serverName, regattaData.getCourseName(), GameState.getNumberOfPlayers(), GameState.getCapacity(),
|
||||
"localhost", server.getPortNumber());
|
||||
}
|
||||
|
||||
private void tearDownConnection() {
|
||||
@@ -159,16 +192,6 @@ public class GameClient {
|
||||
}
|
||||
}
|
||||
|
||||
private void loadStartScreen() {
|
||||
FXMLLoader fxmlLoader = new FXMLLoader(
|
||||
getClass().getResource("/views/StartScreenView.fxml"));
|
||||
try {
|
||||
holderPane.getChildren().clear();
|
||||
holderPane.getChildren().add(fxmlLoader.load());
|
||||
} catch (IOException e) {
|
||||
showConnectionError("JavaFX crashed. Please restart the app");
|
||||
}
|
||||
}
|
||||
|
||||
private void showConnectionError (String message) {
|
||||
Platform.runLater(() -> {
|
||||
@@ -261,10 +284,12 @@ public class GameClient {
|
||||
case CHATTER_TEXT:
|
||||
Pair<Integer, String> playerIdMessagePair = StreamParser
|
||||
.extractChatterText(packet);
|
||||
raceView.updateChatHistory(
|
||||
allBoatsMap.get(playerIdMessagePair.getKey()).getColour(),
|
||||
playerIdMessagePair.getValue()
|
||||
);
|
||||
if (playerIdMessagePair != null) {
|
||||
raceView.updateChatHistory(
|
||||
allBoatsMap.get(playerIdMessagePair.getKey()).getColour(),
|
||||
playerIdMessagePair.getValue()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -275,12 +300,14 @@ public class GameClient {
|
||||
|
||||
ClientYacht player = allBoatsMap.get(socketThread.getClientId());
|
||||
raceView.loadRace(allBoatsMap, courseData, raceState, player);
|
||||
|
||||
raceView.showView();
|
||||
raceView.getSendPressedProperty().addListener((obs, old, isPressed) -> {
|
||||
if (isPressed) {
|
||||
formatAndSendChatMessage(raceView.readChatInput());
|
||||
}
|
||||
});
|
||||
gameKeyBind.toggleTurningMode();
|
||||
sendToggleTurningModePacket(); // notify the server about player's steering mode
|
||||
}
|
||||
}
|
||||
|
||||
@@ -299,8 +326,6 @@ public class GameClient {
|
||||
positionData.getLon(), positionData.getHeading(),
|
||||
positionData.getGroundSpeed());
|
||||
}
|
||||
} else if (positionData.getType() == DeviceType.MARK_TYPE) {
|
||||
//CompoundMark mark = courseData.getCompoundMarks().get(positionData.getDeviceId());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -338,21 +363,17 @@ public class GameClient {
|
||||
ClientYacht clientYacht = allBoatsMap.get((int) boatData[0]);
|
||||
clientYacht.setEstimateTimeTillNextMark(raceState.getRaceTime() - boatData[1]);
|
||||
clientYacht.setEstimateTimeAtFinish(boatData[2]);
|
||||
// int legNumber = (int) boatData[3];
|
||||
clientYacht.setBoatStatus((int) boatData[4]);
|
||||
// if (legNumber != clientYacht.getLegNumber()) {
|
||||
// clientYacht.setLegNumber(legNumber);
|
||||
// }
|
||||
}
|
||||
|
||||
if (raceFinished) {
|
||||
raceViewController.showFinishDialog(finishedBoats);
|
||||
if (raceFinished && !raceState.getRaceFinished()) {
|
||||
raceState.setRaceFinished();
|
||||
Sounds.playFinishSound();
|
||||
close();
|
||||
ViewManager.getInstance().getGameClient().stopGame();
|
||||
raceViewController.showFinishDialog(finishedBoats);
|
||||
// close();
|
||||
// ViewManager.getInstance().getGameClient().stopGame();
|
||||
//loadFinishScreenView();
|
||||
}
|
||||
raceState.setRaceFinished();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -363,10 +384,6 @@ public class GameClient {
|
||||
}
|
||||
}
|
||||
|
||||
private void close() {
|
||||
socketThread.setSocketToClose();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the key-pressed event from the text field.
|
||||
* @param e The key event triggering this call
|
||||
@@ -493,10 +510,6 @@ public class GameClient {
|
||||
return socketThread;
|
||||
}
|
||||
|
||||
public List<String> getPlayerNames(){
|
||||
return Collections.unmodifiableList(clientLobbyList.sorted());
|
||||
}
|
||||
|
||||
public void stopGame() {
|
||||
GameState.setCurrentStage(GameStages.CANCELLED);
|
||||
if (server != null) server.terminate();
|
||||
|
||||
@@ -1,443 +1,64 @@
|
||||
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.HashMap;
|
||||
import java.util.List;
|
||||
import javafx.scene.Group;
|
||||
import javafx.scene.Node;
|
||||
import seng302.model.ClientYacht;
|
||||
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.*;
|
||||
import seng302.utilities.Sounds;
|
||||
import seng302.visualiser.fxObjects.Marker;
|
||||
|
||||
/**
|
||||
* 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<>();
|
||||
HashMap<Mark, Marker> markerObjects = new HashMap<>();
|
||||
|
||||
private double distanceScaleFactor;
|
||||
private ScaleDirection scaleDirection;
|
||||
private GeoPoint minLatPoint, minLonPoint, maxLatPoint, maxLonPoint;
|
||||
private double referencePointX, referencePointY;
|
||||
public abstract Node getAssets();
|
||||
public abstract void updateCourse(List<CompoundMark> newCourse, List<Corner> sequence);
|
||||
public abstract void updateBorder(List<Limit> border);
|
||||
|
||||
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);
|
||||
void updateMarkArrows (ClientYacht yacht, int legNumber) {
|
||||
CompoundMark compoundMark;
|
||||
if (legNumber - 1 >= 0 && legNumber-1 < course.size()) {
|
||||
Sounds.playMarkRoundingSound();
|
||||
compoundMark = course.get(legNumber-1);
|
||||
for (Mark mark : compoundMark.getMarks()) {
|
||||
markerObjects.get(mark).showNextExitArrow();
|
||||
}
|
||||
}
|
||||
CompoundMark nextMark = null;
|
||||
if (legNumber < course.size()) {
|
||||
Sounds.playMarkRoundingSound();
|
||||
nextMark = course.get(legNumber);
|
||||
for (Mark mark : nextMark.getMarks()) {
|
||||
markerObjects.get(mark).showNextEnterArrow();
|
||||
}
|
||||
}
|
||||
if (legNumber - 2 >= 0) {
|
||||
CompoundMark lastMark = course.get(Math.max(0, legNumber - 2));
|
||||
if (lastMark != nextMark) {
|
||||
for (Mark mark : lastMark.getMarks()) {
|
||||
markerObjects.get(mark).hideAllArrows();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,12 +2,12 @@ package seng302.visualiser;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javafx.animation.AnimationTimer;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.geometry.Point2D;
|
||||
import javafx.geometry.Point3D;
|
||||
import javafx.scene.Camera;
|
||||
@@ -16,24 +16,24 @@ 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.ScaledPoint;
|
||||
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;
|
||||
import seng302.visualiser.cameras.IsometricCamera;
|
||||
import seng302.visualiser.cameras.RaceCamera;
|
||||
@@ -42,6 +42,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,76 +50,63 @@ 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 = 160;
|
||||
|
||||
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 BoatObject playerBoat;
|
||||
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 Skybox skybox;
|
||||
|
||||
private enum ScaleDirection {
|
||||
HORIZONTAL,
|
||||
VERTICAL
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
gameObjects = new Group();
|
||||
root3D = new Group(isometricCam, gameObjects);
|
||||
root3D = new Group(chaseCam, gameObjects);
|
||||
view = new SubScene(
|
||||
root3D, 1000, 1000, true, SceneAntialiasing.BALANCED
|
||||
root3D, 5000, 3000, true, SceneAntialiasing.BALANCED
|
||||
);
|
||||
view.setCamera(isometricCam);
|
||||
view.setCamera(chaseCam);
|
||||
|
||||
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 +114,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 +168,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 +194,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 +209,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 +268,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 +310,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
|
||||
@@ -491,9 +332,36 @@ public class GameView3D {
|
||||
ViewManager.getInstance().getGameClient().getServerThread().getClientId())) {
|
||||
((ChaseCamera) chaseCam).setPlayerBoat(newBoat);
|
||||
((TopDownCamera) topDownCam).setPlayerBoat(newBoat);
|
||||
|
||||
newBoat.setMarkIndicator(ModelFactory.importSTL("mark_pointer.stl"));
|
||||
playerBoat = newBoat;
|
||||
|
||||
}
|
||||
}
|
||||
Platform.runLater(() -> {
|
||||
ClientYacht playerYacht = ViewManager.getInstance().getGameClient().getAllBoatsMap()
|
||||
.get(ViewManager.getInstance().getGameClient().getServerThread().getClientId());
|
||||
|
||||
for (ObservableValue o : Arrays
|
||||
.asList(playerBoat.layoutXProperty(), playerBoat.layoutXProperty())) {
|
||||
o.addListener((obs, oldVal, newVal) -> {
|
||||
if (playerYacht.getLegNumber() < course.size()) {
|
||||
List<Mark> marks = course.get(playerYacht.getLegNumber()).getMarks();
|
||||
Point2D midPoint = new Point2D(0, 0);
|
||||
if (marks.size() == 1) {
|
||||
midPoint = scaledPoint.findScaledXY(marks.get(0));
|
||||
} else if (marks.size() == 2) {
|
||||
midPoint = (scaledPoint.findScaledXY(marks.get(0)))
|
||||
.midpoint(scaledPoint.findScaledXY(marks.get(1)));
|
||||
}
|
||||
|
||||
if (midPoint != null) {
|
||||
playerBoat.updateMarkIndicator(midPoint);
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
gameObjects.getChildren().addAll(wakes);
|
||||
gameObjects.getChildren().addAll(boatObjectGroup);
|
||||
});
|
||||
@@ -503,6 +371,10 @@ public class GameView3D {
|
||||
return view;
|
||||
}
|
||||
|
||||
public SubScene getView() {
|
||||
return view;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the boatObjects color with that of the clientYachts object. Used in notification from
|
||||
* a listener on this attribute in clientYacht to re paint the boat mesh
|
||||
@@ -516,7 +388,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 +401,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 +440,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 +468,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()) {
|
||||
@@ -627,22 +501,21 @@ public class GameView3D {
|
||||
}
|
||||
|
||||
public void setBoatAsPlayer (ClientYacht playerYacht) {
|
||||
playerBoat.updateMarkIndicator(scaledPoint.findScaledXY(course.get(0).getMidPoint()));
|
||||
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) {
|
||||
@@ -662,31 +535,4 @@ public class GameView3D {
|
||||
public void setWindDir(double windDir) {
|
||||
this.windDir = windDir;
|
||||
}
|
||||
|
||||
private void updateMarkArrows (ClientYacht yacht, int legNumber) {
|
||||
CompoundMark compoundMark;
|
||||
if (legNumber - 1 >= 0) {
|
||||
Sounds.playMarkRoundingSound();
|
||||
compoundMark = course.get(legNumber-1);
|
||||
for (Mark mark : compoundMark.getMarks()) {
|
||||
markerObjects.get(mark).showNextExitArrow();
|
||||
}
|
||||
}
|
||||
CompoundMark nextMark = null;
|
||||
if (legNumber < course.size() - 1) {
|
||||
Sounds.playMarkRoundingSound();
|
||||
nextMark = course.get(legNumber);
|
||||
for (Mark mark : nextMark.getMarks()) {
|
||||
markerObjects.get(mark).showNextEnterArrow();
|
||||
}
|
||||
}
|
||||
if (legNumber - 2 >= 0) {
|
||||
CompoundMark lastMark = course.get(Math.max(0, legNumber - 2));
|
||||
if (lastMark != nextMark) {
|
||||
for (Mark mark : lastMark.getMarks()) {
|
||||
markerObjects.get(mark).hideAllArrows();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
package seng302.visualiser;
|
||||
|
||||
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", "loop.xml", "madagascar.xml", "waiheke.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 RegattaXMLData getCurrentRegatta() {
|
||||
return regattas.get(index);
|
||||
}
|
||||
|
||||
public String getCurrentRacePath() {
|
||||
return filePaths.get(index);
|
||||
}
|
||||
|
||||
public Integer getMaxPlayers() {
|
||||
return maxPlayers.get(index);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,282 @@
|
||||
package seng302.visualiser;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
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.Marker;
|
||||
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();
|
||||
|
||||
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(Marker m1, Marker 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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package seng302.visualiser;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import javafx.application.Platform;
|
||||
import javafx.geometry.Point2D;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.shape.Polygon;
|
||||
import javafx.scene.transform.Rotate;
|
||||
import seng302.model.ClientYacht;
|
||||
import seng302.model.Limit;
|
||||
import seng302.model.mark.CompoundMark;
|
||||
import seng302.model.mark.Corner;
|
||||
|
||||
/**
|
||||
* Class converts a map preview to a minimap by adding boats.
|
||||
*/
|
||||
public class MiniMap extends MapPreview {
|
||||
|
||||
private HashMap<ClientYacht, Polygon> boatIcons = new HashMap<>();
|
||||
|
||||
public MiniMap (List<CompoundMark> marks, List<Corner> course, List<Limit> border, List<ClientYacht> boats, ClientYacht player) {
|
||||
super(marks, course, border);
|
||||
setBoats(boats);
|
||||
player.addMarkRoundingListener(this::updateMarkArrows);
|
||||
}
|
||||
|
||||
public void setBoats(List<ClientYacht> yachts) {
|
||||
for (ClientYacht yacht : yachts) {
|
||||
Polygon boatIcon = new Polygon(0, -3.5, 3.5, 3.5, -3.5, 3.5);
|
||||
boatIcon.setStroke(Color.BLACK);
|
||||
boatIcon.setFill(Color.GRAY);
|
||||
boatIcon.setFill(yacht.getColour());
|
||||
boatIcon.setFill(yacht.getColour());
|
||||
boatIcons.put(yacht, boatIcon);
|
||||
boatIcon.getTransforms().add(new Rotate(0));
|
||||
yacht.addLocationListener((boat, lat, lon, heading, sailIn, velocity) -> {
|
||||
Platform.runLater(() -> {
|
||||
Polygon bi = boatIcons.get(boat);
|
||||
Point2D p2d = scaledPoint.findScaledXY(lat, lon);
|
||||
bi.setLayoutX(p2d.getX());
|
||||
bi.setLayoutY(p2d.getY());
|
||||
((Rotate) bi.getTransforms().get(0)).setAngle(heading);
|
||||
});
|
||||
});
|
||||
}
|
||||
Platform.runLater(() -> {
|
||||
gameObjects.getChildren().addAll(boatIcons.values());
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,23 +1,23 @@
|
||||
package seng302.visualiser;
|
||||
|
||||
import seng302.gameServer.ServerAdvertiser;
|
||||
import seng302.gameServer.ServerDescription;
|
||||
import static seng302.gameServer.ServerAdvertiser.getLocalHostIp;
|
||||
|
||||
import javax.jmdns.JmDNS;
|
||||
import javax.jmdns.ServiceEvent;
|
||||
import javax.jmdns.ServiceListener;
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import static seng302.gameServer.ServerAdvertiser.getLocalHostIp;
|
||||
import javax.jmdns.JmDNS;
|
||||
import javax.jmdns.ServiceEvent;
|
||||
import javax.jmdns.ServiceListener;
|
||||
import seng302.gameServer.ServerAdvertiser;
|
||||
import seng302.gameServer.ServerDescription;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
@@ -58,7 +58,7 @@ public class ServerListener{
|
||||
servers.remove(toRemove);
|
||||
}
|
||||
|
||||
delegate.serverRemoved(new ArrayList<ServerDescription>(servers));
|
||||
delegate.serverRemoved(new ArrayList<>(servers));
|
||||
|
||||
// Get all other servers with the same name to respond if they are up
|
||||
jmdns.requestServiceInfo(ServerAdvertiser.SERVICE_TYPE, serverName);
|
||||
@@ -91,6 +91,7 @@ public class ServerListener{
|
||||
|
||||
private ServerListener() throws IOException {
|
||||
jmdns = JmDNS.create(InetAddress.getByName(getLocalHostIp()));
|
||||
|
||||
listener = new GameServeMonitor();
|
||||
jmdns.addServiceListener(ServerAdvertiser.SERVICE_TYPE, listener);
|
||||
}
|
||||
@@ -110,4 +111,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<>(listener.servers));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,8 +16,8 @@ public class IsometricCamera extends PerspectiveCamera implements RaceCamera {
|
||||
private final Double MAX_Y = 170.0;
|
||||
|
||||
private final Double PAN_LIMIT = 160.0;
|
||||
private final Double NEAR_ZOOM_LIMIT = -50.0;
|
||||
private final Double FAR_ZOOM_LIMIT = -160.0;
|
||||
private final Double NEAR_ZOOM_LIMIT = -30.0;
|
||||
private final Double FAR_ZOOM_LIMIT = -180.0;
|
||||
|
||||
private Double horizontalPan;
|
||||
private Double verticalPan;
|
||||
@@ -29,7 +29,7 @@ public class IsometricCamera extends PerspectiveCamera implements RaceCamera {
|
||||
super(true);
|
||||
transforms = this.getTransforms();
|
||||
|
||||
zoomFactor = (FAR_ZOOM_LIMIT + NEAR_ZOOM_LIMIT) / 2.0;
|
||||
zoomFactor = FAR_ZOOM_LIMIT;
|
||||
horizontalPan = cameraStartX;
|
||||
verticalPan = cameraStartY;
|
||||
|
||||
|
||||
@@ -11,9 +11,9 @@ import seng302.visualiser.fxObjects.assets_3D.BoatObject;
|
||||
|
||||
public class TopDownCamera extends PerspectiveCamera implements RaceCamera {
|
||||
|
||||
private final Double PAN_LIMIT = 30.0;
|
||||
private final Double NEAR_ZOOM_LIMIT = -30.0;
|
||||
private final Double FAR_ZOOM_LIMIT = -130.0;
|
||||
private final Double PAN_LIMIT = 40d;
|
||||
private final Double NEAR_ZOOM_LIMIT = -20.0;
|
||||
private final Double FAR_ZOOM_LIMIT = -200d;
|
||||
private final Double ZOOM_STEP = 2.5;
|
||||
|
||||
private ObservableList<Transform> transforms;
|
||||
@@ -27,7 +27,7 @@ public class TopDownCamera extends PerspectiveCamera implements RaceCamera {
|
||||
super(true);
|
||||
transforms = this.getTransforms();
|
||||
|
||||
zoomFactor = (FAR_ZOOM_LIMIT + NEAR_ZOOM_LIMIT) / 2.0;
|
||||
zoomFactor = FAR_ZOOM_LIMIT;
|
||||
horizontalPan = 0.0;
|
||||
verticalPan = 0.0;
|
||||
}
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
package seng302.visualiser.controllers;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.ResourceBundle;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.fxml.Initializable;
|
||||
import javafx.scene.control.TableColumn;
|
||||
import javafx.scene.control.TableView;
|
||||
import javafx.scene.control.cell.PropertyValueFactory;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.AnchorPane;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.Pane;
|
||||
import seng302.model.ClientYacht;
|
||||
import seng302.utilities.Sounds;
|
||||
|
||||
public class FinishScreenViewController implements Initializable {
|
||||
|
||||
@FXML
|
||||
private GridPane finishScreenGridPane;
|
||||
@FXML
|
||||
private TableView<ClientYacht> finishOrderTable;
|
||||
@FXML
|
||||
private TableColumn<ClientYacht, String> posCol;
|
||||
@FXML
|
||||
private TableColumn<ClientYacht, String> boatNameCol;
|
||||
@FXML
|
||||
private TableColumn<ClientYacht, String> shortNameCol;
|
||||
@FXML
|
||||
private TableColumn<ClientYacht, String> countryCol;
|
||||
|
||||
ObservableList<ClientYacht> data = FXCollections.observableArrayList();
|
||||
|
||||
@Override
|
||||
public void initialize(URL location, ResourceBundle resources) {
|
||||
finishScreenGridPane.getStylesheets()
|
||||
.add(getClass().getResource("/css/Master.css").toString());
|
||||
finishOrderTable.getStylesheets().add(getClass().getResource("/css/Master.css").toString());
|
||||
|
||||
// set up data for table
|
||||
finishOrderTable.setItems(data);
|
||||
|
||||
// setting table col data
|
||||
posCol.setCellValueFactory(
|
||||
new PropertyValueFactory<>("position")
|
||||
);
|
||||
boatNameCol.setCellValueFactory(
|
||||
new PropertyValueFactory<>("boatName")
|
||||
);
|
||||
shortNameCol.setCellValueFactory(
|
||||
new PropertyValueFactory<>("shortName")
|
||||
);
|
||||
countryCol.setCellValueFactory(
|
||||
new PropertyValueFactory<>("country")
|
||||
);
|
||||
finishOrderTable.refresh();
|
||||
}
|
||||
|
||||
public void setFinishers(Collection<ClientYacht> participants) {
|
||||
List<ClientYacht> sorted = new ArrayList<>(participants);
|
||||
sorted.sort(Comparator.comparingInt(ClientYacht::getPlacing));
|
||||
finishOrderTable.getItems().setAll(sorted);
|
||||
}
|
||||
|
||||
private void setContentPane(String jfxUrl) {
|
||||
try {
|
||||
// get the main controller anchor pane (FinishView -> MainView)
|
||||
AnchorPane contentPane = (AnchorPane) finishScreenGridPane.getParent();
|
||||
contentPane.getChildren().removeAll();
|
||||
contentPane.getChildren().clear();
|
||||
contentPane.getStylesheets().add(getClass().getResource("/css/master.css").toString());
|
||||
contentPane.getChildren()
|
||||
.addAll((Pane) FXMLLoader.load(getClass().getResource(jfxUrl)));
|
||||
} catch (javafx.fxml.LoadException e) {
|
||||
System.out.println("[Controller] FXML load exception");
|
||||
} catch (IOException e) {
|
||||
System.out.println("[Controller] IO exception");
|
||||
}
|
||||
}
|
||||
|
||||
public void switchToStartScreenView() {
|
||||
Sounds.playButtonClick();
|
||||
setContentPane("/views/StartScreenView.fxml");
|
||||
}
|
||||
|
||||
public void playButtonHoverSound(MouseEvent mouseEvent) {
|
||||
Sounds.playHoverSound();
|
||||
}
|
||||
}
|
||||
@@ -2,17 +2,34 @@ package seng302.visualiser.controllers;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXDialog;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.ResourceBundle;
|
||||
import javafx.application.Platform;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.fxml.Initializable;
|
||||
import javafx.geometry.Point3D;
|
||||
import javafx.scene.Group;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.AnchorPane;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.transform.Rotate;
|
||||
import javafx.scene.transform.Scale;
|
||||
import javafx.scene.transform.Translate;
|
||||
import seng302.discoveryServer.DiscoveryServerClient;
|
||||
import seng302.gameServer.GameStages;
|
||||
import seng302.gameServer.GameState;
|
||||
import seng302.model.ClientYacht;
|
||||
@@ -23,20 +40,19 @@ 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;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.ResourceBundle;
|
||||
import seng302.visualiser.fxObjects.assets_3D.BoatMeshType;
|
||||
import seng302.visualiser.controllers.dialogs.PopupDialogController;
|
||||
import seng302.visualiser.controllers.dialogs.TokenInfoDialogController;
|
||||
import seng302.visualiser.fxObjects.assets_3D.ModelFactory;
|
||||
import seng302.visualiser.fxObjects.assets_3D.ModelType;
|
||||
|
||||
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,18 +67,27 @@ public class LobbyController implements Initializable {
|
||||
@FXML
|
||||
private Label mapName;
|
||||
@FXML
|
||||
private Pane serverMap;
|
||||
private AnchorPane serverMap;
|
||||
@FXML
|
||||
private Label roomLabel;
|
||||
@FXML
|
||||
private Label portNumber;
|
||||
@FXML
|
||||
private Pane speedTokenPane, handlingTokenPane, windWalkerTokenPane, bumperTokenPane, randomTokenPane;
|
||||
//---------FXML END---------//
|
||||
|
||||
private RaceState raceState;
|
||||
private JFXDialog customizationDialog;
|
||||
private JFXDialog tokenInfoDialog;
|
||||
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("");
|
||||
portNumber.setText("");
|
||||
|
||||
this.playerBoats = ViewManager.getInstance().getGameClient().getAllBoatsMap();
|
||||
|
||||
@@ -86,6 +111,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());
|
||||
@@ -108,17 +148,118 @@ public class LobbyController implements Initializable {
|
||||
beginRaceButton.setOnMouseEntered(e -> Sounds.playHoverSound());
|
||||
|
||||
initMapPreview();
|
||||
initTokenPreviews();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Initialises the tokens in the side panel
|
||||
*/
|
||||
private void initTokenPreviews() {
|
||||
Group speedToken = ModelFactory.importModel(ModelType.VELOCITY_PICKUP).getAssets();
|
||||
Group handlingToken = ModelFactory.importModel(ModelType.HANDLING_PICKUP).getAssets();
|
||||
Group windWalkerToken = ModelFactory.importModel(ModelType.WIND_WALKER_PICKUP).getAssets();
|
||||
Group bumperToken = ModelFactory.importModel(ModelType.BUMPER_PICKUP).getAssets();
|
||||
Group randomToken = ModelFactory.importModel(ModelType.RANDOM_PICKUP).getAssets();
|
||||
|
||||
HashMap<Pane, Group> tokenPanes = new HashMap<>();
|
||||
tokenPanes.put(speedTokenPane, speedToken);
|
||||
tokenPanes.put(handlingTokenPane, handlingToken);
|
||||
tokenPanes.put(windWalkerTokenPane, windWalkerToken);
|
||||
tokenPanes.put(bumperTokenPane, bumperToken);
|
||||
tokenPanes.put(randomTokenPane, randomToken);
|
||||
|
||||
Scale hoverScale = new Scale(1.2, 1.2, 1.2);
|
||||
|
||||
tokenPanes.entrySet().forEach((entry) -> {
|
||||
Pane thisPane = entry.getKey();
|
||||
Group thisToken = entry.getValue();
|
||||
|
||||
thisToken.getTransforms().addAll(
|
||||
new Translate(40, 50, 0),
|
||||
new Scale(13, 13, 13));
|
||||
|
||||
thisPane.setOnMouseEntered(event -> {
|
||||
thisToken.getTransforms().add(hoverScale);
|
||||
});
|
||||
thisPane.setOnMouseExited(event -> {
|
||||
thisToken.getTransforms().remove(hoverScale);
|
||||
});
|
||||
thisPane.setOnMouseReleased(event -> {
|
||||
tokenInfoDialog = makeTokenDialog(thisPane);
|
||||
tokenInfoDialog.show();
|
||||
});
|
||||
|
||||
thisPane.getChildren().add(thisToken);
|
||||
});
|
||||
|
||||
//Hacky rotations for wind and random to level it in the plane
|
||||
windWalkerToken.getTransforms().addAll(
|
||||
new Rotate(-70, new Point3D(1, 0, 0)),
|
||||
new Translate(0, 2,0)
|
||||
);
|
||||
randomToken.getTransforms().addAll(
|
||||
new Rotate(-90, new Point3D(1, 0, 0)),
|
||||
new Translate(0, 0,1)
|
||||
);
|
||||
}
|
||||
|
||||
private JFXDialog makeTokenDialog(Pane inducingPane) {
|
||||
String header = "...";
|
||||
String body = "Nothing to see here";
|
||||
ModelType modelType = ModelType.RANDOM_PICKUP;
|
||||
|
||||
if (inducingPane == speedTokenPane) {
|
||||
header = "Speed Boost";
|
||||
body = "Increases your max velocity";
|
||||
modelType = ModelType.VELOCITY_PICKUP;
|
||||
} else if (inducingPane == handlingTokenPane) {
|
||||
header = "Handling Boost";
|
||||
body = "Increases your turing rate";
|
||||
modelType = ModelType.HANDLING_PICKUP;
|
||||
} else if (inducingPane == windWalkerTokenPane) {
|
||||
header = "Wind Walker";
|
||||
body = "The wind now rotates with you, giving you your optimal speed in all directions";
|
||||
modelType = ModelType.WIND_WALKER_PICKUP;
|
||||
} else if (inducingPane == bumperTokenPane) {
|
||||
header = "Bumper";
|
||||
body = "While this is active, upon hitting another boat, you will power it down for a short time";
|
||||
modelType = ModelType.BUMPER_PICKUP;
|
||||
} else if (inducingPane == randomTokenPane) {
|
||||
header = "Random";
|
||||
body = "A 50% chance of becoming any other token and a 50% chance of slowing your boat for a time";
|
||||
modelType = ModelType.RANDOM_PICKUP;
|
||||
}
|
||||
|
||||
FXMLLoader dialog = new FXMLLoader(
|
||||
getClass().getResource("/views/dialogs/TokenInfoDialog.fxml"));
|
||||
|
||||
JFXDialog tokenInfoDialog = null;
|
||||
|
||||
try {
|
||||
tokenInfoDialog = new JFXDialog(serverListMainStackPane, dialog.load(),
|
||||
JFXDialog.DialogTransition.CENTER);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
TokenInfoDialogController controller = dialog.getController();
|
||||
controller.setParentController(this);
|
||||
controller.setHeader(header);
|
||||
controller.setContent(body);
|
||||
controller.setToken(modelType);
|
||||
return tokenInfoDialog;
|
||||
}
|
||||
|
||||
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 +277,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 +372,16 @@ public class LobbyController implements Initializable {
|
||||
public void closeCustomizationDialog() {
|
||||
customizationDialog.close();
|
||||
}
|
||||
|
||||
public void closeTokenInfoDialog() {
|
||||
tokenInfoDialog.close();
|
||||
}
|
||||
|
||||
public void setRoomCode(String roomCode) {
|
||||
roomLabel.setText("Room: " + roomCode);
|
||||
}
|
||||
|
||||
public void setPortNumber(String p){
|
||||
portNumber.setText("Port: " + p);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,102 +4,68 @@ import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXDialog;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
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;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.geometry.Point2D;
|
||||
import javafx.scene.Group;
|
||||
import javafx.scene.Node;
|
||||
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;
|
||||
import javafx.scene.control.ComboBox;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.Slider;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.AnchorPane;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.paint.Paint;
|
||||
import javafx.scene.shape.Line;
|
||||
import javafx.scene.shape.Polyline;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.StageStyle;
|
||||
import javax.swing.ImageIcon;
|
||||
import seng302.model.ClientYacht;
|
||||
import seng302.model.ClientYacht.PowerUpListener;
|
||||
import seng302.model.RaceState;
|
||||
import seng302.model.mark.CompoundMark;
|
||||
import seng302.model.mark.Mark;
|
||||
import seng302.model.stream.xml.parser.RaceXMLData;
|
||||
import seng302.model.token.Token;
|
||||
import seng302.model.token.TokenType;
|
||||
import seng302.utilities.Sounds;
|
||||
import seng302.visualiser.GameView3D;
|
||||
import seng302.visualiser.controllers.annotations.ImportantAnnotationController;
|
||||
import seng302.visualiser.controllers.annotations.ImportantAnnotationDelegate;
|
||||
import seng302.visualiser.controllers.annotations.ImportantAnnotationsState;
|
||||
import seng302.visualiser.MiniMap;
|
||||
import seng302.visualiser.controllers.cells.WindCell;
|
||||
import seng302.visualiser.controllers.dialogs.FinishDialogController;
|
||||
import seng302.visualiser.fxObjects.ChatHistory;
|
||||
import seng302.visualiser.fxObjects.assets_2D.WindArrow;
|
||||
import seng302.visualiser.fxObjects.assets_3D.BoatObject;
|
||||
import seng302.visualiser.fxObjects.assets_3D.ModelFactory;
|
||||
import seng302.visualiser.fxObjects.assets_3D.ModelType;
|
||||
|
||||
/**
|
||||
* Controller class that manages the display of a race
|
||||
*/
|
||||
public class RaceViewController extends Thread implements ImportantAnnotationDelegate {
|
||||
public class RaceViewController extends Thread {
|
||||
|
||||
private final int CHAT_LIMIT = 128;
|
||||
private static final Double ICON_BLINK_TIMEOUT_RATIO = 0.6;
|
||||
private static final Integer ICON_BLINK_PERIOD = 500;
|
||||
|
||||
@FXML
|
||||
private Pane basePane;
|
||||
private AnchorPane loadingScreenPane;
|
||||
@FXML
|
||||
private ImageView loadingScreen;
|
||||
@FXML
|
||||
private JFXButton chatSend;
|
||||
@FXML
|
||||
private Pane chatHistoryHolder;
|
||||
@FXML
|
||||
private JFXButton chatToggleButton;
|
||||
@FXML
|
||||
private TextField chatInput;
|
||||
@FXML
|
||||
private LineChart<String, Double> raceSparkLine;
|
||||
@FXML
|
||||
private NumberAxis sparklineYAxis;
|
||||
@FXML
|
||||
private VBox positionVbox;
|
||||
@FXML
|
||||
private CheckBox toggleFps;
|
||||
@FXML
|
||||
private Label timerLabel;
|
||||
@FXML
|
||||
private StackPane contentStackPane;
|
||||
|
||||
private GridPane contentGridPane;
|
||||
@FXML
|
||||
private Pane miniMapPane;
|
||||
@FXML
|
||||
private ImageView windImageView;
|
||||
@FXML
|
||||
private AnchorPane rvAnchorPane;
|
||||
@FXML
|
||||
@@ -112,8 +78,8 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
|
||||
private ComboBox<ClientYacht> yachtSelectionComboBox;
|
||||
@FXML
|
||||
private Text fpsDisplay;
|
||||
@FXML
|
||||
private ImageView windImageView;
|
||||
// @FXML
|
||||
// private ImageView windImageView;
|
||||
@FXML
|
||||
private Label windDirectionLabel;
|
||||
@FXML
|
||||
@@ -122,60 +88,55 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
|
||||
private Label positionLabel, boatSpeedLabel, boatHeadingLabel;
|
||||
@FXML
|
||||
private ImageView velocityIcon, handlingIcon, windWalkerIcon, bumperIcon, badRandomIcon;
|
||||
@FXML
|
||||
private VBox windArrowVBox;
|
||||
@FXML
|
||||
private JFXButton miniMapButton;
|
||||
|
||||
|
||||
private WindCell windCell;
|
||||
//Race Data
|
||||
private Map<Integer, ClientYacht> participants;
|
||||
private Map<Integer, CompoundMark> markers;
|
||||
private RaceXMLData courseData;
|
||||
private GameView3D gameView;
|
||||
private RaceState raceState;
|
||||
|
||||
private ChatHistory chatHistory;
|
||||
|
||||
private Timeline timerTimeline;
|
||||
private Timer timer = new Timer();
|
||||
private List<Series<String, Double>> sparkLineData = new ArrayList<>();
|
||||
private ImportantAnnotationsState importantAnnotations;
|
||||
private Polyline windArrow = new WindArrow(Color.LIGHTGRAY);
|
||||
private ObservableList<ClientYacht> selectionComboBoxList = FXCollections.observableArrayList();
|
||||
private ClientYacht player;
|
||||
private JFXDialog finishScreenDialog;
|
||||
private FinishDialogController finishDialogController;
|
||||
|
||||
//Icon stuff
|
||||
private Timer blinkingTimer = new Timer();
|
||||
private ImageView iconToDisplay;
|
||||
private Double lastWindDirection;
|
||||
private MiniMap miniMap;
|
||||
|
||||
public void initialize() {
|
||||
miniMapPane.setVisible(false);
|
||||
miniMapButton.setVisible(false);
|
||||
chatHistoryHolder.setVisible(false);
|
||||
chatToggleButton.setVisible(false);
|
||||
contentStackPane.setVisible(false);
|
||||
Image loadingImage = new Image("PP.png");
|
||||
loadingScreen.setImage(loadingImage);
|
||||
//Centers the Image within the image view
|
||||
double w = 0;
|
||||
double h = 0;
|
||||
double ratioX = loadingScreen.getFitWidth() / loadingImage.getWidth();
|
||||
double ratioY = loadingScreen.getFitHeight() / loadingImage.getHeight();
|
||||
double reduceRatio = 0;
|
||||
if(ratioX >= ratioY) {
|
||||
reduceRatio = ratioY;
|
||||
} else {
|
||||
reduceRatio = ratioX;
|
||||
}
|
||||
w = loadingImage.getWidth() * reduceRatio;
|
||||
h = loadingImage.getHeight() * reduceRatio;
|
||||
loadingScreen.setX((loadingScreen.getFitWidth() - w) / 2);
|
||||
loadingScreen.setY((loadingScreen.getFitHeight() - h) / 2);
|
||||
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));
|
||||
@@ -189,28 +150,48 @@ 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.getChildren().remove(chatToggleButton);
|
||||
contentStackPane.getChildren().add(chatToggleButton);
|
||||
|
||||
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();
|
||||
}
|
||||
});
|
||||
|
||||
lastWindDirection = 0d;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialise wind arrow cell.
|
||||
*/
|
||||
private void initialiseWindArrow() {
|
||||
FXMLLoader loader = new FXMLLoader(
|
||||
getClass().getResource("/views/cells/WindCell.fxml"));
|
||||
windCell = new WindCell();
|
||||
loader.setController(windCell);
|
||||
|
||||
try {
|
||||
loader.load();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
windCell.init(player, raceState.getWindDirection());
|
||||
windCell.setCamera(gameView.getView().getCamera());
|
||||
gameView.getView().cameraProperty()
|
||||
.addListener((obs, oldVal, newVal) -> windCell.setCamera(newVal));
|
||||
|
||||
windArrowVBox.getChildren().add(windCell.getAssets());
|
||||
}
|
||||
|
||||
public void showFinishDialog(ArrayList<ClientYacht> finishedBoats) {
|
||||
@@ -218,6 +199,17 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
|
||||
createFinishDialog(finishedBoats);
|
||||
}
|
||||
|
||||
public void showView(){
|
||||
loadingScreenPane.setVisible(false);
|
||||
contentStackPane.setVisible(true);
|
||||
miniMapPane.setVisible(true);
|
||||
miniMapButton.setVisible(true);
|
||||
chatHistoryHolder.setVisible(true);
|
||||
chatToggleButton.setVisible(true);
|
||||
|
||||
Platform.runLater(() -> contentStackPane.requestFocus());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create finishScreenDialog and set up finishDialogController.
|
||||
*/
|
||||
@@ -243,33 +235,46 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
|
||||
Map<Integer, ClientYacht> participants, RaceXMLData raceData, RaceState raceState,
|
||||
ClientYacht player) {
|
||||
|
||||
this.participants = participants;
|
||||
this.courseData = raceData;
|
||||
this.markers = raceData.getCompoundMarks();
|
||||
this.raceState = raceState;
|
||||
this.player = player;
|
||||
|
||||
raceState.getPlayerPositions().addListener((ListChangeListener<ClientYacht>) c -> {
|
||||
while (c.next()) {
|
||||
if (c.wasPermutated()) {
|
||||
updateOrder(raceState.getPlayerPositions());
|
||||
updateSparkLine();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
player.addPowerUpListener(this::displayPowerUpIcon);
|
||||
player.addPowerDownListener(this::removeIcon);
|
||||
|
||||
updateOrder(raceState.getPlayerPositions());
|
||||
gameView = new GameView3D();
|
||||
// gameView.setFrameRateFXText(fpsDisplay);
|
||||
miniMap = new MiniMap(
|
||||
new ArrayList<>(raceData.getCompoundMarks().values()),
|
||||
raceData.getMarkSequence(), raceData.getCourseLimit(),
|
||||
new ArrayList<>(participants.values()), player
|
||||
);
|
||||
|
||||
miniMapButton.setOnMouseClicked((event) -> {
|
||||
if (miniMapPane.visibleProperty().get()) {
|
||||
miniMapPane.setVisible(false);
|
||||
miniMapButton.setText("+");
|
||||
} else {
|
||||
miniMapPane.setVisible(true);
|
||||
miniMapButton.setText("—");
|
||||
}
|
||||
});
|
||||
|
||||
chatToggleButton.setOnMouseClicked((event) -> {
|
||||
if (chatHistoryHolder.visibleProperty().get()) {
|
||||
chatHistoryHolder.setVisible(false);
|
||||
chatToggleButton.setText("+");
|
||||
} else {
|
||||
chatHistoryHolder.setVisible(true);
|
||||
chatToggleButton.setText("—");
|
||||
}
|
||||
});
|
||||
|
||||
Platform.runLater(() -> {
|
||||
contentStackPane.getChildren().add(0, gameView.getAssets());
|
||||
((SubScene) gameView.getAssets()).widthProperty()
|
||||
.bind(ViewManager.getInstance().getStage().widthProperty());
|
||||
((SubScene) gameView.getAssets()).heightProperty()
|
||||
.bind(ViewManager.getInstance().getStage().heightProperty());
|
||||
miniMapPane.getChildren().add(miniMap.getAssets());
|
||||
});
|
||||
gameView.setBoats(new ArrayList<>(participants.values()));
|
||||
gameView.updateBorder(raceData.getCourseLimit());
|
||||
@@ -277,11 +282,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()));
|
||||
@@ -294,8 +298,12 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
|
||||
updateWindSpeed(raceState.getWindSpeed());
|
||||
});
|
||||
gameView.setWindDir(raceState.windDirectionProperty().doubleValue());
|
||||
Platform.runLater(this::initializeUpdateTimer);
|
||||
|
||||
Platform.runLater(() -> {
|
||||
initializeUpdateTimer();
|
||||
//windCell.setCamera(gameView.getView().getCamera());
|
||||
|
||||
initialiseWindArrow();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -352,7 +360,7 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
|
||||
}
|
||||
}
|
||||
|
||||
public void removeIcon(ClientYacht yacht) {
|
||||
private void removeIcon(ClientYacht yacht) {
|
||||
if (yacht == player) {
|
||||
blinkingTimer.cancel();
|
||||
iconToDisplay.setVisible(false);
|
||||
@@ -360,176 +368,6 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The important annotations have been changed, update this view
|
||||
*
|
||||
* @param importantAnnotationsState The current state of the selected annotations
|
||||
*/
|
||||
public void importantAnnotationsChanged(ImportantAnnotationsState importantAnnotationsState) {
|
||||
this.importantAnnotations = importantAnnotationsState;
|
||||
setAnnotations((int) annotationSlider.getValue()); // Refresh the displayed annotations
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Loads the "select annotations" view in a new window
|
||||
*/
|
||||
private void loadSelectAnnotationView() {
|
||||
try {
|
||||
FXMLLoader fxmlLoader = new FXMLLoader();
|
||||
Stage stage = new Stage();
|
||||
// Set controller
|
||||
ImportantAnnotationController controller = new ImportantAnnotationController(
|
||||
this, stage
|
||||
);
|
||||
fxmlLoader.setController(controller);
|
||||
// Load FXML and set CSS
|
||||
fxmlLoader.setLocation(
|
||||
getClass().getResource("/views/importantAnnotationSelectView.fxml")
|
||||
);
|
||||
Scene scene = new Scene(fxmlLoader.load(), 469, 298);
|
||||
scene.getStylesheets().add(getClass().getResource("/css/master.css").toString());
|
||||
stage.initStyle(StageStyle.UNDECORATED);
|
||||
stage.setScene(scene);
|
||||
stage.show();
|
||||
controller.loadState(importantAnnotations);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
@@ -547,42 +385,19 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
|
||||
}, 0, 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterates over all corners until ones SeqID matches with the yachts current leg number.
|
||||
* Then it gets the compoundMarkID of that corner and uses it to fetch the appropriate mark
|
||||
* Returns null if no next mark found.
|
||||
* @param bg The BoatGroup to find the next mark of
|
||||
* @return The next Mark or null if none found
|
||||
*/
|
||||
private Mark getNextMark(BoatObject bg) {
|
||||
// TODO: 1/08/17 Move to GameView
|
||||
//
|
||||
// Integer legNumber = bg.getClientYacht().getLegNumber();
|
||||
// List<Corner> markSequence = courseData.getMarkSequence();
|
||||
//
|
||||
// if (legNumber == 0) {
|
||||
// return null;
|
||||
// } else if (legNumber == markSequence.size() - 1) {
|
||||
// return null;
|
||||
// }
|
||||
//
|
||||
// for (Corner corner : markSequence) {
|
||||
// if (legNumber + 2 == corner.getSeqID()) {
|
||||
// return courseData.getCompoundMarks().get(corner.getCompoundMarkID());
|
||||
// }
|
||||
// }
|
||||
// return null;
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Updates the wind direction arrow and text as from info from the StreamParser
|
||||
* @param direction the from north angle of the wind.
|
||||
*/
|
||||
private void updateWindDirection(double direction) {
|
||||
windDirectionLabel.setText(String.format("%.1f°", direction));
|
||||
windImageView.setRotate(direction);
|
||||
// RotateTransition rt = new RotateTransition(Duration.millis(300), windImageView);
|
||||
// rt.setByAngle(direction - lastWindDirection);
|
||||
// rt.setCycleCount(3);
|
||||
// rt.setAutoReverse(true);
|
||||
// rt.play();
|
||||
// lastWindDirection = direction;
|
||||
// windImageView.setRotate(direction);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -651,226 +466,6 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
|
||||
boatHeadingLabel.setText(String.format("Boat Heading:\n%.1f°", player.getHeading()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the order of the yachts as from the StreamParser and sets them in the yacht order
|
||||
* section
|
||||
*/
|
||||
private void updateOrder(ObservableList<ClientYacht> yachts) {
|
||||
// List<Text> vboxEntries = new ArrayList<>();
|
||||
//
|
||||
// for (int i = 0; i < yachts.size(); i++) {
|
||||
//// System.out.println("yacht == null " + String.valueOf(yacht == null));
|
||||
// if (yachts.get(i).getBoatStatus() == BoatStatus.FINISHED
|
||||
// .getCode()) { // 3 is finish status
|
||||
// Text textToAdd = new Text(i + 1 + ". " +
|
||||
// yachts.get(i).getShortName() + " (Finished)");
|
||||
// textToAdd.setFill(Paint.valueOf("#d3d3d3"));
|
||||
// vboxEntries.add(textToAdd);
|
||||
//
|
||||
// } else {
|
||||
// Text textToAdd = new Text(i + 1 + ". " +
|
||||
// yachts.get(i).getShortName() + " ");
|
||||
// textToAdd.setFill(Paint.valueOf("#d3d3d3"));
|
||||
// textToAdd.setStyle("");
|
||||
// vboxEntries.add(textToAdd);
|
||||
// }
|
||||
// }
|
||||
// Platform.runLater(() ->
|
||||
// positionVbox.getChildren().setAll(vboxEntries)
|
||||
// );
|
||||
}
|
||||
|
||||
|
||||
private void updateLaylines(BoatObject bg) {
|
||||
// TODO: 1/08/17 move to GameView
|
||||
//
|
||||
// Mark nextMark = getNextMark(bg);
|
||||
// Boolean isUpwind = null;
|
||||
// // Can only calc leg direction if there is a next mark and it is a gate mark
|
||||
// if (nextMark != null) {
|
||||
// if (nextMark instanceof GateMark) {
|
||||
// if (bg.isUpwindLeg(gameViewController, nextMark)) {
|
||||
// isUpwind = true;
|
||||
// } else {
|
||||
// isUpwind = false;
|
||||
// }
|
||||
//
|
||||
// for(MarkObject mg : gameViewController.getMarkGroups()) {
|
||||
//
|
||||
// mg.removeLaylines();
|
||||
//
|
||||
// if (mg.getMainMark().getId() == nextMark.getId()) {
|
||||
//
|
||||
// SingleMark singleMark1 = ((GateMark) nextMark).getSingleMark1();
|
||||
// SingleMark singleMark2 = ((GateMark) nextMark).getSingleMark2();
|
||||
// Point2D markPoint1 = gameViewController
|
||||
// .findScaledXY(singleMark1.getLatitude(), singleMark1.getLongitude());
|
||||
// Point2D markPoint2 = gameViewController
|
||||
// .findScaledXY(singleMark2.getLatitude(), singleMark2.getLongitude());
|
||||
// HashMap<Double, Double> angleAndSpeed;
|
||||
// if (isUpwind) {
|
||||
// angleAndSpeed = PolarTable.getOptimalUpwindVMG(StreamParser.getWindSpeed());
|
||||
// } else {
|
||||
// angleAndSpeed = PolarTable.getOptimalDownwindVMG(StreamParser.getWindSpeed());
|
||||
// }
|
||||
//
|
||||
// Double resultingAngle = angleAndSpeed.keySet().iterator().next();
|
||||
//
|
||||
//
|
||||
// Point2D yachtCurrentPos = new Point2D(bg.getBoatLayoutX(), bg.getBoatLayoutY());
|
||||
// Point2D gateMidPoint = markPoint1.midpoint(markPoint2);
|
||||
// Integer lineFuncResult = GeoUtility.lineFunction(yachtCurrentPos, gateMidPoint, markPoint2);
|
||||
// Line rightLayline = new Line();
|
||||
// Line leftLayline = new Line();
|
||||
// if (lineFuncResult == 1) {
|
||||
// rightLayline = makeRightLayline(markPoint2, 180 - resultingAngle, StreamParser.getWindDirection());
|
||||
// leftLayline = makeLeftLayline(markPoint1, 180 - resultingAngle, StreamParser.getWindDirection());
|
||||
// } else if (lineFuncResult == -1) {
|
||||
// rightLayline = makeRightLayline(markPoint1, 180 - resultingAngle, StreamParser.getWindDirection());
|
||||
// leftLayline = makeLeftLayline(markPoint2, 180 - resultingAngle, StreamParser.getWindDirection());
|
||||
// }
|
||||
//
|
||||
// leftLayline.setStrokeWidth(0.5);
|
||||
// leftLayline.setStroke(bg.getBoat().getColour());
|
||||
//
|
||||
// rightLayline.setStrokeWidth(0.5);
|
||||
// rightLayline.setStroke(bg.getBoat().getColour());
|
||||
//
|
||||
// bg.setLaylines(leftLayline, rightLayline);
|
||||
// mg.addLaylines(leftLayline, rightLayline);
|
||||
//
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
|
||||
private Point2D getPointRotation(Point2D ref, Double distance, Double angle) {
|
||||
Double newX = ref.getX() + (ref.getX() + distance - ref.getX()) * Math.cos(angle)
|
||||
- (ref.getY() + distance - ref.getY()) * Math.sin(angle);
|
||||
Double newY = ref.getY() + (ref.getX() + distance - ref.getX()) * Math.sin(angle)
|
||||
+ (ref.getY() + distance - ref.getY()) * Math.cos(angle);
|
||||
|
||||
return new Point2D(newX, newY);
|
||||
}
|
||||
|
||||
|
||||
public Line makeLeftLayline(Point2D startPoint, Double layLineAngle, Double baseAngle) {
|
||||
Point2D ep = getPointRotation(startPoint, 50.0, baseAngle + layLineAngle);
|
||||
Line line = new Line(startPoint.getX(), startPoint.getY(), ep.getX(), ep.getY());
|
||||
return line;
|
||||
|
||||
}
|
||||
|
||||
|
||||
public Line makeRightLayline(Point2D startPoint, Double layLineAngle, Double baseAngle) {
|
||||
|
||||
Point2D ep = getPointRotation(startPoint, 50.0, baseAngle - layLineAngle);
|
||||
Line line = new Line(startPoint.getX(), startPoint.getY(), ep.getX(), ep.getY());
|
||||
return line;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Initialised the combo box with any yachts currently in the race and adds the required listener
|
||||
* for the combobox to take action upon selection
|
||||
*/
|
||||
private void initialiseBoatSelectionComboBox() {
|
||||
// yachtSelectionComboBox.setItems(
|
||||
// FXCollections.observableArrayList(participants.values())
|
||||
// );
|
||||
// //Null check is if the listener is fired but nothing selected
|
||||
// yachtSelectionComboBox.valueProperty().addListener((obs, lastSelection, selectedBoat) -> {
|
||||
// if (selectedBoat != null) {
|
||||
// gameView.selectBoat(selectedBoat);
|
||||
// }
|
||||
// });
|
||||
|
||||
//TODO uncomment out
|
||||
// selectionComboBoxList.setAll(participants.values());
|
||||
// yachtSelectionComboBox.setItems(selectionComboBoxList);
|
||||
// yachtSelectionComboBox.valueProperty().addListener((obs, lastSelection, selectedBoat) -> {
|
||||
// if (selectedBoat != null) {
|
||||
// gameView.selectBoat(selectedBoat);
|
||||
// }
|
||||
// });
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the list of yachts in the order they finished the race
|
||||
*/
|
||||
private void loadRaceResultView() {
|
||||
FXMLLoader loader = new FXMLLoader(getClass().getResource("/views/FinishView.fxml"));
|
||||
|
||||
try {
|
||||
contentGridPane.getChildren().removeAll();
|
||||
contentGridPane.getChildren().clear();
|
||||
contentGridPane.getChildren().addAll((Pane) loader.load());
|
||||
|
||||
} catch (javafx.fxml.LoadException e) {
|
||||
System.err.println(e.getCause().toString());
|
||||
} catch (IOException e) {
|
||||
System.err.println(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private String getMillisToFormattedTime(long milliseconds) {
|
||||
return String.format("%02d:%02d:%02d",
|
||||
TimeUnit.MILLISECONDS.toHours(milliseconds),
|
||||
TimeUnit.MILLISECONDS.toMinutes(milliseconds) % 60, //Modulus 60 minutes per hour
|
||||
TimeUnit.MILLISECONDS.toSeconds(milliseconds) % 60 //Modulus 60 seconds per minute
|
||||
);
|
||||
}
|
||||
|
||||
private void setAnnotations(Integer annotationLevel) {
|
||||
// switch (annotationLevel) {
|
||||
// // No Annotations
|
||||
// case 0:
|
||||
// gameView.setAnnotationVisibilities(
|
||||
// false, false, false, false, false, false
|
||||
// );
|
||||
// break;
|
||||
// // Important Annotations
|
||||
// case 1:
|
||||
// gameView.setAnnotationVisibilities(
|
||||
// importantAnnotations.getAnnotationState(Annotation.NAME),
|
||||
// importantAnnotations.getAnnotationState(Annotation.SPEED),
|
||||
// importantAnnotations.getAnnotationState(Annotation.ESTTIMETONEXTMARK),
|
||||
// importantAnnotations.getAnnotationState(Annotation.LEGTIME),
|
||||
// importantAnnotations.getAnnotationState(Annotation.TRACK),
|
||||
// importantAnnotations.getAnnotationState(Annotation.WAKE)
|
||||
// );
|
||||
// break;
|
||||
// // All Annotations
|
||||
// case 2:
|
||||
// gameView.setAnnotationVisibilities(
|
||||
// true, true, true, true, true, true
|
||||
// );
|
||||
// break;
|
||||
// }
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets all the annotations of the selected yacht to be visible and all others to be hidden
|
||||
*
|
||||
* @param yacht The yacht for which we want to view all annotations
|
||||
*/
|
||||
private void setSelectedBoat(ClientYacht yacht) {
|
||||
// for (BoatObject bg : gameViewController.getBoatGroups()) {
|
||||
// //We need to iterate over all race groups to get the matching yacht group belonging to this yacht if we
|
||||
// //are to toggle its annotations, there is no other backwards knowledge of a yacht to its yachtgroup.
|
||||
// if (bg.getBoat().getHullID().equals(yacht.getHullID())) {
|
||||
//// updateLaylines(bg);
|
||||
// bg.setIsSelected(true);
|
||||
//// selectedBoat = yacht;
|
||||
// } else {
|
||||
// bg.setIsSelected(false);
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
public void updateTokens(RaceXMLData raceData) {
|
||||
gameView.updateTokens(raceData.getTokens());
|
||||
|
||||
@@ -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,43 @@ 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;
|
||||
}
|
||||
|
||||
if (!ViewManager.getInstance().getGameClient().runAsClient(listing.getAddress(), listing.getPortNumber())){
|
||||
ViewManager.getInstance().showErrorSnackBar("Could not connect to server");
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
// 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 +177,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 +205,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 +237,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 +249,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 +290,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()));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
package seng302.visualiser.controllers;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.ResourceBundle;
|
||||
import javafx.application.Platform;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.Initializable;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
/**
|
||||
* The pre loading screen before launch the start view
|
||||
* Created by Kusal on 26-Sep-17.
|
||||
*/
|
||||
public class SplashScreenController implements Initializable{
|
||||
|
||||
@FXML
|
||||
private StackPane rootPane;
|
||||
|
||||
@Override
|
||||
public void initialize(URL location, ResourceBundle resources) {
|
||||
new SplashScreen().start();
|
||||
}
|
||||
|
||||
|
||||
class SplashScreen extends Thread {
|
||||
public void run(){
|
||||
try {
|
||||
Thread.sleep(3000);
|
||||
Platform.runLater(() -> {
|
||||
try {
|
||||
Stage stage = new Stage();
|
||||
ViewManager.getInstance().initialStartView(stage);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
rootPane.getScene().getWindow().hide();
|
||||
});
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -21,10 +21,10 @@ import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.stage.Stage;
|
||||
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;
|
||||
@@ -56,10 +56,20 @@ public class ViewManager {
|
||||
if (instance == null) {
|
||||
instance = new ViewManager();
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
public void initialiseSplashScreen(Stage stage) throws IOException {
|
||||
this.stage = stage;
|
||||
Parent root = FXMLLoader.load(getClass().getResource("/views/SplashScreen.fxml"));
|
||||
Scene scene = new Scene(root);
|
||||
stage.setTitle("Party Parrots At Sea");
|
||||
stage.getIcons().add(new Image(getClass().getResourceAsStream("/PP.png")));
|
||||
stage.setScene(scene);
|
||||
stage.initStyle(StageStyle.UNDECORATED);
|
||||
stage.show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the start view in the given stage.
|
||||
*/
|
||||
@@ -67,13 +77,12 @@ public class ViewManager {
|
||||
this.stage = stage;
|
||||
Parent root = FXMLLoader.load(getClass().getResource("/views/StartScreenView.fxml"));
|
||||
stage.setTitle("Party Parrots At Sea");
|
||||
|
||||
JFXDecorator decorator = new JFXDecorator(stage, root, false, true, true);
|
||||
decorator.setCustomMaximize(true);
|
||||
decorator.applyCss();
|
||||
decorator.getStylesheets()
|
||||
.add(getClass().getResource("/css/Master.css").toExternalForm());
|
||||
gameClient = new GameClient(decorator);
|
||||
gameClient = new GameClient();
|
||||
setDecorator(decorator);
|
||||
|
||||
stage.getIcons().add(new Image(getClass().getResourceAsStream("/PP.png")));
|
||||
@@ -267,16 +276,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) {
|
||||
@@ -303,7 +305,7 @@ public class ViewManager {
|
||||
Stage stage = new Stage();
|
||||
initialStartView(stage);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
logger.warn("Could not go to start view");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -342,8 +344,9 @@ public class ViewManager {
|
||||
logger.error("Could not load lobby view");
|
||||
}
|
||||
|
||||
LobbyController lobbyController = loader.getController();
|
||||
|
||||
if (disableReadyButton) {
|
||||
LobbyController lobbyController = loader.getController();
|
||||
lobbyController.disableReadyButton();
|
||||
}
|
||||
|
||||
@@ -355,7 +358,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
|
||||
@@ -377,8 +379,8 @@ public class ViewManager {
|
||||
scene.setOnKeyPressed(gameClient::keyPressed);
|
||||
scene.setOnKeyReleased(gameClient::keyReleased);
|
||||
|
||||
stage.setMinHeight(500);
|
||||
stage.setMinWidth(800);
|
||||
stage.setMinHeight(800);
|
||||
stage.setMinWidth(1200);
|
||||
stage.setTitle("Party Parrots At Sea");
|
||||
stage.getIcons().add(new Image(getClass().getResourceAsStream("/PP.png")));
|
||||
stage.setOnCloseRequest(e -> closeAll());
|
||||
@@ -400,6 +402,16 @@ 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);
|
||||
Platform.runLater(() -> {
|
||||
bar.enqueue(new JFXSnackbar.SnackbarEvent(msg));
|
||||
});
|
||||
}
|
||||
|
||||
public Stage getStage() {
|
||||
return stage;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
package seng302.visualiser.controllers.cells;
|
||||
|
||||
import java.util.Arrays;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.DoubleProperty;
|
||||
import javafx.beans.property.ReadOnlyDoubleWrapper;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.geometry.Point3D;
|
||||
import javafx.scene.Camera;
|
||||
import javafx.scene.Group;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.PerspectiveCamera;
|
||||
import javafx.scene.SceneAntialiasing;
|
||||
import javafx.scene.SubScene;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.scene.transform.Rotate;
|
||||
import javafx.scene.transform.Transform;
|
||||
import javafx.scene.transform.Translate;
|
||||
import seng302.model.ClientYacht;
|
||||
import seng302.visualiser.cameras.ChaseCamera;
|
||||
import seng302.visualiser.fxObjects.assets_3D.Model;
|
||||
import seng302.visualiser.fxObjects.assets_3D.ModelFactory;
|
||||
|
||||
public class WindCell {
|
||||
|
||||
//--------FXML BEGIN--------//
|
||||
@FXML
|
||||
private Pane windPane;
|
||||
//---------FXML END---------//
|
||||
|
||||
private final double FOV = 60;
|
||||
private final double DEFAULT_CAMERA_X = 0;
|
||||
private final double DEFAULT_CAMERA_Y = 50;
|
||||
|
||||
private Group root3D;
|
||||
private SubScene view;
|
||||
private Group gameObjects;
|
||||
|
||||
private ChaseCamera chaseCam;
|
||||
|
||||
private ClientYacht playerYacht;
|
||||
|
||||
// Cameras
|
||||
private PerspectiveCamera camera = null;
|
||||
|
||||
private Model windArrowModel;
|
||||
private Boolean isChaseCam;
|
||||
|
||||
/**
|
||||
* Initialise WindCell fxml and load 3D wind arrow into a group.
|
||||
*/
|
||||
public void init(ClientYacht playerYacht, ReadOnlyDoubleWrapper windDirection) {
|
||||
|
||||
this.playerYacht = playerYacht;
|
||||
camera = new PerspectiveCamera();
|
||||
camera.setFarClip(1000);
|
||||
camera.setNearClip(0.1);
|
||||
camera.setFieldOfView(60);
|
||||
initialiseWindView();
|
||||
|
||||
for (DoubleProperty o : Arrays.asList(playerYacht.getHeadingProperty(), windDirection)) {
|
||||
o.addListener((obs, oldValue, newValue) -> {
|
||||
Platform.runLater(() -> {
|
||||
if (isChaseCam) {
|
||||
camera.getTransforms().clear();
|
||||
for (Transform t : chaseCam.getTransforms()) {
|
||||
if (t instanceof Rotate) {
|
||||
camera.getTransforms().add(t);
|
||||
}
|
||||
}
|
||||
this.camera.getTransforms().addAll(
|
||||
new Translate(-55, -60, 0)
|
||||
);
|
||||
}
|
||||
|
||||
windArrowModel.getAssets().getTransforms().clear();
|
||||
windArrowModel.getAssets().getTransforms().addAll(
|
||||
new Rotate(windDirection.getValue(),
|
||||
new Point3D(0, 0, 1))
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void initialiseWindView() {
|
||||
gameObjects = new Group();
|
||||
windPane.getChildren().add(gameObjects);
|
||||
|
||||
root3D = new Group(camera, gameObjects);
|
||||
view = new SubScene(
|
||||
root3D, 110, 120, true, SceneAntialiasing.BALANCED
|
||||
);
|
||||
view.setCamera(camera);
|
||||
|
||||
windArrowModel = ModelFactory.makeWindArrow();
|
||||
|
||||
gameObjects.getChildren().addAll(
|
||||
windArrowModel.getAssets()
|
||||
);
|
||||
}
|
||||
|
||||
public Node getAssets() {
|
||||
return view;
|
||||
}
|
||||
|
||||
|
||||
public void updateCameraTransforms(Camera camera) {
|
||||
this.camera.getTransforms().clear();
|
||||
|
||||
for (Transform transform : camera.getTransforms()) {
|
||||
if (!(transform instanceof Translate)) {
|
||||
this.camera.getTransforms().add(transform);
|
||||
}
|
||||
}
|
||||
this.camera.getTransforms().addAll(
|
||||
new Translate(-55, -60, 0)
|
||||
);
|
||||
windArrowModel.getAssets().getTransforms().clear();
|
||||
}
|
||||
|
||||
public void setCamera(Camera camera) {
|
||||
isChaseCam = camera instanceof ChaseCamera;
|
||||
if (isChaseCam) {
|
||||
this.chaseCam = (ChaseCamera) camera;
|
||||
} else {
|
||||
this.chaseCam = null;
|
||||
}
|
||||
updateCameraTransforms(camera);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -27,7 +27,7 @@ public class KeyBindingDialogController implements Initializable {
|
||||
@FXML
|
||||
private Label closeLabel;
|
||||
@FXML
|
||||
private JFXButton zoomInbtn;
|
||||
private JFXButton zoomInBtn;
|
||||
@FXML
|
||||
private JFXButton zoomOutBtn;
|
||||
@FXML
|
||||
@@ -43,6 +43,8 @@ public class KeyBindingDialogController implements Initializable {
|
||||
@FXML
|
||||
private JFXButton resetBtn;
|
||||
@FXML
|
||||
private JFXButton confirmBtn;
|
||||
@FXML
|
||||
private Label upwindLabel;
|
||||
@FXML
|
||||
private Label downwindLabel;
|
||||
@@ -70,7 +72,7 @@ public class KeyBindingDialogController implements Initializable {
|
||||
gameKeyBind = GameKeyBind.getInstance();
|
||||
buttons = new ArrayList<>();
|
||||
Collections.addAll(buttons,
|
||||
zoomInbtn, zoomOutBtn, vmgBtn, sailInOutBtn, tackGybeBtn, upwindBtn, downwindBtn,
|
||||
zoomInBtn, zoomOutBtn, vmgBtn, sailInOutBtn, tackGybeBtn, upwindBtn, downwindBtn,
|
||||
viewButton, rightButton, leftButton, forwardButton, backwardButton);
|
||||
bindButtonWithAction();
|
||||
loadKeyBind();
|
||||
@@ -91,6 +93,7 @@ public class KeyBindingDialogController implements Initializable {
|
||||
});
|
||||
|
||||
closeLabel.setOnMouseClicked(event -> ViewManager.getInstance().closeKeyBindingDialog());
|
||||
confirmBtn.setOnMouseClicked(event -> ViewManager.getInstance().closeKeyBindingDialog());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
@@ -26,20 +28,46 @@ public class ServerCreationController implements Initializable {
|
||||
@FXML
|
||||
private JFXSlider maxPlayersSlider;
|
||||
@FXML
|
||||
private Label maxPlayersLabel;
|
||||
@FXML
|
||||
private JFXButton submitBtn;
|
||||
@FXML
|
||||
private Label closeLabel;
|
||||
@FXML
|
||||
private Label maxPlayersLabel;
|
||||
@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;
|
||||
//---------FXML END---------//
|
||||
|
||||
private MapMaker mapMaker = MapMaker.getInstance();
|
||||
|
||||
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,6 +82,19 @@ public class ServerCreationController implements Initializable {
|
||||
validateServerSettings();
|
||||
});
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
@@ -74,8 +115,14 @@ public class ServerCreationController implements Initializable {
|
||||
*/
|
||||
private void createServer() {
|
||||
ServerDescription serverDescription = ViewManager.getInstance().getGameClient()
|
||||
.runAsHost("localhost", 4941, serverName.getText(), (int) maxPlayersSlider
|
||||
.getValue());
|
||||
.runAsHost(serverName.getText(), (int) maxPlayersSlider
|
||||
.getValue(), mapMaker.getCurrentRacePath(), (int) legsSlider.getValue(), pickupsCheckBox.isSelected());
|
||||
|
||||
if (serverDescription == null){
|
||||
ViewManager.getInstance().getGameClient().getServerThread().closeSocket();
|
||||
ViewManager.getInstance().getGameClient().stopGame();
|
||||
return;
|
||||
}
|
||||
|
||||
ViewManager.getInstance().setProperty("serverName", serverDescription.getName());
|
||||
ViewManager.getInstance().setProperty("mapName", serverDescription.getMapName());
|
||||
@@ -86,13 +133,39 @@ 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("Only %.0f players are allowed into the game", 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;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
package seng302.visualiser.controllers.dialogs;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXTextArea;
|
||||
import java.net.URL;
|
||||
import java.util.ResourceBundle;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.Initializable;
|
||||
import javafx.geometry.Point3D;
|
||||
import javafx.scene.Group;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.TextArea;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.scene.transform.Rotate;
|
||||
import javafx.scene.transform.Scale;
|
||||
import javafx.scene.transform.Translate;
|
||||
import seng302.utilities.Sounds;
|
||||
import seng302.visualiser.controllers.LobbyController;
|
||||
import seng302.visualiser.fxObjects.assets_3D.ModelFactory;
|
||||
import seng302.visualiser.fxObjects.assets_3D.ModelType;
|
||||
|
||||
/**
|
||||
* Created by wmu16 on 28/09/17.
|
||||
*/
|
||||
public class TokenInfoDialogController implements Initializable {
|
||||
|
||||
@FXML
|
||||
private Label headerLabel;
|
||||
@FXML
|
||||
private TextArea contentText;
|
||||
@FXML
|
||||
private Pane tokenPane;
|
||||
@FXML
|
||||
private Button optionButton;
|
||||
|
||||
private LobbyController lobbyController;
|
||||
|
||||
@Override
|
||||
public void initialize(URL location, ResourceBundle resources) {
|
||||
optionButton.setOnMouseReleased(event -> {
|
||||
Sounds.playButtonClick();
|
||||
lobbyController.closeTokenInfoDialog();
|
||||
});
|
||||
|
||||
contentText.setEditable(false);
|
||||
|
||||
}
|
||||
|
||||
|
||||
public void setContent(String content) {
|
||||
contentText.setText(content);
|
||||
}
|
||||
|
||||
public void setHeader(String header) {
|
||||
this.headerLabel.setText(header);
|
||||
}
|
||||
|
||||
public void setToken(ModelType token) {
|
||||
tokenPane.getChildren().clear();
|
||||
|
||||
Group tokenObject = ModelFactory.importModel(token).getAssets();
|
||||
|
||||
tokenObject.getTransforms().addAll(
|
||||
new Translate(138 / 2, 138 / 2, 0),
|
||||
new Scale(20, 20, 20));
|
||||
|
||||
if (token == ModelType.WIND_WALKER_PICKUP) {
|
||||
tokenObject.getTransforms().addAll(
|
||||
new Rotate(-70, new Point3D(1, 0, 0)),
|
||||
new Translate(0, 2, 0)
|
||||
);
|
||||
} else if (token == ModelType.RANDOM_PICKUP) {
|
||||
tokenObject.getTransforms().addAll(
|
||||
new Rotate(-90, new Point3D(1, 0, 0)),
|
||||
new Translate(0, 0, 1)
|
||||
);
|
||||
}
|
||||
|
||||
tokenPane.getChildren().add(tokenObject);
|
||||
}
|
||||
|
||||
public void setParentController(LobbyController lobbyController) {
|
||||
this.lobbyController = lobbyController;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -29,11 +29,11 @@ public class MarkArrowFactory {
|
||||
STARBOARD,
|
||||
}
|
||||
|
||||
public static final double MARK_ARROW_SEPARATION = 15;
|
||||
public static final double ARROW_LENGTH = 75;
|
||||
public static final double ARROW_HEAD_DEPTH = 10;
|
||||
public static final double ARROW_HEAD_WIDTH = 6;
|
||||
public static final double STROKE_WIDTH = 3;
|
||||
public static final double MARK_ARROW_SEPARATION = 8;
|
||||
public static final double ARROW_LENGTH = 20;
|
||||
public static final double ARROW_HEAD_DEPTH = 5;
|
||||
public static final double ARROW_HEAD_WIDTH = 3;
|
||||
public static final double STROKE_WIDTH = 1;
|
||||
|
||||
public static Model constructEntryArrow3D (
|
||||
RoundingSide roundingSide, double angle, ModelType type) {
|
||||
@@ -106,7 +106,7 @@ public class MarkArrowFactory {
|
||||
Arc roundSection = new Arc(
|
||||
0, 0, MARK_ARROW_SEPARATION, MARK_ARROW_SEPARATION,
|
||||
//Where to start drawing arc from
|
||||
(roundingSide == RoundingSide.PORT ? 0 : angleOfEntry),
|
||||
(roundingSide == RoundingSide.PORT ? 180 + angleOfEntry : angleOfEntry),
|
||||
//Which way to go around the mark. (clockwise vs anticlockwise)
|
||||
roundingSide == RoundingSide.PORT ? Math.abs(angleOfExit - angleOfEntry) : -Math.abs(angleOfEntry - angleOfExit)
|
||||
);
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
package seng302.visualiser.fxObjects;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import javafx.scene.Group;
|
||||
import seng302.visualiser.fxObjects.MarkArrowFactory.RoundingSide;
|
||||
|
||||
/**
|
||||
* Created by cir27 on 28/09/17.
|
||||
*/
|
||||
public abstract class Marker extends Group{
|
||||
|
||||
protected List<Group> enterArrows = new ArrayList<>();
|
||||
protected List<Group> exitArrows = new ArrayList<>();
|
||||
protected int enterArrowIndex = 0;
|
||||
protected int exitArrowIndex = 0;
|
||||
|
||||
public abstract void addArrows(RoundingSide roundingSide, double entryAngle, double exitAngle);
|
||||
/**
|
||||
* Shows the next EnterArrow. Does nothing if there are no more enter arrows. Other arrows become hidden.
|
||||
*/
|
||||
public void showNextEnterArrow() {
|
||||
showArrow(enterArrows, enterArrowIndex);
|
||||
enterArrowIndex++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the next ExitArrow. Does nothing if there are no more enter arrows. Other arrows become hidden.
|
||||
*/
|
||||
public void showNextExitArrow() {
|
||||
showArrow(exitArrows, exitArrowIndex);
|
||||
exitArrowIndex++;
|
||||
}
|
||||
|
||||
protected abstract void showArrow(List<Group> arrowList, int arrowListIndex);
|
||||
|
||||
public abstract void hideAllArrows();
|
||||
}
|
||||
@@ -1,230 +0,0 @@
|
||||
package seng302.visualiser.fxObjects.assets_2D;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.scene.CacheHint;
|
||||
import javafx.scene.Group;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.paint.Paint;
|
||||
import javafx.scene.shape.Rectangle;
|
||||
import javafx.scene.text.Text;
|
||||
|
||||
/**
|
||||
* Grouping of string objects over a semi transparent background.
|
||||
*/
|
||||
public class AnnotationBox extends Group {
|
||||
|
||||
@FunctionalInterface
|
||||
public interface AnnotationFormatter<T> {
|
||||
String transformString (T input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Class stores a text object and relationship for updating the text object if needed
|
||||
*
|
||||
* @param <T> The type of observable value passed to the annotation, if there is one.
|
||||
*/
|
||||
public class Annotation<T> {
|
||||
private Text text;
|
||||
private ObservableValue<T> source;
|
||||
private AnnotationFormatter<T> format;
|
||||
|
||||
/**
|
||||
* Constructor for observing annotation
|
||||
* @param textObject the javaFX text object the annotation is displayed in
|
||||
* @param source observable value that the annotation is taken from
|
||||
* @param formatter interface describing how to format the source data if needed
|
||||
*/
|
||||
public Annotation (Text textObject, ObservableValue<T> source, AnnotationFormatter<T> formatter) {
|
||||
this.text = textObject;
|
||||
this.source = source;
|
||||
this.format = formatter;
|
||||
source.addListener((obs, oldVal, newVal) ->
|
||||
Platform.runLater(() -> text.setText(format.transformString(newVal)))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for a static annotation
|
||||
* @param textObject the javaFX text object the annotation is displayed in
|
||||
* @param annotationText the static value of the test object
|
||||
*/
|
||||
public Annotation (Text textObject, String annotationText) {
|
||||
textObject.setText(annotationText);
|
||||
text = textObject;
|
||||
}
|
||||
|
||||
private Text getText () {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
//Text offset constants
|
||||
private static final double X_OFFSET_TEXT = 20d;
|
||||
private static final double Y_OFFSET_TEXT_INIT = -35d;
|
||||
private static final double Y_OFFSET_PER_TEXT = 12d;
|
||||
//Background constants
|
||||
private static final double TEXT_BUFFER = 3;
|
||||
private static final double BACKGROUND_X = X_OFFSET_TEXT - TEXT_BUFFER;
|
||||
private static final double BACKGROUND_Y = Y_OFFSET_TEXT_INIT - TEXT_BUFFER;
|
||||
private static final double BACKGROUND_H_PER_TEXT = 9.5d;
|
||||
private static final double BACKGROUND_ARC_SIZE = 10;
|
||||
|
||||
private int visibleAnnotations = 0;
|
||||
private double backgroundWidth = 145d;
|
||||
|
||||
private Rectangle background = new Rectangle();
|
||||
private Paint theme = Color.BLACK;
|
||||
|
||||
private Map<String, Annotation> annotationsByName = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Creates an empty annotation box. The box is offset from (0,0) by (17, -38).
|
||||
*/
|
||||
public AnnotationBox() {
|
||||
this.setCache(true);
|
||||
background.setX(BACKGROUND_X);
|
||||
background.setY(BACKGROUND_Y);
|
||||
background.setWidth(backgroundWidth);
|
||||
background.setHeight(Math.abs(BACKGROUND_X) + TEXT_BUFFER + BACKGROUND_H_PER_TEXT * 4);
|
||||
background.setArcHeight(BACKGROUND_ARC_SIZE);
|
||||
background.setArcWidth(BACKGROUND_ARC_SIZE);
|
||||
background.setFill(new Color(1, 1, 1, 0.75));
|
||||
background.setStroke(theme);
|
||||
background.setStrokeWidth(2);
|
||||
background.setCache(true);
|
||||
background.setCacheHint(CacheHint.SCALE);
|
||||
this.getChildren().add(background);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an annotation to the box. Use the name to reference the annotation for removal or\
|
||||
* changing visibility.
|
||||
* @param annotationName the name of the annotation.
|
||||
* @param annotation the annotation.
|
||||
*/
|
||||
public void addAnnotation (String annotationName, Annotation annotation) {
|
||||
annotationsByName.put(annotationName, annotation);
|
||||
Platform.runLater(() -> {
|
||||
this.getChildren().add(annotation.getText());
|
||||
visibleAnnotations++;
|
||||
update();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an annotation with a constant text.
|
||||
* @param annotationName The name of the annotation. Will be used to reference it later.
|
||||
* @param annotationText The desired text.
|
||||
*/
|
||||
public void addAnnotation (String annotationName, String annotationText) {
|
||||
Text text = getTextObject();
|
||||
addAnnotation(annotationName, new Annotation(text, annotationText));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an annotation with the given name. The annotation will contain the value of the given
|
||||
* ObservableValue. The formatter should return a String and takes an object of the same type as
|
||||
* the ObservableValue as a parameter. The String is how you want the annotation to look.
|
||||
* @param annotationName The annotation name.
|
||||
* @param observable The observable value the annotation will display.
|
||||
* @param formatter A formatting function for the observable value.
|
||||
* @param <E> The type of ObservableValue.
|
||||
*/
|
||||
public <E> void addAnnotation (String annotationName, ObservableValue<E> observable,
|
||||
AnnotationFormatter<E> formatter) {
|
||||
Text newText = getTextObject();
|
||||
addAnnotation(annotationName, new Annotation<>(newText, observable, formatter));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the visibility of the annotation with the given name if it exists.
|
||||
* @param annotationName The name of the annotation
|
||||
* @param visibility the desired visibility
|
||||
*/
|
||||
public void setAnnotationVisibility (String annotationName, boolean visibility) {
|
||||
if (annotationsByName.containsKey(annotationName)) {
|
||||
Text textField = annotationsByName.get(annotationName).text;
|
||||
boolean currentState = textField.visibleProperty().get();
|
||||
if (visibility != currentState) {
|
||||
if (visibility)
|
||||
visibleAnnotations++;
|
||||
else
|
||||
visibleAnnotations--;
|
||||
}
|
||||
textField.setVisible(visibility);
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the annotation with the given name if it exits.
|
||||
* @param annotationName The name given when the annotation was created.
|
||||
*/
|
||||
public void removeAnnotation (String annotationName) {
|
||||
if (annotationName.contains(annotationName)) {
|
||||
Platform.runLater(() -> {
|
||||
this.getChildren().remove(annotationsByName.remove(annotationName).getText());
|
||||
visibleAnnotations--;
|
||||
update();
|
||||
});
|
||||
annotationsByName.remove(annotationName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the annotation.
|
||||
* @param x x location
|
||||
* @param y y location
|
||||
*/
|
||||
public void setLocation (double x, double y) {
|
||||
Platform.runLater(()-> this.relocate(x + BACKGROUND_X, y + BACKGROUND_Y));
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the width of the annotation box. Default is 145.
|
||||
* @param width new width.
|
||||
*/
|
||||
public void setWidth (double width) {
|
||||
backgroundWidth = width;
|
||||
Platform.runLater(() -> background.setWidth(backgroundWidth));
|
||||
}
|
||||
|
||||
private void update () {
|
||||
background.setVisible(visibleAnnotations != 0);
|
||||
background.setHeight(Math.abs(BACKGROUND_X) + TEXT_BUFFER + BACKGROUND_H_PER_TEXT * visibleAnnotations);
|
||||
for (int i = 1; i <= visibleAnnotations; i++) {
|
||||
Text text = (Text) this.getChildren().get(i);
|
||||
if (text.visibleProperty().get()) {
|
||||
text.setX(X_OFFSET_TEXT);
|
||||
text.setY(Y_OFFSET_TEXT_INIT + Y_OFFSET_PER_TEXT * i);
|
||||
// });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a text object for an annotation.
|
||||
* @return The text object
|
||||
*/
|
||||
private Text getTextObject() {
|
||||
Text text = new Text();
|
||||
text.setFill(theme);
|
||||
text.setStrokeWidth(2);
|
||||
// text.setCacheHint(CacheHint.QUALITY);
|
||||
text.setCache(true);
|
||||
return text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the colour of the annotation box's border and text colour.
|
||||
* @param value desired colour.
|
||||
*/
|
||||
public void setFill (Paint value) {
|
||||
theme = value;
|
||||
background.setStroke(theme);
|
||||
annotationsByName.forEach((name, annotation) -> annotation.getText().setFill(theme));
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
package seng302.visualiser.fxObjects.assets_2D;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.Group;
|
||||
@@ -8,18 +7,15 @@ import javafx.scene.paint.Color;
|
||||
import javafx.scene.paint.Paint;
|
||||
import javafx.scene.shape.Circle;
|
||||
import seng302.visualiser.fxObjects.MarkArrowFactory;
|
||||
import seng302.visualiser.fxObjects.Marker;
|
||||
|
||||
/**
|
||||
* Visual object for a mark. Contains a coloured circle and any specified arrows.
|
||||
*/
|
||||
public class Marker2D extends Group {
|
||||
public class Marker2D extends Marker {
|
||||
|
||||
private Circle mark = new Circle();
|
||||
private Paint colour = Color.BLACK;
|
||||
private List<Group> enterArrows = new ArrayList<>();
|
||||
private List<Group> exitArrows = new ArrayList<>();
|
||||
private int enterArrowIndex = 0;
|
||||
private int exitArrowIndex = 0;
|
||||
|
||||
/**
|
||||
* Creates a new Marker containing only a circle. The default colour is black.
|
||||
@@ -28,8 +24,7 @@ public class Marker2D extends Group {
|
||||
mark.setRadius(5);
|
||||
mark.setCenterX(0);
|
||||
mark.setCenterY(0);
|
||||
Platform.runLater(() -> this.getChildren()
|
||||
.addAll(mark, new Group())); //Empty group placeholder or arrows.
|
||||
Platform.runLater(() -> this.getChildren().add(mark));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -80,15 +75,12 @@ public class Marker2D extends Group {
|
||||
exitArrowIndex++;
|
||||
}
|
||||
|
||||
private void showArrow(List<Group> arrowList, int arrowListIndex) {
|
||||
@Override
|
||||
protected void showArrow(List<Group> arrowList, int arrowListIndex) {
|
||||
if (arrowListIndex < arrowList.size()) {
|
||||
if (arrowListIndex == 1) {
|
||||
;
|
||||
}
|
||||
Platform.runLater(() -> {
|
||||
this.getChildren().remove(1);
|
||||
this.getChildren().add(arrowList.get(arrowListIndex));
|
||||
});
|
||||
Platform.runLater(() ->
|
||||
this.getChildren().setAll(mark, arrowList.get(arrowListIndex))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,10 +16,6 @@ public class Wake extends Group {
|
||||
|
||||
//The number of wakes
|
||||
private int numWakes = 8;
|
||||
//The total possible difference between the first wake and the last. Increasing/Decreasing this will make wakes fan out more/less.
|
||||
private final double MAX_DIFF = 75;
|
||||
//Increasing/decreasing this will alter the speed that wakes converge when the heading stop changing. Anything over about 1500 may cause oscillation.
|
||||
private final int UNIFICATION_SPEED = 45;
|
||||
|
||||
|
||||
private Arc[] arcs = new Arc[numWakes];
|
||||
@@ -69,34 +65,6 @@ public class Wake extends Group {
|
||||
rad += (14 / numWakes) + (velocity / 2.5);
|
||||
}
|
||||
});
|
||||
// } else {
|
||||
// rotations[0] = rotation;
|
||||
// ((Rotate) arcs[0].getTransforms().get(0)).setAngle(rotation);
|
||||
// for (int i = 1; i < numWakes; i++) {
|
||||
// double wakeSeparationRad = Math.toRadians(rotations[i - 1] - rotations[i]);
|
||||
// double shortestDistance = Math.atan2(
|
||||
// Math.sin(wakeSeparationRad),
|
||||
// Math.cos(wakeSeparationRad)
|
||||
// );
|
||||
// double distDeg = Math.toDegrees(shortestDistance);
|
||||
// if (rotationalVelocities[i - 1] < 0.01 && rotationalVelocities[i - 1] > -0.01) {
|
||||
// rotationalVelocities[i] = distDeg / UNIFICATION_SPEED * 2 * Math.log(Math.abs(distDeg) + 1) / Math.log(MAX_DIFF / numWakes);
|
||||
//
|
||||
// } else {
|
||||
// if (distDeg < (MAX_DIFF / numWakes)) {
|
||||
// rotationalVelocities[i] = distDeg / UNIFICATION_SPEED * Math.log(Math.abs(distDeg) + 1) / Math.log(MAX_DIFF / numWakes);
|
||||
// } else
|
||||
// rotationalVelocities[i] = rotationalVelocities[i - 1];
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// double rad = (14 / numWakes) + velocity;
|
||||
// for (Arc arc : arcs) {
|
||||
// arc.setRadiusX(rad);
|
||||
// arc.setRadiusY(rad);
|
||||
// rad += (14 / numWakes) + (velocity / 2.5);
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
package seng302.visualiser.fxObjects.assets_2D;
|
||||
|
||||
import javafx.scene.paint.Paint;
|
||||
import javafx.scene.shape.Polyline;
|
||||
import javafx.scene.shape.StrokeLineCap;
|
||||
import javafx.scene.shape.StrokeLineJoin;
|
||||
|
||||
/**
|
||||
* Created by cir27 on 5/09/17.
|
||||
*/
|
||||
public class WindArrow extends Polyline {
|
||||
public WindArrow(Paint fill) {
|
||||
this.getPoints().addAll(
|
||||
-10d, 15d,
|
||||
0d, 25d,
|
||||
0d, -25d,
|
||||
0d, 25d,
|
||||
10d, 15d
|
||||
);
|
||||
this.setStrokeLineCap(StrokeLineCap.ROUND);
|
||||
this.setStroke(fill);
|
||||
this.setStrokeWidth(5);
|
||||
this.setStrokeLineJoin(StrokeLineJoin.ROUND);
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,10 @@ public enum BoatMeshType {
|
||||
CATAMARAN("catamaran_hull.stl", "catamaran_mast.stl", 0.997, "catamaran_sail.stl",
|
||||
0.997, null, false, 1.0, 1.4, 2.0),
|
||||
PIRATE_SHIP("pirateship_hull.stl", "pirateship_mast.stl", -0.5415, "pirateship_mainsail.stl",
|
||||
-0.5415, "pirateship_frontsail.stl", true, 1.2, 1.6, 1.2);
|
||||
-0.5415, "pirateship_frontsail.stl", true, 1.2, 1.6, 1.2),
|
||||
DUCKY("ducky_hull.stl", "ducky_mast.stl", -2.18539, "ducky_sail.stl", -2.18539, "ducky_eyes.stl", false, 1.2, 1.1, 1.4),
|
||||
PARROT("parrot_hull.stl", null, 0, "parrot_features.stl", 0, "parrot_sail.stl", true, 1, 1, 1),
|
||||
WAKA("waka_hull.stl", "waka_mast.stl", 0, "waka_sail.stl", 0, null, true, 1.7, 0.5, 1.5);
|
||||
|
||||
final String hullFile, mastFile, sailFile, jibFile;
|
||||
final double mastOffset, sailOffset;
|
||||
@@ -19,7 +22,7 @@ public enum BoatMeshType {
|
||||
public final double accelerationMultiplier;
|
||||
public final double turnStep;
|
||||
final boolean fixedSail;
|
||||
final static BoatMeshType[] boatTypes = new BoatMeshType[]{DINGHY, CATAMARAN, PIRATE_SHIP};
|
||||
final static BoatMeshType[] boatTypes = new BoatMeshType[]{DINGHY, CATAMARAN, PIRATE_SHIP, DUCKY, PARROT, WAKA};
|
||||
|
||||
BoatMeshType(String hullFile, String mastFile, double mastOffset, String sailFile,
|
||||
double sailOffset, String jibFile, boolean fixedSail, double maxSpeedMultiplier, double accelerationMultiplier, double turnStep) {
|
||||
|
||||
@@ -60,7 +60,9 @@ public class BoatModel extends Model {
|
||||
*/
|
||||
public void changeColour(Color newColour) {
|
||||
changeColourChild(HULL_INDEX, newColour);
|
||||
changeColourChild(MAST_INDEX, newColour);
|
||||
if (meshType != BoatMeshType.PARROT) {
|
||||
changeColourChild(MAST_INDEX, newColour);
|
||||
}
|
||||
}
|
||||
|
||||
private void changeColourChild(int index, Color newColour) {
|
||||
|
||||
@@ -4,10 +4,14 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.ReadOnlyDoubleWrapper;
|
||||
import javafx.geometry.Point2D;
|
||||
import javafx.geometry.Point3D;
|
||||
import javafx.scene.Group;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.paint.PhongMaterial;
|
||||
import javafx.scene.shape.MeshView;
|
||||
import javafx.scene.transform.Rotate;
|
||||
import javafx.scene.transform.Translate;
|
||||
|
||||
/**
|
||||
* BoatGroup is a javafx group that by default contains a graphical objects for representing a 2
|
||||
@@ -31,6 +35,9 @@ public class BoatObject extends Group {
|
||||
private Boolean isSelected = false;
|
||||
private Rotate rotation = new Rotate(0, new Point3D(0,0,1));
|
||||
|
||||
// This stuff only matters to the players boat object.
|
||||
private MeshView markIndicator;
|
||||
private MeshView playerIndicator;
|
||||
private ReadOnlyDoubleWrapper rotationProperty;
|
||||
|
||||
private List<SelectedBoatListener> selectedBoatListenerListeners = new ArrayList<>();
|
||||
@@ -79,6 +86,19 @@ public class BoatObject extends Group {
|
||||
});
|
||||
}
|
||||
|
||||
public void updateMarkIndicator(Point2D markPoint) {
|
||||
Point2D boatLoc = new Point2D(this.getLayoutX(), this.getLayoutY());
|
||||
Double angle = Math.toDegrees(
|
||||
Math.atan2(boatLoc.getY() - markPoint.getY(), boatLoc.getX() - markPoint.getX())) - 90;
|
||||
|
||||
Double radius = 0.5;
|
||||
markIndicator.getTransforms().clear();
|
||||
markIndicator.getTransforms().addAll(
|
||||
new Rotate(angle, new Point3D(0, 0, 1)),
|
||||
new Translate(0, -radius, 0)
|
||||
);
|
||||
}
|
||||
|
||||
private Double normalizeHeading(double heading, double windDirection) {
|
||||
Double normalizedHeading = heading - windDirection;
|
||||
normalizedHeading = (double) Math.floorMod(normalizedHeading.longValue(), 360L);
|
||||
@@ -118,6 +138,26 @@ public class BoatObject extends Group {
|
||||
}
|
||||
}
|
||||
|
||||
public void setMarkIndicator(MeshView indicator) {
|
||||
this.markIndicator = indicator;
|
||||
this.getChildren().add(markIndicator);
|
||||
createPlayerIndicator();
|
||||
setIndicatorColor();
|
||||
}
|
||||
|
||||
private void createPlayerIndicator() {
|
||||
MeshView torus = ModelFactory.importSTL("player_circle.stl");
|
||||
playerIndicator = torus;
|
||||
this.getChildren().add(torus);
|
||||
}
|
||||
|
||||
public void setIndicatorColor() {
|
||||
Platform.runLater(() -> {
|
||||
markIndicator.setMaterial(new PhongMaterial(Color.DARKORANGE));
|
||||
playerIndicator.setMaterial(new PhongMaterial(colour));
|
||||
});
|
||||
}
|
||||
|
||||
public Group getWake () {
|
||||
return wake;
|
||||
}
|
||||
|
||||
@@ -1,22 +1,17 @@
|
||||
package seng302.visualiser.fxObjects.assets_3D;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.Group;
|
||||
import seng302.visualiser.fxObjects.MarkArrowFactory;
|
||||
import seng302.visualiser.fxObjects.MarkArrowFactory.RoundingSide;
|
||||
import seng302.visualiser.fxObjects.Marker;
|
||||
|
||||
/**
|
||||
* Visual object for a mark. Contains a coloured circle and any specified arrows.
|
||||
*/
|
||||
public class Marker3D extends Group {
|
||||
public class Marker3D extends Marker {
|
||||
|
||||
private Model mark;
|
||||
private List<Group> enterArrows = new ArrayList<>();
|
||||
private List<Group> exitArrows = new ArrayList<>();
|
||||
private int enterArrowIndex = 0;
|
||||
private int exitArrowIndex = 0;
|
||||
private ModelType markType;
|
||||
private ModelType arrowType;
|
||||
|
||||
@@ -60,23 +55,8 @@ public class Marker3D extends Group {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the next EnterArrow. Does nothing if there are no more enter arrows. Other arrows become hidden.
|
||||
*/
|
||||
public void showNextEnterArrow() {
|
||||
showArrow(enterArrows, enterArrowIndex);
|
||||
enterArrowIndex++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the next ExitArrow. Does nothing if there are no more enter arrows. Other arrows become hidden.
|
||||
*/
|
||||
public void showNextExitArrow() {
|
||||
showArrow(exitArrows, exitArrowIndex);
|
||||
exitArrowIndex++;
|
||||
}
|
||||
|
||||
private void showArrow(List<Group> arrowList, int arrowListIndex) {
|
||||
@Override
|
||||
protected void showArrow(List<Group> arrowList, int arrowListIndex) {
|
||||
if (arrowListIndex < arrowList.size()) {
|
||||
Platform.runLater(() ->
|
||||
this.getChildren().setAll(mark.getAssets(), arrowList.get(arrowListIndex))
|
||||
|
||||
@@ -7,7 +7,6 @@ import javafx.geometry.Point3D;
|
||||
import javafx.scene.AmbientLight;
|
||||
import javafx.scene.CacheHint;
|
||||
import javafx.scene.Group;
|
||||
import javafx.scene.PointLight;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.paint.PhongMaterial;
|
||||
import javafx.scene.shape.Circle;
|
||||
@@ -17,7 +16,6 @@ import javafx.scene.transform.Scale;
|
||||
import javafx.scene.transform.Translate;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Factory class for creating 3D models of boatTypes.
|
||||
*/
|
||||
@@ -81,30 +79,6 @@ public class ModelFactory {
|
||||
return bo;
|
||||
}
|
||||
|
||||
public static BoatModel boatRotatingView(BoatMeshType boatType, Color primaryColour) {
|
||||
Group boatAssets = getUnmodifiedBoatModel(boatType, primaryColour);
|
||||
boatAssets.getTransforms().addAll(
|
||||
new Scale(40, 40, 40),
|
||||
new Rotate(90, new Point3D(0,0,1)),
|
||||
new Rotate(90, new Point3D(0, 1, 0))
|
||||
);
|
||||
|
||||
final Rotate animationRotate = new Rotate(0, new Point3D(1,1,1));
|
||||
boatAssets.getTransforms().add(animationRotate);
|
||||
|
||||
return new BoatModel(boatAssets, new AnimationTimer() {
|
||||
|
||||
private double rotation = 0;
|
||||
private Rotate rotate = animationRotate;
|
||||
|
||||
@Override
|
||||
public void handle(long now) {
|
||||
rotation += 0.5;
|
||||
rotate.setAngle(rotation);
|
||||
}
|
||||
}, boatType);
|
||||
}
|
||||
|
||||
public static BoatModel boatGameView(BoatMeshType boatType, Color primaryColour) {
|
||||
Group boatAssets = getUnmodifiedBoatModel(boatType, primaryColour);
|
||||
boatAssets.getTransforms().setAll(
|
||||
@@ -116,27 +90,42 @@ public class ModelFactory {
|
||||
private static Group getUnmodifiedBoatModel(BoatMeshType boatType, Color primaryColour) {
|
||||
|
||||
Group boatAssets = new Group();
|
||||
MeshView hull = importSTL(boatType.hullFile);
|
||||
MeshView hull = importBoatSTL(boatType.hullFile);
|
||||
hull.setMaterial(new PhongMaterial(primaryColour));
|
||||
MeshView mast = importSTL(boatType.mastFile);
|
||||
mast.setMaterial(new PhongMaterial(primaryColour));
|
||||
MeshView sail = importSTL(boatType.sailFile);
|
||||
sail.setMaterial(new PhongMaterial(Color.WHITE));
|
||||
boatAssets.getChildren().add(hull);
|
||||
|
||||
if (boatType.mastFile != null) {
|
||||
MeshView mast = importBoatSTL(boatType.mastFile);
|
||||
mast.setMaterial(new PhongMaterial(primaryColour));
|
||||
boatAssets.getChildren().add(mast);
|
||||
} else {
|
||||
boatAssets.getChildren().add(new MeshView());
|
||||
}
|
||||
|
||||
MeshView sail = importBoatSTL(boatType.sailFile);
|
||||
sail.setMaterial(
|
||||
new PhongMaterial(boatType == BoatMeshType.PARROT ? Color.BLACK : Color.WHITE)
|
||||
);
|
||||
boatAssets.getChildren().add(sail);
|
||||
|
||||
if (boatType.jibFile != null) {
|
||||
MeshView jib = importSTL(boatType.jibFile);
|
||||
sail.setMaterial(new PhongMaterial(Color.WHITE));
|
||||
boatAssets.getChildren().addAll(hull, mast, sail, jib);
|
||||
} else {
|
||||
boatAssets.getChildren().addAll(hull, mast, sail);
|
||||
MeshView jib = importBoatSTL(boatType.jibFile);
|
||||
jib.setMaterial(
|
||||
new PhongMaterial(boatType == BoatMeshType.PARROT ? Color.DARKGRAY : Color.WHITE)
|
||||
);
|
||||
boatAssets.getChildren().add(jib);
|
||||
}
|
||||
|
||||
return boatAssets;
|
||||
}
|
||||
|
||||
private static MeshView importSTL(String fileName) {
|
||||
private static MeshView importBoatSTL(String fileName) {
|
||||
return importSTL("boatSTLs/" + fileName);
|
||||
}
|
||||
|
||||
public static MeshView importSTL(String fileName) {
|
||||
StlMeshImporter importer = new StlMeshImporter();
|
||||
importer.read(ModelFactory.class.getResource("/meshes/boatSTLs/" + fileName));
|
||||
importer.read(ModelFactory.class.getResource("/meshes/" + fileName));
|
||||
MeshView importedFile = new MeshView(importer.getImport());
|
||||
importedFile.setCache(true);
|
||||
importedFile.setCacheHint(CacheHint.SCALE_AND_ROTATE);
|
||||
@@ -155,6 +144,10 @@ public class ModelFactory {
|
||||
assets.setCacheHint(CacheHint.SCALE_AND_ROTATE);
|
||||
}
|
||||
switch (tokenType) {
|
||||
case PLAYER_IDENTIFIER_TORUS:
|
||||
return makeIdentifierTorus(assets);
|
||||
case NEXT_MARK_INDICATOR:
|
||||
return makeNextMarkIndicator(assets);
|
||||
case VELOCITY_PICKUP:
|
||||
case BUMPER_PICKUP:
|
||||
case RANDOM_PICKUP:
|
||||
@@ -189,6 +182,16 @@ public class ModelFactory {
|
||||
}
|
||||
}
|
||||
|
||||
private static Model makeIdentifierTorus(Group assets) {
|
||||
// assets.getChildren().add(new AmbientLight());
|
||||
return new Model(new Group(assets), null);
|
||||
}
|
||||
|
||||
private static Model makeNextMarkIndicator(Group assets) {
|
||||
// assets.getChildren().add(new AmbientLight());
|
||||
return new Model(new Group(assets), null);
|
||||
}
|
||||
|
||||
private static Model makeTokenPickup(Group assets) {
|
||||
Rotate animationRotate = new Rotate(0, new Point3D(0, 0, 1));
|
||||
assets.getTransforms().addAll(
|
||||
@@ -272,4 +275,31 @@ public class ModelFactory {
|
||||
);
|
||||
return new Model(new Group(assets), null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a 3D wind arrow.
|
||||
*
|
||||
* @return 3D wind arrow object
|
||||
*/
|
||||
public static Model makeWindArrow() {
|
||||
ColModelImporter importer = new ColModelImporter();
|
||||
importer.read(ModelFactory.class.getResource("/meshes/" + ModelType.WIND_ARROW.filename));
|
||||
Group assets = new Group(importer.getImport());
|
||||
assets.setCache(true);
|
||||
assets.setCacheHint(CacheHint.SCALE_AND_ROTATE);
|
||||
|
||||
Rotate animationRotate = new Rotate(0, new Point3D(0, 1, 0));
|
||||
assets.getTransforms().addAll(
|
||||
new Translate(0, 0, 0),
|
||||
new Scale(5, 5, 5),
|
||||
new Rotate(270, new Point3D(1, 0, 0)),
|
||||
animationRotate
|
||||
);
|
||||
|
||||
assets.getChildren().addAll(
|
||||
new AmbientLight()
|
||||
);
|
||||
|
||||
return new Model(new Group(assets), null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,12 @@ 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"),
|
||||
NEXT_MARK_INDICATOR("indicator_arrow.dae"),
|
||||
PLAYER_IDENTIFIER_TORUS("torus.dae"),
|
||||
WIND_ARROW("windFiles/arrow56.dae"); // change filename
|
||||
|
||||
final String filename;
|
||||
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
package seng302.visualiser.map;
|
||||
|
||||
/**
|
||||
* The Boundary class represents a rectangle territorial boundary on a map. It
|
||||
* contains four extremity double values(N, E, S, W). N and S are represented as
|
||||
* latitudes in radians. E and W are represented as longitudes in radians.
|
||||
*
|
||||
* Created by Haoming on 10/5/17
|
||||
*/
|
||||
public class Boundary {
|
||||
|
||||
private double northLat, eastLng, southLat, westLng;
|
||||
|
||||
public Boundary(double northLat, double eastLng, double southLat, double westLng) {
|
||||
this.northLat = northLat;
|
||||
this.eastLng = eastLng;
|
||||
this.southLat = southLat;
|
||||
this.westLng = westLng;
|
||||
}
|
||||
|
||||
double getCentreLat() {
|
||||
return (northLat + southLat) / 2;
|
||||
}
|
||||
|
||||
double getCentreLng() {
|
||||
return (eastLng + westLng) / 2;
|
||||
}
|
||||
|
||||
double getNorthLat() {
|
||||
return northLat;
|
||||
}
|
||||
|
||||
double getEastLng() {
|
||||
return eastLng;
|
||||
}
|
||||
|
||||
double getSouthLat() {
|
||||
return southLat;
|
||||
}
|
||||
|
||||
double getWestLng() {
|
||||
return westLng;
|
||||
}
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
package seng302.visualiser.map;
|
||||
|
||||
import java.net.URL;
|
||||
import javafx.geometry.Point2D;
|
||||
import javafx.scene.image.Image;
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
import seng302.model.GeoPoint;
|
||||
|
||||
/**
|
||||
* CanvasMap retrieves a map image with given geo boundary from Google Map server.
|
||||
* By passing a rectangle like geo boundary, it returns a map image with the
|
||||
* highest resolution. However, due to free quote account usage limit, the maximum
|
||||
* resolution is only 1280 * 1280.
|
||||
*
|
||||
* Created by Haoming on 15/5/2017
|
||||
*/
|
||||
public class CanvasMap {
|
||||
|
||||
private Boundary boundary;
|
||||
private long width, height; // desired image size
|
||||
private int zoom;
|
||||
|
||||
private String KEY = "AIzaSyC-5oOShMCY5Oy_9L7guYMPUPFHDMr37wE";
|
||||
|
||||
public CanvasMap(Boundary boundary) {
|
||||
this.boundary = boundary;
|
||||
calculateOptimalMapSize();
|
||||
}
|
||||
|
||||
public Image getMapImage() {
|
||||
try {
|
||||
URL url = new URL(getRequest());
|
||||
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
|
||||
|
||||
return new Image(connection.getInputStream());
|
||||
} catch (Exception e) {
|
||||
System.out.println("[CanvasMap] Exception");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private String getRequest() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("https://maps.googleapis.com/maps/api/staticmap?");
|
||||
sb.append(String.format("center=%f,%f", boundary.getCentreLat(), boundary.getCentreLng()));
|
||||
sb.append(String.format("&zoom=%d", zoom));
|
||||
sb.append(String.format("&size=%dx%d&scale=2", width, height));
|
||||
sb.append("&style=feature:all|element:labels|visibility:off"); // hide all labels on map
|
||||
// sb.append(String.format("&markers=%f,%f", boundary.getSouthLat(), boundary.getWestLng()));
|
||||
// sb.append(String.format("&key=%s", KEY));
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private void calculateOptimalMapSize() {
|
||||
for (int z = 20; z > 0; z--) {
|
||||
MapSize mapSize = getMapSize(z, boundary);
|
||||
zoom = z;
|
||||
width = mapSize.width;
|
||||
height = mapSize.height;
|
||||
// if map size is valid, exit the loop as we have the highest resolution
|
||||
if (mapSize.isValid()) break;
|
||||
}
|
||||
}
|
||||
|
||||
private MapSize getMapSize(int zoom, Boundary boundary) {
|
||||
double scale = Math.pow(2, zoom);
|
||||
GeoPoint geoSW = new GeoPoint(boundary.getSouthLat(), boundary.getWestLng());
|
||||
GeoPoint geoNE = new GeoPoint(boundary.getNorthLat(), boundary.getEastLng());
|
||||
Point2D pointSW = MercatorProjection.toMapPoint(geoSW);
|
||||
Point2D pointNE = MercatorProjection.toMapPoint(geoNE);
|
||||
return new MapSize(Math.abs(pointNE.getX() - pointSW.getX()) * scale,
|
||||
Math.abs(pointNE.getY() - pointSW.getY()) * scale);
|
||||
}
|
||||
|
||||
class MapSize {
|
||||
long width, height;
|
||||
|
||||
MapSize(double width, double height) {
|
||||
this.width = Math.round(width);
|
||||
this.height = Math.round(height);
|
||||
}
|
||||
|
||||
/**
|
||||
* Map size is valid when width and height are both less than 640 pixels
|
||||
* @return true if both dimensions are less than 640px
|
||||
*/
|
||||
boolean isValid() {
|
||||
return Math.max(width, height) <= 640;
|
||||
}
|
||||
}
|
||||
|
||||
public long getWidth() {
|
||||
return width;
|
||||
}
|
||||
|
||||
public long getHeight() {
|
||||
return height;
|
||||
}
|
||||
|
||||
public int getZoom() {
|
||||
return zoom;
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
package seng302.visualiser.map;
|
||||
|
||||
import javafx.geometry.Point2D;
|
||||
import seng302.model.GeoPoint;
|
||||
|
||||
/**
|
||||
* An utility class useful to convert between Geo locations and Mercator projection
|
||||
* planar coordinates.
|
||||
* Created by Haoming on 15/5/2017
|
||||
*/
|
||||
public class MercatorProjection {
|
||||
|
||||
private static final double MERCATOR_RANGE = 256;
|
||||
private static final double pixelsPerLngDegree = MERCATOR_RANGE / 360.0;
|
||||
private static final double pixelsPerLngRadian = MERCATOR_RANGE / (2 * Math.PI);
|
||||
|
||||
/**
|
||||
* A help function keeps the value in bound between -0.9999 and 0.9999.
|
||||
* @param value in bound value
|
||||
* @return the value in bound
|
||||
*/
|
||||
private static double bound(double value) {
|
||||
return Math.min(Math.max(value, -0.9999), 0.9999);
|
||||
}
|
||||
|
||||
/**
|
||||
* Projects a Geo Location (lat, lng) on a planar
|
||||
* @param geo GeoPoint (lat, lng) location to be projected
|
||||
* @return the projection Point2D (x, y) on planar
|
||||
*/
|
||||
public static Point2D toMapPoint(GeoPoint geo) {
|
||||
double x, y;
|
||||
Point2D origin = new Point2D(MERCATOR_RANGE / 2.0, MERCATOR_RANGE / 2.0);
|
||||
x = (origin.getX() + geo.getLng() * pixelsPerLngDegree);
|
||||
|
||||
// NOTE(appleton): Truncating to 0.9999 effectively limits latitude to
|
||||
// 89.189. This is about a third of a tile past the edge of the world tile.
|
||||
double sinY = bound(Math.sin(Math.toRadians(geo.getLat())));
|
||||
y = origin.getY() + 0.5 * Math.log((1 + sinY) / (1 - sinY)) * (-pixelsPerLngRadian);
|
||||
return new Point2D(x, y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the planar projection (x, y) back to Geo Location (lat, lng)
|
||||
* @param point Point2D (x, y) to be converted back
|
||||
* @return the original Geo location converted from the given projection point
|
||||
*/
|
||||
public static GeoPoint toMapGeo(Point2D point) {
|
||||
Point2D origin = new Point2D(MERCATOR_RANGE / 2.0, MERCATOR_RANGE / 2.0);
|
||||
double lng = (point.getX() - origin.getX()) / pixelsPerLngDegree;
|
||||
double latRadians = (point.getY() - origin.getY()) / (-pixelsPerLngRadian);
|
||||
double lat = Math.toDegrees(2 * Math.atan(Math.exp(latRadians)) - Math.PI / 2.0);
|
||||
return new GeoPoint(lat, lng);
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package seng302.visualiser.map;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.ResourceBundle;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.Initializable;
|
||||
import javafx.scene.canvas.Canvas;
|
||||
import javafx.scene.canvas.GraphicsContext;
|
||||
|
||||
public class TestMapController implements Initializable{
|
||||
|
||||
@FXML
|
||||
private Canvas mapCanvas;
|
||||
|
||||
@Override
|
||||
public void initialize(URL location, ResourceBundle resources) {
|
||||
GraphicsContext gc = mapCanvas.getGraphicsContext2D();
|
||||
Boundary bound = new Boundary(57.662943, 11.848501, 57.673945, 11.824966);
|
||||
CanvasMap canvasMap = new CanvasMap(bound);
|
||||
gc.drawImage(canvasMap.getMapImage(), 0, 0, canvasMap.getWidth(), canvasMap.getHeight());
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
<?xml version="1.0" ?>
|
||||
|
||||
<configurations>
|
||||
<race-name>AC35</race-name>
|
||||
<race-size>6</race-size>
|
||||
<time-scale>10.0</time-scale>
|
||||
<windDir-direction>135</windDir-direction>
|
||||
</configurations>
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
<?xml version="1.0" ?>
|
||||
|
||||
<markers>
|
||||
<marks>
|
||||
<gate>
|
||||
<name type="start-line">Start</name>
|
||||
<mark>
|
||||
<name>Start1</name>
|
||||
<latitude>57.6703330</latitude>
|
||||
<longitude>11.8278330</longitude>
|
||||
<id>122</id>
|
||||
</mark>
|
||||
<mark>
|
||||
<name>Start2</name>
|
||||
<latitude>57.6703330</latitude>
|
||||
<longitude>11.8271333</longitude>
|
||||
<id>123</id>
|
||||
</mark>
|
||||
</gate>
|
||||
<mark>
|
||||
<name>Mid Mark</name>
|
||||
<latitude>57.6675700</latitude>
|
||||
<longitude>11.8359880</longitude>
|
||||
<id>131</id>
|
||||
</mark>
|
||||
<gate>
|
||||
<name>Leeward Gate</name>
|
||||
<mark>
|
||||
<name>Leeward Gate1</name>
|
||||
<latitude>57.6708220</latitude>
|
||||
<longitude>11.8433900</longitude>
|
||||
<id>124</id>
|
||||
</mark>
|
||||
<mark>
|
||||
<name>Leeward Gate2</name>
|
||||
<latitude>57.6711220</latitude>
|
||||
<longitude>11.8436900</longitude>
|
||||
<id>125</id>
|
||||
</mark>
|
||||
</gate>
|
||||
<gate>
|
||||
<name>Windward Gate</name>
|
||||
<mark>
|
||||
<name>Windward Gate1</name>
|
||||
<latitude>57.6650170</latitude>
|
||||
<longitude>11.8279170</longitude>
|
||||
<id>126</id>
|
||||
</mark>
|
||||
<mark>
|
||||
<name>Windward Gate2</name>
|
||||
<latitude>57.6653170</latitude>
|
||||
<longitude>11.8282170</longitude>
|
||||
<id>127</id>
|
||||
</mark>
|
||||
</gate>
|
||||
<gate type="finish-line">
|
||||
<name>Finish</name>
|
||||
<mark>
|
||||
<name>Finish1</name>
|
||||
<latitude>57.6715240</latitude>
|
||||
<longitude>11.8444950</longitude>
|
||||
<id>128</id>
|
||||
</mark>
|
||||
<mark>
|
||||
<name>Finish2</name>
|
||||
<latitude>57.6718240</latitude>
|
||||
<longitude>11.8447950</longitude>
|
||||
<id>129</id>
|
||||
</mark>
|
||||
</gate>
|
||||
</marks>
|
||||
<order>
|
||||
<one>Start</one>
|
||||
<two>Mid Mark</two>
|
||||
<three>Leeward Gate</three>
|
||||
<four>Windward Gate</four>
|
||||
<five>Leeward Gate</five>
|
||||
<six>Finish</six>
|
||||
</order>
|
||||
</markers>
|
||||
@@ -1,72 +0,0 @@
|
||||
<?xml version="1.0" ?>
|
||||
|
||||
<course>
|
||||
<marks>
|
||||
<gate>
|
||||
<name type="start-line">Start</name>
|
||||
<mark>
|
||||
<name>Start1</name>
|
||||
<latitude>32.296577</latitude>
|
||||
<longitude>-64.854304</longitude>
|
||||
</mark>
|
||||
<mark>
|
||||
<name>Start2</name>
|
||||
<latitude>32.293771</latitude>
|
||||
<longitude>-64.855242</longitude>
|
||||
</mark>
|
||||
</gate>
|
||||
<mark>
|
||||
<name>Mid Mark</name>
|
||||
<latitude>32.293039</latitude>
|
||||
<longitude>-64.843983</longitude>
|
||||
</mark>
|
||||
<gate>
|
||||
<name>Leeward Gate</name>
|
||||
<mark>
|
||||
<name>Leeward Gate1</name>
|
||||
<latitude>32.284680</latitude>
|
||||
<longitude>-64.850045</longitude>
|
||||
</mark>
|
||||
<mark>
|
||||
<name>Leeward Gate2</name>
|
||||
<latitude>32.280164</latitude>
|
||||
<longitude>-64.847591</longitude>
|
||||
</mark>
|
||||
</gate>
|
||||
<gate>
|
||||
<name>Windward Gate</name>
|
||||
<mark>
|
||||
<name>Windward Gate1</name>
|
||||
<latitude>32.309693</latitude>
|
||||
<longitude>-64.835249</longitude>
|
||||
</mark>
|
||||
<mark>
|
||||
<name>Windward Gate2</name>
|
||||
<latitude>32.308046</latitude>
|
||||
<longitude>-64.831785</longitude>
|
||||
</mark>
|
||||
</gate>
|
||||
<gate type="finish-line">
|
||||
<name>Finish</name>
|
||||
<mark>
|
||||
<name>Finish1</name>
|
||||
<latitude>32.317379</latitude>
|
||||
<longitude>-64.839291</longitude>
|
||||
</mark>
|
||||
<mark>
|
||||
<name>Finish2</name>
|
||||
<latitude>32.317257</latitude>
|
||||
<longitude>-64.836260</longitude>
|
||||
</mark>
|
||||
</gate>
|
||||
</marks>
|
||||
<order>
|
||||
<one>Start</one>
|
||||
<two>Mid Mark</two>
|
||||
<three>Leeward Gate</three>
|
||||
<four>Windward Gate</four>
|
||||
<five>Leeward Gate</five>
|
||||
<six>Finish</six>
|
||||
</order>
|
||||
</course>
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
<?xml version="1.0" ?>
|
||||
|
||||
<teams>
|
||||
<team>
|
||||
<name>Oracle Team USA</name>
|
||||
<alias>USA</alias>
|
||||
<currentVelocity>0.0</currentVelocity>
|
||||
<id>102</id>
|
||||
</team>
|
||||
<team>
|
||||
<name>Artemis Racing</name>
|
||||
<alias>ART</alias>
|
||||
<currentVelocity>0.0</currentVelocity>
|
||||
<id>101</id>
|
||||
</team>
|
||||
<team>
|
||||
<name>Emirates Team New Zealand</name>
|
||||
<alias>NZL</alias>
|
||||
<currentVelocity>0.0</currentVelocity>
|
||||
<id>103</id>
|
||||
</team>
|
||||
<team>
|
||||
<name>Land Rover BAR</name>
|
||||
<alias>BAR</alias>
|
||||
<currentVelocity>0.0</currentVelocity>
|
||||
<id>104</id>
|
||||
</team>
|
||||
<team>
|
||||
<name>SoftBank Team Japan</name>
|
||||
<alias>JAP</alias>
|
||||
<currentVelocity>0.0</currentVelocity>
|
||||
<id>105</id>
|
||||
</team>
|
||||
<team>
|
||||
<name>Groupama Team France</name>
|
||||
<alias>FRC</alias>
|
||||
<currentVelocity>0.0</currentVelocity>
|
||||
<id>106</id>
|
||||
</team>
|
||||
</teams>
|
||||
@@ -65,4 +65,12 @@
|
||||
/*-fx-background-repeat: no-repeat;*/
|
||||
/*-fx-background-size: cover;*/
|
||||
-fx-background-color: dodgerblue;
|
||||
}
|
||||
|
||||
.tokenView {
|
||||
-fx-cursor: hand;
|
||||
}
|
||||
|
||||
.tokenGridView StackPane {
|
||||
-fx-background-color: white;
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
#timerGrid{
|
||||
-fx-background-color: rgba(255, 255, 255, 0.6);
|
||||
-fx-effect: -fx-pp-dropshadow-light;
|
||||
-fx-background-radius: 5;
|
||||
}
|
||||
|
||||
GridPane .timer * {
|
||||
@@ -27,20 +28,24 @@ GridPane .timer * {
|
||||
#chatHistoryHolder {
|
||||
-fx-background-color: rgba(255, 255, 255, 0.6);
|
||||
-fx-effect: -fx-pp-dropshadow-light;
|
||||
-fx-background-radius: 5;
|
||||
}
|
||||
|
||||
#chatInputHolder {
|
||||
-fx-background-color: rgba(255, 255, 255, 0.6);
|
||||
-fx-effect: -fx-pp-dropshadow-light;
|
||||
-fx-background-radius: 5;
|
||||
}
|
||||
|
||||
#windGridPane {
|
||||
-fx-background-color: rgba(255, 255, 255, 0.6);
|
||||
-fx-effect: -fx-pp-dropshadow-light;
|
||||
-fx-background-radius: 5;
|
||||
}
|
||||
|
||||
#windHolder {
|
||||
-fx-background-color: rgba(255, 255, 255, 0.5);
|
||||
-fx-background-radius: 5;
|
||||
}
|
||||
|
||||
#chatSend {
|
||||
@@ -61,6 +66,25 @@ GridPane .timer * {
|
||||
-fx-background-color: transparent;
|
||||
}
|
||||
|
||||
#chatToggleButton {
|
||||
-fx-background-color: rgba(255, 255, 255, 0.6);
|
||||
-fx-background-radius: 5;
|
||||
-fx-min-width: 30;
|
||||
-fx-min-height: 30;
|
||||
}
|
||||
|
||||
#windImageView {
|
||||
-fx-image: url("/images/wind-180.png");
|
||||
}
|
||||
|
||||
#miniMapPane {
|
||||
-fx-background-color: rgba(255, 255, 255, 0.6);
|
||||
-fx-background-radius: 5;
|
||||
}
|
||||
|
||||
#miniMapButton {
|
||||
-fx-background-color: rgba(255, 255, 255, 0.6);
|
||||
-fx-background-radius: 5;
|
||||
-fx-min-width: 30;
|
||||
-fx-min-height: 30;
|
||||
}
|
||||
@@ -38,30 +38,31 @@
|
||||
-fx-font-size: 23px;
|
||||
}
|
||||
|
||||
#connectButton {
|
||||
#connectButton, #roomConnectButton, #directConnectButton, #autoSelectGame {
|
||||
-fx-background-color: -fx-pp-light-text-color; /* inverted */
|
||||
-fx-text-fill: -fx-pp-theme-color; /* inverted */
|
||||
-fx-font-size: 20px;
|
||||
-fx-pref-height: 65px;
|
||||
-fx-pref-height: 45px;
|
||||
-fx-effect: -fx-pp-dropshadow-dark;
|
||||
}
|
||||
|
||||
#connectButton:hover {
|
||||
-fx-font-size: 23px;
|
||||
#connectButton:hover, #roomConnectButton:hover, #directConnectButton:hover, #autoSelectGame:hover {
|
||||
-fx-font-size: 21px;
|
||||
}
|
||||
|
||||
#connectLabel, #serverPortNumber, #serverHostName {
|
||||
#connectLabel, #connectLabel1, #serverPortNumber, #roomNumber, #serverHostName {
|
||||
-fx-text-fill: -fx-pp-light-text-color;
|
||||
-fx-font-size: 18px;
|
||||
}
|
||||
|
||||
#serverHostName, #serverPortNumber {
|
||||
#serverHostName, #serverPortNumber, #roomNumber {
|
||||
-jfx-focus-color: -fx-pp-light-text-color;
|
||||
-jfx-unfocus-color: -fx-pp-light-text-color;
|
||||
-fx-prompt-text-fill: -fx-pp-light-text-color;
|
||||
}
|
||||
|
||||
|
||||
#serverHostName .error-label, #serverPortNumber .error-label {
|
||||
#serverHostName .error-label, #serverPortNumber .error-label, #roomNumber .error-label {
|
||||
-fx-font-size: 12px;
|
||||
-fx-text-fill: lightblue;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
.root {
|
||||
-fx-effect: -fx-pp-dropshadow-dark;
|
||||
-fx-background-radius: 5;
|
||||
-fx-padding: 10;
|
||||
}
|
||||
|
||||
#rootPane {
|
||||
-fx-background-image: url("/images/waves.png");
|
||||
|
||||
}
|
||||
|
||||
#headText {
|
||||
-fx-font-size: 52px;
|
||||
-fx-text-fill: rgb(30, 30, 30);
|
||||
-fx-effect: -fx-pp-dropshadow-dark;
|
||||
}
|
||||
|
||||
#subHeadLabel {
|
||||
-fx-text-fill: rgb(30, 30, 30);
|
||||
-fx-effect: -fx-pp-dropshadow-dark;
|
||||
-fx-font-size: 12px;
|
||||
}
|
||||
|
||||
.materialDesign-purple .arc {
|
||||
-fx-stroke: #ab47bc;
|
||||
}
|
||||
|
||||
.materialDesign-blue .arc {
|
||||
-fx-stroke: #2962ff;
|
||||
}
|
||||
|
||||
.materialDesign-cyan .arc {
|
||||
-fx-stroke: #00b8d4;
|
||||
}
|
||||
|
||||
.materialDesign-green .arc {
|
||||
-fx-stroke: #00c853;
|
||||
}
|
||||
|
||||
.materialDesign-yellow .arc {
|
||||
-fx-stroke: #ffd600;
|
||||
}
|
||||
|
||||
.materialDesign-orange .arc {
|
||||
-fx-stroke: #ff6d00;
|
||||
}
|
||||
|
||||
.materialDesign-red .arc {
|
||||
-fx-stroke: #d50000;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
.text-area {
|
||||
-fx-background-insets: 0;
|
||||
-fx-background-color: transparent, white, transparent, white;
|
||||
}
|
||||
|
||||
.text-area .content {
|
||||
-fx-background-color: transparent, white, transparent, white;
|
||||
}
|
||||
|
||||
.text-area:focused .content {
|
||||
-fx-background-color: transparent, white, transparent, white;
|
||||
}
|
||||
|
||||
.text-area:focused {
|
||||
-fx-highlight-fill: #7ecfff;
|
||||
}
|
||||
|
||||
.text-area .content {
|
||||
-fx-padding: 10px;
|
||||
-fx-text-fill: gray;
|
||||
-fx-highlight-fill: #7ecfff;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
#windPane {
|
||||
-fx-background-color: rgba(255, 255, 255, 0);
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
#maxPlayersGridPane VBox * {
|
||||
-fx-font-family: monospace !important;
|
||||
}
|
||||
|
||||
#submitBtn {
|
||||
-fx-background-color: -fx-pp-theme-color;
|
||||
-fx-text-fill: -fx-pp-light-text-color;
|
||||
-fx-font-size: 20px;
|
||||
-fx-effect: -fx-pp-dropshadow-dark;
|
||||
}
|
||||
|
||||
.jfx-rippler {
|
||||
-jfx-rippler-fill: -fx-pp-light-theme-color; /* set rippler color for button */
|
||||
}
|
||||
|
||||
#submitBtn:hover {
|
||||
-fx-font-size: 23px;
|
||||
-fx-background-color: -fx-pp-light-theme-color;
|
||||
}
|
||||
|
||||
#hostDialogHeader {
|
||||
-fx-font-size: 30px;
|
||||
-fx-text-fill: -fx-pp-dark-text-color;
|
||||
}
|
||||
|
||||
|
||||
#serverName {
|
||||
-jfx-focus-color: -fx-pp-dark-text-color;
|
||||
-jfx-unfocus-color: -fx-pp-dark-text-color;
|
||||
-fx-text-fill: -fx-pp-dark-text-color;
|
||||
-fx-prompt-text-fill: -fx-pp-dark-text-color;
|
||||
-fx-font-size: 16px;
|
||||
}
|
||||
|
||||
#maxPlayersLabel {
|
||||
-fx-text-fill: -fx-pp-dark-text-color;
|
||||
-fx-font-size: 16px;
|
||||
}
|
||||
|
||||
#maxPlayerPromptLabel {
|
||||
-fx-text-fill: -fx-pp-dark-text-color;
|
||||
-fx-font-size: 16px;
|
||||
}
|
||||
|
||||
.maxPlayers {
|
||||
-fx-font-size: 13px;
|
||||
}
|
||||
@@ -35,14 +35,14 @@ JFXToggleButton {
|
||||
-fx-text-fill: -fx-pp-theme-color;
|
||||
}
|
||||
|
||||
#resetBtn {
|
||||
#resetBtn, #confirmBtn {
|
||||
-fx-background-color: -fx-pp-theme-color;
|
||||
-fx-text-fill: -fx-pp-front-color;
|
||||
-fx-effect: -fx-pp-dropshadow-light;
|
||||
-fx-font-size: 18;
|
||||
}
|
||||
|
||||
#resetBtn:hover {
|
||||
#resetBtn:hover, #confirmBtn:hover {
|
||||
-fx-font-size: 20;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,14 @@
|
||||
-fx-font-family: monospace !important;
|
||||
}
|
||||
|
||||
.sliderLabel {
|
||||
-fx-text-fill: -fx-pp-dark-text-color;
|
||||
}
|
||||
|
||||
.error-label * {
|
||||
-fx-text-fill: red;
|
||||
}
|
||||
|
||||
#submitBtn {
|
||||
-fx-background-color: -fx-pp-theme-color;
|
||||
-fx-text-fill: -fx-pp-light-text-color;
|
||||
@@ -32,12 +40,7 @@
|
||||
-fx-font-size: 16px;
|
||||
}
|
||||
|
||||
#maxPlayersLabel {
|
||||
-fx-text-fill: -fx-pp-dark-text-color;
|
||||
-fx-font-size: 16px;
|
||||
}
|
||||
|
||||
#maxPlayerPromptLabel {
|
||||
.optionLabel {
|
||||
-fx-text-fill: -fx-pp-dark-text-color;
|
||||
-fx-font-size: 16px;
|
||||
}
|
||||
@@ -55,3 +58,8 @@
|
||||
-fx-text-fill: red;
|
||||
-fx-font-size: 33px;
|
||||
}
|
||||
|
||||
JFXCheckBox {
|
||||
-jfx-checked-color: -fx-pp-theme-color;
|
||||
-fx-text-fill: -fx-pp-dark-text-color;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
/* a separate file to dynamically change snackbar's color */
|
||||
.jfx-snackbar-toast {
|
||||
-fx-text-fill: red !important;
|
||||
-fx-text-fill: black !important;
|
||||
}
|
||||
|
Before Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 7.8 KiB |
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 9.1 KiB |
|
After Width: | Height: | Size: 9.4 KiB |
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 9.9 KiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 8.8 KiB |
|
After Width: | Height: | Size: 866 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 27 KiB |
@@ -0,0 +1,64 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RaceDefinition>
|
||||
|
||||
<CourseName> El Classico </CourseName>
|
||||
|
||||
<CentralLat> 57.6679590 </CentralLat>
|
||||
<CentralLng> 11.8503233 </CentralLng>
|
||||
|
||||
<MaxPlayers> 10 </MaxPlayers>
|
||||
|
||||
<Marks>
|
||||
<CompoundMark CompoundMarkID="1">
|
||||
<Mark Lat="57.670603" Lng="11.828262"/>
|
||||
<Mark Lat="57.669445" Lng="11.826413"/>
|
||||
</CompoundMark>
|
||||
<CompoundMark CompoundMarkID="2">
|
||||
<Mark Lat="57.6675700" Lng="11.8359880"/>
|
||||
</CompoundMark>
|
||||
<CompoundMark CompoundMarkID="3">
|
||||
<Mark Lat="57.6708220" Lng="11.8433900"/>
|
||||
<Mark Lat="57.671629" Lng="11.840951"/>
|
||||
</CompoundMark>
|
||||
<CompoundMark CompoundMarkID="4">
|
||||
<Mark Lat="57.664190" Lng="11.829576"/>
|
||||
<Mark Lat="57.665316" Lng="11.827184"/>
|
||||
</CompoundMark>
|
||||
<CompoundMark CompoundMarkID="5">
|
||||
<Mark Lat="57.672350" Lng="11.842535"/>
|
||||
<Mark Lat="57.6715240" Lng="11.8444950"/>
|
||||
</CompoundMark>
|
||||
</Marks>
|
||||
|
||||
<Course>
|
||||
<OpeningSegment>
|
||||
<Corner CompoundMarkID="1" Rounding="PS"/>
|
||||
<Corner CompoundMarkID="2" Rounding="P"/>
|
||||
</OpeningSegment>
|
||||
|
||||
<RepeatingSegment>
|
||||
<Corner CompoundMarkID="3" Rounding="SP"/>
|
||||
<Corner CompoundMarkID="4" Rounding="PS"/>
|
||||
</RepeatingSegment>
|
||||
|
||||
<ClosingSegment>
|
||||
<Corner CompoundMarkID="5" Rounding="PS"/>
|
||||
</ClosingSegment>
|
||||
</Course>
|
||||
|
||||
<CourseLimit>
|
||||
<Limit Lat="57.6739450" Lng="11.8417100" />
|
||||
<Limit Lat="57.6709520" Lng="11.8485010" />
|
||||
<Limit Lat="57.6690260" Lng="11.8472790" />
|
||||
<Limit Lat="57.6693140" Lng="11.8457610" />
|
||||
<Limit Lat="57.6665370" Lng="11.8432910" />
|
||||
<Limit Lat="57.6641400" Lng="11.8385840" />
|
||||
<Limit Lat="57.6629430" Lng="11.8332030" />
|
||||
<Limit Lat="57.6629480" Lng="11.8249660" />
|
||||
<Limit Lat="57.6686890" Lng="11.8250920" />
|
||||
<Limit Lat="57.6692230" Lng="11.8231430" />
|
||||
<Limit Lat="57.6725370" Lng="11.8272480" />
|
||||
<Limit Lat="57.6708220" Lng="11.8321340" />
|
||||
</CourseLimit>
|
||||
|
||||
</RaceDefinition>
|
||||
@@ -0,0 +1,79 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RaceDefinition>
|
||||
|
||||
<CourseName> HorseShoe </CourseName>
|
||||
|
||||
<CentralLat> -14.6457 </CentralLat>
|
||||
<CentralLng> 47.612855 </CentralLng>
|
||||
|
||||
<MaxPlayers> 5 </MaxPlayers>
|
||||
|
||||
<Marks>
|
||||
<CompoundMark CompoundMarkID="1">
|
||||
<Mark Lat="-14.070412" Lng="47.05746"/>
|
||||
<Mark Lat="-14.068014" Lng="47.057541"/>
|
||||
</CompoundMark>
|
||||
<CompoundMark CompoundMarkID="2">
|
||||
<Mark Lat="-14.067194" Lng="47.053818" />
|
||||
</CompoundMark>
|
||||
<CompoundMark CompoundMarkID="3">
|
||||
<Mark Lat="-14.061248" Lng="47.058556" />
|
||||
</CompoundMark>
|
||||
<CompoundMark CompoundMarkID="4">
|
||||
<Mark Lat="-14.060584" Lng="47.063088"/>
|
||||
<Mark Lat="-14.060617" Lng="47.06374" />
|
||||
</CompoundMark>
|
||||
<CompoundMark CompoundMarkID="5">
|
||||
<Mark Lat="-14.064637" Lng="47.060307"/>
|
||||
<Mark Lat="-14.06477" Lng="47.061165"/>
|
||||
</CompoundMark>
|
||||
<CompoundMark CompoundMarkID="6">
|
||||
<Mark Lat="-14.063839" Lng="47.06762"/>
|
||||
</CompoundMark>
|
||||
<CompoundMark CompoundMarkID="7">
|
||||
<Mark Lat="-14.068954" Lng="47.066349"/>
|
||||
</CompoundMark>
|
||||
<CompoundMark CompoundMarkID="8">
|
||||
<Mark Lat="-14.07025" Lng="47.060238"/>
|
||||
<Mark Lat="-14.071047" Lng="47.060307"/>
|
||||
</CompoundMark>
|
||||
</Marks>
|
||||
|
||||
<Course>
|
||||
|
||||
<OpeningSegment>
|
||||
<Corner CompoundMarkID="1" Rounding="PS"/>
|
||||
<Corner CompoundMarkID="2" Rounding="S"/>
|
||||
<Corner CompoundMarkID="3" Rounding="S"/>
|
||||
<Corner CompoundMarkID="4" Rounding="SP"/>
|
||||
</OpeningSegment>
|
||||
|
||||
<RepeatingSegment>
|
||||
<Corner CompoundMarkID="5" Rounding="SP"/>
|
||||
<Corner CompoundMarkID="4" Rounding="PS"/>
|
||||
</RepeatingSegment>
|
||||
|
||||
<ClosingSegment>
|
||||
<Corner CompoundMarkID="6" Rounding="S"/>
|
||||
<Corner CompoundMarkID="7" Rounding="S"/>
|
||||
<Corner CompoundMarkID="8" Rounding="SP" />
|
||||
</ClosingSegment>
|
||||
|
||||
</Course>
|
||||
|
||||
<CourseLimit>
|
||||
<Limit Lat="-14.073371" Lng="47.058213" />
|
||||
<Limit Lat="-14.06453" Lng="47.050003" />
|
||||
<Limit Lat="-14.057022" Lng="47.057286" />
|
||||
<Limit Lat="-14.058723" Lng="47.064358" />
|
||||
<Limit Lat="-14.06261" Lng="47.071293" />
|
||||
<Limit Lat="-14.070814" Lng="47.06762" />
|
||||
<Limit Lat="-14.072773" Lng="47.059689" />
|
||||
<Limit Lat="-14.068323" Lng="47.059449" />
|
||||
<Limit Lat="-14.064969" Lng="47.065113" />
|
||||
<Limit Lat="-14.066131" Lng="47.05986" />
|
||||
<Limit Lat="-14.063441" Lng="47.058419" />
|
||||
<Limit Lat="-14.068091" Lng="47.059174" />
|
||||
</CourseLimit>
|
||||
|
||||
</RaceDefinition>
|
||||
@@ -0,0 +1,66 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RaceDefinition>
|
||||
|
||||
<CourseName> Loopty Loop </CourseName>
|
||||
|
||||
<CentralLat> 57.6679590 </CentralLat>
|
||||
<CentralLng> 11.8503233 </CentralLng>
|
||||
|
||||
<MaxPlayers> 5 </MaxPlayers>
|
||||
|
||||
<Marks>
|
||||
<CompoundMark CompoundMarkID="1">
|
||||
<Mark Lat="57.6675700" Lng="11.8359880"/>
|
||||
<Mark Lat="57.667610" Lng="11.833473"/>
|
||||
</CompoundMark>
|
||||
<CompoundMark CompoundMarkID="2">
|
||||
<Mark Lat="57.670914" Lng="11.835263"/>
|
||||
</CompoundMark>
|
||||
<CompoundMark CompoundMarkID="3">
|
||||
<Mark Lat="57.6674596" Lng="11.8417500"/>
|
||||
<Mark Lat="57.667473" Lng="11.84429"/>
|
||||
</CompoundMark>
|
||||
<CompoundMark CompoundMarkID="4">
|
||||
<Mark Lat="57.6657945" Lng="11.8351409"/>
|
||||
<Mark Lat="57.6643158" Lng="11.8352297"/>
|
||||
</CompoundMark>
|
||||
<CompoundMark CompoundMarkID="5">
|
||||
<Mark Lat="57.667311" Lng="11.828857"/>
|
||||
<Mark Lat="57.667334" Lng="11.825853"/>
|
||||
</CompoundMark>
|
||||
<CompoundMark CompoundMarkID="6">
|
||||
<Mark Lat="57.6675700" Lng="11.8359880"/>
|
||||
<Mark Lat="57.667610" Lng="11.833473"/>
|
||||
</CompoundMark>
|
||||
</Marks>
|
||||
|
||||
<Course>
|
||||
<OpeningSegment>
|
||||
<Corner CompoundMarkID="1" Rounding="PS"/>
|
||||
<Corner CompoundMarkID="2" Rounding="S"/>
|
||||
</OpeningSegment>
|
||||
|
||||
<RepeatingSegment>
|
||||
<Corner CompoundMarkID="3" Rounding="SP"/>
|
||||
<Corner CompoundMarkID="4" Rounding="PS"/>
|
||||
<Corner CompoundMarkID="5" Rounding="PS"/>
|
||||
<Corner CompoundMarkID="2" Rounding="S"/>
|
||||
</RepeatingSegment>
|
||||
|
||||
<ClosingSegment>
|
||||
<Corner CompoundMarkID="6" Rounding="PS"/>
|
||||
</ClosingSegment>
|
||||
</Course>
|
||||
|
||||
<CourseLimit>
|
||||
<Limit Lat="57.672937" Lng="11.836257" />
|
||||
<Limit Lat="57.671239" Lng="11.843078" />
|
||||
<Limit Lat="57.667128" Lng="11.848022" />
|
||||
<Limit Lat="57.664512" Lng="11.844839" />
|
||||
<Limit Lat="57.662515" Lng="11.835569" />
|
||||
<Limit Lat="57.663939" Lng="11.827501" />
|
||||
<Limit Lat="57.667542" Lng="11.822952" />
|
||||
<Limit Lat="57.671123" Lng="11.826858" />
|
||||
</CourseLimit>
|
||||
|
||||
</RaceDefinition>
|
||||
@@ -0,0 +1,65 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RaceDefinition>
|
||||
|
||||
<CourseName>Madagascar</CourseName>
|
||||
|
||||
<CentralLat>-15.67707</CentralLat>
|
||||
<CentralLng>49.79338</CentralLng>
|
||||
|
||||
<MaxPlayers>10</MaxPlayers>
|
||||
|
||||
<Marks>
|
||||
<CompoundMark CompoundMarkID="1">
|
||||
<Mark Lat="-15.67466" Lng="49.79104"/>
|
||||
<Mark Lat="-15.67408" Lng="49.79224"/>
|
||||
</CompoundMark>
|
||||
<CompoundMark CompoundMarkID="2">
|
||||
<Mark Lat="-15.67548" Lng="49.79271"/>
|
||||
</CompoundMark>
|
||||
<CompoundMark CompoundMarkID="3">
|
||||
<Mark Lat="-15.67744" Lng="49.79235"/>
|
||||
</CompoundMark>
|
||||
<CompoundMark CompoundMarkID="4">
|
||||
<Mark Lat="-15.67691" Lng="49.79501"/>
|
||||
</CompoundMark>
|
||||
<CompoundMark CompoundMarkID="5">
|
||||
<Mark Lat="-15.67775" Lng="49.79619"/>
|
||||
<Mark Lat="-15.67827" Lng="49.79713"/>
|
||||
</CompoundMark>
|
||||
<CompoundMark CompoundMarkID="6">
|
||||
<Mark Lat="-15.67922" Lng="49.79318"/>
|
||||
<Mark Lat="-15.67972" Lng="49.79393"/>
|
||||
</CompoundMark>
|
||||
</Marks>
|
||||
|
||||
<Course>
|
||||
<OpeningSegment>
|
||||
<Corner CompoundMarkID="1" Rounding="PS"/>
|
||||
</OpeningSegment>
|
||||
|
||||
<RepeatingSegment>
|
||||
<Corner CompoundMarkID="2" Rounding="P"/>
|
||||
<Corner CompoundMarkID="3" Rounding="S"/>
|
||||
<Corner CompoundMarkID="4" Rounding="S"/>
|
||||
<Corner CompoundMarkID="3" Rounding="P"/>
|
||||
</RepeatingSegment>
|
||||
|
||||
<ClosingSegment>
|
||||
<Corner CompoundMarkID="5" Rounding="PS"/>
|
||||
<Corner CompoundMarkID="6" Rounding="PS"/>
|
||||
</ClosingSegment>
|
||||
</Course>
|
||||
|
||||
<CourseLimit>
|
||||
<Limit Lat="-15.67571" Lng="49.78984"/>
|
||||
<Limit Lat="-15.6787" Lng="49.79024"/>
|
||||
<Limit Lat="-15.68046" Lng="49.79247"/>
|
||||
<Limit Lat="-15.68073" Lng="49.79599"/>
|
||||
<Limit Lat="-15.67939" Lng="49.79855"/>
|
||||
<Limit Lat="-15.67662" Lng="49.79855"/>
|
||||
<Limit Lat="-15.67474" Lng="49.79694"/>
|
||||
<Limit Lat="-15.67271" Lng="49.79355"/>
|
||||
<Limit Lat="-15.67333" Lng="49.79071"/>
|
||||
</CourseLimit>
|
||||
|
||||
</RaceDefinition>
|
||||
@@ -0,0 +1,61 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RaceDefinition>
|
||||
|
||||
<CourseName>Waiheke</CourseName>
|
||||
|
||||
<CentralLat>-36.80008</CentralLat>
|
||||
<CentralLng>175.012225</CentralLng>
|
||||
|
||||
<MaxPlayers>10</MaxPlayers>
|
||||
|
||||
<Marks>
|
||||
<CompoundMark CompoundMarkID="1">
|
||||
<Mark Lat="-36.79512" Lng="175.0116"/>
|
||||
<Mark Lat="-36.79468" Lng="175.01312"/>
|
||||
</CompoundMark>
|
||||
<CompoundMark CompoundMarkID="2">
|
||||
<Mark Lat="-36.80069" Lng="175.01495"/>
|
||||
</CompoundMark>
|
||||
<CompoundMark CompoundMarkID="3">
|
||||
<Mark Lat="-36.79892" Lng="175.01832"/>
|
||||
<Mark Lat="-36.79988" Lng="175.01913"/>
|
||||
</CompoundMark>
|
||||
<CompoundMark CompoundMarkID="4">
|
||||
<Mark Lat="-36.80064" Lng="175.01171"/>
|
||||
<Mark Lat="-36.80186" Lng="175.0124"/>
|
||||
</CompoundMark>
|
||||
<CompoundMark CompoundMarkID="5">
|
||||
<Mark Lat="-36.79579" Lng="175.01194"/>
|
||||
<Mark Lat="-36.79545" Lng="175.01349"/>
|
||||
</CompoundMark>
|
||||
</Marks>
|
||||
|
||||
<Course>
|
||||
<OpeningSegment>
|
||||
<Corner CompoundMarkID="1" Rounding="PS"/>
|
||||
<Corner CompoundMarkID="2" Rounding="P"/>
|
||||
</OpeningSegment>
|
||||
|
||||
<RepeatingSegment>
|
||||
<Corner CompoundMarkID="3" Rounding="SP"/>
|
||||
<Corner CompoundMarkID="4" Rounding="PS"/>
|
||||
</RepeatingSegment>
|
||||
|
||||
<ClosingSegment>
|
||||
<Corner CompoundMarkID="5" Rounding="PS"/>
|
||||
</ClosingSegment>
|
||||
</Course>
|
||||
|
||||
<CourseLimit>
|
||||
<Limit Lat="-36.7938" Lng="175.01194"/>
|
||||
<Limit Lat="-36.79411" Lng="175.01555"/>
|
||||
<Limit Lat="-36.79765" Lng="175.01898"/>
|
||||
<Limit Lat="-36.79909" Lng="175.02149"/>
|
||||
<Limit Lat="-36.80163" Lng="175.02014"/>
|
||||
<Limit Lat="-36.80292" Lng="175.0175"/>
|
||||
<Limit Lat="-36.80325" Lng="175.01008"/>
|
||||
<Limit Lat="-36.80107" Lng="175.0089"/>
|
||||
<Limit Lat="-36.79567" Lng="175.00961"/>
|
||||
</CourseLimit>
|
||||
|
||||
</RaceDefinition>
|
||||