mirror of
https://github.com/michaelrausch/Party-Parrots-At-Sea.git
synced 2026-05-09 06:18:44 +00:00
Compare commits
291 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a8599d788b | |||
| 5101e55413 | |||
| 6ecd1c82f4 | |||
| ffd94b34bc | |||
| ba49660868 | |||
| 5dbc23866a | |||
| 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 | |||
| e5af7bf666 | |||
| 85ca91db96 | |||
| 2eb7e603f1 | |||
| 658a342118 | |||
| c0bd498f1b | |||
| ea52977aeb | |||
| 10fa51a105 | |||
| b18d9e8573 | |||
| e9881bb24a | |||
| c54a1e141d | |||
| 6d51ea3574 | |||
| d56468e4aa | |||
| ab5ad58237 | |||
| df7264cc1f | |||
| 5248921576 | |||
| 330ccd272d | |||
| 4b7dfe38c4 | |||
| ab07c7f298 | |||
| b5076bc976 | |||
| 2dcdd1c248 | |||
| 9b00f76907 | |||
| f11c457d28 | |||
| 9e4fa30787 | |||
| 99ce4fa11d | |||
| 69d1fa9488 | |||
| 66e6a8a2a4 | |||
| 671efcaf08 | |||
| 8ba44d7476 | |||
| dd43097677 | |||
| 98abe64f00 | |||
| 1a53579317 | |||
| 132a729758 | |||
| 870d7a6e82 | |||
| 2e7487fdfc | |||
| 1bd4db73cd | |||
| 7a4cdbe0c9 | |||
| 735699dc85 | |||
| 81e791bd1a | |||
| 06e5f4ae00 | |||
| cd2b4cb93c | |||
| dde1c82dbb | |||
| d9c832168b | |||
| 9cfb3b9e5d | |||
| e990c68d40 | |||
| 83871a0336 | |||
| 6cba024d64 | |||
| 51747e2d13 | |||
| b3981b19e0 | |||
| 3d7a64068f | |||
| c12f7408ad | |||
| 7027de80c4 | |||
| 35b50d1436 | |||
| d0844e861d | |||
| 5d32d76d9d | |||
| 9ca39d1a7c | |||
| 30dad8509e | |||
| 0211f2df38 | |||
| dba5a5680f | |||
| 5c50e77efa | |||
| 0e93be7b36 | |||
| 191b818e38 | |||
| 00b09997b0 | |||
| d250c635d8 | |||
| 376c4d25a8 | |||
| e66abb4340 | |||
| a19e191684 | |||
| 29b97a194d | |||
| 8a0ad8d6a9 | |||
| 19db6668da | |||
| 44275aec04 | |||
| ca320f7fb8 | |||
| 64245833cd | |||
| aa0149b9a7 | |||
| f6b41f0513 | |||
| 9b00ba654a | |||
| 8dfdb228e9 | |||
| 1042817e4e | |||
| 066557584f | |||
| 4011295b8b | |||
| 78259f8e33 | |||
| c47e5b1450 | |||
| 0a885dd8fd | |||
| e9b50038a9 | |||
| 364264377a | |||
| 9112183ac3 | |||
| 8c7f9a878d | |||
| ecb3d4ecbf | |||
| e61b6d50a1 | |||
| 957821f1f2 | |||
| 061e49bab9 | |||
| 094eb4c1cf | |||
| a3c555d5fe | |||
| 607acff7c6 | |||
| 22fdf1e4ac | |||
| da8c91f5c1 | |||
| 52dc7a956d | |||
| 9f64b2380d | |||
| b05580f018 | |||
| c20c6fb264 | |||
| faeece27ff | |||
| 5e3ae40d03 | |||
| 95ad7a4840 | |||
| 6ca75b2cac | |||
| 6ff309a40c | |||
| 40a7f9bc5b | |||
| c4a6113f6c | |||
| 307e79ecfc | |||
| e17e9749d8 | |||
| 7d8a6afa5f | |||
| ea0be5e952 | |||
| 3be8cd264d | |||
| 7197bc2bee | |||
| 66d9a06f9e | |||
| fba522d0c3 | |||
| 0e829874c2 | |||
| c5d56065b6 | |||
| 410d765745 | |||
| fe76e85c71 | |||
| 9d61a43bd7 | |||
| c39582de5c | |||
| 034e4c252a | |||
| 6cde016401 | |||
| d4d7ddf8e2 | |||
| a1933c2869 | |||
| 8084a61333 | |||
| 52d3cea592 | |||
| 03f5f91043 | |||
| 9ed52a1225 | |||
| 027324cc4f | |||
| 78596ea111 | |||
| da263355f4 | |||
| ebecd25ed2 | |||
| 0f5137c2b6 | |||
| 73799954e4 | |||
| edfeb2b287 | |||
| 0355784000 | |||
| 02df69b7b4 | |||
| 242132b800 | |||
| 3a671d4ed0 | |||
| 482d987839 | |||
| cc124b2d19 | |||
| b3320ad805 | |||
| 33779ad5c1 | |||
| 3a41c27d8d | |||
| e24203904b | |||
| eb188495ce | |||
| 62a7e2b8fa | |||
| acd0e790fe | |||
| 46013474c0 | |||
| bf427f24d3 | |||
| 7d0a47446d | |||
| 889098bb50 | |||
| 6223a8fc0b | |||
| 391bd33548 | |||
| 7e0c2abbfd | |||
| 42a5e86bf8 | |||
| 20d73d8f2f | |||
| 0a62c538ca | |||
| 24a04aa530 | |||
| 7ee6a09626 | |||
| d5ce61a0ff | |||
| 71f626f57e | |||
| 8dc3e54186 | |||
| 8fd35392b0 |
@@ -1,13 +1,29 @@
|
||||
# SENG302 Project Template
|
||||
# Party Parrots At Sea
|
||||
|
||||
Basic Maven project with required Maven reporting setup and basic GitLab CI.
|
||||
**Authors:** Michael Rausch, William Muir, Haoming Yin, Alistair Mcintyre, Kusal Ekanayake, Calum Irwin, Peter Galloway, Tan Zhi You
|
||||
|
||||
It is a requirement that your product should be completely built to a deliverable form using the Maven package goal.
|
||||
### SENG302
|
||||
> The Software Engineering group project gives students in-depth experience in developing software applications in groups. Participants work in groups to develop a complex real application. At the end of this course you will have practiced the skills required to be a Software Engineer in the real world, including gaining the required skills to be able to develop complex applications, dealing with vague (and often conflicting) customer requirements, working under pressure and being a valuable member of a software development team.
|
||||
|
||||
Remember to set up your GitLab CI server (refer to the student guide for instructions).
|
||||
**Find out more:** www.canterbury.ac.nz/courseinfo/GetCourses.aspx?course=SENG302
|
||||
|
||||
### Running the discovery server
|
||||
|
||||
|
||||
```
|
||||
xvfb-run -a -e server.log java -jar app.jar -runAsDiscoveryServer
|
||||
```
|
||||
|
||||
### Video
|
||||
|
||||
https://youtu.be/aHxJsfZLg54
|
||||
|
||||
### Screenshots
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
# Basic Project Structure
|
||||
- `src/` Your application source
|
||||
- `doc/` User and design documentation
|
||||
- `doc/examples/` Demo example files for use with your application
|
||||
|
||||
|
||||
@@ -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,13 @@ import org.apache.commons.cli.Options;
|
||||
import org.apache.commons.cli.ParseException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import seng302.discoveryServer.DiscoveryServer;
|
||||
import seng302.visualiser.controllers.ViewManager;
|
||||
|
||||
public class App extends Application {
|
||||
|
||||
private static Logger logger = LoggerFactory.getLogger(App.class);
|
||||
private static boolean isRunningAsCache = false;
|
||||
|
||||
public static void parseArgs(String[] args) throws ParseException {
|
||||
Options options = new Options();
|
||||
@@ -25,56 +27,90 @@ 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 {
|
||||
// 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,199 @@
|
||||
package seng302.discoveryServer;
|
||||
|
||||
import javafx.application.Platform;
|
||||
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;
|
||||
Platform.runLater(() -> {
|
||||
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,20 +1,21 @@
|
||||
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;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
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;
|
||||
@@ -23,8 +24,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;
|
||||
@@ -33,10 +32,12 @@ 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.XMLParser;
|
||||
import seng302.utilities.RandomSpawn;
|
||||
import seng302.visualiser.fxObjects.assets_3D.BoatMeshType;
|
||||
|
||||
/**
|
||||
* A Static class to hold information about the current state of the game (model)
|
||||
@@ -53,24 +54,39 @@ public class GameState implements Runnable {
|
||||
private static Logger logger = LoggerFactory.getLogger(GameState.class);
|
||||
|
||||
|
||||
private static final Integer STATE_UPDATES_PER_SECOND = 60;
|
||||
|
||||
//Scheduling constants
|
||||
static final int WARNING_TIME = 10 * -1000;
|
||||
static final int PREPATORY_TIME = 5 * -1000;
|
||||
private static final int TIME_TILL_START = 10 * 1000;
|
||||
|
||||
private static final Long POWERUP_TIMEOUT_MS = 10_000L;
|
||||
//Wind Constants
|
||||
private static final int MAX_WIND_SPEED = 12000;
|
||||
private static final int MIN_WIND_SPEED = 8000;
|
||||
|
||||
private static final Integer STATE_UPDATES_PER_SECOND = 60;
|
||||
private static Double ROUNDING_DISTANCE = 50d; // TODO: 14/08/17 wmu16 - Look into this value further
|
||||
//Rounding Constants
|
||||
private static final Double ROUNDING_DISTANCE = 50d; // TODO: 14/08/17 wmu16 - Look into this value further
|
||||
|
||||
//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;
|
||||
|
||||
//Powerup Constants
|
||||
public static final Double VELOCITY_BOOST_MULTIPLIER = 2d;
|
||||
public static final Integer HANDLING_BOOST_MULTIPLIER = 2;
|
||||
private static final Double BAD_RANDOM_SPEED_PENALTY = 0.3;
|
||||
public static final Long BUMPER_DISABLE_TIME = 5_000L;
|
||||
private static final Long TOKEN_SPAWN_TIME = 30_000L;
|
||||
|
||||
private static Long previousUpdateTime;
|
||||
public static Double windDirection;
|
||||
public static ReadOnlyDoubleWrapper windDirectionProperty = new ReadOnlyDoubleWrapper();
|
||||
private static Double windSpeed;
|
||||
private static Double speedMultiplier = 1d;
|
||||
private static Double serverSpeedMultiplier;
|
||||
|
||||
private static Boolean customizationFlag; // dirty flag to tell if a player has customized their boat.
|
||||
private static Boolean playerHasLeftFlag;
|
||||
@@ -78,85 +94,47 @@ 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 Set<Mark> marks;
|
||||
private static List<Limit> courseLimit;
|
||||
private static Integer maxPlayers = 8;
|
||||
private static Set<Mark> marks = new HashSet<>();
|
||||
private static List<Limit> courseLimit = new ArrayList<>();
|
||||
private static Integer maxPlayers = 12;
|
||||
|
||||
|
||||
private static List<Token> allTokens;
|
||||
private static List<Token> tokensInPlay;
|
||||
private static RandomSpawn randomSpawn;
|
||||
|
||||
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<>();
|
||||
|
||||
tokensInPlay = new CopyOnWriteArrayList<>();
|
||||
players = new ArrayList<>();
|
||||
GameState.hostIpAddress = hostIpAddress;
|
||||
customizationFlag = false;
|
||||
playerHasLeftFlag = false;
|
||||
speedMultiplier = 1.0;
|
||||
serverSpeedMultiplier = 1.0;
|
||||
currentStage = GameStages.LOBBYING;
|
||||
isRaceStarted = false;
|
||||
//set this when game stage changes to prerace
|
||||
previousUpdateTime = System.currentTimeMillis();
|
||||
markOrder = new MarkOrder(); //This could be instantiated at some point with a select map?
|
||||
newMessageListeners = new ArrayList<>();
|
||||
allTokens = makeTokens();
|
||||
|
||||
resetStartTime();
|
||||
|
||||
new Thread(this, "GameState").start(); //Run the auto updates on the game state
|
||||
|
||||
marks = new MarkOrder().getAllMarks();
|
||||
setCourseLimit("/server_config/race.xml");
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Make a pre defined set of tokensInPlay. //TODO wmu16 - Should read from some file for each
|
||||
* race ideally
|
||||
*
|
||||
* @return A list of possible tokensInPlay for this race
|
||||
*/
|
||||
private ArrayList<Token> makeTokens() {
|
||||
Token token1 = new Token(TokenType.BOOST, 57.66946, 11.83154);
|
||||
Token token2 = new Token(TokenType.BOOST, 57.66877, 11.83382);
|
||||
Token token3 = new Token(TokenType.BOOST, 57.66914, 11.83965);
|
||||
Token token4 = new Token(TokenType.BOOST, 57.66684, 11.83214);
|
||||
return new ArrayList<>(Arrays.asList(token1, token2, token3, token4));
|
||||
}
|
||||
|
||||
public static String getHostIpAddress() {
|
||||
return hostIpAddress;
|
||||
}
|
||||
|
||||
public static Set<Mark> getMarks() {
|
||||
return Collections.unmodifiableSet(marks);
|
||||
randomSpawn = new RandomSpawn(markOrder.getOrderedUniqueCompoundMarks());
|
||||
courseLimit = raceXMLData.getCourseLimit();
|
||||
}
|
||||
|
||||
public static List<Player> getPlayers() {
|
||||
@@ -167,6 +145,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()
|
||||
@@ -187,10 +169,6 @@ public class GameState implements Runnable {
|
||||
yachts.remove(yachtId);
|
||||
}
|
||||
|
||||
public static Boolean getIsRaceStarted() {
|
||||
return isRaceStarted;
|
||||
}
|
||||
|
||||
public static GameStages getCurrentStage() {
|
||||
return currentStage;
|
||||
}
|
||||
@@ -217,6 +195,7 @@ public class GameState implements Runnable {
|
||||
|
||||
public static void setWindDirection(Double newWindDirection) {
|
||||
windDirection = newWindDirection;
|
||||
windDirectionProperty.set(newWindDirection);
|
||||
}
|
||||
|
||||
public static void setWindSpeed(Double newWindSpeed) {
|
||||
@@ -259,16 +238,77 @@ public class GameState implements Runnable {
|
||||
} catch (InterruptedException e) {
|
||||
System.out.println("[GameState] interrupted exception");
|
||||
}
|
||||
if (currentStage == GameStages.PRE_RACE || currentStage == GameStages.RACING) {
|
||||
if (currentStage == GameStages.PRE_RACE) {
|
||||
update();
|
||||
if (System.currentTimeMillis() > startTime) {
|
||||
startSpawningTokens();
|
||||
startUpdatingWind();
|
||||
GameState.currentStage = GameStages.RACING;
|
||||
}
|
||||
}
|
||||
|
||||
if (currentStage == GameStages.RACING) {
|
||||
update();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start spawning coins every 60s after the first minute
|
||||
*/
|
||||
private void startSpawningTokens() {
|
||||
Timer timer = new Timer("Token Spawning Timer");
|
||||
timer.schedule(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (tokensEnabled) {
|
||||
spawnNewToken();
|
||||
notifyMessageListeners(MessageFactory.getRaceXML());
|
||||
}
|
||||
}
|
||||
}, 0, TOKEN_SPAWN_TIME);
|
||||
}
|
||||
|
||||
// TODO: 29/08/17 wmu16 - This sort of update should be in game state
|
||||
private static void startUpdatingWind() {
|
||||
Timer timer = new Timer("Wind Updating Timer");
|
||||
timer.schedule(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
updateWind();
|
||||
}
|
||||
}, 0, 500);
|
||||
}
|
||||
|
||||
|
||||
private static void updateWind() {
|
||||
Integer direction = GameState.getWindDirection().intValue();
|
||||
Integer windSpeed = GameState.getWindSpeedMMS().intValue();
|
||||
|
||||
Random random = new Random();
|
||||
|
||||
if (Math.floorMod(random.nextInt(), 2) == 0) {
|
||||
direction += random.nextInt(4);
|
||||
windSpeed += random.nextInt(20) + 459;
|
||||
} else {
|
||||
direction -= random.nextInt(4);
|
||||
windSpeed -= random.nextInt(20) + 459;
|
||||
}
|
||||
|
||||
direction = Math.floorMod(direction, 360);
|
||||
|
||||
if (windSpeed > MAX_WIND_SPEED) {
|
||||
windSpeed -= random.nextInt(500);
|
||||
}
|
||||
|
||||
if (windSpeed <= MIN_WIND_SPEED) {
|
||||
windSpeed += random.nextInt(500);
|
||||
}
|
||||
|
||||
GameState.windSpeed = Double.valueOf(windSpeed);
|
||||
GameState.windDirection = direction.doubleValue();
|
||||
}
|
||||
|
||||
|
||||
public static void updateBoat(Integer sourceId, BoatAction actionType) {
|
||||
ServerYacht playerYacht = yachts.get(sourceId);
|
||||
switch (actionType) {
|
||||
@@ -290,6 +330,12 @@ public class GameState implements Runnable {
|
||||
case DOWNWIND:
|
||||
playerYacht.turnDownwind();
|
||||
break;
|
||||
case CONTINUOUSLY_TURNING:
|
||||
playerYacht.setContinuouslyTurning(true);
|
||||
break;
|
||||
case DEFAULT_TURNING:
|
||||
playerYacht.setContinuouslyTurning(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -297,10 +343,13 @@ public class GameState implements Runnable {
|
||||
* Randomly select a subset of tokensInPlay from a pre defined superset
|
||||
* Broadasts a new race status message to show this update
|
||||
*/
|
||||
public static void spawnNewToken() {
|
||||
Random random = new Random();
|
||||
private void spawnNewToken() {
|
||||
tokensInPlay.clear();
|
||||
tokensInPlay.add(allTokens.get(random.nextInt(allTokens.size())));
|
||||
Token token = randomSpawn.getRandomToken();
|
||||
// token.assignType(TokenType.WIND_WALKER);
|
||||
logger.debug("Spawned token of type " + token.getTokenType());
|
||||
tokensInPlay.add(token);
|
||||
MessageFactory.updateTokens(tokensInPlay);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -317,14 +366,12 @@ public class GameState implements Runnable {
|
||||
|
||||
Double timeInterval = (System.currentTimeMillis() - previousUpdateTime) / 1000000.0;
|
||||
previousUpdateTime = System.currentTimeMillis();
|
||||
if (System.currentTimeMillis() > startTime) {
|
||||
GameState.setCurrentStage(GameStages.RACING);
|
||||
}
|
||||
|
||||
for (ServerYacht yacht : yachts.values()) {
|
||||
updateVelocity(yacht);
|
||||
checkPowerUpTimeout(yacht);
|
||||
yacht.runAutoPilot();
|
||||
yacht.updateLocation(timeInterval);
|
||||
preformTokenUpdates(yacht); //This update must be done before collision. Sorta hacky
|
||||
checkCollision(yacht);
|
||||
if (yacht.getBoatStatus() != BoatStatus.FINISHED) {
|
||||
checkForLegProgression(yacht);
|
||||
@@ -338,16 +385,139 @@ public class GameState implements Runnable {
|
||||
}
|
||||
|
||||
|
||||
private void checkPowerUpTimeout(ServerYacht yacht) {
|
||||
if (yacht.getPowerUp() != null) {
|
||||
if (System.currentTimeMillis() - yacht.getPowerUpStartTime() > POWERUP_TIMEOUT_MS) {
|
||||
yacht.powerDown();
|
||||
logger.debug("Yacht: " + yacht.getShortName() + " powered down!");
|
||||
/**
|
||||
* All token functionality entry points is taken care of here. So can be disabled and enabled
|
||||
* easily
|
||||
*
|
||||
* @param yacht The yacht to perform token checks on
|
||||
*/
|
||||
private void preformTokenUpdates(ServerYacht yacht) {
|
||||
Token collidedToken = checkTokenPickUp(yacht);
|
||||
if (collidedToken != null) {
|
||||
tokensInPlay.remove(collidedToken);
|
||||
powerUpYacht(yacht, collidedToken);
|
||||
MessageFactory.updateTokens(tokensInPlay);
|
||||
notifyMessageListeners(MessageFactory.getRaceXML());
|
||||
}
|
||||
|
||||
checkPowerUpTimeout(yacht);
|
||||
TokenType powerUp = yacht.getPowerUp();
|
||||
|
||||
if (powerUp != null) {
|
||||
switch (powerUp) {
|
||||
case WIND_WALKER:
|
||||
windWalk(yacht);
|
||||
break;
|
||||
case BUMPER:
|
||||
ServerYacht collidedYacht = checkYachtCollision(yacht, true);
|
||||
if (collidedYacht != null) {
|
||||
yacht.powerDown();
|
||||
boatTempShutDown(collidedYacht);
|
||||
notifyMessageListeners(MessageFactory.makePowerDownMessage(yacht));
|
||||
notifyMessageListeners(
|
||||
MessageFactory.makeStatusEffectMessage(collidedYacht, powerUp));
|
||||
}
|
||||
break;
|
||||
case RANDOM:
|
||||
yacht.setPowerUpSpeedMultiplier(BAD_RANDOM_SPEED_PENALTY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Powers up a thisYacht with the given token type.
|
||||
*
|
||||
* @param thisYacht The yacht to be powered up
|
||||
* @param collidedToken The token which this thisYacht collided with
|
||||
*/
|
||||
private void powerUpYacht(ServerYacht thisYacht, Token collidedToken) {
|
||||
//The random token has a 50% chance of becoming another token else becoming a speed detriment!
|
||||
if (collidedToken.getTokenType() == TokenType.RANDOM && new Random().nextBoolean()) {
|
||||
collidedToken.realiseRandom();
|
||||
}
|
||||
|
||||
//If another yacht has the wind walker token, They should be powered down. Only one allowed!
|
||||
else if (collidedToken.getTokenType() == TokenType.WIND_WALKER) {
|
||||
for (ServerYacht otherYacht : yachts.values()) {
|
||||
if (otherYacht != thisYacht && otherYacht.getPowerUp() == TokenType.WIND_WALKER) {
|
||||
powerDownYacht(otherYacht);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
thisYacht.powerUp(collidedToken.getTokenType());
|
||||
String logMessage =
|
||||
thisYacht.getBoatName() + " has picked up a " + collidedToken.getTokenType().getName()
|
||||
+ " token";
|
||||
notifyMessageListeners(
|
||||
MessageFactory.makeChatterMessage(thisYacht.getSourceId(), logMessage));
|
||||
notifyMessageListeners(MessageFactory.getRaceXML());
|
||||
notifyMessageListeners(MessageFactory.makePickupMessage(thisYacht, collidedToken));
|
||||
logger.debug(
|
||||
"Yacht: " + thisYacht.getShortName() + " got powerup " + collidedToken.getTokenType());
|
||||
}
|
||||
|
||||
private void powerDownYacht(ServerYacht yacht) {
|
||||
String logMessage =
|
||||
yacht.getBoatName() + "'s " + yacht.getPowerUp().getName() + " expired";
|
||||
notifyMessageListeners(
|
||||
MessageFactory.makeChatterMessage(yacht.getSourceId(), logMessage));
|
||||
notifyMessageListeners(MessageFactory.makePowerDownMessage(yacht));
|
||||
logger.debug("Yacht: " + yacht.getShortName() + " powered down!");
|
||||
|
||||
yacht.powerDown();
|
||||
}
|
||||
|
||||
// TODO: 23/09/17 wmu16 - This is a hacky way to have the boat power down. Need some sort of separation between token and status effect :/
|
||||
|
||||
/**
|
||||
* Disables the given boat for BUMPER_DISABLE_TIME ms.
|
||||
*
|
||||
* @param yacht The yacht to disable
|
||||
*/
|
||||
private void boatTempShutDown(ServerYacht yacht) {
|
||||
yacht.setPowerUpSpeedMultiplier(0d);
|
||||
Timer shutDownTimer = new Timer("Shutdown Timer");
|
||||
shutDownTimer.schedule(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
yacht.powerDown(); //Note this actually resets the boat to normal.
|
||||
}
|
||||
}, BUMPER_DISABLE_TIME);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks how long a powerup has been active for. If it has exceeded its timeout, it powers the
|
||||
* yacht down.
|
||||
*
|
||||
* @param yacht The yacht to check to power down
|
||||
*/
|
||||
private void checkPowerUpTimeout(ServerYacht yacht) {
|
||||
if (yacht.getPowerUp() != null) {
|
||||
if (System.currentTimeMillis() - yacht.getPowerUpStartTime() > yacht.getPowerUp()
|
||||
.getTimeout()) {
|
||||
powerDownYacht(yacht);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This function changes the wind to be at an angle that causes the yacht in question to be at
|
||||
* its fastest velocity
|
||||
*
|
||||
* @param yacht The yacht to fix the wind for
|
||||
*/
|
||||
private void windWalk(ServerYacht yacht) {
|
||||
Double optimalAngle = PolarTable.getOptimalAngle();
|
||||
Double heading = yacht.getHeading();
|
||||
windDirection = (double) Math.floorMod(Math.round(heading + optimalAngle), 360L);
|
||||
windDirectionProperty.set(windDirection);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if the yacht has crossed the course limit
|
||||
*
|
||||
@@ -361,6 +531,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;
|
||||
@@ -369,13 +540,15 @@ public class GameState implements Runnable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks all tokensInPlay to see if a yacht has picked one up
|
||||
* @return Token which was collided with
|
||||
* @param serverYacht The yacht to check for collision with a token
|
||||
* Checks all tokensInPlay to see if a yacht has picked one up. If so, the yacht is powered up
|
||||
* in the appropriate way
|
||||
* @param yacht The yacht to check for collision with a token
|
||||
*
|
||||
* @return The token collided with
|
||||
*/
|
||||
private static Token checkTokenPickUp(ServerYacht serverYacht) {
|
||||
private Token checkTokenPickUp(ServerYacht yacht) {
|
||||
for (Token token : tokensInPlay) {
|
||||
Double distance = GeoUtility.getDistance(token, serverYacht.getLocation());
|
||||
Double distance = GeoUtility.getDistance(token, yacht.getLocation());
|
||||
if (distance < YACHT_COLLISION_DISTANCE) {
|
||||
return token;
|
||||
}
|
||||
@@ -398,7 +571,7 @@ public class GameState implements Runnable {
|
||||
*/
|
||||
public static void checkCollision(ServerYacht serverYacht) {
|
||||
//Yacht Collision
|
||||
ServerYacht collidedYacht = checkYachtCollision(serverYacht);
|
||||
ServerYacht collidedYacht = checkYachtCollision(serverYacht, false);
|
||||
Mark collidedMark = checkMarkCollision(serverYacht);
|
||||
|
||||
if (collidedYacht != null) {
|
||||
@@ -415,9 +588,7 @@ public class GameState implements Runnable {
|
||||
collidedYacht.setCurrentVelocity(
|
||||
collidedYacht.getCurrentVelocity() * COLLISION_VELOCITY_PENALTY
|
||||
);
|
||||
notifyMessageListeners(
|
||||
new YachtEventCodeMessage(serverYacht.getSourceId(), YachtEventType.COLLISION)
|
||||
);
|
||||
notifyMessageListeners(MessageFactory.makeCollisionMessage(serverYacht));
|
||||
}
|
||||
|
||||
//Mark Collision
|
||||
@@ -429,9 +600,7 @@ public class GameState implements Runnable {
|
||||
serverYacht.setCurrentVelocity(
|
||||
serverYacht.getCurrentVelocity() * COLLISION_VELOCITY_PENALTY
|
||||
);
|
||||
notifyMessageListeners(
|
||||
new YachtEventCodeMessage(serverYacht.getSourceId(), YachtEventType.COLLISION)
|
||||
);
|
||||
notifyMessageListeners(MessageFactory.makeCollisionMessage(serverYacht));
|
||||
}
|
||||
|
||||
//Boundary Collision
|
||||
@@ -444,21 +613,7 @@ public class GameState implements Runnable {
|
||||
serverYacht.setCurrentVelocity(
|
||||
serverYacht.getCurrentVelocity() * COLLISION_VELOCITY_PENALTY
|
||||
);
|
||||
notifyMessageListeners(
|
||||
new YachtEventCodeMessage(serverYacht.getSourceId(), YachtEventType.COLLISION)
|
||||
);
|
||||
}
|
||||
|
||||
//Token Collision
|
||||
Token collidedToken = checkTokenPickUp(serverYacht);
|
||||
if (collidedToken != null) {
|
||||
tokensInPlay.remove(collidedToken);
|
||||
serverYacht.powerUp(collidedToken.getTokenType());
|
||||
logger.debug("Yacht: " + serverYacht.getShortName() + " got powerup " + collidedToken
|
||||
.getTokenType());
|
||||
notifyMessageListeners(MessageFactory.getRaceXML());
|
||||
notifyMessageListeners(
|
||||
new YachtEventCodeMessage(serverYacht.getSourceId(), YachtEventType.TOKEN));
|
||||
notifyMessageListeners(MessageFactory.makeCollisionMessage(serverYacht));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -466,29 +621,30 @@ public class GameState implements Runnable {
|
||||
private void updateVelocity(ServerYacht yacht) {
|
||||
Double trueWindAngle = Math.abs(windDirection - yacht.getHeading());
|
||||
Double boatSpeedInKnots = PolarTable.getBoatSpeed(getWindSpeedKnots(), trueWindAngle);
|
||||
Double maxBoatSpeed = GeoUtility.knotsToMMS(boatSpeedInKnots) * speedMultiplier;
|
||||
if (yacht.getPowerUp() != null) {
|
||||
if (yacht.getPowerUp().equals(TokenType.BOOST)) {
|
||||
// TODO: 11/09/17 wmu16 CHANGE THIS TO MAGIC NUMBER
|
||||
maxBoatSpeed *= 2;
|
||||
}
|
||||
}
|
||||
Double maxBoatSpeed =
|
||||
GeoUtility.knotsToMMS(boatSpeedInKnots) * serverSpeedMultiplier * yacht
|
||||
.getPowerUpSpeedMultiplier() * yacht.getBoatTypeSpeedMultiplier();
|
||||
|
||||
Double currentVelocity = yacht.getCurrentVelocity();
|
||||
// TODO: 15/08/17 remove magic numbers from these equations.
|
||||
if (yacht.getSailIn()) {
|
||||
if (currentVelocity < maxBoatSpeed - 500) {
|
||||
yacht.changeVelocity(maxBoatSpeed / 100);
|
||||
yacht.changeVelocity(
|
||||
(maxBoatSpeed / 100) * yacht.getBoatTypeAccelerationMultiplier());
|
||||
} else if (currentVelocity > maxBoatSpeed + 500) {
|
||||
yacht.changeVelocity(-currentVelocity / 200);
|
||||
yacht.changeVelocity(
|
||||
(-currentVelocity / 200) * yacht.getBoatTypeAccelerationMultiplier());
|
||||
} else {
|
||||
yacht.setCurrentVelocity(maxBoatSpeed);
|
||||
yacht
|
||||
.setCurrentVelocity((maxBoatSpeed) * yacht.getBoatTypeAccelerationMultiplier());
|
||||
}
|
||||
} else {
|
||||
if (currentVelocity > 3000) {
|
||||
yacht.changeVelocity(-currentVelocity / 200);
|
||||
yacht.changeVelocity(
|
||||
(-currentVelocity / 200) * yacht.getBoatTypeAccelerationMultiplier());
|
||||
} else if (currentVelocity > 100) {
|
||||
yacht.changeVelocity(-currentVelocity / 50);
|
||||
yacht.changeVelocity(
|
||||
(-currentVelocity / 50) * yacht.getBoatTypeAccelerationMultiplier());
|
||||
} else if (currentVelocity <= 100) {
|
||||
yacht.setCurrentVelocity(0d);
|
||||
}
|
||||
@@ -550,6 +706,12 @@ public class GameState implements Runnable {
|
||||
}
|
||||
|
||||
if (hasProgressed) {
|
||||
if (currentMarkSeqID != 0 && !markOrder.isLastMark(currentMarkSeqID)) {
|
||||
|
||||
String logMessage = yacht.getBoatName() + " passed leg " + yacht.getLegNumber();
|
||||
notifyMessageListeners(
|
||||
MessageFactory.makeChatterMessage(yacht.getSourceId(), logMessage));
|
||||
}
|
||||
yacht.incrementLegNumber();
|
||||
sendMarkRoundingMessage(yacht);
|
||||
logMarkRounding(yacht);
|
||||
@@ -569,6 +731,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();
|
||||
@@ -584,6 +750,9 @@ public class GameState implements Runnable {
|
||||
if (crossedLine == 2 && isClockwiseCross || crossedLine == 1 && !isClockwiseCross) {
|
||||
yacht.setClosestCurrentMark(mark1);
|
||||
yacht.setBoatStatus(BoatStatus.RACING);
|
||||
String logMessage = yacht.getBoatName() + " passed start line";
|
||||
notifyMessageListeners(
|
||||
MessageFactory.makeChatterMessage(yacht.getSourceId(), logMessage));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -687,6 +856,10 @@ public class GameState implements Runnable {
|
||||
if (crossedLine == 1 && isClockwiseCross || crossedLine == 2 && !isClockwiseCross) {
|
||||
yacht.setClosestCurrentMark(mark1);
|
||||
yacht.setBoatStatus(BoatStatus.FINISHED);
|
||||
|
||||
String logMessage = yacht.getBoatName() + " passed finish line";
|
||||
notifyMessageListeners(
|
||||
MessageFactory.makeChatterMessage(yacht.getSourceId(), logMessage));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -709,16 +882,20 @@ public class GameState implements Runnable {
|
||||
String name = new String(customizeData);
|
||||
playerYacht.setBoatName(name);
|
||||
} else if (requestType.equals(CustomizeRequestType.COLOR)) {
|
||||
//This low level stuff shouldnt be here alistair! In fact no logic LIKE THIS should! - wmu16
|
||||
int red = customizeData[0] & 0xFF;
|
||||
int green = customizeData[1] & 0xFF;
|
||||
int blue = customizeData[2] & 0xFF;
|
||||
Color yachtColor = Color.rgb(red, green, blue);
|
||||
playerYacht.setBoatColor(yachtColor);
|
||||
} else if (requestType.equals(CustomizeRequestType.SHAPE)) {
|
||||
String type = new String(customizeData);
|
||||
playerYacht.setBoatType(BoatMeshType.valueOf(type));
|
||||
}
|
||||
}
|
||||
|
||||
private static Mark checkMarkCollision(ServerYacht yacht) {
|
||||
Set<Mark> marksInRace = GameState.getMarks();
|
||||
Set<Mark> marksInRace = new HashSet<>(marks);
|
||||
for (Mark mark : marksInRace) {
|
||||
if (GeoUtility.getDistance(yacht.getLocation(), mark)
|
||||
<= MARK_COLLISION_DISTANCE) {
|
||||
@@ -747,15 +924,22 @@ public class GameState implements Runnable {
|
||||
* Collision detection which iterates through all the yachts and check if any yacht collided
|
||||
* with this yacht. Return collided yacht or null if no collision.
|
||||
*
|
||||
* UPDATE: HACK!!! wmu16 - forBumperCollision is (the goddamn dirtiest) dirty flag to fix a
|
||||
* weird bug where the bumper collision would not be registerd but the knock back collision would.
|
||||
* In other words, only set the 'forBumperCollision' flag true if used for the bumper power up.
|
||||
*
|
||||
* @return yacht to compare to all other yachts.
|
||||
*/
|
||||
private static ServerYacht checkYachtCollision(ServerYacht yacht) {
|
||||
private static ServerYacht checkYachtCollision(ServerYacht yacht, Boolean forBumperCollision) {
|
||||
Double collisionDistance =
|
||||
(forBumperCollision) ? YACHT_COLLISION_DISTANCE + 2.5 : YACHT_COLLISION_DISTANCE;
|
||||
|
||||
for (ServerYacht otherYacht : GameState.getYachts().values()) {
|
||||
if (otherYacht != yacht) {
|
||||
Double distance = GeoUtility
|
||||
.getDistance(otherYacht.getLocation(), yacht.getLocation());
|
||||
if (distance < YACHT_COLLISION_DISTANCE) {
|
||||
;
|
||||
if (distance < collisionDistance) {
|
||||
return otherYacht;
|
||||
}
|
||||
}
|
||||
@@ -791,29 +975,26 @@ public class GameState implements Runnable {
|
||||
roundingMark.getSourceID()));
|
||||
}
|
||||
|
||||
|
||||
public static void processChatter(ChatterMessage chatterMessage, boolean isHost) {
|
||||
String chatterText = chatterMessage.getMessage();
|
||||
String[] words = chatterText.split("\\s+");
|
||||
if (words.length > 2 && isHost) {
|
||||
switch (words[2].trim()) {
|
||||
case ">speed":
|
||||
case "/speed":
|
||||
try {
|
||||
setSpeedMultiplier(Double.valueOf(words[3]));
|
||||
notifyMessageListeners(new ChatterMessage(
|
||||
chatterMessage.getMessage_type(),
|
||||
"SERVER: Speed modifier set to x" + words[3]
|
||||
));
|
||||
serverSpeedMultiplier = Double.valueOf(words[3]);
|
||||
String logMessage = "Speed modifier set to x" + words[3];
|
||||
notifyMessageListeners(MessageFactory
|
||||
.makeChatterMessage(chatterMessage.getMessageType(), logMessage));
|
||||
} catch (Exception e) {
|
||||
Logger logger = LoggerFactory.getLogger(GameState.class);
|
||||
logger.error("cannot parse >speed value");
|
||||
}
|
||||
return;
|
||||
case ">finish":
|
||||
notifyMessageListeners(new ChatterMessage(
|
||||
chatterMessage.getMessage_type(),
|
||||
"SERVER: Game will now finish"
|
||||
));
|
||||
case "/finish":
|
||||
String logMessage = "Game will now finish";
|
||||
notifyMessageListeners(MessageFactory
|
||||
.makeChatterMessage(chatterMessage.getMessageType(), logMessage));
|
||||
endRace();
|
||||
return;
|
||||
}
|
||||
@@ -863,6 +1044,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 () {
|
||||
@@ -870,11 +1057,15 @@ public class GameState implements Runnable {
|
||||
currentStage = GameStages.FINISHED;
|
||||
}
|
||||
|
||||
public static void setSpeedMultiplier (double multiplier) {
|
||||
speedMultiplier = multiplier;
|
||||
public static double getServerSpeedMultiplier() {
|
||||
return serverSpeedMultiplier;
|
||||
}
|
||||
|
||||
public static double getSpeedMultiplier () {
|
||||
return speedMultiplier;
|
||||
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,42 +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 static final int MAX_WIND_SPEED = 12000;
|
||||
private static final int MIN_WIND_SPEED = 8000;
|
||||
|
||||
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;
|
||||
@@ -79,39 +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");
|
||||
startUpdatingWind();
|
||||
startSpawningTokens();
|
||||
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()) {
|
||||
@@ -135,7 +114,7 @@ public class MainServerThread implements Runnable, ClientConnectionDelegate {
|
||||
}
|
||||
if (GameState.getCurrentStage() == GameStages.LOBBYING && GameState
|
||||
.getCustomizationFlag()) {
|
||||
// TODO: 16/08/17 ajm412: This can probably be done in a nicer way via those fancy functional interfaces.
|
||||
MessageFactory.updateBoats(new ArrayList<>(GameState.getYachts().values()));
|
||||
sendSetupMessages();
|
||||
GameState.resetCustomizationFlag();
|
||||
}
|
||||
@@ -153,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);
|
||||
@@ -161,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) {
|
||||
@@ -177,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());
|
||||
@@ -188,63 +171,6 @@ public class MainServerThread implements Runnable, ClientConnectionDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
private static void updateWind(){
|
||||
Integer direction = GameState.getWindDirection().intValue();
|
||||
Integer windSpeed = GameState.getWindSpeedMMS().intValue();
|
||||
|
||||
Random random = new Random();
|
||||
|
||||
if (Math.floorMod(random.nextInt(), 2) == 0){
|
||||
direction += random.nextInt(4);
|
||||
windSpeed += random.nextInt(20) + 50;
|
||||
}
|
||||
else{
|
||||
direction -= random.nextInt(4);
|
||||
windSpeed -= random.nextInt(20) + 50;
|
||||
}
|
||||
|
||||
direction = Math.floorMod(direction, 360);
|
||||
|
||||
if (windSpeed > MAX_WIND_SPEED){
|
||||
windSpeed -= random.nextInt(1000);
|
||||
}
|
||||
|
||||
if (windSpeed <= MIN_WIND_SPEED){
|
||||
windSpeed += random.nextInt(1000);
|
||||
}
|
||||
|
||||
GameState.setWindSpeed(Double.valueOf(windSpeed));
|
||||
GameState.setWindDirection(direction.doubleValue());
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// TODO: 29/08/17 wmu16 - This sort of update should be in game state
|
||||
private static void startUpdatingWind(){
|
||||
Timer timer = new Timer();
|
||||
timer.schedule(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
updateWind();
|
||||
}
|
||||
}, 0, 500);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start spawning coins every 60s after the first minute
|
||||
*/
|
||||
private void startSpawningTokens() {
|
||||
Timer timer = new Timer();
|
||||
timer.schedule(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
GameState.spawnNewToken();
|
||||
broadcastMessage(MessageFactory.getRaceXML());
|
||||
}
|
||||
}, 10000, 60000);
|
||||
}
|
||||
|
||||
/**
|
||||
* A client has tried to connect to the server
|
||||
*
|
||||
@@ -255,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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -281,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 {
|
||||
@@ -293,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() {
|
||||
@@ -311,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() {
|
||||
@@ -331,39 +282,72 @@ 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 = 35d;
|
||||
|
||||
// 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)
|
||||
) - YACHT_SEPARATION;
|
||||
|
||||
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 midPoint = 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 numYachts = spacesAlongLine > randomisedYachts.size() ? randomisedYachts.size() : spacesAlongLine;
|
||||
double yachtSpace = (numYachts - 1) * YACHT_SEPARATION / 2;
|
||||
|
||||
GeoPoint firstYachtPoint = GeoUtility.getGeoCoordinate(
|
||||
midPoint, startMarkToMarkAngle + 180, yachtSpace
|
||||
);
|
||||
|
||||
for (int i = 0; i < numYachts; i++) {
|
||||
randomisedYachts.get(0).setHeading(angleFromStart);
|
||||
randomisedYachts.get(0).setLocation(firstYachtPoint);
|
||||
firstYachtPoint = GeoUtility.getGeoCoordinate(
|
||||
firstYachtPoint, startMarkToMarkAngle, YACHT_SEPARATION
|
||||
);
|
||||
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++;
|
||||
midPoint = GeoUtility.getGeoCoordinate(
|
||||
midPoint, angleToStart, YACHT_SEPARATION * 1.5
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasStarted() {
|
||||
return hasStarted;
|
||||
}
|
||||
|
||||
public int getPortNumber() {
|
||||
return selectedPort;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import seng302.gameServer.messages.BoatLocationMessage;
|
||||
import seng302.gameServer.messages.BoatSubMessage;
|
||||
import seng302.gameServer.messages.ChatterMessage;
|
||||
import seng302.gameServer.messages.RaceStartNotificationType;
|
||||
import seng302.gameServer.messages.RaceStartStatusMessage;
|
||||
import seng302.gameServer.messages.RaceStatus;
|
||||
@@ -11,11 +12,16 @@ import seng302.gameServer.messages.RaceStatusMessage;
|
||||
import seng302.gameServer.messages.RaceType;
|
||||
import seng302.gameServer.messages.XMLMessage;
|
||||
import seng302.gameServer.messages.XMLMessageSubType;
|
||||
import seng302.gameServer.messages.YachtEventCodeMessage;
|
||||
import seng302.gameServer.messages.YachtEventType;
|
||||
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;
|
||||
|
||||
/**
|
||||
@@ -35,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() {
|
||||
@@ -95,37 +143,83 @@ 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 boats;
|
||||
}
|
||||
|
||||
return new XMLMessage(
|
||||
xmlGenerator.getBoatsAsXml(),
|
||||
XMLMessageSubType.BOAT,
|
||||
xmlGenerator.getBoatsAsXml().length());
|
||||
public static YachtEventCodeMessage makeCollisionMessage(ServerYacht serverYacht) {
|
||||
return new YachtEventCodeMessage(serverYacht.getSourceId(), YachtEventType.COLLISION);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a message to be sent out whenever a yacht picks up a boost
|
||||
*
|
||||
* @param serverYacht The yacht that has picked up a power up
|
||||
* @param token The token which they picked up
|
||||
* @return The corresponding YachtEventCodeMessage
|
||||
*/
|
||||
public static YachtEventCodeMessage makePickupMessage(ServerYacht serverYacht, Token token) {
|
||||
YachtEventType yachtEventType = null;
|
||||
switch (token.getTokenType()) {
|
||||
case BOOST:
|
||||
yachtEventType = YachtEventType.TOKEN_VELOCITY;
|
||||
break;
|
||||
case HANDLING:
|
||||
yachtEventType = YachtEventType.TOKEN_HANDLING;
|
||||
break;
|
||||
case WIND_WALKER:
|
||||
yachtEventType = YachtEventType.TOKEN_WIND_WALKER;
|
||||
break;
|
||||
case BUMPER:
|
||||
yachtEventType = YachtEventType.TOKEN_BUMPER;
|
||||
break;
|
||||
case RANDOM:
|
||||
yachtEventType = YachtEventType.TOKEN_RANDOM;
|
||||
break;
|
||||
}
|
||||
return new YachtEventCodeMessage(serverYacht.getSourceId(), yachtEventType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a message representing a certain buff / debuff for a given yacht. For now this is
|
||||
* just for the bumper debuff so the affected boat is aware that it has been crashed. This could
|
||||
* however be extended to render affects for all boats given a certain debuff.
|
||||
*
|
||||
* @param yacht The yacht affected by some status
|
||||
* @param token The token indicating what status they have
|
||||
* @return A YachtEventCodeMessage
|
||||
*/
|
||||
public static YachtEventCodeMessage makeStatusEffectMessage(ServerYacht yacht,
|
||||
TokenType token) {
|
||||
YachtEventType yachtEventType = null;
|
||||
switch (token) {
|
||||
case BUMPER:
|
||||
yachtEventType = YachtEventType.BUMPER_CRASH;
|
||||
break;
|
||||
}
|
||||
return new YachtEventCodeMessage(yacht.getSourceId(), yachtEventType);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a message to be sent out when a given yacht powers down (From a boost of any type)
|
||||
*
|
||||
* @param yacht The yacht that is powering down
|
||||
* @return A YachtEventCodeMessage representing this action
|
||||
*/
|
||||
public static YachtEventCodeMessage makePowerDownMessage(ServerYacht yacht) {
|
||||
return new YachtEventCodeMessage(yacht.getSourceId(), YachtEventType.POWER_DOWN);
|
||||
}
|
||||
|
||||
public static ChatterMessage makeChatterMessage(Integer messageType, String message) {
|
||||
return new ChatterMessage(messageType, "SERVER: " + message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,14 +13,28 @@ 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
|
||||
@@ -77,9 +79,11 @@ public class ServerToClientThread implements Runnable {
|
||||
private List<ConnectionListener> connectionListeners = new ArrayList<>();
|
||||
private DisconnectListener disconnectListener;
|
||||
|
||||
private ServerYacht yacht;
|
||||
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,33 +104,11 @@ public class ServerToClientThread implements Runnable {
|
||||
}
|
||||
|
||||
private void setUpPlayer(){
|
||||
BufferedReader fn;
|
||||
String fName = "";
|
||||
BufferedReader ln;
|
||||
String lName = "";
|
||||
|
||||
fn = new BufferedReader(
|
||||
new InputStreamReader(
|
||||
ServerToClientThread.class.getResourceAsStream(
|
||||
"/server_config/CSV_Database_of_First_Names.csv"
|
||||
)
|
||||
)
|
||||
);
|
||||
List<String> all = fn.lines().collect(Collectors.toList());
|
||||
fName = all.get(ThreadLocalRandom.current().nextInt(0, all.size()));
|
||||
ln = new BufferedReader(
|
||||
new InputStreamReader(
|
||||
ServerToClientThread.class.getResourceAsStream(
|
||||
"/server_config/CSV_Database_of_Last_Names.csv"
|
||||
)
|
||||
)
|
||||
);
|
||||
all = ln.lines().collect(Collectors.toList());
|
||||
lName = all.get(ThreadLocalRandom.current().nextInt(0, all.size()));
|
||||
String shortName = "P" + sourceId;
|
||||
String longName = "Player " + sourceId;
|
||||
|
||||
ServerYacht yacht = new ServerYacht(
|
||||
"Yacht", sourceId, sourceId.toString(), fName, fName + " " + lName, "NZ"
|
||||
);
|
||||
BoatMeshType.DINGHY, sourceId, sourceId.toString(), shortName, longName, "NZ");
|
||||
|
||||
player = new Player(socket, yacht);
|
||||
GameState.addYacht(sourceId, yacht);
|
||||
@@ -185,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);
|
||||
@@ -232,25 +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);
|
||||
|
||||
System.out.println(xmlGenerator.getRegatta().getName());
|
||||
|
||||
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,10 +300,6 @@ public class ServerToClientThread implements Runnable {
|
||||
return socket;
|
||||
}
|
||||
|
||||
public ServerYacht getYacht() {
|
||||
return yacht;
|
||||
}
|
||||
|
||||
public void addConnectionListener(ConnectionListener listener) {
|
||||
connectionListeners.add(listener);
|
||||
}
|
||||
@@ -346,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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,9 @@ public enum BoatAction {
|
||||
TACK_GYBE(4),
|
||||
UPWIND(5),
|
||||
DOWNWIND(6),
|
||||
MAINTAIN_HEADING(7);
|
||||
MAINTAIN_HEADING(7),
|
||||
CONTINUOUSLY_TURNING(8),
|
||||
DEFAULT_TURNING(9);
|
||||
|
||||
private final int type;
|
||||
private static final Map<Integer, BoatAction> intToTypeMap = new HashMap<>();
|
||||
|
||||
@@ -5,19 +5,19 @@ package seng302.gameServer.messages;
|
||||
*/
|
||||
public class BoatActionMessage extends Message{
|
||||
private final MessageType MESSAGE_TYPE = MessageType.BOAT_ACTION;
|
||||
private final int MESSAGE_SIZE = 1;
|
||||
private final int MESSAGE_SIZE = 5;
|
||||
private BoatAction actionType;
|
||||
|
||||
public BoatActionMessage(BoatAction actionType) {
|
||||
public BoatActionMessage(BoatAction actionType, int sourceId) {
|
||||
this.actionType = actionType;
|
||||
setHeader(new Header(MessageType.BOAT_ACTION, 0, (short) 1)); // the second variable is the source id
|
||||
setHeader(new Header(MessageType.BOAT_ACTION, sourceId, (short) MESSAGE_SIZE)); // the second variable is the source id
|
||||
allocateBuffer();
|
||||
writeHeaderToBuffer();
|
||||
// Write message fields
|
||||
putInt(actionType.getValue(), 1);
|
||||
putInt(sourceId, 4);
|
||||
writeCRC();
|
||||
rewind();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -40,7 +40,7 @@ public class ChatterMessage extends Message {
|
||||
return message;
|
||||
}
|
||||
|
||||
public int getMessage_type() {
|
||||
public int getMessageType() {
|
||||
return message_type;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -4,8 +4,8 @@ package seng302.gameServer.messages;
|
||||
public class RegistrationRequestMessage extends Message {
|
||||
private static int MESSAGE_LENGTH = 2;
|
||||
|
||||
public RegistrationRequestMessage(ClientType type){
|
||||
setHeader(new Header(MessageType.REGISTRATION_REQUEST, 1, (short) getSize()));
|
||||
public RegistrationRequestMessage(ClientType type, int clientID){
|
||||
setHeader(new Header(MessageType.REGISTRATION_REQUEST, clientID, (short) getSize()));
|
||||
|
||||
allocateBuffer();
|
||||
writeHeaderToBuffer();
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,11 +1,18 @@
|
||||
package seng302.gameServer.messages;
|
||||
|
||||
/**
|
||||
* Created by wmu16 on 11/09/17.
|
||||
* Enum for different event types for the yacht
|
||||
*/
|
||||
public enum YachtEventType {
|
||||
COLLISION(33),
|
||||
TOKEN(34);
|
||||
TOKEN_VELOCITY(34),
|
||||
TOKEN_BUMPER(35),
|
||||
TOKEN_HANDLING(36),
|
||||
TOKEN_WIND_WALKER(37),
|
||||
TOKEN_RANDOM(38),
|
||||
POWER_DOWN(39),
|
||||
BUMPER_CRASH(40);
|
||||
|
||||
|
||||
private int code;
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Observable;
|
||||
import java.util.Observer;
|
||||
import java.util.Timer;
|
||||
import javafx.beans.property.ReadOnlyDoubleProperty;
|
||||
import javafx.beans.property.ReadOnlyDoubleWrapper;
|
||||
import javafx.beans.property.ReadOnlyIntegerProperty;
|
||||
@@ -15,6 +16,9 @@ import javafx.beans.property.ReadOnlyLongWrapper;
|
||||
import javafx.scene.paint.Color;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import seng302.model.token.TokenType;
|
||||
import seng302.visualiser.fxObjects.assets_3D.BoatMeshType;
|
||||
import seng302.visualiser.fxObjects.assets_3D.BoatObject;
|
||||
|
||||
/**
|
||||
* Yacht class for the racing boat. <p> Class created to store more variables (eg. boat statuses)
|
||||
@@ -34,19 +38,40 @@ public class ClientYacht extends Observable {
|
||||
void notifyRounding(ClientYacht yacht, int legNumber);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface ColorChangeListener {
|
||||
|
||||
void notifyColorChange(ClientYacht yacht);
|
||||
}
|
||||
|
||||
//This notifies RaceViewController so it can display icon - wmu16
|
||||
@FunctionalInterface
|
||||
public interface PowerUpListener {
|
||||
void notifyPowerUp(ClientYacht yacht, TokenType tokenType);
|
||||
}
|
||||
|
||||
//This notifies RaceViewController so it can remove token icon - wmu16
|
||||
@FunctionalInterface
|
||||
public interface PowerDownListener {
|
||||
void notifyPowerDown(ClientYacht yacht);
|
||||
}
|
||||
|
||||
|
||||
|
||||
private Logger logger = LoggerFactory.getLogger(ClientYacht.class);
|
||||
|
||||
|
||||
private String boatType;
|
||||
private BoatMeshType boatType;
|
||||
private Integer sourceId;
|
||||
private String hullID; //matches HullNum in the XML spec.
|
||||
private String shortName;
|
||||
private String boatName;
|
||||
private String country;
|
||||
private Integer position;
|
||||
private TokenType powerUp;
|
||||
|
||||
private Long estimateTimeAtFinish;
|
||||
private Boolean sailIn = false;
|
||||
private Boolean sailIn = true;
|
||||
private Integer currentMarkSeqID = 0;
|
||||
private Long markRoundTime;
|
||||
private Long timeTillNext;
|
||||
@@ -56,15 +81,24 @@ public class ClientYacht extends Observable {
|
||||
private Integer boatStatus;
|
||||
private Double currentVelocity;
|
||||
|
||||
Timer t;
|
||||
|
||||
private BoatObject boatObject;
|
||||
|
||||
private List<YachtLocationListener> locationListeners = new ArrayList<>();
|
||||
private List<MarkRoundingListener> markRoundingListeners = new ArrayList<>();
|
||||
private List<PowerUpListener> powerUpListeners = new ArrayList<>();
|
||||
private List<PowerDownListener> powerDownListeners = new ArrayList<>();
|
||||
private List<ColorChangeListener> colorChangeListeners = new ArrayList<>();
|
||||
|
||||
private ReadOnlyDoubleWrapper velocityProperty = new ReadOnlyDoubleWrapper();
|
||||
private ReadOnlyLongWrapper timeTillNextProperty = new ReadOnlyLongWrapper();
|
||||
private ReadOnlyLongWrapper timeSinceLastMarkProperty = new ReadOnlyLongWrapper();
|
||||
private ReadOnlyIntegerWrapper placingProperty = new ReadOnlyIntegerWrapper();
|
||||
private ReadOnlyDoubleWrapper headingProperty = new ReadOnlyDoubleWrapper();
|
||||
private Color colour;
|
||||
|
||||
public ClientYacht(String boatType, Integer sourceId, String hullID, String shortName,
|
||||
public ClientYacht(BoatMeshType boatType, Integer sourceId, String hullID, String shortName,
|
||||
String boatName, String country) {
|
||||
this.boatType = boatType;
|
||||
this.sourceId = sourceId;
|
||||
@@ -74,6 +108,7 @@ public class ClientYacht extends Observable {
|
||||
this.country = country;
|
||||
this.location = new GeoPoint(57.670341, 11.826856);
|
||||
this.heading = 120.0; //In degrees
|
||||
this.headingProperty.set(this.heading);
|
||||
this.currentVelocity = 0d;
|
||||
this.boatStatus = 1;
|
||||
this.colour = Color.rgb(0, 0, 0, 1.0);
|
||||
@@ -88,7 +123,7 @@ public class ClientYacht extends Observable {
|
||||
super.addObserver(o);
|
||||
}
|
||||
|
||||
public String getBoatType() {
|
||||
public BoatMeshType getBoatType() {
|
||||
return boatType;
|
||||
}
|
||||
|
||||
@@ -199,6 +234,32 @@ public class ClientYacht extends Observable {
|
||||
this.position = position;
|
||||
}
|
||||
|
||||
/**
|
||||
* Powers down the boat and notifies the raceViewController to display
|
||||
*/
|
||||
public void powerDown() {
|
||||
this.powerUp = null;
|
||||
for (PowerDownListener listener : powerDownListeners) {
|
||||
listener.notifyPowerDown(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* powers up the boat and notifies the raceViewController to display
|
||||
*
|
||||
* @param tokenType The type of token that this boat is being powered up with
|
||||
*/
|
||||
public void setPowerUp(TokenType tokenType) {
|
||||
this.powerUp = tokenType;
|
||||
for (PowerUpListener listener : powerUpListeners) {
|
||||
listener.notifyPowerUp(this, tokenType);
|
||||
}
|
||||
}
|
||||
|
||||
public TokenType getPowerUp() {
|
||||
return powerUp;
|
||||
}
|
||||
|
||||
public void toggleSail() {
|
||||
sailIn = !sailIn;
|
||||
}
|
||||
@@ -221,6 +282,8 @@ public class ClientYacht extends Observable {
|
||||
|
||||
public void setHeading(Double heading) {
|
||||
this.heading = heading;
|
||||
System.out.println(heading);
|
||||
setHeadingProperty();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -247,12 +310,15 @@ public class ClientYacht extends Observable {
|
||||
|
||||
public void setColour(Color colour) {
|
||||
this.colour = colour;
|
||||
for (ColorChangeListener listener : colorChangeListeners) {
|
||||
listener.notifyColorChange(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void updateLocation(double lat, double lng, double heading, double velocity) {
|
||||
setLocation(lat, lng);
|
||||
this.heading = heading;
|
||||
setHeadingProperty();
|
||||
this.currentVelocity = velocity;
|
||||
updateVelocityProperty(velocity);
|
||||
for (YachtLocationListener yll : locationListeners) {
|
||||
@@ -260,6 +326,10 @@ public class ClientYacht extends Observable {
|
||||
}
|
||||
}
|
||||
|
||||
private void setHeadingProperty() {
|
||||
headingProperty.set(heading);
|
||||
}
|
||||
|
||||
public void addLocationListener(YachtLocationListener listener) {
|
||||
locationListeners.add(listener);
|
||||
}
|
||||
@@ -268,6 +338,18 @@ public class ClientYacht extends Observable {
|
||||
markRoundingListeners.add(listener);
|
||||
}
|
||||
|
||||
public void addPowerUpListener(PowerUpListener listener) {
|
||||
powerUpListeners.add(listener);
|
||||
}
|
||||
|
||||
public void addPowerDownListener(PowerDownListener listener) {
|
||||
powerDownListeners.add(listener);
|
||||
}
|
||||
|
||||
public void addColorChangeListener(ColorChangeListener listener) {
|
||||
colorChangeListeners.add(listener);
|
||||
}
|
||||
|
||||
public void removeMarkRoundingListener(MarkRoundingListener listener) {
|
||||
markRoundingListeners.remove(listener);
|
||||
}
|
||||
@@ -288,4 +370,17 @@ public class ClientYacht extends Observable {
|
||||
public Double getCurrentVelocity() {
|
||||
return currentVelocity;
|
||||
}
|
||||
|
||||
public void setBoatObject(BoatObject newBoatObject) {
|
||||
this.boatObject = newBoatObject;
|
||||
}
|
||||
|
||||
public BoatObject getBoatObject() {
|
||||
return this.boatObject;
|
||||
}
|
||||
|
||||
public ReadOnlyDoubleWrapper getHeadingProperty() {
|
||||
return headingProperty;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
package seng302.model;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import javafx.scene.input.KeyCode;
|
||||
|
||||
public class GameKeyBind {
|
||||
|
||||
private static GameKeyBind instance;
|
||||
private Map<KeyCode, KeyAction> keyToActionMap;
|
||||
private Map<KeyAction, KeyCode> actionToKeyMap;
|
||||
private Boolean continuouslyTurning;
|
||||
|
||||
|
||||
private GameKeyBind() {
|
||||
setToDefault();
|
||||
}
|
||||
|
||||
public void setToDefault() {
|
||||
actionToKeyMap = new HashMap<>();
|
||||
keyToActionMap = new HashMap<>();
|
||||
continuouslyTurning = false;
|
||||
// default key bindings
|
||||
ArrayList<KeyCode> keys = new ArrayList<>();
|
||||
keys.add(KeyCode.Z);
|
||||
keys.add(KeyCode.X);
|
||||
keys.add(KeyCode.SPACE);
|
||||
keys.add(KeyCode.SHIFT);
|
||||
keys.add(KeyCode.ENTER);
|
||||
keys.add(KeyCode.PAGE_UP);
|
||||
keys.add(KeyCode.PAGE_DOWN);
|
||||
keys.add(KeyCode.F1);
|
||||
keys.add(KeyCode.D);
|
||||
keys.add(KeyCode.A);
|
||||
keys.add(KeyCode.W);
|
||||
keys.add(KeyCode.S);
|
||||
for (int i = 0; i < 12; i++) {
|
||||
actionToKeyMap.put(KeyAction.getType(i + 1), keys.get(i));
|
||||
keyToActionMap.put(keys.get(i), KeyAction.getType(i + 1));
|
||||
}
|
||||
}
|
||||
|
||||
public static GameKeyBind getInstance() {
|
||||
if (instance == null) {
|
||||
instance = new GameKeyBind();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
public KeyCode getKeyCode(KeyAction keyAction) {
|
||||
return instance.actionToKeyMap.get(keyAction);
|
||||
}
|
||||
|
||||
public KeyAction getKeyAction(KeyCode keyCode) {
|
||||
return instance.keyToActionMap.get(keyCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds a key to a key action
|
||||
*
|
||||
* @return true if successfully bind
|
||||
*/
|
||||
public boolean bindKeyToAction(KeyCode keyCode, KeyAction keyAction) {
|
||||
if (instance.keyToActionMap.containsKey(keyCode)) {
|
||||
// if the key has been bound to other action, return false
|
||||
return false;
|
||||
} else {
|
||||
instance.keyToActionMap.put(keyCode, keyAction); // add key -> action
|
||||
KeyCode oldKeyCode = instance.actionToKeyMap
|
||||
.get(keyAction); // get old key for the action
|
||||
instance.keyToActionMap.remove(oldKeyCode); // remove the old key -> action
|
||||
instance.actionToKeyMap
|
||||
.replace(keyAction, keyCode); // replace the old key by the newer one
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public void toggleTurningMode() {
|
||||
continuouslyTurning = !continuouslyTurning;
|
||||
}
|
||||
|
||||
public Boolean isContinuouslyTurning() {
|
||||
return continuouslyTurning;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package seng302.model;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public enum KeyAction {
|
||||
ZOOM_IN(1),
|
||||
ZOOM_OUT(2),
|
||||
VMG(3),
|
||||
SAILS_STATE(4),
|
||||
TACK_GYBE(5),
|
||||
UPWIND(6),
|
||||
DOWNWIND(7),
|
||||
VIEW(8),
|
||||
RIGHT(9),
|
||||
LEFT(10),
|
||||
FORWARD(11),
|
||||
BACKWARD(12);
|
||||
|
||||
private final int type;
|
||||
private static final Map<Integer, KeyAction> intToTypeMap = new HashMap<>();
|
||||
|
||||
static {
|
||||
for (KeyAction type : KeyAction.values()) {
|
||||
intToTypeMap.put(type.getValue(), type);
|
||||
}
|
||||
}
|
||||
|
||||
KeyAction(int type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public static KeyAction getType(int value) {
|
||||
return intToTypeMap.get(value);
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return this.type;
|
||||
}
|
||||
}
|
||||
@@ -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() + "}";
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,9 @@ import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A static class for parsing and storing the polars. Will parse the whole polar table and also store the optimised
|
||||
@@ -17,6 +19,7 @@ public final class PolarTable {
|
||||
private static HashMap<Double, HashMap<Double, Double>> polarTable;
|
||||
private static HashMap<Double, HashMap<Double, Double>> upwindOptimal;
|
||||
private static HashMap<Double, HashMap<Double, Double>> downwindOptimal;
|
||||
private static Double optimalAngle;
|
||||
|
||||
private static int upTwaIndex;
|
||||
private static int dnTwaIndex;
|
||||
@@ -33,11 +36,13 @@ public final class PolarTable {
|
||||
upwindOptimal = new HashMap<>();
|
||||
downwindOptimal = new HashMap<>();
|
||||
|
||||
String line;
|
||||
String line = null;
|
||||
String check;
|
||||
Boolean isHeaderLine = true;
|
||||
|
||||
try (BufferedReader br = new BufferedReader(new InputStreamReader(polarFile))) {
|
||||
while ((line = br.readLine()) != null) {
|
||||
while ((check = br.readLine()) != null) {
|
||||
line = check;
|
||||
String[] thisLine = line.split(",");
|
||||
|
||||
//Initial line in file
|
||||
@@ -66,7 +71,10 @@ public final class PolarTable {
|
||||
upwindOptimal.put(thisWindSpeed, thisUpWindPolar);
|
||||
downwindOptimal.put(thisWindSpeed, thisDnWindPolar);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
getMaxSpeedAngle(line);
|
||||
|
||||
} catch (IOException e) {
|
||||
System.out.println("[PolarTable] IO exception");
|
||||
@@ -74,6 +82,27 @@ public final class PolarTable {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Passes the final line of the polar table and iterates over the speeds for each
|
||||
* angle, velocity pair to find the angle that produces the highest velocity
|
||||
*
|
||||
* @param line The last line of the polar file
|
||||
*/
|
||||
private static void getMaxSpeedAngle(String line) {
|
||||
String[] theLastLine = line.split(",");
|
||||
Double maxWindVal = Double.parseDouble(theLastLine[0]);
|
||||
Double optimalAngle = Double.parseDouble(theLastLine[1]);
|
||||
Double maxSpeed = Double.parseDouble(theLastLine[2]);
|
||||
for (Map.Entry<Double, Double> entry : polarTable.get(maxWindVal).entrySet()) {
|
||||
if (entry.getValue() > maxSpeed) {
|
||||
maxSpeed = entry.getValue();
|
||||
optimalAngle = entry.getKey();
|
||||
}
|
||||
}
|
||||
PolarTable.optimalAngle = optimalAngle;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Parses the header line of a polar file
|
||||
@@ -85,14 +114,18 @@ public final class PolarTable {
|
||||
String thisItem = thisLine[i];
|
||||
if (thisItem.toLowerCase().startsWith("uptwa")) {
|
||||
upTwaIndex = i;
|
||||
}
|
||||
else if (thisItem.toLowerCase().startsWith("dntwa")) {
|
||||
} else if (thisItem.toLowerCase().startsWith("dntwa")) {
|
||||
dnTwaIndex = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static Double getOptimalAngle() {
|
||||
return optimalAngle;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return The entire polar table
|
||||
*/
|
||||
|
||||
@@ -3,11 +3,8 @@ package seng302.model;
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Observable;
|
||||
import java.util.TimeZone;
|
||||
import javafx.beans.property.ReadOnlyDoubleProperty;
|
||||
import javafx.beans.property.ReadOnlyDoubleWrapper;
|
||||
@@ -34,8 +31,9 @@ public class RaceState {
|
||||
private ReadOnlyDoubleWrapper windDirection = new ReadOnlyDoubleWrapper();
|
||||
private long serverSystemTime;
|
||||
private long expectedStartTime;
|
||||
private boolean isRaceStarted = false;
|
||||
private boolean raceRunning = false;
|
||||
private boolean gunFired = false;
|
||||
private boolean raceFinished = false;
|
||||
long timeTillStart;
|
||||
private ObservableList<ClientYacht> playerPositions;
|
||||
private List<ClientYacht> collisions = new ArrayList<>();
|
||||
@@ -50,7 +48,7 @@ public class RaceState {
|
||||
this.windDirection.set(data.getWindDirection());
|
||||
this.serverSystemTime = data.getCurrentTime();
|
||||
this.expectedStartTime = data.getExpectedStartTime();
|
||||
this.isRaceStarted = data.isRaceStarted();
|
||||
this.raceRunning = data.isRaceStarted();
|
||||
}
|
||||
|
||||
public void setTimeZone (TimeZone timeZone) {
|
||||
@@ -95,9 +93,12 @@ public class RaceState {
|
||||
}
|
||||
|
||||
public boolean isRaceStarted () {
|
||||
return isRaceStarted;
|
||||
return raceRunning;
|
||||
}
|
||||
|
||||
public void setRaceStarted(Boolean value) {
|
||||
this.raceRunning = value;
|
||||
}
|
||||
public void setBoats(Collection<ClientYacht> clientYachts) {
|
||||
playerPositions.setAll(clientYachts);
|
||||
}
|
||||
@@ -125,4 +126,16 @@ public class RaceState {
|
||||
public void removeCollisionListener(CollisionListener collisionListener) {
|
||||
collisionListeners.remove(collisionListener);
|
||||
}
|
||||
|
||||
public void setRaceFinished() {
|
||||
raceFinished = true;
|
||||
}
|
||||
|
||||
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,5 +1,6 @@
|
||||
package seng302.model;
|
||||
|
||||
import java.util.HashMap;
|
||||
import javafx.scene.paint.Color;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -8,10 +9,7 @@ import seng302.gameServer.messages.BoatStatus;
|
||||
import seng302.model.mark.Mark;
|
||||
import seng302.model.token.TokenType;
|
||||
import seng302.utilities.GeoUtility;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Observable;
|
||||
import java.util.Observer;
|
||||
import seng302.visualiser.fxObjects.assets_3D.BoatMeshType;
|
||||
|
||||
/**
|
||||
* Yacht class for the racing boat. <p> Class created to store more variables (eg. boat statuses)
|
||||
@@ -20,12 +18,14 @@ import java.util.Observer;
|
||||
*/
|
||||
public class ServerYacht {
|
||||
|
||||
private Logger logger = LoggerFactory.getLogger(ClientYacht.class);
|
||||
|
||||
public static final Double TURN_STEP = 5.0;
|
||||
private Logger logger = LoggerFactory.getLogger(ServerYacht.class);
|
||||
|
||||
//Boat info
|
||||
private String boatType;
|
||||
private BoatMeshType boatType;
|
||||
private Double turnStep = 5.0;
|
||||
private Double boatTypeSpeedMultiplier = 1.0;
|
||||
private Double boatTypeTurnStepMultiplier = 1.0;
|
||||
private Double boatTypeAccelerationMultiplier = 1.0;
|
||||
private Integer sourceId;
|
||||
private String hullID; //matches HullNum in the XML spec.
|
||||
private String shortName;
|
||||
@@ -55,11 +55,15 @@ public class ServerYacht {
|
||||
//PowerUp
|
||||
private TokenType powerUp;
|
||||
private Long powerUpStartTime;
|
||||
private Double powerUpSpeedMultiplier;
|
||||
private Integer powerUpHandlingMultiplier;
|
||||
|
||||
//turning mode
|
||||
private Boolean continuouslyTurning;
|
||||
|
||||
public ServerYacht(String boatType, Integer sourceId, String hullID, String shortName,
|
||||
public ServerYacht(BoatMeshType boatType, Integer sourceId, String hullID, String shortName,
|
||||
String boatName, String country) {
|
||||
this.boatType = boatType;
|
||||
setBoatType(boatType);
|
||||
this.boatStatus = BoatStatus.PRESTART;
|
||||
this.sourceId = sourceId;
|
||||
this.hullID = hullID;
|
||||
@@ -76,10 +80,12 @@ public class ServerYacht {
|
||||
this.legNumber = 0;
|
||||
this.boatColor = Colors.getColor(sourceId - 1);
|
||||
this.powerUp = null;
|
||||
|
||||
this.powerUpSpeedMultiplier = 1d;
|
||||
this.powerUpHandlingMultiplier = 1;
|
||||
this.hasEnteredRoundingZone = false;
|
||||
this.hasPassedLine = false;
|
||||
this.hasPassedThroughGate = false;
|
||||
this.continuouslyTurning = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -106,13 +112,33 @@ public class ServerYacht {
|
||||
location = geoPoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Powers up a yacht with a given yacht, only after powering it down first to avoid double power
|
||||
* ups
|
||||
*
|
||||
* @param powerUp The given power up
|
||||
*/
|
||||
public void powerUp(TokenType powerUp) {
|
||||
powerDown();
|
||||
switch (powerUp) {
|
||||
case BOOST:
|
||||
powerUpSpeedMultiplier = GameState.VELOCITY_BOOST_MULTIPLIER;
|
||||
break;
|
||||
case HANDLING:
|
||||
powerUpHandlingMultiplier = GameState.HANDLING_BOOST_MULTIPLIER;
|
||||
break;
|
||||
}
|
||||
this.powerUp = powerUp;
|
||||
powerUpStartTime = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Powers down a yacht, returning its power multipliers back to 1
|
||||
*/
|
||||
public void powerDown() {
|
||||
this.powerUp = null;
|
||||
this.powerUpSpeedMultiplier = 1d;
|
||||
this.powerUpHandlingMultiplier = 1;
|
||||
}
|
||||
|
||||
public Long getPowerUpStartTime() {
|
||||
@@ -129,7 +155,7 @@ public class ServerYacht {
|
||||
* @param amount the amount by which to adjust the boat heading.
|
||||
*/
|
||||
public void adjustHeading(Double amount) {
|
||||
Double newVal = heading + amount;
|
||||
Double newVal = heading + amount * powerUpHandlingMultiplier * boatTypeTurnStepMultiplier;
|
||||
lastHeading = heading;
|
||||
heading = (double) Math.floorMod(newVal.longValue(), 360L);
|
||||
}
|
||||
@@ -152,11 +178,11 @@ public class ServerYacht {
|
||||
/**
|
||||
* Enables the boats auto pilot feature, which will move the boat towards a given heading.
|
||||
*
|
||||
* @param thisHeading The heading to move the boat towards.
|
||||
* @param newHeading The heading to move the boat towards.
|
||||
*/
|
||||
private void setAutoPilot(Double thisHeading) {
|
||||
private void setAutoPilot(Double newHeading) {
|
||||
isAuto = true;
|
||||
autoHeading = thisHeading;
|
||||
autoHeading = newHeading;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -174,8 +200,9 @@ public class ServerYacht {
|
||||
if (isAuto) {
|
||||
turnTowardsHeading(autoHeading);
|
||||
if (Math.abs(heading - autoHeading)
|
||||
<= TURN_STEP) { //Cancel when within 1 turn step of target.
|
||||
<= turnStep*1.5) {
|
||||
isAuto = false;
|
||||
setHeading(autoHeading);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -187,44 +214,52 @@ public class ServerYacht {
|
||||
public void turnUpwind() {
|
||||
disableAutoPilot();
|
||||
Double normalizedHeading = normalizeHeading();
|
||||
if (normalizedHeading == 0) {
|
||||
if (lastHeading < 180) {
|
||||
adjustHeading(-TURN_STEP);
|
||||
} else {
|
||||
adjustHeading(TURN_STEP);
|
||||
}
|
||||
} else if (normalizedHeading == 180) {
|
||||
if (lastHeading < 180) {
|
||||
adjustHeading(TURN_STEP);
|
||||
} else {
|
||||
adjustHeading(-TURN_STEP);
|
||||
}
|
||||
} else if (normalizedHeading < 180) {
|
||||
adjustHeading(-TURN_STEP);
|
||||
if (continuouslyTurning) {
|
||||
adjustHeading(turnStep);
|
||||
} else {
|
||||
adjustHeading(TURN_STEP);
|
||||
if (normalizedHeading == 0) {
|
||||
if (lastHeading < 180) {
|
||||
adjustHeading(-turnStep);
|
||||
} else {
|
||||
adjustHeading(turnStep);
|
||||
}
|
||||
} else if (normalizedHeading == 180) {
|
||||
if (lastHeading < 180) {
|
||||
adjustHeading(turnStep);
|
||||
} else {
|
||||
adjustHeading(-turnStep);
|
||||
}
|
||||
} else if (normalizedHeading < 180) {
|
||||
adjustHeading(-turnStep);
|
||||
} else {
|
||||
adjustHeading(turnStep);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void turnDownwind() {
|
||||
disableAutoPilot();
|
||||
Double normalizedHeading = normalizeHeading();
|
||||
if (normalizedHeading == 0) {
|
||||
if (lastHeading < 180) {
|
||||
adjustHeading(TURN_STEP);
|
||||
} else {
|
||||
adjustHeading(-TURN_STEP);
|
||||
}
|
||||
} else if (normalizedHeading == 180) {
|
||||
if (lastHeading < 180) {
|
||||
adjustHeading(-TURN_STEP);
|
||||
} else {
|
||||
adjustHeading(TURN_STEP);
|
||||
}
|
||||
} else if (normalizedHeading < 180) {
|
||||
adjustHeading(TURN_STEP);
|
||||
if (continuouslyTurning) {
|
||||
adjustHeading(-turnStep);
|
||||
} else {
|
||||
adjustHeading(-TURN_STEP);
|
||||
if (normalizedHeading == 0) {
|
||||
if (lastHeading < 180) {
|
||||
adjustHeading(turnStep);
|
||||
} else {
|
||||
adjustHeading(-turnStep);
|
||||
}
|
||||
} else if (normalizedHeading == 180) {
|
||||
if (lastHeading < 180) {
|
||||
adjustHeading(-turnStep);
|
||||
} else {
|
||||
adjustHeading(turnStep);
|
||||
}
|
||||
} else if (normalizedHeading < 180) {
|
||||
adjustHeading(turnStep);
|
||||
} else {
|
||||
adjustHeading(-turnStep);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -253,7 +288,7 @@ public class ServerYacht {
|
||||
|
||||
// Take optimal heading and turn into a boat heading rather than a wind heading.
|
||||
optimalHeading =
|
||||
optimalHeading + GameState.getWindDirection();
|
||||
(optimalHeading + GameState.getWindDirection()) % 360;
|
||||
|
||||
setAutoPilot(optimalHeading);
|
||||
}
|
||||
@@ -268,9 +303,9 @@ public class ServerYacht {
|
||||
private void turnTowardsHeading(Double newHeading) {
|
||||
Double newVal = heading - newHeading;
|
||||
if (Math.floorMod(newVal.longValue(), 360L) > 180) {
|
||||
adjustHeading(TURN_STEP / 5);
|
||||
adjustHeading(turnStep / 5);
|
||||
} else {
|
||||
adjustHeading(-TURN_STEP / 5);
|
||||
adjustHeading(-turnStep / 5);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -421,4 +456,43 @@ public class ServerYacht {
|
||||
return boatColor;
|
||||
}
|
||||
|
||||
public void setBoatType(BoatMeshType boatType) {
|
||||
this.boatTypeAccelerationMultiplier = boatType.accelerationMultiplier;
|
||||
this.boatTypeSpeedMultiplier = boatType.maxSpeedMultiplier;
|
||||
this.boatTypeTurnStepMultiplier = boatType.turnStep;
|
||||
this.boatType = boatType;
|
||||
}
|
||||
|
||||
public Double getBoatTypeSpeedMultiplier() {
|
||||
return boatTypeSpeedMultiplier;
|
||||
}
|
||||
|
||||
public Double getBoatTypeAccelerationMultiplier() {
|
||||
return boatTypeAccelerationMultiplier;
|
||||
}
|
||||
|
||||
|
||||
public BoatMeshType getBoatType() {
|
||||
return boatType;
|
||||
}
|
||||
|
||||
public void setContinuouslyTurning(Boolean continuouslyTurning) {
|
||||
this.continuouslyTurning = continuouslyTurning;
|
||||
}
|
||||
|
||||
public Double getPowerUpSpeedMultiplier() {
|
||||
return powerUpSpeedMultiplier;
|
||||
}
|
||||
|
||||
public void setPowerUpSpeedMultiplier(Double powerUpSpeedMultiplier) {
|
||||
this.powerUpSpeedMultiplier = powerUpSpeedMultiplier;
|
||||
}
|
||||
|
||||
public Integer getPowerUpHandlingMultiplier() {
|
||||
return powerUpHandlingMultiplier;
|
||||
}
|
||||
|
||||
public void setPowerUpHandlingMultiplier(Integer powerUpHandlingMultiplier) {
|
||||
this.powerUpHandlingMultiplier = powerUpHandlingMultiplier;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,34 +1,33 @@
|
||||
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.
|
||||
*/
|
||||
public class MarkOrder {
|
||||
private List<CompoundMark> raceMarkOrder;
|
||||
private List<CompoundMark> orderedUniqueCompoundMarks;
|
||||
private Logger logger = LoggerFactory.getLogger(MarkOrder.class);
|
||||
private Set<Mark> allMarks;
|
||||
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());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -40,10 +39,13 @@ public class MarkOrder {
|
||||
logger.warn("Race order accessed but not instantiated");
|
||||
return null;
|
||||
}
|
||||
|
||||
return Collections.unmodifiableList(raceMarkOrder);
|
||||
}
|
||||
|
||||
public List<CompoundMark> getOrderedUniqueCompoundMarks() {
|
||||
return orderedUniqueCompoundMarks;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param seqID The seqID of the current mark the boat is heading to
|
||||
* @return A Boolean indicating if this coming mark is the last one (finish line)
|
||||
@@ -74,70 +76,4 @@ public class MarkOrder {
|
||||
public CompoundMark getNextMark(Integer currentSeqID) throws IndexOutOfBoundsException {
|
||||
return raceMarkOrder.get(currentSeqID + 1);
|
||||
}
|
||||
|
||||
public Set<Mark> getAllMarks(){
|
||||
return Collections.unmodifiableSet(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 HashSet<>();
|
||||
|
||||
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();
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
package seng302.model.token;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import seng302.model.GeoPoint;
|
||||
|
||||
/**
|
||||
@@ -9,13 +13,50 @@ import seng302.model.GeoPoint;
|
||||
public class Token extends GeoPoint {
|
||||
|
||||
private TokenType tokenType;
|
||||
private Random random = new Random();
|
||||
|
||||
//Constructor for creating a specific type client side
|
||||
public Token(TokenType tokenType, double lat, double lng) {
|
||||
super(lat, lng);
|
||||
this.tokenType = tokenType;
|
||||
}
|
||||
|
||||
//Making random type server side
|
||||
public Token(double lat, double lng) {
|
||||
super(lat, lng);
|
||||
assignRandomType();
|
||||
}
|
||||
|
||||
//Making random type server side
|
||||
public Token(GeoPoint geoPoint) {
|
||||
super(geoPoint.getLat(), geoPoint.getLng());
|
||||
assignRandomType();
|
||||
}
|
||||
|
||||
public TokenType getTokenType() {
|
||||
return tokenType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns a random type to the token (including the random type token)
|
||||
*/
|
||||
public void assignRandomType() {
|
||||
tokenType = TokenType.values()[random.nextInt(TokenType.values().length)];
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns a random, concrete type to the token (cannot be the random type)
|
||||
*/
|
||||
public void realiseRandom() {
|
||||
List<TokenType> tokenTypeList = new ArrayList<>(Arrays.asList(TokenType.values()));
|
||||
tokenTypeList.remove(TokenType.RANDOM);
|
||||
tokenType = tokenTypeList.get(random.nextInt(tokenTypeList.size()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Exists for testing purposes only
|
||||
*/
|
||||
public void assignType(TokenType tokenType) {
|
||||
this.tokenType = tokenType;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,27 +5,32 @@ package seng302.model.token;
|
||||
* Created by wmu16 on 28/08/17.
|
||||
*/
|
||||
public enum TokenType {
|
||||
BOOST(0),
|
||||
HANDLING(1);
|
||||
|
||||
BOOST(0, "Boost", 10_000),
|
||||
HANDLING(1, "Handling", 10_000),
|
||||
BUMPER(2, "Bumper", 10_000),
|
||||
WIND_WALKER(3, "Wind Walker", 10_000),
|
||||
RANDOM(4, "Random", 10_000);
|
||||
|
||||
private int value;
|
||||
private String name;
|
||||
private int timeout;
|
||||
|
||||
TokenType(int value) {
|
||||
TokenType(int value, String name, int timeout) {
|
||||
this.value = value;
|
||||
this.name = name;
|
||||
this.timeout = timeout;
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public static TokenType getToken(int value) {
|
||||
switch (value) {
|
||||
case 0:
|
||||
return BOOST;
|
||||
case 1:
|
||||
return HANDLING;
|
||||
default:
|
||||
return BOOST;
|
||||
}
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public int getTimeout() {
|
||||
return timeout;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
package seng302.utilities;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import seng302.model.GeoPoint;
|
||||
import seng302.model.mark.CompoundMark;
|
||||
import seng302.model.token.Token;
|
||||
|
||||
/**
|
||||
* A class for generating and spawning tokens in random locations
|
||||
* Created by wmu16 on 27/09/17.
|
||||
*/
|
||||
public class RandomSpawn {
|
||||
|
||||
private static final Integer DEGREES_IN_CIRCLE = 360;
|
||||
|
||||
private HashMap<GeoPoint, Double> spawnRadii;
|
||||
private Object[] spawnCentres;
|
||||
private Random random;
|
||||
|
||||
/**
|
||||
* @param markOrder this must be the ORDERED list of marks. Better yet UNIQUE to avoid over
|
||||
* computation
|
||||
*/
|
||||
public RandomSpawn(List<CompoundMark> markOrder) {
|
||||
this.spawnRadii = new HashMap<>();
|
||||
random = new Random();
|
||||
|
||||
spawnRadii = generateSpawnRadii(markOrder);
|
||||
spawnCentres = spawnRadii.keySet().toArray();
|
||||
}
|
||||
|
||||
private HashMap<GeoPoint, Double> generateSpawnRadii(List<CompoundMark> markOrder) {
|
||||
HashMap<GeoPoint, Double> spawnRadii = new HashMap<>();
|
||||
for (int i = 0; i < markOrder.size() - 1; i++) {
|
||||
GeoPoint spawnCentre = GeoUtility.getDirtyMidPoint(
|
||||
markOrder.get(i).getMidPoint(),
|
||||
markOrder.get(i + 1).getMidPoint());
|
||||
|
||||
Double distance = GeoUtility.getDistance(spawnCentre, markOrder.get(i).getMidPoint());
|
||||
spawnRadii.put(spawnCentre, distance);
|
||||
}
|
||||
|
||||
return spawnRadii;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return A random token type at a random location in a random radii of the set of possible
|
||||
* radii
|
||||
*/
|
||||
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;
|
||||
GeoPoint randomLocation = GeoUtility
|
||||
.getGeoCoordinate(randomSpawnCentre, randomAngle, randomDistance);
|
||||
return new Token(randomLocation);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -14,8 +14,10 @@ public class Sounds {
|
||||
private static MediaPlayer soundEffect;
|
||||
private static MediaPlayer soundPlayer;
|
||||
private static MediaPlayer hoverSoundPlayer;
|
||||
private static MediaPlayer crashSoundPlayer;
|
||||
|
||||
private static boolean hoverInitialized = false;
|
||||
private static boolean crashInitialized = false;
|
||||
private static boolean musicMuted = false;
|
||||
private static boolean soundEffectsMuted = false;
|
||||
|
||||
@@ -99,6 +101,10 @@ public class Sounds {
|
||||
musicPlayer.setCycleCount(MediaPlayer.INDEFINITE);
|
||||
musicPlayer.setVolume(0.3);
|
||||
musicPlayer.play();
|
||||
musicPlayer.setMute(musicMuted);
|
||||
if (soundEffect != null) {
|
||||
soundEffect.stop();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -155,11 +161,17 @@ public class Sounds {
|
||||
|
||||
public static void playCrashSound() {
|
||||
if (!soundEffectsMuted) {
|
||||
Media crashSound = new Media(
|
||||
Sounds.class.getClassLoader().getResource("sounds/Large-metal-door-slam.mp3")
|
||||
.toString());
|
||||
soundPlayer = new MediaPlayer(crashSound);
|
||||
soundPlayer.play();
|
||||
if (!crashInitialized) {
|
||||
Media pickupSound = new Media(
|
||||
Sounds.class.getClassLoader().getResource("sounds/Large-metal-door-slam.mp3")
|
||||
.toString());
|
||||
crashSoundPlayer = new MediaPlayer(pickupSound);
|
||||
crashInitialized = true;
|
||||
}
|
||||
if (crashSoundPlayer != null) {
|
||||
crashSoundPlayer.stop();
|
||||
}
|
||||
crashSoundPlayer.play();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,10 +188,10 @@ public class Sounds {
|
||||
public static void playHoverSound() {
|
||||
if (!soundEffectsMuted) {
|
||||
if (!hoverInitialized) {
|
||||
Media crashSound = new Media(
|
||||
Media hoverSound = new Media(
|
||||
Sounds.class.getClassLoader().getResource("sounds/Error-sound-effect.mp3")
|
||||
.toString());
|
||||
hoverSoundPlayer = new MediaPlayer(crashSound);
|
||||
hoverSoundPlayer = new MediaPlayer(hoverSound);
|
||||
hoverInitialized = true;
|
||||
}
|
||||
hoverSoundPlayer.setVolume(0.5);
|
||||
|
||||
@@ -2,7 +2,6 @@ package seng302.utilities;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
@@ -15,8 +14,12 @@ import org.xml.sax.InputSource;
|
||||
import org.xml.sax.SAXException;
|
||||
import seng302.model.stream.packets.PacketType;
|
||||
import seng302.model.stream.packets.StreamPacket;
|
||||
import seng302.model.stream.parser.*;
|
||||
import seng302.model.stream.parser.MarkRoundingData;
|
||||
import seng302.model.stream.parser.PositionUpdateData;
|
||||
import seng302.model.stream.parser.PositionUpdateData.DeviceType;
|
||||
import seng302.model.stream.parser.RaceStartData;
|
||||
import seng302.model.stream.parser.RaceStatusData;
|
||||
import seng302.model.stream.parser.YachtEventData;
|
||||
|
||||
/**
|
||||
* StreamParser is a utilities class for taking byte data, formatted according to the AC35 streaming
|
||||
@@ -37,7 +40,6 @@ public class StreamParser {
|
||||
return null;
|
||||
}
|
||||
long heartbeat = bytesToLong(packet.getPayload());
|
||||
System.out.println("heartbeat = " + heartbeat);
|
||||
return heartbeat;
|
||||
}
|
||||
|
||||
@@ -134,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,29 +1,44 @@
|
||||
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;
|
||||
import seng302.model.token.TokenType;
|
||||
import seng302.visualiser.fxObjects.assets_3D.BoatMeshType;
|
||||
|
||||
/**
|
||||
* Utilities for parsing XML documents
|
||||
*/
|
||||
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.
|
||||
*
|
||||
@@ -34,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;
|
||||
}
|
||||
@@ -66,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;
|
||||
}
|
||||
@@ -138,17 +153,27 @@ public class XMLParser {
|
||||
Node currentBoat = boatsList.item(i);
|
||||
if (currentBoat.getNodeName().equals("Boat")) {
|
||||
// Boat boat = new Boat(currentBoat);
|
||||
BoatMeshType boatMeshType;
|
||||
try {
|
||||
boatMeshType = BoatMeshType.valueOf(XMLParser.getNodeAttributeString(currentBoat, "Type"));
|
||||
} catch (IllegalArgumentException e){
|
||||
boatMeshType = BoatMeshType.DINGHY;
|
||||
}
|
||||
Color color;
|
||||
try {
|
||||
color = Color.web(getNodeAttributeString(currentBoat, "Color"));
|
||||
} catch (NullPointerException npe) {
|
||||
color = Colors.getColor(new Random().nextInt(8));
|
||||
}
|
||||
ClientYacht yacht = new ClientYacht(
|
||||
XMLParser.getNodeAttributeString(currentBoat, "Type"),
|
||||
boatMeshType,
|
||||
XMLParser.getNodeAttributeInt(currentBoat, "SourceID"),
|
||||
XMLParser.getNodeAttributeString(currentBoat, "HullNum"),
|
||||
XMLParser.getNodeAttributeString(currentBoat, "ShortName"),
|
||||
XMLParser.getNodeAttributeString(currentBoat, "BoatName"),
|
||||
XMLParser.getNodeAttributeString(currentBoat, "Country"));
|
||||
yacht.setColour(Color.web(getNodeAttributeString(currentBoat, "Color")));
|
||||
if (yacht.getBoatType().equals("Yacht")) {
|
||||
competingBoats.put(yacht.getSourceId(), yacht);
|
||||
}
|
||||
yacht.setColour(color);
|
||||
competingBoats.put(yacht.getSourceId(), yacht);
|
||||
}
|
||||
}
|
||||
return competingBoats;
|
||||
@@ -174,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
|
||||
*
|
||||
@@ -183,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),
|
||||
@@ -196,17 +251,20 @@ public class XMLParser {
|
||||
*/
|
||||
private static List<Token> extractTokens(Element docEle) {
|
||||
List<Token> tokens = new ArrayList<>();
|
||||
NodeList tokenList = docEle.getElementsByTagName("Tokens").item(0).getChildNodes();
|
||||
for (int i = 0; i < tokenList.getLength(); i++) {
|
||||
Node tokenNode = tokenList.item(i);
|
||||
if (tokenNode.getNodeName().equals("Token")) {
|
||||
String tokenType = getNodeAttributeString(tokenNode, "TokenType");
|
||||
Double lat = getNodeAttributeDouble(tokenNode, "TargetLat");
|
||||
Double lng = getNodeAttributeDouble(tokenNode, "TargetLng");
|
||||
tokens.add(new Token(TokenType.valueOf(tokenType), lat, lng));
|
||||
try {
|
||||
NodeList tokenList = docEle.getElementsByTagName("Tokens").item(0).getChildNodes();
|
||||
for (int i = 0; i < tokenList.getLength(); i++) {
|
||||
Node tokenNode = tokenList.item(i);
|
||||
if (tokenNode.getNodeName().equals("Token")) {
|
||||
String tokenType = getNodeAttributeString(tokenNode, "TokenType");
|
||||
Double lat = getNodeAttributeDouble(tokenNode, "TargetLat");
|
||||
Double lng = getNodeAttributeDouble(tokenNode, "TargetLng");
|
||||
tokens.add(new Token(TokenType.valueOf(tokenType), lat, lng));
|
||||
}
|
||||
}
|
||||
} catch (NullPointerException npe) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
return tokens;
|
||||
}
|
||||
|
||||
@@ -219,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;
|
||||
@@ -257,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++) {
|
||||
@@ -279,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);
|
||||
}
|
||||
@@ -303,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,16 +193,22 @@ 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
|
||||
*/
|
||||
private void sendRegistrationRequest() {
|
||||
RegistrationRequestMessage requestMessage = new RegistrationRequestMessage(ClientType.PLAYER);
|
||||
RegistrationRequestMessage requestMessage = new RegistrationRequestMessage(ClientType.PLAYER, clientId);
|
||||
|
||||
try {
|
||||
os.write(requestMessage.getBuffer());
|
||||
@@ -191,9 +224,8 @@ 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);
|
||||
|
||||
if (status.equals(RegistrationResponseStatus.SUCCESS_PLAYING)){
|
||||
@@ -211,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();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -243,7 +277,7 @@ public class ClientToServerThread implements Runnable {
|
||||
new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
sendBoatActionMessage(new BoatActionMessage(BoatAction.DOWNWIND));
|
||||
sendBoatActionMessage(new BoatActionMessage(BoatAction.DOWNWIND, clientId));
|
||||
}
|
||||
}, 0, PACKET_SENDING_INTERVAL_MS
|
||||
);
|
||||
@@ -256,14 +290,14 @@ public class ClientToServerThread implements Runnable {
|
||||
new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
sendBoatActionMessage(new BoatActionMessage(BoatAction.UPWIND));
|
||||
sendBoatActionMessage(new BoatActionMessage(BoatAction.UPWIND, clientId));
|
||||
}
|
||||
}, 0, PACKET_SENDING_INTERVAL_MS
|
||||
);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
sendBoatActionMessage(new BoatActionMessage(actionType));
|
||||
sendBoatActionMessage(new BoatActionMessage(actionType, clientId));
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -319,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) {
|
||||
@@ -327,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 {
|
||||
@@ -346,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;
|
||||
}
|
||||
@@ -369,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,21 +5,17 @@ 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;
|
||||
import java.util.TimerTask;
|
||||
import javafx.application.Platform;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.Alert.AlertType;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.util.Pair;
|
||||
import seng302.gameServer.GameStages;
|
||||
import seng302.gameServer.GameState;
|
||||
@@ -29,6 +25,8 @@ import seng302.gameServer.messages.BoatAction;
|
||||
import seng302.gameServer.messages.BoatStatus;
|
||||
import seng302.gameServer.messages.YachtEventType;
|
||||
import seng302.model.ClientYacht;
|
||||
import seng302.model.GameKeyBind;
|
||||
import seng302.model.KeyAction;
|
||||
import seng302.model.RaceState;
|
||||
import seng302.model.stream.packets.StreamPacket;
|
||||
import seng302.model.stream.parser.MarkRoundingData;
|
||||
@@ -38,14 +36,15 @@ import seng302.model.stream.parser.RaceStatusData;
|
||||
import seng302.model.stream.parser.YachtEventData;
|
||||
import seng302.model.stream.xml.parser.RaceXMLData;
|
||||
import seng302.model.stream.xml.parser.RegattaXMLData;
|
||||
import seng302.model.token.TokenType;
|
||||
import seng302.utilities.Sounds;
|
||||
import seng302.utilities.StreamParser;
|
||||
import seng302.utilities.XMLGenerator;
|
||||
import seng302.utilities.XMLParser;
|
||||
import seng302.visualiser.controllers.FinishScreenViewController;
|
||||
import seng302.visualiser.controllers.LobbyController;
|
||||
import seng302.visualiser.controllers.RaceViewController;
|
||||
import seng302.visualiser.controllers.ViewManager;
|
||||
import seng302.visualiser.controllers.dialogs.PopupDialogController;
|
||||
|
||||
/**
|
||||
* This class is a client side instance of a yacht racing game in JavaFX. The game is instantiated
|
||||
@@ -53,7 +52,6 @@ import seng302.visualiser.controllers.ViewManager;
|
||||
*/
|
||||
public class GameClient {
|
||||
|
||||
private Pane holderPane;
|
||||
private ClientToServerThread socketThread;
|
||||
private MainServerThread server;
|
||||
|
||||
@@ -64,29 +62,20 @@ public class GameClient {
|
||||
private RaceXMLData courseData;
|
||||
private RaceState raceState = new RaceState();
|
||||
private LobbyController lobbyController;
|
||||
private RaceViewController raceViewController;
|
||||
|
||||
private ArrayList<ClientYacht> finishedBoats = new ArrayList<>();
|
||||
|
||||
private GameKeyBind gameKeyBind; // all the key binding setting.
|
||||
|
||||
private ObservableList<String> clientLobbyList = FXCollections.observableArrayList();
|
||||
|
||||
/**
|
||||
* 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;
|
||||
// if (holderPane.getParent() == null) {
|
||||
// this.holderPane.parentProperty().addListener(((observable, oldValue, newValue) -> {
|
||||
// if (newValue != null) {
|
||||
// newValue.getScene().setOnKeyPressed(this::keyPressed);
|
||||
// newValue.getScene().setOnKeyReleased(this::keyReleased);
|
||||
// }
|
||||
// }));
|
||||
// } else {
|
||||
// this.holderPane.getParent().getScene().setOnKeyPressed(this::keyPressed);
|
||||
// this.holderPane.getParent().getScene().setOnKeyReleased(this::keyReleased);
|
||||
// }
|
||||
public GameClient() {
|
||||
this.gameKeyBind = GameKeyBind.getInstance();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -94,77 +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());
|
||||
|
||||
// TODO disable ready button;
|
||||
getServerThread().setConnectionErrorListener((eMessage) -> ViewManager.getInstance().showErrorSnackBar(eMessage));
|
||||
|
||||
//LobbyController_old lobbyController = loadLobby();
|
||||
//lobbyController.setSocketThread(socketThread);
|
||||
//lobbyController.setPlayerID(socketThread.getClientId());
|
||||
//lobbyController.setPlayerListSource(clientLobbyList);
|
||||
//lobbyController.disableReadyButton();
|
||||
|
||||
|
||||
|
||||
// lobbyController.addCloseListener((exitCause) -> this.loadStartScreen());
|
||||
this.lobbyController = ViewManager.getInstance().goToLobby(true);
|
||||
|
||||
} catch (IOException ioe) {
|
||||
showConnectionError("Unable to find server");
|
||||
//Platform.runLater(this::loadStartScreen);
|
||||
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() {
|
||||
@@ -175,23 +192,15 @@ 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(() -> {
|
||||
Alert alert = new Alert(AlertType.ERROR);
|
||||
alert.setHeaderText("Connection Error");
|
||||
alert.setContentText(message);
|
||||
alert.showAndWait();
|
||||
PopupDialogController controller = ViewManager.getInstance().showPopupDialog();
|
||||
controller.setHeader("Oops");
|
||||
controller.setContent(message);
|
||||
controller.setOptionButtonText("GO HOME");
|
||||
controller
|
||||
.setOptionButtonEventHandler(event -> ViewManager.getInstance().goToStartView());
|
||||
});
|
||||
}
|
||||
|
||||
@@ -200,41 +209,8 @@ public class GameClient {
|
||||
socketThread.addStreamObserver(this::parsePackets);
|
||||
}
|
||||
|
||||
private void loadRaceView() {
|
||||
FXMLLoader fxmlLoader = loadFXMLToHolder("/views/RaceView.fxml");
|
||||
holderPane.getScene().setOnKeyPressed(this::keyPressed);
|
||||
holderPane.getScene().setOnKeyReleased(this::keyReleased);
|
||||
raceView = fxmlLoader.getController();
|
||||
ClientYacht player = allBoatsMap.get(socketThread.getClientId());
|
||||
raceView.loadRace(allBoatsMap, courseData, raceState, player);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void loadFinishScreenView() {
|
||||
Sounds.stopMusic();
|
||||
Sounds.stopSoundEffects();
|
||||
Sounds.playFinishMusic();
|
||||
FXMLLoader fxmlLoader = loadFXMLToHolder("/views/FinishScreenView.fxml");
|
||||
FinishScreenViewController controller = fxmlLoader.getController();
|
||||
controller.setFinishers(raceState.getPlayerPositions());
|
||||
}
|
||||
|
||||
private FXMLLoader loadFXMLToHolder(String fxmlLocation) {
|
||||
FXMLLoader fxmlLoader = new FXMLLoader(
|
||||
getClass().getResource(fxmlLocation)
|
||||
);
|
||||
try {
|
||||
final Node fxmlLoaderFX = fxmlLoader.load();
|
||||
Platform.runLater(() -> {
|
||||
holderPane.getChildren().clear();
|
||||
holderPane.getChildren().add(fxmlLoaderFX);
|
||||
});
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return fxmlLoader;
|
||||
public void setRaceViewController(RaceViewController controller) {
|
||||
this.raceViewController = controller;
|
||||
}
|
||||
|
||||
private void parsePackets() {
|
||||
@@ -302,21 +278,18 @@ public class GameClient {
|
||||
break;
|
||||
|
||||
case YACHT_EVENT_CODE:
|
||||
YachtEventData yachtEventData = StreamParser.extractYachtEventCode(packet);
|
||||
if (yachtEventData.getEventId() == YachtEventType.COLLISION.getCode()) {
|
||||
showCollisionAlert(StreamParser.extractYachtEventCode(packet));
|
||||
} else if (yachtEventData.getEventId() == YachtEventType.TOKEN.getCode()) {
|
||||
showPickUp();
|
||||
}
|
||||
processYachtEvent(StreamParser.extractYachtEventCode(packet));
|
||||
break;
|
||||
|
||||
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()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -327,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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -351,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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -390,17 +363,16 @@ 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) {
|
||||
if (raceFinished && !raceState.getRaceFinished()) {
|
||||
raceState.setRaceFinished();
|
||||
Sounds.playFinishSound();
|
||||
close();
|
||||
loadFinishScreenView();
|
||||
raceViewController.showFinishDialog(finishedBoats);
|
||||
// close();
|
||||
// ViewManager.getInstance().getGameClient().stopGame();
|
||||
//loadFinishScreenView();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -412,11 +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
|
||||
@@ -428,16 +395,16 @@ public class GameClient {
|
||||
}
|
||||
return;
|
||||
}
|
||||
switch (e.getCode()) {
|
||||
case SPACE: // align with vmg
|
||||
socketThread.sendBoatAction(BoatAction.VMG); break;
|
||||
case PAGE_UP: // upwind
|
||||
socketThread.sendBoatAction(BoatAction.UPWIND); break;
|
||||
case PAGE_DOWN: // downwind
|
||||
socketThread.sendBoatAction(BoatAction.DOWNWIND); break;
|
||||
case ENTER: // tack/gybe
|
||||
// if chat box is active take whatever is in there and send it to server
|
||||
socketThread.sendBoatAction(BoatAction.TACK_GYBE); break;
|
||||
|
||||
if (gameKeyBind.getKeyCode(KeyAction.VMG) == e.getCode()) { // align with vmg
|
||||
socketThread.sendBoatAction(BoatAction.VMG);
|
||||
} else if (gameKeyBind.getKeyCode(KeyAction.UPWIND) == e.getCode()) { // upwind
|
||||
socketThread.sendBoatAction(BoatAction.UPWIND);
|
||||
} else if (gameKeyBind.getKeyCode(KeyAction.DOWNWIND) == e.getCode()) { // downwind
|
||||
socketThread.sendBoatAction(BoatAction.DOWNWIND);
|
||||
} else if (gameKeyBind.getKeyCode(KeyAction.TACK_GYBE) == e.getCode()) { // tack/gybe
|
||||
// if chat box is active take whatever is in there and send it to server
|
||||
socketThread.sendBoatAction(BoatAction.TACK_GYBE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -446,15 +413,17 @@ public class GameClient {
|
||||
if (raceView.isChatInputFocused()) {
|
||||
return;
|
||||
}
|
||||
switch (e.getCode()) {
|
||||
//TODO 12/07/17 Determine the sail state and send the appropriate packet (eg. if sails are in, send a sail out packet)
|
||||
case SHIFT: // sails in/sails out
|
||||
|
||||
if (gameKeyBind.getKeyCode(KeyAction.SAILS_STATE) == e.getCode()) { // sails in/sails out
|
||||
if (allBoatsMap.get(socketThread.getClientId()).getSailIn()) {
|
||||
socketThread.sendBoatAction(BoatAction.SAILS_OUT);
|
||||
} else {
|
||||
socketThread.sendBoatAction(BoatAction.SAILS_IN);
|
||||
allBoatsMap.get(socketThread.getClientId()).toggleSail();
|
||||
break;
|
||||
case PAGE_UP:
|
||||
case PAGE_DOWN:
|
||||
socketThread.sendBoatAction(BoatAction.MAINTAIN_HEADING); break;
|
||||
}
|
||||
allBoatsMap.get(socketThread.getClientId()).toggleSail();
|
||||
} else if (gameKeyBind.getKeyCode(KeyAction.UPWIND) == e.getCode()
|
||||
|| gameKeyBind.getKeyCode(KeyAction.DOWNWIND) == e.getCode()) {
|
||||
socketThread.sendBoatAction(BoatAction.MAINTAIN_HEADING);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -462,21 +431,66 @@ public class GameClient {
|
||||
return courseData;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Appropriately displays the event client side given the YachtEventCode (collision / token..)
|
||||
*
|
||||
* @param yachtEventData The YachtEvent data packet
|
||||
*/
|
||||
private void processYachtEvent(YachtEventData yachtEventData) {
|
||||
ClientYacht thisYacht = allBoatsMap.get(yachtEventData.getSubjectId().intValue());
|
||||
|
||||
if (yachtEventData.getEventId() == YachtEventType.COLLISION.getCode()) {
|
||||
showCollisionAlert(thisYacht);
|
||||
} else if (yachtEventData.getEventId() == YachtEventType.POWER_DOWN.getCode()) {
|
||||
thisYacht.powerDown();
|
||||
Sounds.playTokenPickupSound(); // TODO: 23/09/17 This should be power down sound
|
||||
} else if (yachtEventData.getEventId() == YachtEventType.BUMPER_CRASH.getCode()) {
|
||||
showDisableAlert(thisYacht);
|
||||
} else { //Else all token pickup types
|
||||
TokenType tokenType = null;
|
||||
if (yachtEventData.getEventId() == YachtEventType.TOKEN_VELOCITY.getCode()) {
|
||||
tokenType = TokenType.BOOST;
|
||||
} else if (yachtEventData.getEventId() == YachtEventType.TOKEN_BUMPER.getCode()) {
|
||||
tokenType = TokenType.BUMPER;
|
||||
} else if (yachtEventData.getEventId() == YachtEventType.TOKEN_HANDLING.getCode()) {
|
||||
tokenType = TokenType.HANDLING;
|
||||
} else if (yachtEventData.getEventId() == YachtEventType.TOKEN_RANDOM.getCode()) {
|
||||
tokenType = TokenType.RANDOM;
|
||||
} else if (yachtEventData.getEventId() == YachtEventType.TOKEN_WIND_WALKER.getCode()) {
|
||||
tokenType = TokenType.WIND_WALKER;
|
||||
}
|
||||
|
||||
Sounds.playTokenPickupSound();
|
||||
thisYacht.setPowerUp(tokenType);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Turns a disabled boat black until the bumper affect wears off
|
||||
*
|
||||
* @param yacht The yacht to show as disabled
|
||||
*/
|
||||
private void showDisableAlert(ClientYacht yacht) {
|
||||
Color originalColor = yacht.getColour();
|
||||
yacht.setColour(Color.BLACK);
|
||||
|
||||
Timer disableTimer = new Timer("Disable Timer");
|
||||
disableTimer.schedule(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
yacht.setColour(originalColor);
|
||||
}
|
||||
}, GameState.BUMPER_DISABLE_TIME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells race view to show a collision animation.
|
||||
*/
|
||||
private void showCollisionAlert(YachtEventData yachtEventData) {
|
||||
private void showCollisionAlert(ClientYacht yacht) {
|
||||
Sounds.playCrashSound();
|
||||
raceState.storeCollision(
|
||||
allBoatsMap.get(
|
||||
yachtEventData.getSubjectId().intValue()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: 11/09/17 wmu16 - Add in functionality to viually indicate a pickup to a user
|
||||
private void showPickUp() {
|
||||
Sounds.playTokenPickupSound();
|
||||
raceState.storeCollision(yacht);
|
||||
}
|
||||
|
||||
private void formatAndSendChatMessage(String rawChat) {
|
||||
@@ -496,17 +510,25 @@ 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();
|
||||
if (socketThread != null) socketThread.setSocketToClose();
|
||||
server = null;
|
||||
// socketThread = null;
|
||||
}
|
||||
|
||||
public Map<Integer, ClientYacht> getAllBoatsMap() {
|
||||
return allBoatsMap;
|
||||
}
|
||||
|
||||
public void sendToggleTurningModePacket() {
|
||||
if (socketThread != null) {
|
||||
if (gameKeyBind.isContinuouslyTurning()) {
|
||||
socketThread.sendBoatAction(BoatAction.CONTINUOUSLY_TURNING);
|
||||
} else {
|
||||
socketThread.sendBoatAction(BoatAction.DEFAULT_TURNING);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,442 +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.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,43 +2,47 @@ 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 java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import javafx.animation.AnimationTimer;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.geometry.Point2D;
|
||||
import javafx.geometry.Point3D;
|
||||
import javafx.scene.AmbientLight;
|
||||
import javafx.scene.Camera;
|
||||
import javafx.scene.Group;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.PerspectiveCamera;
|
||||
import javafx.scene.PointLight;
|
||||
import javafx.scene.SceneAntialiasing;
|
||||
import javafx.scene.SubScene;
|
||||
import javafx.scene.effect.BlendMode;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.paint.PhongMaterial;
|
||||
import javafx.scene.shape.Sphere;
|
||||
import javafx.scene.text.Text;
|
||||
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.GeoPoint;
|
||||
import seng302.model.GameKeyBind;
|
||||
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.utilities.GeoUtility;
|
||||
import seng302.visualiser.fxObjects.assets_2D.BoatObject;
|
||||
import seng302.visualiser.cameras.ChaseCamera;
|
||||
import seng302.visualiser.cameras.IsometricCamera;
|
||||
import seng302.visualiser.cameras.RaceCamera;
|
||||
import seng302.visualiser.cameras.TopDownCamera;
|
||||
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;
|
||||
|
||||
@@ -46,241 +50,74 @@ 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_DEPTH = 100;
|
||||
private final double DEFAULT_CAMERA_X = 0;
|
||||
private final double DEFAULT_CAMERA_Y = 160;
|
||||
|
||||
private Group root3D;
|
||||
private SubScene view;
|
||||
// ParallelCamera camera;
|
||||
private PerspectiveCamera camera;
|
||||
private Group gameObjects;
|
||||
|
||||
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 double metersPerPixelX, metersPerPixelY;
|
||||
|
||||
final double SCALE_DELTA = 1.1;
|
||||
|
||||
private Text fpsDisplay = new Text();
|
||||
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, Group> markerObjects;
|
||||
|
||||
// Cameras
|
||||
private PerspectiveCamera isometricCam;
|
||||
private PerspectiveCamera topDownCam;
|
||||
private PerspectiveCamera chaseCam;
|
||||
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 Group playerAnnotation = new Group();
|
||||
private List<CompoundMark> course = new ArrayList<>();
|
||||
private List<Node> mapTokens;
|
||||
private Timer playerBoatAnimationTimer = new Timer();
|
||||
private AnimationTimer playerBoatAnimationTimer;
|
||||
private Group trail = new Group();
|
||||
private ImageView mapImage = new ImageView();
|
||||
|
||||
//FRAME RATE
|
||||
|
||||
private AnimationTimer timer;
|
||||
private int NUM_SAMPLES = 10;
|
||||
private final long[] frameTimes = new long[NUM_SAMPLES];
|
||||
private Double frameRate = 60.0;
|
||||
private int frameTimeIndex = 0;
|
||||
private boolean arrayFilled = false;
|
||||
private ClientYacht playerYacht;
|
||||
private double windDir = 0.0;
|
||||
|
||||
double scaleFactor = 1;
|
||||
|
||||
private enum ScaleDirection {
|
||||
HORIZONTAL,
|
||||
VERTICAL
|
||||
}
|
||||
|
||||
|
||||
private Double windDir;
|
||||
private Skybox skybox;
|
||||
|
||||
|
||||
public GameView3D () {
|
||||
camera = new PerspectiveCamera(true);
|
||||
// camera = new ParallelCamera();
|
||||
// gameObjects.getTransforms().add(new Scale(4,4,4));
|
||||
// camera.setLayoutX(camera.getLayoutX()-400);
|
||||
// camera.setLayoutY(camera.getLayoutY()-400);
|
||||
camera.getTransforms().addAll(
|
||||
new Translate(0,0, -DEFAULT_CAMERA_DEPTH)
|
||||
);
|
||||
camera.setFarClip(Double.MAX_VALUE);
|
||||
camera.setNearClip(0.1);
|
||||
camera.setFieldOfView(FOV);
|
||||
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(100000);
|
||||
pc.setNearClip(0.1);
|
||||
pc.setFieldOfView(FOV);
|
||||
}
|
||||
|
||||
gameObjects = new Group();
|
||||
PointLight pl = new PointLight(Color.DARKGRAY);
|
||||
pl.setLightOn(true);
|
||||
pl.setBlendMode(BlendMode.ADD);
|
||||
pl.setOpacity(0.5);
|
||||
pl.getTransforms().add(new Translate(0,0,-500));
|
||||
AmbientLight al = new AmbientLight();
|
||||
al.setLightOn(true);
|
||||
al.setBlendMode(BlendMode.SOFT_LIGHT);
|
||||
al.getTransforms().add(new Translate(0, 0, -100));
|
||||
root3D = new Group(camera, gameObjects);
|
||||
root3D = new Group(chaseCam, gameObjects);
|
||||
view = new SubScene(
|
||||
root3D, 1000, 1000, true, SceneAntialiasing.BALANCED
|
||||
root3D, 5000, 3000, true, SceneAntialiasing.BALANCED
|
||||
);
|
||||
view.setCamera(camera);
|
||||
// view.setFill(Color.LIGHTBLUE);
|
||||
camera.getTransforms().add(new Rotate(30, new Point3D(1,0,0)));
|
||||
// gameObjects.getChildren().addAll(raceBorder, markers, tokens);
|
||||
System.out.println(camera.getLayoutX());
|
||||
System.out.println(camera.getTranslateX());
|
||||
System.out.println(camera.getLayoutY());
|
||||
System.out.println(camera.getTranslateY());
|
||||
System.out.println(camera.getTranslateZ());
|
||||
camera.setTranslateZ(-80);
|
||||
camera.setTranslateY(170);
|
||||
Sphere red = new Sphere(1);
|
||||
red.setMaterial(new PhongMaterial(Color.RED));
|
||||
red.setLayoutX(0);
|
||||
red.setLayoutY(0);
|
||||
view.setCamera(chaseCam);
|
||||
|
||||
Sphere blue = new Sphere(1);
|
||||
blue.setMaterial(new PhongMaterial(Color.BLUE));
|
||||
blue.setLayoutX(1);
|
||||
blue.setLayoutY(0);
|
||||
skybox = new Skybox(new Image(getClass().getResourceAsStream("/images/skybox.jpg")), 100000, isometricCam);
|
||||
skybox.getTransforms().addAll(new Rotate(90, Rotate.X_AXIS));
|
||||
|
||||
Sphere green = new Sphere(1);
|
||||
green.setMaterial(new PhongMaterial(Color.GREEN));
|
||||
green.setLayoutX(-.5);
|
||||
green.setLayoutY(0);
|
||||
|
||||
Sphere white = new Sphere(1);
|
||||
white.setMaterial(new PhongMaterial(Color.WHITE));
|
||||
white.setLayoutX(-.25);
|
||||
white.setLayoutY(0);
|
||||
|
||||
Sphere black = new Sphere(1);
|
||||
black.setMaterial(new PhongMaterial(Color.BLACK));
|
||||
black.setLayoutX(-.125);
|
||||
black.setLayoutY(0);
|
||||
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, playerAnnotation,
|
||||
white, blue, green, black, red
|
||||
raceBorder, trail, markers, tokens, skybox, land.getAssets()
|
||||
);
|
||||
|
||||
System.out.println(camera.getLayoutX());
|
||||
System.out.println(camera.getTranslateX());
|
||||
System.out.println(camera.getLayoutY());
|
||||
System.out.println(camera.getTranslateY());
|
||||
System.out.println(camera.getTranslateZ());
|
||||
// Sphere s = new Sphere(1);
|
||||
// s.setMaterial(new PhongMaterial(Color.RED));
|
||||
// Sphere left = new Sphere(1);
|
||||
// left.setMaterial(new PhongMaterial(Color.LEMONCHIFFON));
|
||||
// left.getTransforms().add(new Translate(-Math.tan(Math.toRadians(FOV / 2)) * DEFAULT_CAMERA_DEPTH, 0, 0));
|
||||
// Sphere right = new Sphere(1);
|
||||
// right.setMaterial(new PhongMaterial(Color.ROSYBROWN));
|
||||
// right.getTransforms().add(new Translate(Math.tan(Math.toRadians(FOV / 2)) * DEFAULT_CAMERA_DEPTH, 0, 0));
|
||||
// Sphere top = new Sphere(1);
|
||||
// top.setMaterial(new PhongMaterial(Color.TEAL));
|
||||
// top.getTransforms().add(new Translate(0,-Math.tan(Math.toRadians(FOV / 2)) * DEFAULT_CAMERA_DEPTH, 0));
|
||||
// Sphere bottom = new Sphere(1);
|
||||
// bottom.setMaterial(new PhongMaterial(Color.BLANCHEDALMOND));
|
||||
// bottom.getTransforms().add(new Translate(0, Math.tan(Math.toRadians(FOV / 2)) * DEFAULT_CAMERA_DEPTH, 0));
|
||||
//
|
||||
// Node boat = ModelFactory.boatGameView(BoatMeshType.DINGHY, Color.BLUE).getAssets();
|
||||
// Node boat2 = ModelFactory.boatGameView(BoatMeshType.DINGHY, Color.BROWN).getAssets();
|
||||
// boat2.getTransforms().add(new Translate(0,20, 0));
|
||||
// Node boat3 = ModelFactory.boatGameView(BoatMeshType.DINGHY, Color.RED).getAssets();
|
||||
// boat3.getTransforms().add(new Translate(0,-20, 0));
|
||||
//
|
||||
// Node sMarker = ModelFactory.importModel(ModelType.START_MARKER).getAssets();
|
||||
// sMarker.getTransforms().add(0, new Translate(30, 30, 0));
|
||||
//
|
||||
// Node fMarker = ModelFactory.importModel(ModelType.FINISH_MARKER).getAssets();
|
||||
// fMarker.getTransforms().add(0, new Translate(30, -30, 0));
|
||||
//
|
||||
// Node marker = ModelFactory.importModel(ModelType.PLAIN_MARKER).getAssets();
|
||||
// marker.getTransforms().add(0, new Translate(30, 0, 0));
|
||||
//
|
||||
// Node coin = ModelFactory.importModel(ModelType.VELOCITY_PICKUP).getAssets();
|
||||
// coin.setTranslateX(coin.getTranslateX() - 30);
|
||||
//
|
||||
// gameObjects.getChildren().addAll(
|
||||
// ModelFactory.importModel(ModelType.OCEAN).getAssets(),
|
||||
// s, left, right, top, bottom,
|
||||
// boat, boat2, boat3,
|
||||
// sMarker, fMarker, marker,
|
||||
// coin
|
||||
// );
|
||||
|
||||
view.sceneProperty().addListener((obs, old, scene) -> {
|
||||
if (scene != null) {
|
||||
scene.addEventHandler(KeyEvent.KEY_PRESSED, this::cameraMovement);
|
||||
}
|
||||
});
|
||||
|
||||
initializeTimer();
|
||||
}
|
||||
|
||||
private void initializeTimer() {
|
||||
Arrays.fill(frameTimes, 1_000_000_000 / 60);
|
||||
timer = new AnimationTimer() {
|
||||
private long lastTime = 0;
|
||||
private int FPSCount = 30;
|
||||
private Double frameRate = 60.0;
|
||||
private int index = 0;
|
||||
private boolean arrayFilled = false;
|
||||
private long sum = 1_000_000_000 / 3;
|
||||
|
||||
@Override
|
||||
public void handle(long now) {
|
||||
System.out.println(now);
|
||||
if (lastTime == 0) {
|
||||
lastTime = now;
|
||||
} else {
|
||||
if (now - lastTime >= (1e8 / 60)) { //Fix for framerate going above 60 when minimized
|
||||
long oldFrameTime = frameTimes[frameTimeIndex];
|
||||
frameTimes[frameTimeIndex] = now;
|
||||
frameTimeIndex = (frameTimeIndex + 1) % frameTimes.length;
|
||||
if (frameTimeIndex == 0) {
|
||||
arrayFilled = true;
|
||||
}
|
||||
long elapsedNanos;
|
||||
if (arrayFilled) {
|
||||
elapsedNanos = now - oldFrameTime;
|
||||
long elapsedNanosPerFrame = elapsedNanos / frameTimes.length;
|
||||
frameRate = 1_000_000_000.0 / elapsedNanosPerFrame;
|
||||
if (FPSCount-- == 0) {
|
||||
FPSCount = 30;
|
||||
drawFps(frameRate);
|
||||
}
|
||||
}
|
||||
lastTime = now;
|
||||
}
|
||||
}
|
||||
boatObjects.forEach((boat, boatObject) -> boatObject.updateLocation());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@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) {
|
||||
@@ -327,14 +164,17 @@ public class GameView3D {
|
||||
}
|
||||
}
|
||||
|
||||
createMarkArrows();
|
||||
|
||||
//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());
|
||||
System.out.println(mark.toString() + " " + p2d.toString());
|
||||
Point2D p2d = scaledPoint.findScaledXY(mark.getLat(), mark.getLng());
|
||||
marker.setLayoutX(p2d.getX());
|
||||
marker.setLayoutY(p2d.getY());
|
||||
}));
|
||||
@@ -352,12 +192,9 @@ public class GameView3D {
|
||||
* @param markerType the type of marker as a ModelType. Should be PLAIN_MARKER, START_MARKER or END_MARKER
|
||||
*/
|
||||
private void makeAndBindMarker(Mark observableMark, ModelType markerType) {
|
||||
|
||||
Group marker = ModelFactory.importModel(markerType).getAssets();
|
||||
|
||||
markerObjects.put(observableMark, marker);
|
||||
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());
|
||||
});
|
||||
@@ -372,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(
|
||||
@@ -394,190 +231,83 @@ 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.
|
||||
* Calculates all the data needed for to create mark arrows. Requires that a course has been
|
||||
* added to the gameview.
|
||||
*/
|
||||
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;
|
||||
private void createMarkArrows () {
|
||||
for (int i=1; i < course.size()-1; i++) { //General case.
|
||||
for (Mark mark : course.get(i).getMarks()) {
|
||||
markerObjects.get(mark).addArrows(
|
||||
mark.getRoundingSide() == RoundingSide.STARBOARD ? MarkArrowFactory.RoundingSide.STARBOARD : MarkArrowFactory.RoundingSide.PORT,
|
||||
GeoUtility.getBearing(course.get(i-1).getMidPoint(), mark),
|
||||
GeoUtility.getBearing(mark, course.get(i+1).getMidPoint())
|
||||
);
|
||||
}
|
||||
}
|
||||
createStartLineArrows();
|
||||
createFinishLineArrows();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
private void createStartLineArrows () {
|
||||
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, course.get(1).getMidPoint())
|
||||
);
|
||||
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)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private void createFinishLineArrows () {
|
||||
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(course.get(course.size()-2).getMidPoint(), mark),
|
||||
GeoUtility.getBearing(mark, mark)
|
||||
);
|
||||
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) {
|
||||
switch (event.getCode()) {
|
||||
case NUMPAD8:
|
||||
camera.getTransforms().addAll(new Rotate(0.5, new Point3D(1,0,0)));
|
||||
break;
|
||||
case NUMPAD2:
|
||||
camera.getTransforms().addAll(new Rotate(-0.5, new Point3D(1,0,0)));
|
||||
break;
|
||||
case NUMPAD4:
|
||||
camera.getTransforms().addAll(new Rotate(-0.5, new Point3D(0,1,0)));
|
||||
break;
|
||||
case NUMPAD6:
|
||||
camera.getTransforms().addAll(new Rotate(0.5, new Point3D(0,1,0)));
|
||||
break;
|
||||
case X:
|
||||
camera.getTransforms().addAll(new Translate(0, 0, 1.5));
|
||||
break;
|
||||
case Z:
|
||||
camera.getTransforms().addAll(new Translate(0, 0, -1.5));
|
||||
break;
|
||||
case W:
|
||||
camera.getTransforms().addAll(new Translate(0, -1, 0));
|
||||
break;
|
||||
case S:
|
||||
camera.getTransforms().addAll(new Translate(0, 1, 0));
|
||||
break;
|
||||
case A:
|
||||
camera.getTransforms().addAll(new Translate(-1, 0, 0));
|
||||
break;
|
||||
case D:
|
||||
camera.getTransforms().addAll(new Translate(1, 0, 0));
|
||||
break;
|
||||
GameKeyBind keyBinds = GameKeyBind.getInstance();
|
||||
KeyAction keyPressed = keyBinds.getKeyAction(event.getCode());
|
||||
if (keyPressed != null) {
|
||||
switch (keyPressed) {
|
||||
case ZOOM_IN:
|
||||
((RaceCamera) view.getCamera()).zoomIn();
|
||||
break;
|
||||
case ZOOM_OUT:
|
||||
((RaceCamera) view.getCamera()).zoomOut();
|
||||
break;
|
||||
case FORWARD:
|
||||
((RaceCamera) view.getCamera()).panUp();
|
||||
break;
|
||||
case BACKWARD:
|
||||
((RaceCamera) view.getCamera()).panDown();
|
||||
break;
|
||||
case LEFT:
|
||||
((RaceCamera) view.getCamera()).panLeft();
|
||||
break;
|
||||
case RIGHT:
|
||||
((RaceCamera) view.getCamera()).panRight();
|
||||
break;
|
||||
case VIEW:
|
||||
toggleCamera();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
private void toggleCamera() {
|
||||
Camera currCamera = view.getCamera();
|
||||
|
||||
if (currCamera.equals(isometricCam)) {
|
||||
view.setCamera(topDownCam);
|
||||
} else if (currCamera.equals(topDownCam)) {
|
||||
view.setCamera(chaseCam);
|
||||
} else {
|
||||
view.setCamera(isometricCam);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -589,36 +319,51 @@ public class GameView3D {
|
||||
final List<Group> wakes = new ArrayList<>();
|
||||
for (ClientYacht clientYacht : yachts) {
|
||||
Color colour = clientYacht.getColour();
|
||||
newBoat = new BoatObject();
|
||||
// newBoat.addSelectedBoatListener(this::setSelectedBoat);
|
||||
newBoat = new BoatObject(clientYacht.getBoatType());
|
||||
newBoat.setFill(colour);
|
||||
boatObjects.put(clientYacht, newBoat);
|
||||
// createAndBindAnnotationBox(clientYacht, colour);
|
||||
wakesGroup.getChildren().add(newBoat.getWake());
|
||||
wakes.add(newBoat.getWake());
|
||||
boatObjectGroup.getChildren().add(newBoat);
|
||||
// trails.getChildren().add(newBoat.getTrail());
|
||||
clientYacht.addLocationListener(this::updateBoatLocation);
|
||||
clientYacht.addColorChangeListener(this::updateBoatColor);
|
||||
|
||||
clientYacht.addLocationListener((boat, lat, lon, heading, sailIn, velocity) -> {
|
||||
BoatObject bo = boatObjects.get(boat);
|
||||
Point2D p2d = findScaledXY(lat, lon);
|
||||
bo.moveTo(p2d.getX(), p2d.getY(), heading, velocity, sailIn, windDir);
|
||||
// annotations.get(boat).setLocation(p2d.getX(), p2d.getY());
|
||||
bo.setTrajectory(
|
||||
heading,
|
||||
velocity,
|
||||
metersPerPixelX,
|
||||
metersPerPixelY);
|
||||
});
|
||||
if (clientYacht.getSourceId().equals(
|
||||
ViewManager.getInstance().getGameClient().getServerThread().getClientId())) {
|
||||
((ChaseCamera) chaseCam).setPlayerBoat(newBoat);
|
||||
((TopDownCamera) topDownCam).setPlayerBoat(newBoat);
|
||||
|
||||
newBoat.setMarkIndicator(ModelFactory.importSTL("mark_pointer.stl"));
|
||||
playerBoat = newBoat;
|
||||
|
||||
}
|
||||
}
|
||||
// annotationsGroup.getChildren().addAll(annotations.values());
|
||||
Platform.runLater(() -> {
|
||||
ClientYacht playerYacht = ViewManager.getInstance().getGameClient().getAllBoatsMap()
|
||||
.get(ViewManager.getInstance().getGameClient().getServerThread().getClientId());
|
||||
|
||||
// gameObjects.addAll(trails);
|
||||
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);
|
||||
// gameObjects.addAll(annotationsGroup);
|
||||
// gameObjects.addAll(boatObjectGroup);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -626,6 +371,27 @@ 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
|
||||
*
|
||||
* @param clientYacht The yacht to update the colour for
|
||||
*/
|
||||
private void updateBoatColor(ClientYacht clientYacht) {
|
||||
boatObjects.get(clientYacht).setFill(clientYacht.getColour());
|
||||
}
|
||||
|
||||
private void updateBoatLocation(ClientYacht boat, Double lat, Double lon, Double heading,
|
||||
Boolean sailIn, Double velocity) {
|
||||
BoatObject bo = boatObjects.get(boat);
|
||||
Point2D p2d = scaledPoint.findScaledXY(lat, lon);
|
||||
bo.moveTo(p2d.getX(), p2d.getY(), heading, velocity, sailIn, windDir);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
@@ -635,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());
|
||||
@@ -659,7 +427,7 @@ public class GameView3D {
|
||||
),
|
||||
new Point3D(0,0,1)
|
||||
),
|
||||
new Scale((lastLocation.distance(location) / 15)-0.2, 1, 1)
|
||||
new Scale((lastLocation.distance(location) / 10)-0.2, 1, 1)
|
||||
);
|
||||
|
||||
Point2D midPoint = location.midpoint(lastLocation);
|
||||
@@ -672,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(
|
||||
@@ -681,7 +449,7 @@ public class GameView3D {
|
||||
),
|
||||
new Point3D(0,0,1)
|
||||
),
|
||||
new Scale((firstLocation.distance(lastLocation) / 15)-0.2, 1, 1)
|
||||
new Scale((firstLocation.distance(lastLocation) / 10)-0.2, 1, 1)
|
||||
);
|
||||
|
||||
Point2D midPoint = lastLocation.midpoint(firstLocation);
|
||||
@@ -700,8 +468,28 @@ public class GameView3D {
|
||||
public void updateTokens(List<Token> newTokens) {
|
||||
mapTokens = new ArrayList<>();
|
||||
for (Token token : newTokens) {
|
||||
Point2D location = findScaledXY(token.getLat(), token.getLng());
|
||||
Node tokenObject = ModelFactory.importModel(ModelType.VELOCITY_PICKUP).getAssets();
|
||||
Point2D location = scaledPoint.findScaledXY(token.getLat(), token.getLng());
|
||||
|
||||
ModelType modelType = null;
|
||||
switch (token.getTokenType()) {
|
||||
case BOOST:
|
||||
modelType = ModelType.VELOCITY_PICKUP;
|
||||
break;
|
||||
case HANDLING:
|
||||
modelType = ModelType.HANDLING_PICKUP;
|
||||
break;
|
||||
case BUMPER:
|
||||
modelType = ModelType.BUMPER_PICKUP;
|
||||
break;
|
||||
case RANDOM:
|
||||
modelType = ModelType.RANDOM_PICKUP;
|
||||
break;
|
||||
case WIND_WALKER:
|
||||
modelType = ModelType.WIND_WALKER_PICKUP;
|
||||
break;
|
||||
}
|
||||
|
||||
Node tokenObject = ModelFactory.importModel(modelType).getAssets();
|
||||
tokenObject.setLayoutX(location.getX());
|
||||
tokenObject.setLayoutY(location.getY());
|
||||
mapTokens.add(tokenObject);
|
||||
@@ -713,88 +501,38 @@ public class GameView3D {
|
||||
}
|
||||
|
||||
public void setBoatAsPlayer (ClientYacht playerYacht) {
|
||||
this.playerYacht = playerYacht;
|
||||
playerBoat.updateMarkIndicator(scaledPoint.findScaledXY(course.get(0).getMidPoint()));
|
||||
playerYacht.toggleSail();
|
||||
playerBoatAnimationTimer = new AnimationTimer() {
|
||||
|
||||
Platform.runLater(() ->
|
||||
playerAnnotation.getChildren().setAll(ModelFactory.importModel(ModelType.PLAYER_IDENTIFIER).getAssets())
|
||||
);
|
||||
BoatObject playerAssets = boatObjects.get(playerYacht);
|
||||
playerAnnotation.layoutXProperty().bind(playerAssets.layoutXProperty());
|
||||
playerAnnotation.layoutYProperty().bind(playerAssets.layoutYProperty());
|
||||
|
||||
playerBoatAnimationTimer.scheduleAtFixedRate(new TimerTask() {
|
||||
|
||||
private Point2D lastLocation = findScaledXY(playerYacht.getLocation());
|
||||
Point2D lastLocation = scaledPoint.findScaledXY(playerYacht.getLocation());
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
Node segment = ModelFactory.importModel(ModelType.TRAIL_SEGMENT).getAssets();
|
||||
Point2D location = findScaledXY(playerYacht.getLocation());
|
||||
segment.getTransforms().addAll(
|
||||
new Translate(location.getX(), location.getY()),
|
||||
new Rotate(playerYacht.getHeading(), new Point3D(0,0,1)),
|
||||
new Scale(1, lastLocation.distance(location) / 5)
|
||||
);
|
||||
Platform.runLater(() -> {
|
||||
public void handle(long now) {
|
||||
Point2D location = scaledPoint.findScaledXY(playerYacht.getLocation());
|
||||
if (Math.abs(lastLocation.distance(location)) > 2) {
|
||||
Node segment = ModelFactory.importModel(ModelType.TRAIL_SEGMENT).getAssets();
|
||||
location = scaledPoint.findScaledXY(playerYacht.getLocation());
|
||||
segment.getTransforms().addAll(
|
||||
new Translate(location.getX(), location.getY(), 0),
|
||||
new Rotate(playerYacht.getHeading(), new Point3D(0,0,1))
|
||||
);
|
||||
trail.getChildren().add(segment);
|
||||
if (trail.getChildren().size() > 100) {
|
||||
if (trail.getChildren().size() > 50) {
|
||||
trail.getChildren().remove(0);
|
||||
}
|
||||
});
|
||||
lastLocation = location;
|
||||
// TODO: 11/09/2017 ROTATE PLAYER ICON
|
||||
// double leg = playerYacht.getLegNumber();
|
||||
// if (compoundMark != null) {
|
||||
// for (Mark mark : compoundMark.getMarks()) {
|
||||
//// System.out.println("markerObjects.get(mark) = " + markerObjects.get(mark));
|
||||
// markerObjects.get(mark).showNextExitArrow();
|
||||
// }
|
||||
// }
|
||||
// CompoundMark nextMark = null;
|
||||
// if (legNumber < course.size() - 1) {
|
||||
// nextMark = course.get(legNumber);
|
||||
// for (Mark mark : nextMark.getMarks()) {
|
||||
// markerObjects.get(mark).showNextEnterArrow();
|
||||
// }
|
||||
// }
|
||||
lastLocation = location;
|
||||
}
|
||||
}
|
||||
}, 0L, 500L);
|
||||
|
||||
// playerYacht.toggleSail();
|
||||
// boatObjects.get(playerYacht).setAsPlayer();
|
||||
// CompoundMark currentMark = course.get(playerYacht.getLegNumber());
|
||||
// for (Mark mark : currentMark.getMarks()) {
|
||||
// markerObjects.get(mark).showNextExitArrow();
|
||||
// }
|
||||
// annotations.get(playerYacht).addAnnotation(
|
||||
// "velocity",
|
||||
// playerYacht.getVelocityProperty(),
|
||||
// (velocity) -> String.format("Speed: %.2f ms", velocity.doubleValue())
|
||||
// );
|
||||
// Platform.runLater(() -> {
|
||||
// boatObjectGroup.getChildren().remove(boatObjects.get(playerYacht));
|
||||
// gameObjects.add(boatObjects.get(playerYacht));
|
||||
// annotationsGroup.getChildren().remove(annotations.get(playerYacht));
|
||||
// gameObjects.add(annotations.get(playerYacht));
|
||||
// });
|
||||
// playerYacht.addMarkRoundingListener(this::updateMarkArrows);
|
||||
};
|
||||
playerBoatAnimationTimer.start();
|
||||
playerYacht.addMarkRoundingListener(this::updateMarkArrows);
|
||||
boatObjects.get(playerYacht).addSelectedBoatListener((boatObject, isSelected) -> {
|
||||
System.out.println("IS SELECTED " + isSelected);
|
||||
});
|
||||
}
|
||||
|
||||
public void setWindDir(double windDir) {
|
||||
this.windDir = windDir;
|
||||
}
|
||||
|
||||
public void setFrameRateFXText(Text fpsDisplay) {
|
||||
this.fpsDisplay = null;
|
||||
this.fpsDisplay = fpsDisplay;
|
||||
}
|
||||
|
||||
private void drawFps(Double fps) {
|
||||
System.out.println(fps);
|
||||
Platform.runLater(() -> fpsDisplay.setText(String.format("%d FPS", Math.round(fps))));
|
||||
}
|
||||
|
||||
// public void setFPSVisibility(boolean visibility) {
|
||||
// fpsDisplay.setVisible(visibility);
|
||||
// }
|
||||
}
|
||||
}
|
||||
@@ -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).addFinishArrow(
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
package seng302.visualiser.cameras;
|
||||
|
||||
import java.util.Arrays;
|
||||
import javafx.beans.property.DoubleProperty;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.geometry.Point3D;
|
||||
import javafx.scene.PerspectiveCamera;
|
||||
import javafx.scene.transform.Rotate;
|
||||
import javafx.scene.transform.Transform;
|
||||
import javafx.scene.transform.Translate;
|
||||
import seng302.visualiser.fxObjects.assets_3D.BoatObject;
|
||||
|
||||
|
||||
public class ChaseCamera extends PerspectiveCamera implements RaceCamera {
|
||||
|
||||
private final Double VERTICAL_PAN_LIMIT = 20.0;
|
||||
private final Double NEAR_ZOOM_LIMIT = -15.0;
|
||||
private final Double FAR_ZOOM_LIMIT = -125.0;
|
||||
|
||||
private final Double ZOOM_STEP = 2.5;
|
||||
private final Double PAN_STEP = 2.5;
|
||||
|
||||
private ObservableList<Transform> transforms;
|
||||
private BoatObject playerBoat;
|
||||
|
||||
private Double zoomFactor;
|
||||
private Double horizontalPan;
|
||||
private Double verticalPan;
|
||||
|
||||
|
||||
public ChaseCamera() {
|
||||
super(true);
|
||||
transforms = this.getTransforms();
|
||||
|
||||
zoomFactor = (FAR_ZOOM_LIMIT + NEAR_ZOOM_LIMIT) / 2.0;
|
||||
this.horizontalPan = 0.0;
|
||||
this.verticalPan = 0.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a player boat object to observe and update the camera with.
|
||||
*
|
||||
* @param playerBoat The player boat to be observed.
|
||||
*/
|
||||
public void setPlayerBoat(BoatObject playerBoat) {
|
||||
this.playerBoat = playerBoat;
|
||||
|
||||
for (DoubleProperty o : Arrays
|
||||
.asList(playerBoat.getRotationProperty(), playerBoat.layoutYProperty(),
|
||||
playerBoat.layoutXProperty())) {
|
||||
o.addListener((obs, oldVal, newVal) -> repositionCamera());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the camera to a new position after some change (Zooming or Panning)
|
||||
*/
|
||||
private void repositionCamera() {
|
||||
transforms.clear();
|
||||
transforms.addAll(
|
||||
new Translate(playerBoat.getLayoutX(), playerBoat.getLayoutY(), 0),
|
||||
new Rotate(playerBoat.getRotationProperty().getValue() + horizontalPan,
|
||||
new Point3D(0, 0, 1)),
|
||||
new Rotate(60 + verticalPan, new Point3D(1, 0, 0)),
|
||||
new Translate(0, 0, zoomFactor)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjusts the zoom amount (camera depth) by some adjustment value
|
||||
* @param adjustment the adjustment to be made to the camera
|
||||
*/
|
||||
private void adjustZoomFactor(Double adjustment) {
|
||||
if (zoomFactor + adjustment < NEAR_ZOOM_LIMIT && zoomFactor + adjustment > FAR_ZOOM_LIMIT) {
|
||||
zoomFactor = zoomFactor + adjustment;
|
||||
repositionCamera();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjusts the Vertical Panning of the Camera
|
||||
* @param adjustment the adjustment to be made to the camera
|
||||
*/
|
||||
private void adjustVerticalPan(Double adjustment) {
|
||||
if (verticalPan + adjustment >= -VERTICAL_PAN_LIMIT
|
||||
&& verticalPan + adjustment <= VERTICAL_PAN_LIMIT) {
|
||||
verticalPan += adjustment;
|
||||
repositionCamera();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjusts the Horizontal Panning of the Camera.
|
||||
* @param adjustment the adjustment to be made to the camera
|
||||
*/
|
||||
private void adjustHorizontalPan(Double adjustment) {
|
||||
this.horizontalPan += adjustment;
|
||||
repositionCamera();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void zoomIn() {
|
||||
adjustZoomFactor(ZOOM_STEP);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void zoomOut() {
|
||||
adjustZoomFactor(-ZOOM_STEP);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void panLeft() {
|
||||
adjustHorizontalPan(-PAN_STEP);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void panRight() {
|
||||
adjustHorizontalPan(PAN_STEP);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void panUp() {
|
||||
adjustVerticalPan(-PAN_STEP);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void panDown() {
|
||||
adjustVerticalPan(PAN_STEP);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
package seng302.visualiser.cameras;
|
||||
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.geometry.Point3D;
|
||||
import javafx.scene.PerspectiveCamera;
|
||||
import javafx.scene.transform.Rotate;
|
||||
import javafx.scene.transform.Transform;
|
||||
import javafx.scene.transform.Translate;
|
||||
|
||||
public class IsometricCamera extends PerspectiveCamera implements RaceCamera {
|
||||
|
||||
private final Double MIN_X = -120.0;
|
||||
private final Double MAX_X = 125.0;
|
||||
|
||||
private final Double MIN_Y = 40.0;
|
||||
private final Double MAX_Y = 170.0;
|
||||
|
||||
private final Double PAN_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;
|
||||
private Double zoomFactor;
|
||||
|
||||
private ObservableList<Transform> transforms;
|
||||
|
||||
public IsometricCamera(Double cameraStartX, Double cameraStartY) {
|
||||
super(true);
|
||||
transforms = this.getTransforms();
|
||||
|
||||
zoomFactor = FAR_ZOOM_LIMIT;
|
||||
horizontalPan = cameraStartX;
|
||||
verticalPan = cameraStartY;
|
||||
|
||||
updateCamera();
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the camera to a new position after some change (Zooming or Panning)
|
||||
*/
|
||||
private void updateCamera() {
|
||||
transforms.clear();
|
||||
transforms.addAll(
|
||||
new Translate(horizontalPan, verticalPan, zoomFactor),
|
||||
new Rotate(30, new Point3D(1, 0, 0))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjusts the zoom amount (camera depth) by some adjustment value
|
||||
*
|
||||
* @param adjustment the adjustment to be made to the camera
|
||||
*/
|
||||
private void adjustZoomFactor(Double adjustment) {
|
||||
if (zoomFactor + adjustment < NEAR_ZOOM_LIMIT && zoomFactor + adjustment > FAR_ZOOM_LIMIT) {
|
||||
zoomFactor = zoomFactor + adjustment;
|
||||
updateCamera();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjusts the Vertical Panning of the Camera
|
||||
* @param adjustment the adjustment to be made to the camera
|
||||
*/
|
||||
private void adjustVerticalPan(Double adjustment) {
|
||||
if (verticalPan + adjustment >= MIN_Y && verticalPan + adjustment <= MAX_Y) {
|
||||
verticalPan += adjustment;
|
||||
updateCamera();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjusts the Horizontal Panning of the Camera.
|
||||
* @param adjustment the adjustment to be made to the camera
|
||||
*/
|
||||
private void adjustHorizontalPan(Double adjustment) {
|
||||
if (horizontalPan + adjustment >= MIN_X && horizontalPan + adjustment <= MIN_Y) {
|
||||
this.horizontalPan += adjustment;
|
||||
updateCamera();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void zoomIn() {
|
||||
adjustZoomFactor(-2.5);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void zoomOut() {
|
||||
adjustZoomFactor(2.5);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void panLeft() {
|
||||
adjustHorizontalPan(-2.5);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void panRight() {
|
||||
adjustHorizontalPan(2.5);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void panUp() {
|
||||
adjustVerticalPan(-2.5);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void panDown() {
|
||||
adjustVerticalPan(2.5);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package seng302.visualiser.cameras;
|
||||
|
||||
|
||||
public interface RaceCamera {
|
||||
|
||||
void zoomIn();
|
||||
|
||||
void zoomOut();
|
||||
|
||||
void panLeft();
|
||||
|
||||
void panRight();
|
||||
|
||||
void panUp();
|
||||
|
||||
void panDown();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
package seng302.visualiser.cameras;
|
||||
|
||||
|
||||
import java.util.Arrays;
|
||||
import javafx.beans.property.DoubleProperty;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.scene.PerspectiveCamera;
|
||||
import javafx.scene.transform.Transform;
|
||||
import javafx.scene.transform.Translate;
|
||||
import seng302.visualiser.fxObjects.assets_3D.BoatObject;
|
||||
|
||||
public class TopDownCamera extends PerspectiveCamera implements RaceCamera {
|
||||
|
||||
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;
|
||||
private BoatObject playerBoat;
|
||||
|
||||
private Double zoomFactor;
|
||||
private Double horizontalPan;
|
||||
private Double verticalPan;
|
||||
|
||||
public TopDownCamera() {
|
||||
super(true);
|
||||
transforms = this.getTransforms();
|
||||
|
||||
zoomFactor = FAR_ZOOM_LIMIT;
|
||||
horizontalPan = 0.0;
|
||||
verticalPan = 0.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a player boat object to observe and update the camera with.
|
||||
*
|
||||
* @param playerBoat The player boat to be observed.
|
||||
*/
|
||||
public void setPlayerBoat(BoatObject playerBoat) {
|
||||
this.playerBoat = playerBoat;
|
||||
|
||||
for (DoubleProperty o : Arrays
|
||||
.asList(playerBoat.layoutXProperty(), playerBoat.layoutYProperty())) {
|
||||
o.addListener((obs, oldVal, newVal) -> updateCamera());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the camera to a new position after some change (Zooming or Panning)
|
||||
*/
|
||||
private void updateCamera() {
|
||||
transforms.clear();
|
||||
transforms.addAll(
|
||||
new Translate(playerBoat.getLayoutX() + horizontalPan,
|
||||
playerBoat.getLayoutY() + verticalPan, zoomFactor)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjusts the zoom amount (camera depth) by some adjustment value
|
||||
* @param adjustment the adjustment to be made to the camera
|
||||
*/
|
||||
private void adjustZoomFactor(Double adjustment) {
|
||||
if (zoomFactor + adjustment < NEAR_ZOOM_LIMIT && zoomFactor + adjustment > FAR_ZOOM_LIMIT) {
|
||||
zoomFactor = zoomFactor + adjustment;
|
||||
updateCamera();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjusts the Vertical Panning of the Camera
|
||||
* @param adjustment the adjustment to be made to the camera
|
||||
*/
|
||||
private void adjustVerticalPan(Double adjustment) {
|
||||
if (verticalPan + adjustment >= -PAN_LIMIT && verticalPan + adjustment <= PAN_LIMIT) {
|
||||
verticalPan += adjustment;
|
||||
updateCamera();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjusts the Horizontal Panning of the Camera.
|
||||
* @param adjustment the adjustment to be made to the camera
|
||||
*/
|
||||
private void adjustHorizontalPan(Double adjustment) {
|
||||
if (horizontalPan + adjustment >= -PAN_LIMIT && horizontalPan + adjustment <= PAN_LIMIT) {
|
||||
horizontalPan += adjustment;
|
||||
updateCamera();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void zoomIn() {
|
||||
adjustZoomFactor(ZOOM_STEP);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void zoomOut() {
|
||||
adjustZoomFactor(-ZOOM_STEP);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void panLeft() {
|
||||
adjustHorizontalPan(-1.0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void panRight() {
|
||||
adjustHorizontalPan(1.0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void panUp() {
|
||||
adjustVerticalPan(-1.0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void panDown() {
|
||||
adjustVerticalPan(1.0);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,98 +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();
|
||||
//TODO merge fix
|
||||
setContentPane("/views/StartScreenView.fxml");
|
||||
}
|
||||
|
||||
public void playButtonHoverSound(MouseEvent mouseEvent) {
|
||||
Sounds.playHoverSound();
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import com.jfoenix.controls.JFXDialog;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.ResourceBundle;
|
||||
@@ -13,12 +14,19 @@ import javafx.collections.ListChangeListener;
|
||||
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.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;
|
||||
@@ -29,12 +37,18 @@ 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 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;
|
||||
@@ -49,18 +63,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();
|
||||
|
||||
@@ -84,22 +107,36 @@ 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());
|
||||
});
|
||||
|
||||
customizeButton.setOnMouseReleased(event -> {
|
||||
customizationDialog = createCustomizeDialog();
|
||||
Sounds.playButtonClick();
|
||||
customizationDialog.show();
|
||||
});
|
||||
|
||||
Platform.runLater(() -> {
|
||||
Integer playerId = ViewManager.getInstance().getGameClient().getServerThread().getClientId();
|
||||
String name = ViewManager.getInstance().getGameClient().getPlayerNames().get(playerId - 1);
|
||||
|
||||
playersColor = Colors.getColor(playerId - 1);
|
||||
customizationDialog = createCustomizeDialog();
|
||||
|
||||
customizeButton.setOnMouseReleased(event -> {
|
||||
Sounds.playButtonClick();
|
||||
customizationDialog.show();
|
||||
});
|
||||
});
|
||||
|
||||
leaveLobbyButton.setOnMouseEntered(e -> Sounds.playHoverSound());
|
||||
@@ -107,19 +144,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() {
|
||||
// TODO: 12/09/17 ajm412: Why is this here? is there no better way we can do this? Ideally inside the LobbyController.
|
||||
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();
|
||||
}
|
||||
@@ -128,48 +264,39 @@ public class LobbyController implements Initializable {
|
||||
|
||||
controller.setParentController(this);
|
||||
controller.setPlayerColor(this.playersColor);
|
||||
controller.setPlayerName(this.playerBoats
|
||||
.get(ViewManager.getInstance().getGameClient().getServerThread().getClientId())
|
||||
.getBoatName());
|
||||
controller.setCurrentBoat(this.playerBoats.get(ViewManager.getInstance().getGameClient().getServerThread().getClientId())
|
||||
.getBoatType().toString());
|
||||
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -201,7 +328,7 @@ public class LobbyController implements Initializable {
|
||||
FXMLLoader loader = new FXMLLoader(
|
||||
getClass().getResource("/views/cells/PlayerCell.fxml"));
|
||||
|
||||
loader.setController(new PlayerCell(playerId, yacht.getBoatName(), yacht.getColour()));
|
||||
loader.setController(new PlayerCell(playerId, yacht));
|
||||
|
||||
try {
|
||||
pane = loader.load();
|
||||
@@ -226,8 +353,8 @@ public class LobbyController implements Initializable {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param raceState
|
||||
* Updates the state of the race and changes the value
|
||||
* @param raceState the new race state
|
||||
*/
|
||||
public void updateRaceState(RaceState raceState){
|
||||
this.raceState = raceState;
|
||||
@@ -241,4 +368,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,92 +1,71 @@
|
||||
package seng302.visualiser.controllers;
|
||||
|
||||
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.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 seng302.model.ClientYacht;
|
||||
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.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.BoatObject;
|
||||
import seng302.visualiser.fxObjects.assets_2D.WindArrow;
|
||||
|
||||
/**
|
||||
* 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 contentAnchorPane;
|
||||
private GridPane contentGridPane;
|
||||
private StackPane contentStackPane;
|
||||
@FXML
|
||||
private Pane miniMapPane;
|
||||
@FXML
|
||||
private ImageView windImageView;
|
||||
@FXML
|
||||
private AnchorPane rvAnchorPane;
|
||||
@FXML
|
||||
@@ -99,62 +78,65 @@ 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
|
||||
private Label windSpeedLabel;
|
||||
@FXML
|
||||
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;
|
||||
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));
|
||||
@@ -168,60 +150,131 @@ 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);
|
||||
// });
|
||||
rvAnchorPane.setOnMouseClicked((event) ->
|
||||
rvAnchorPane.requestFocus()
|
||||
);
|
||||
|
||||
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) {
|
||||
raceState.setRaceStarted(false);
|
||||
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.
|
||||
*/
|
||||
private void createFinishDialog(ArrayList<ClientYacht> finishedBoats) {
|
||||
FXMLLoader dialog = new FXMLLoader(
|
||||
getClass().getResource("/views/dialogs/RaceFinishDialog.fxml"));
|
||||
|
||||
Platform.runLater(() -> {
|
||||
try {
|
||||
finishScreenDialog = new JFXDialog(contentStackPane, dialog.load(),
|
||||
JFXDialog.DialogTransition.CENTER);
|
||||
finishDialogController = dialog.getController();
|
||||
finishDialogController.setFinishedBoats(finishedBoats);
|
||||
finishScreenDialog.show();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public void loadRace (
|
||||
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);
|
||||
|
||||
gameView = new GameView3D();
|
||||
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("—");
|
||||
}
|
||||
});
|
||||
|
||||
updateOrder(raceState.getPlayerPositions());
|
||||
gameView = new GameView3D();
|
||||
gameView.setFrameRateFXText(fpsDisplay);
|
||||
Platform.runLater(() -> {
|
||||
contentAnchorPane.getChildren().add(0, gameView.getAssets());
|
||||
contentStackPane.getChildren().add(0, gameView.getAssets());
|
||||
((SubScene) gameView.getAssets()).widthProperty()
|
||||
.bind(ViewManager.getInstance().getStage().widthProperty());
|
||||
((SubScene) gameView.getAssets()).heightProperty()
|
||||
.bind(ViewManager.getInstance().getStage().heightProperty());
|
||||
System.out.println(((SubScene) gameView.getAssets()).getHeight());
|
||||
System.out.println(((SubScene) gameView.getAssets()).getWidth());
|
||||
|
||||
miniMapPane.getChildren().add(miniMap.getAssets());
|
||||
});
|
||||
gameView.setBoats(new ArrayList<>(participants.values()));
|
||||
gameView.updateBorder(raceData.getCourseLimit());
|
||||
@@ -229,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()));
|
||||
@@ -245,205 +297,76 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
|
||||
updateWindDirection(raceState.windDirectionProperty().doubleValue());
|
||||
updateWindSpeed(raceState.getWindSpeed());
|
||||
});
|
||||
// gameView.setWindDir(raceState.windDirectionProperty().doubleValue());
|
||||
gameView.setWindDir(raceState.windDirectionProperty().doubleValue());
|
||||
Platform.runLater(this::initializeUpdateTimer);
|
||||
|
||||
//TODO extract chat stuff
|
||||
// raceState.addCollisionListener(gameView::drawCollision);
|
||||
// raceState.windDirectionProperty().addListener((obs, oldDirection, newDirection) -> {
|
||||
// gameView.setWindDir(newDirection.doubleValue());
|
||||
// updateWindDirection(newDirection.doubleValue());
|
||||
// });
|
||||
// raceState.windSpeedProperty().addListener((obs, oldSpeed, newSpeed) -> {
|
||||
// updateWindSpeed(newSpeed.doubleValue());
|
||||
// });
|
||||
// updateWindDirection(raceState.windDirectionProperty().doubleValue());
|
||||
// updateWindSpeed(raceState.getWindSpeed());
|
||||
// gameView.setWindDir(raceState.windDirectionProperty().doubleValue());
|
||||
// chatInput.focusedProperty().addListener((obs, oldValue, newValue) -> {
|
||||
// if (newValue) {
|
||||
// gameView.disableZoom();
|
||||
// } else {
|
||||
// gameView.enableZoom();
|
||||
// }
|
||||
// });
|
||||
Platform.runLater(() -> {
|
||||
initializeUpdateTimer();
|
||||
// initialiseFPSCheckBox();
|
||||
// initialiseAnnotationSlider();
|
||||
// initialiseBoatSelectionComboBox();
|
||||
// initialiseSparkLine();
|
||||
//windCell.setCamera(gameView.getView().getCamera());
|
||||
|
||||
initialiseWindArrow();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* The important annotations have been changed, update this view
|
||||
* Displays the relevant icon, starts blinking it when it is close to turning off and then
|
||||
* switches it off after the tokens time out
|
||||
*
|
||||
* @param importantAnnotationsState The current state of the selected annotations
|
||||
* @param yacht The yacht only for which we are displaying the icon
|
||||
* @param tokenType The type of token, indicating what icon needs to be displayed
|
||||
*/
|
||||
public void importantAnnotationsChanged(ImportantAnnotationsState importantAnnotationsState) {
|
||||
this.importantAnnotations = importantAnnotationsState;
|
||||
setAnnotations((int) annotationSlider.getValue()); // Refresh the displayed annotations
|
||||
}
|
||||
private void displayPowerUpIcon(ClientYacht yacht, TokenType tokenType) {
|
||||
if (yacht == player) {
|
||||
if (iconToDisplay != null) {
|
||||
iconToDisplay.setVisible(false);
|
||||
}
|
||||
|
||||
switch (tokenType) {
|
||||
case BOOST:
|
||||
iconToDisplay = velocityIcon;
|
||||
break;
|
||||
case HANDLING:
|
||||
iconToDisplay = handlingIcon;
|
||||
break;
|
||||
case WIND_WALKER:
|
||||
iconToDisplay = windWalkerIcon;
|
||||
break;
|
||||
case BUMPER:
|
||||
iconToDisplay = bumperIcon;
|
||||
break;
|
||||
case RANDOM:
|
||||
iconToDisplay = badRandomIcon;
|
||||
break;
|
||||
default:
|
||||
iconToDisplay = velocityIcon;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
//Turn icon on
|
||||
iconToDisplay.setVisible(true);
|
||||
|
||||
//Start blinking icon towards end
|
||||
if (blinkingTimer != null) {
|
||||
blinkingTimer.cancel();
|
||||
}
|
||||
blinkingTimer = new Timer("Blinking Timer");
|
||||
blinkingTimer.schedule(new TimerTask() {
|
||||
Boolean isVisible = true;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
isVisible = !isVisible;
|
||||
iconToDisplay.setVisible(isVisible);
|
||||
}
|
||||
}, (int) (tokenType.getTimeout() * ICON_BLINK_TIMEOUT_RATIO), ICON_BLINK_PERIOD);
|
||||
}
|
||||
}
|
||||
|
||||
// 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()
|
||||
)
|
||||
);
|
||||
private void removeIcon(ClientYacht yacht) {
|
||||
if (yacht == player) {
|
||||
blinkingTimer.cancel();
|
||||
iconToDisplay.setVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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.
|
||||
@@ -461,42 +384,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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -565,226 +465,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());
|
||||
@@ -801,7 +481,7 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
|
||||
public String readChatInput() {
|
||||
String chat = chatInput.getText();
|
||||
chatInput.clear();
|
||||
rvAnchorPane.requestFocus();
|
||||
contentStackPane.requestFocus();
|
||||
return chat;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import com.jfoenix.controls.JFXTextField;
|
||||
import com.jfoenix.validation.RequiredFieldValidator;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.ResourceBundle;
|
||||
@@ -22,11 +23,22 @@ 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;
|
||||
import seng302.visualiser.validators.ValidationTools;
|
||||
@@ -46,15 +58,29 @@ 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<>();
|
||||
|
||||
@FunctionalInterface
|
||||
public interface ServerCreationDialogListener {
|
||||
|
||||
void notifyClosure();
|
||||
}
|
||||
|
||||
// TODO: 12/09/17 ajm412: break this method down, its way too long.
|
||||
@Override
|
||||
@@ -62,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)) {
|
||||
@@ -82,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 {
|
||||
@@ -111,30 +177,69 @@ 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();
|
||||
});
|
||||
|
||||
addServerCreationDialogListener(this::closeServerCreationDialog);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows Server Creation Dialog when "Host" button is clicked.
|
||||
*/
|
||||
private void showServerCreationDialog() {
|
||||
Platform.runLater(() -> {
|
||||
FXMLLoader dialogContent = new FXMLLoader(getClass().getResource(
|
||||
"/views/dialogs/ServerCreationDialog.fxml"));
|
||||
try {
|
||||
JFXDialog dialog = new JFXDialog(serverListMainStackPane, dialogContent.load(),
|
||||
serverCreationDialog = new JFXDialog(serverListMainStackPane, dialogContent.load(),
|
||||
DialogTransition.CENTER);
|
||||
serverListHostButton.setOnAction(action -> {
|
||||
dialog.show();
|
||||
Sounds.playButtonClick();
|
||||
});
|
||||
ServerCreationController serverCreationController = dialogContent.getController();
|
||||
serverCreationController.setListener(serverCreationDialogListeners);
|
||||
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();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -144,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.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -155,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()));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -196,4 +331,14 @@ public class ServerListController implements Initializable, ServerListenerDelega
|
||||
public void serverDetected(ServerDescription serverDescription, List<ServerDescription> servers) {
|
||||
Platform.runLater(() -> refreshServers(servers));
|
||||
}
|
||||
|
||||
private void addServerCreationDialogListener(
|
||||
ServerCreationDialogListener serverCreationDialogListener) {
|
||||
serverCreationDialogListeners.add(serverCreationDialogListener);
|
||||
}
|
||||
|
||||
private void removeServerCreationDialogListener(
|
||||
ServerCreationDialogListener serverCreationDialogListener) {
|
||||
serverCreationDialogListeners.remove(serverCreationDialogListener);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -2,6 +2,9 @@ package seng302.visualiser.controllers;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXDecorator;
|
||||
import com.jfoenix.controls.JFXDialog;
|
||||
import com.jfoenix.controls.JFXDialog.DialogTransition;
|
||||
import com.jfoenix.controls.JFXSnackbar;
|
||||
import com.jfoenix.svg.SVGGlyph;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
@@ -15,14 +18,17 @@ import javafx.scene.Scene;
|
||||
import javafx.scene.SceneAntialiasing;
|
||||
import javafx.scene.image.Image;
|
||||
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;
|
||||
import seng302.visualiser.controllers.dialogs.PopupDialogController;
|
||||
|
||||
public class ViewManager {
|
||||
|
||||
@@ -32,12 +38,9 @@ public class ViewManager {
|
||||
private HashMap<String, String> properties; //TODO is this the best way to do this??
|
||||
private ObservableList<String> playerList;
|
||||
private Logger logger = LoggerFactory.getLogger(ViewManager.class);
|
||||
|
||||
public Stage getStage() {
|
||||
return stage;
|
||||
}
|
||||
|
||||
private Stage stage;
|
||||
private JFXSnackbar jfxSnackbar;
|
||||
private JFXDialog keyBindingDialog;
|
||||
|
||||
private ViewManager() {
|
||||
properties = new HashMap<>();
|
||||
@@ -53,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.
|
||||
*/
|
||||
@@ -64,17 +77,14 @@ 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();
|
||||
setDecorator(decorator);
|
||||
|
||||
gameClient = new GameClient(decorator);
|
||||
|
||||
stage.getIcons().add(new Image(getClass().getResourceAsStream("/PP.png")));
|
||||
Scene scene = new Scene(decorator, 1200, 800, false, SceneAntialiasing.BALANCED);
|
||||
stage.setMinHeight(800);
|
||||
@@ -112,28 +122,56 @@ public class ViewManager {
|
||||
private void setDecorator(JFXDecorator newDecorator) {
|
||||
decorator = newDecorator;
|
||||
|
||||
decorator.setOnCloseButtonAction(() -> {
|
||||
gameClient.stopGame();
|
||||
System.exit(0);
|
||||
});
|
||||
|
||||
//Injecting a volume toggle into the decorator.
|
||||
//Get the button box
|
||||
HBox btns = (HBox) decorator.getChildren().get(0);
|
||||
|
||||
//Create settings button -- [WIP]
|
||||
JFXButton btnKeyBinding = new JFXButton();
|
||||
btnKeyBinding.setText(" Key Bindings");
|
||||
btnKeyBinding.setStyle("-fx-text-fill:#fff");
|
||||
btnKeyBinding.getStyleClass().add("jfx-decorator-button");
|
||||
btnKeyBinding.setCursor(Cursor.HAND);
|
||||
btnKeyBinding.setFocusTraversable(false);
|
||||
|
||||
btnKeyBinding.setOnMouseClicked(event -> Platform.runLater(() -> {
|
||||
try {
|
||||
if (!checkDialogOpened(decorator.getChildren())) {
|
||||
showKeyBindingDialog();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.warn("Something went wrong when opening key bind dialog");
|
||||
}
|
||||
}));
|
||||
|
||||
//Create new button
|
||||
JFXButton btnMute = new JFXButton();
|
||||
btnMute.setText(" Toggle Sound");
|
||||
btnMute.setStyle("-fx-text-fill:#fff");
|
||||
btnMute.getStyleClass().add("jfx-decorator-button");
|
||||
btnMute.setCursor(Cursor.HAND);
|
||||
btnMute.setFocusTraversable(false);
|
||||
|
||||
//Create Graphics
|
||||
SVGGlyph spacer = new SVGGlyph(0, "SPACER", "", Color.WHITE);
|
||||
SVGGlyph volumeOn = new SVGGlyph(0, "VOLUME_ON",
|
||||
"M39.389,13.769 22.235,28.606 6,28.606 6,47.699 21.989,47.699 39.389,62.75 39.389,13.769 M 48.128,49.03 C 50.057,45.934 51.19,42.291 51.19,38.377 C 51.19,34.399 50.026,30.703 48.043,27.577 M 55.082,20.537 C 58.777,25.523 60.966,31.694 60.966,38.377 C 60.966,44.998 58.815,51.115 55.178,56.076 M 61.71,62.611 C 66.977,55.945 70.128,47.531 70.128,38.378 C 70.128,29.161 66.936,20.696 61.609,14.01",
|
||||
"M0,6 L0,12 L4,12 L9,17 L9,1 L4,6 L0,6 L0,6 Z M13.5,9 C13.5,7.2 12.5,5.7 11,5 L11,13 C12.5,12.3 13.5,10.8 13.5,9 L13.5,9 Z M11,0.2 L11,2.3 C13.9,3.2 16,5.8 16,9 C16,12.2 13.9,14.8 11,15.7 L11,17.8 C15,16.9 18,13.3 18,9 C18,4.7 15,1.1 11,0.2 L11,0.2 Z",
|
||||
Color.WHITE);
|
||||
SVGGlyph volumeOff = new SVGGlyph(0, "VOLUME_ON",
|
||||
"M39.389,13.769 22.235,28.606 6,28.606 6,47.699 21.989,47.699 39.389,62.75 39.389,13.769",
|
||||
"M13.5,9 C13.5,7.2 12.5,5.7 11,5 L11,7.2 L13.5,9.7 L13.5,9 L13.5,9 Z M16,9 C16,9.9 15.8,10.8 15.5,11.6 L17,13.1 C17.7,11.9 18,10.4 18,8.9 C18,4.6 15,1 11,0.1 L11,2.2 C13.9,3.2 16,5.8 16,9 L16,9 Z M1.3,0 L0,1.3 L4.7,6 L0,6 L0,12 L4,12 L9,17 L9,10.3 L13.3,14.6 C12.6,15.1 11.9,15.5 11,15.8 L11,17.9 C12.4,17.6 13.6,17 14.7,16.1 L16.7,18.1 L18,16.8 L9,7.8 L1.3,0 L1.3,0 Z M9,1 L6.9,3.1 L9,5.2 L9,1 L9,1 Z",
|
||||
Color.WHITE);
|
||||
SVGGlyph keyBindingGlyph = new SVGGlyph(0, "KEY_BINDING",
|
||||
"M20 5H4c-1.1 0-1.99.9-1.99 2L2 17c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm-9 3h2v2h-2V8zm0 3h2v2h-2v-2zM8 8h2v2H8V8zm0 3h2v2H8v-2zm-1 2H5v-2h2v2zm0-3H5V8h2v2zm9 7H8v-2h8v2zm0-4h-2v-2h2v2zm0-3h-2V8h2v2zm3 3h-2v-2h2v2zm0-3h-2V8h2v2z",
|
||||
Color.WHITE);
|
||||
volumeOn.setSize(16, 16);
|
||||
volumeOff.setSize(12, 16);
|
||||
volumeOff.setSize(16, 16);
|
||||
spacer.setSize(40, 16);
|
||||
keyBindingGlyph.setSize(24, 16);
|
||||
|
||||
// Determine which graphic should go on the button
|
||||
if (Sounds.isMusicMuted() && Sounds.isSoundEffectsMuted()) {
|
||||
@@ -142,9 +180,12 @@ public class ViewManager {
|
||||
btnMute.setGraphic(volumeOn);
|
||||
}
|
||||
|
||||
btnKeyBinding.setGraphic(keyBindingGlyph);
|
||||
|
||||
// Add Buttons
|
||||
btns.getChildren().add(0, spacer);
|
||||
btns.getChildren().add(0, btnMute);
|
||||
btns.getChildren().add(0, btnKeyBinding);
|
||||
btnMute.setOnAction((action) -> {
|
||||
Sounds.toggleAllSounds();
|
||||
if (btnMute.getGraphic().equals(volumeOff)) {
|
||||
@@ -154,18 +195,90 @@ public class ViewManager {
|
||||
}
|
||||
});
|
||||
|
||||
jfxSnackbar = new JFXSnackbar(decorator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a PC has compatibility with the bonjour protocol for server detection.
|
||||
* Recursively find JFXDialog given a starting node. Will traverse children of StackPane.
|
||||
*
|
||||
* @param nodes children nodes to be check.
|
||||
* @return true if node contains JFXDialog.
|
||||
*/
|
||||
private void checkCompatibility() {
|
||||
if (BonjourInstallChecker.isBonjourSupported()) {
|
||||
BonjourInstallChecker.openInstallUrl();
|
||||
private Boolean checkDialogOpened(ObservableList<Node> nodes) {
|
||||
boolean foundJFXDialog = false;
|
||||
for (Node node : nodes) {
|
||||
if (node instanceof JFXDialog) {
|
||||
return true;
|
||||
} else if (node instanceof StackPane) {
|
||||
foundJFXDialog = checkDialogOpened(((StackPane) node).getChildren());
|
||||
}
|
||||
}
|
||||
return foundJFXDialog;
|
||||
}
|
||||
|
||||
private void showKeyBindingDialog() throws IOException {
|
||||
FXMLLoader dialogContent = new FXMLLoader(getClass().getResource(
|
||||
"/views/dialogs/KeyBindingDialog.fxml"));
|
||||
for (Node node : decorator.getChildren()) {
|
||||
if (node instanceof StackPane) {
|
||||
keyBindingDialog = new JFXDialog((StackPane) node,
|
||||
dialogContent.load(),
|
||||
DialogTransition.CENTER);
|
||||
|
||||
KeyBindingDialogController keyBindingDialogController = dialogContent
|
||||
.getController();
|
||||
keyBindingDialogController.setGameClient(this.gameClient);
|
||||
keyBindingDialog.show();
|
||||
decorator.requestFocus();
|
||||
Sounds.playButtonClick();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void closeKeyBindingDialog() {
|
||||
keyBindingDialog.close();
|
||||
}
|
||||
|
||||
public PopupDialogController showPopupDialog() {
|
||||
FXMLLoader dialogContent = new FXMLLoader(
|
||||
getClass().getResource("/views/dialogs/PopupDialog.fxml"));
|
||||
for (Node node : decorator.getChildren()) {
|
||||
if (node instanceof StackPane) {
|
||||
try {
|
||||
JFXDialog dialog = new JFXDialog((StackPane) node, dialogContent.load(),
|
||||
DialogTransition.CENTER);
|
||||
PopupDialogController popupDialogController = dialogContent.getController();
|
||||
popupDialogController.setPopupDialog(dialog);
|
||||
dialog.show();
|
||||
return popupDialogController;
|
||||
} catch (IOException e) {
|
||||
logger.error("Cannot load Popup dialog");
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a snackbar at the bottom of the app for 1 second.
|
||||
*
|
||||
* @param snackbarText text to be displayed.
|
||||
*/
|
||||
public void showSnackbar(String snackbarText, boolean isWarning) {
|
||||
if (isWarning) {
|
||||
decorator.getStylesheets()
|
||||
.add(getClass().getResource("/css/dialogs/Snackbar.css").toExternalForm());
|
||||
} else {
|
||||
if (decorator.getStylesheets().size() > 1) {
|
||||
decorator.getStylesheets().remove(1);
|
||||
}
|
||||
}
|
||||
jfxSnackbar.show(snackbarText, 1500);
|
||||
}
|
||||
|
||||
private void closeAll() {
|
||||
if (stage!= null) stage.close();
|
||||
|
||||
try {
|
||||
ServerAdvertiser.getInstance().unregister();
|
||||
} catch (IOException e1) {
|
||||
@@ -192,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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,6 +331,7 @@ public class ViewManager {
|
||||
|
||||
/**
|
||||
* Change the view to the Lobby Screen
|
||||
*
|
||||
* @param disableReadyButton Boolean value so that clients can't try start a game.
|
||||
* @return A LobbyController object for the Lobby Screen.
|
||||
*/
|
||||
@@ -230,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();
|
||||
}
|
||||
|
||||
@@ -240,19 +355,17 @@ public class ViewManager {
|
||||
|
||||
/**
|
||||
* Sets up the view for the race. Creating a new decorator and destroying the old one.
|
||||
*
|
||||
* @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
|
||||
// bug causes stage cannot be fully maximised.
|
||||
Platform.runLater(() -> {
|
||||
try {
|
||||
stage.close();
|
||||
stage = new Stage();
|
||||
|
||||
JFXDecorator decorator = new JFXDecorator(stage, loader.load(), false, true, true);
|
||||
decorator.setCustomMaximize(true);
|
||||
decorator.applyCss();
|
||||
@@ -260,21 +373,16 @@ public class ViewManager {
|
||||
.add(getClass().getResource("/css/Master.css").toExternalForm());
|
||||
setDecorator(decorator);
|
||||
Scene scene = new Scene(decorator);
|
||||
RaceViewController raceViewController = loader.getController();
|
||||
gameClient.setRaceViewController(raceViewController);
|
||||
// set key press event to catch key stoke
|
||||
scene.setOnKeyPressed(gameClient::keyPressed);
|
||||
scene.setOnKeyReleased(gameClient::keyReleased);
|
||||
|
||||
// uncomment to make it full screen
|
||||
// Rectangle2D visualBounds = Screen.getPrimary().getVisualBounds();
|
||||
// stage.setX(visualBounds.getMinX());
|
||||
// stage.setY(visualBounds.getMinY());
|
||||
// stage.setWidth(visualBounds.getWidth());
|
||||
// stage.setHeight(visualBounds.getHeight());
|
||||
// stage.setMaximized(true);
|
||||
// stage.setFullScreen(true);
|
||||
|
||||
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());
|
||||
stage.setScene(scene);
|
||||
stage.show();
|
||||
@@ -283,7 +391,7 @@ public class ViewManager {
|
||||
}
|
||||
});
|
||||
|
||||
while (loader.getController() == null){
|
||||
while (loader.getController() == null) {
|
||||
try {
|
||||
Thread.sleep(50);
|
||||
} catch (InterruptedException e) {
|
||||
@@ -293,4 +401,19 @@ 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.scene.paint.Color;
|
||||
import seng302.model.ClientYacht;
|
||||
import seng302.visualiser.fxObjects.assets_3D.BoatMeshType;
|
||||
import seng302.visualiser.fxObjects.assets_3D.BoatModel;
|
||||
import seng302.visualiser.fxObjects.assets_3D.ModelFactory;
|
||||
@@ -24,11 +25,13 @@ public class PlayerCell {
|
||||
private String name;
|
||||
private Color boatColor;
|
||||
private Integer playerId;
|
||||
private BoatMeshType boatType;
|
||||
|
||||
public PlayerCell(Integer playerId, String playerName, Color color) {
|
||||
public PlayerCell(Integer playerId, ClientYacht yacht) {
|
||||
this.playerId = playerId;
|
||||
this.name = playerName;
|
||||
this.boatColor = color;
|
||||
this.name = yacht.getBoatName();
|
||||
this.boatColor = yacht.getColour();
|
||||
this.boatType = yacht.getBoatType();
|
||||
}
|
||||
|
||||
public void initialize() {
|
||||
@@ -37,7 +40,7 @@ public class PlayerCell {
|
||||
// Add Rotating Boat to Player Cell with players color on it.
|
||||
Group group = new Group();
|
||||
boatPane.getChildren().add(group);
|
||||
BoatModel bo = ModelFactory.boatIconView(BoatMeshType.DINGHY, this.boatColor);
|
||||
BoatModel bo = ModelFactory.boatIconView(boatType, boatColor);
|
||||
group.getChildren().add(bo.getAssets());
|
||||
}
|
||||
|
||||
|
||||
@@ -69,7 +69,6 @@ public class ServerCell implements Initializable {
|
||||
* Attempts to connect to the chosen server using the button on the serverCell.
|
||||
*/
|
||||
private void joinServer() {
|
||||
System.out.println("Connecting to " + serverName.getText());
|
||||
ViewManager.getInstance().getGameClient().runAsClient(hostName, portNumber);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -6,15 +6,25 @@ import com.jfoenix.controls.JFXTextField;
|
||||
import com.jfoenix.validation.RequiredFieldValidator;
|
||||
import java.net.URL;
|
||||
import java.util.ResourceBundle;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.Initializable;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.Group;
|
||||
import javafx.scene.PointLight;
|
||||
import javafx.scene.control.ProgressBar;
|
||||
import javafx.scene.layout.Background;
|
||||
import javafx.scene.layout.BackgroundFill;
|
||||
import javafx.scene.layout.CornerRadii;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.scene.paint.Color;
|
||||
import seng302.gameServer.messages.CustomizeRequestType;
|
||||
import seng302.utilities.Sounds;
|
||||
import seng302.visualiser.ClientToServerThread;
|
||||
import seng302.visualiser.controllers.LobbyController;
|
||||
import seng302.visualiser.controllers.ViewManager;
|
||||
import seng302.visualiser.fxObjects.assets_3D.BoatMeshType;
|
||||
import seng302.visualiser.fxObjects.assets_3D.BoatModel;
|
||||
import seng302.visualiser.fxObjects.assets_3D.ModelFactory;
|
||||
import seng302.visualiser.validators.FieldLengthValidator;
|
||||
import seng302.visualiser.validators.ValidationTools;
|
||||
|
||||
@@ -24,27 +34,35 @@ public class BoatCustomizeController implements Initializable{
|
||||
@FXML
|
||||
private JFXColorPicker colorPicker;
|
||||
@FXML
|
||||
private ProgressBar speedBar;
|
||||
@FXML
|
||||
private ProgressBar accelBar;
|
||||
@FXML
|
||||
private ProgressBar handleBar;
|
||||
@FXML
|
||||
private JFXButton submitBtn;
|
||||
@FXML
|
||||
private JFXTextField boatName;
|
||||
@FXML
|
||||
void colorChanged(ActionEvent event) {
|
||||
Color color = colorPicker.getValue();
|
||||
private Pane boatPane;
|
||||
@FXML
|
||||
void colorChanged() {
|
||||
refreshBoat();
|
||||
}
|
||||
//---------FXML END---------//
|
||||
|
||||
private ClientToServerThread socketThread;
|
||||
private LobbyController lobbyController;
|
||||
private BoatMeshType currentBoat;
|
||||
private Double maxSpeedMultiplier = 1.0;
|
||||
private Double maxTurnRateMultiplier = 1.0;
|
||||
private Double maxAccelerationMultiplier = 1.0;
|
||||
|
||||
@Override
|
||||
public void initialize(URL location, ResourceBundle resources) {
|
||||
submitBtn.setOnMouseReleased(event -> {
|
||||
Sounds.playButtonClick();
|
||||
submitCustomization();
|
||||
});
|
||||
|
||||
socketThread = ViewManager.getInstance().getGameClient().getServerThread();
|
||||
|
||||
findMaxStats();
|
||||
RequiredFieldValidator playerNameReqValidator = new RequiredFieldValidator();
|
||||
playerNameReqValidator.setMessage("Player name required.");
|
||||
|
||||
@@ -52,6 +70,13 @@ public class BoatCustomizeController implements Initializable{
|
||||
playerNameLengthValidator.setMessage("Player name too long.");
|
||||
|
||||
boatName.setValidators(playerNameLengthValidator, playerNameReqValidator);
|
||||
boatPane.setBackground(
|
||||
new Background(new BackgroundFill(Color.SKYBLUE, CornerRadii.EMPTY, Insets.EMPTY)));
|
||||
|
||||
submitBtn.setOnMouseReleased(event -> {
|
||||
Sounds.playButtonClick();
|
||||
submitCustomization();
|
||||
});
|
||||
|
||||
submitBtn.setOnMouseEntered(e -> Sounds.playHoverSound());
|
||||
}
|
||||
@@ -77,7 +102,10 @@ public class BoatCustomizeController implements Initializable{
|
||||
colorArray[2] = (byte) blue;
|
||||
|
||||
socketThread.sendCustomizationRequest(CustomizeRequestType.COLOR, colorArray);
|
||||
socketThread.sendCustomizationRequest(CustomizeRequestType.SHAPE, currentBoat.toString().getBytes());
|
||||
lobbyController.closeCustomizationDialog();
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,4 +120,61 @@ public class BoatCustomizeController implements Initializable{
|
||||
public void setParentController(LobbyController lobbyController){
|
||||
this.lobbyController = lobbyController;
|
||||
}
|
||||
|
||||
public void setCurrentBoat(String boatType) {
|
||||
currentBoat = BoatMeshType.valueOf(boatType);
|
||||
displayCurrentBoat();
|
||||
refreshStatBars(currentBoat);
|
||||
}
|
||||
|
||||
public void nextBoat() {
|
||||
currentBoat = BoatMeshType.getNextBoatType(currentBoat);
|
||||
displayCurrentBoat();
|
||||
refreshStatBars(currentBoat);
|
||||
}
|
||||
|
||||
public void prevBoat() {
|
||||
currentBoat = BoatMeshType.getPrevBoatType(currentBoat);
|
||||
displayCurrentBoat();
|
||||
refreshStatBars(currentBoat);
|
||||
|
||||
}
|
||||
|
||||
private void displayCurrentBoat() {
|
||||
boatPane.getChildren().clear();
|
||||
Group group = new Group();
|
||||
boatPane.getChildren().add(group);
|
||||
BoatModel bo = ModelFactory.boatCustomiseView(currentBoat, colorPicker.getValue());
|
||||
group.getChildren().add(bo.getAssets());
|
||||
group.getChildren().add(new PointLight());
|
||||
}
|
||||
|
||||
private void refreshBoat() {
|
||||
boatPane.getChildren().clear();
|
||||
Group group = new Group();
|
||||
boatPane.getChildren().add(group);
|
||||
BoatModel bo = ModelFactory.boatCustomiseView(currentBoat, colorPicker.getValue());
|
||||
group.getChildren().add(bo.getAssets());
|
||||
refreshStatBars(currentBoat);
|
||||
}
|
||||
|
||||
private void findMaxStats() {
|
||||
for (BoatMeshType bmt: BoatMeshType.values()) {
|
||||
if (bmt.turnStep > maxTurnRateMultiplier) {
|
||||
maxTurnRateMultiplier = bmt.turnStep;
|
||||
}
|
||||
if (bmt.maxSpeedMultiplier > maxSpeedMultiplier) {
|
||||
maxSpeedMultiplier = bmt.maxSpeedMultiplier;
|
||||
}
|
||||
if (bmt.accelerationMultiplier > maxAccelerationMultiplier) {
|
||||
maxAccelerationMultiplier = bmt.accelerationMultiplier;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void refreshStatBars(BoatMeshType bo) {
|
||||
speedBar.setProgress((bo.maxSpeedMultiplier) / maxSpeedMultiplier);
|
||||
accelBar.setProgress(bo.accelerationMultiplier / maxAccelerationMultiplier);
|
||||
handleBar.setProgress(bo.turnStep / maxTurnRateMultiplier);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package seng302.visualiser.controllers.dialogs;
|
||||
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXListView;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.ResourceBundle;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.Initializable;
|
||||
import javafx.scene.control.Label;
|
||||
import seng302.model.ClientYacht;
|
||||
import seng302.visualiser.controllers.ViewManager;
|
||||
|
||||
public class FinishDialogController implements Initializable {
|
||||
|
||||
//--------FXML BEGIN--------//
|
||||
@FXML
|
||||
private Label raceFinishLabel;
|
||||
@FXML
|
||||
private JFXListView<Label> finishersList;
|
||||
@FXML
|
||||
private JFXButton playAgain;
|
||||
//---------FXML END---------//
|
||||
|
||||
@Override
|
||||
public void initialize(URL location, ResourceBundle resources) {
|
||||
playAgain.setOnAction(event -> ViewManager.getInstance().goToStartView());
|
||||
}
|
||||
|
||||
public void setFinishedBoats(ArrayList<ClientYacht> finishedBoats) {
|
||||
finishersList.getItems().clear();
|
||||
for (int i = 0; i < finishedBoats.size(); i++) {
|
||||
finishersList.getItems().add(new Label(Integer.toString(i+1) +". " + finishedBoats.get(i).getBoatName()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
package seng302.visualiser.controllers.dialogs;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXToggleButton;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.ResourceBundle;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.Initializable;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import seng302.model.GameKeyBind;
|
||||
import seng302.model.KeyAction;
|
||||
import seng302.visualiser.GameClient;
|
||||
import seng302.visualiser.controllers.ViewManager;
|
||||
|
||||
public class KeyBindingDialogController implements Initializable {
|
||||
|
||||
//--------FXML BEGIN--------//
|
||||
@FXML
|
||||
private Label keyBindingDialogHeader;
|
||||
@FXML
|
||||
private Label closeLabel;
|
||||
@FXML
|
||||
private JFXButton zoomInBtn;
|
||||
@FXML
|
||||
private JFXButton zoomOutBtn;
|
||||
@FXML
|
||||
private JFXButton vmgBtn;
|
||||
@FXML
|
||||
private JFXButton sailInOutBtn;
|
||||
@FXML
|
||||
private JFXButton tackGybeBtn;
|
||||
@FXML
|
||||
private JFXButton upwindBtn;
|
||||
@FXML
|
||||
private JFXButton downwindBtn;
|
||||
@FXML
|
||||
private JFXButton resetBtn;
|
||||
@FXML
|
||||
private JFXButton confirmBtn;
|
||||
@FXML
|
||||
private Label upwindLabel;
|
||||
@FXML
|
||||
private Label downwindLabel;
|
||||
@FXML
|
||||
private JFXToggleButton turningToggle;
|
||||
@FXML
|
||||
private JFXButton viewButton;
|
||||
@FXML
|
||||
private JFXButton rightButton;
|
||||
@FXML
|
||||
private JFXButton leftButton;
|
||||
@FXML
|
||||
private JFXButton forwardButton;
|
||||
@FXML
|
||||
private JFXButton backwardButton;
|
||||
//---------FXML END---------//
|
||||
|
||||
private GameKeyBind gameKeyBind;
|
||||
private List<JFXButton> buttons = new ArrayList<>();
|
||||
private Map<Button, KeyAction> buttonActionMap;
|
||||
private GameClient gameClient; // to send turning mode packet
|
||||
|
||||
@Override
|
||||
public void initialize(URL location, ResourceBundle resources) {
|
||||
gameKeyBind = GameKeyBind.getInstance();
|
||||
buttons = new ArrayList<>();
|
||||
Collections.addAll(buttons,
|
||||
zoomInBtn, zoomOutBtn, vmgBtn, sailInOutBtn, tackGybeBtn, upwindBtn, downwindBtn,
|
||||
viewButton, rightButton, leftButton, forwardButton, backwardButton);
|
||||
bindButtonWithAction();
|
||||
loadKeyBind();
|
||||
|
||||
buttons.forEach(button -> {
|
||||
button.setOnMouseEntered(event -> mouseEnter(button));
|
||||
button.setOnMousePressed(event -> buttonPressed(button));
|
||||
button.setOnMouseExited(event -> mouseExit(button));
|
||||
button.setOnKeyPressed(event -> keyPressed(event, button));
|
||||
});
|
||||
|
||||
turningToggle.setOnMouseClicked(event -> toggleTurningMode());
|
||||
|
||||
resetBtn.setOnMouseClicked(event -> {
|
||||
gameKeyBind.setToDefault();
|
||||
loadKeyBind();
|
||||
showSnackBar("All keys reset!", false);
|
||||
});
|
||||
|
||||
closeLabel.setOnMouseClicked(event -> ViewManager.getInstance().closeKeyBindingDialog());
|
||||
confirmBtn.setOnMouseClicked(event -> ViewManager.getInstance().closeKeyBindingDialog());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set buttons' label according to GameKeyBind settings
|
||||
*/
|
||||
private void loadKeyBind() {
|
||||
buttons.forEach(
|
||||
button -> button
|
||||
.setText(gameKeyBind.getKeyCode(buttonActionMap.get(button)).getName()));
|
||||
turningToggle.setSelected(gameKeyBind.isContinuouslyTurning());
|
||||
if (gameKeyBind.isContinuouslyTurning()) {
|
||||
upwindLabel.setText("ClOCKWISE TURNING");
|
||||
downwindLabel.setText("ANTICLOCKWISE TURNING");
|
||||
} else {
|
||||
upwindLabel.setText("UPWIND");
|
||||
downwindLabel.setText("DOWNWIND");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind buttons with specific action in a map.
|
||||
*/
|
||||
private void bindButtonWithAction() {
|
||||
buttonActionMap = new HashMap<>();
|
||||
for (int i = 0; i < 12; i++) {
|
||||
buttonActionMap.put(buttons.get(i), KeyAction.getType(i + 1));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prompt success / failure message for reassigning key action
|
||||
*/
|
||||
private void showSnackBar(String message, Boolean isWarning) {
|
||||
ViewManager.getInstance().showSnackbar(message, isWarning);
|
||||
}
|
||||
|
||||
/**
|
||||
* When a mouse enters the button, the color and font size should change to highlight
|
||||
* @param button
|
||||
*/
|
||||
private void mouseEnter(Button button) {
|
||||
button.setStyle(""
|
||||
+ "-fx-background-color: -fx-pp-theme-color;"
|
||||
+ "-fx-text-fill: -fx-pp-front-color;"
|
||||
+ "-fx-font-size: 15;");
|
||||
}
|
||||
|
||||
/**
|
||||
* Prompt "press key..." to inform users assign a new key bind by pressing a key
|
||||
* @param button
|
||||
*/
|
||||
private void buttonPressed(Button button) {
|
||||
button.setText("PRESS KEY...");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* When mouse leaves the button, return the button to the normal state in terms of text,
|
||||
* color and font size
|
||||
* @param button
|
||||
*/
|
||||
private void mouseExit(Button button) {
|
||||
button.setText(GameKeyBind.getInstance().getKeyCode(buttonActionMap.get(button)).getName());
|
||||
button.setStyle(""
|
||||
+ "-fx-background-color: -fx-pp-front-color; "
|
||||
+ "-fx-text-fill: -fx-pp-theme-color; "
|
||||
+ "-fx-font-size: 13;");
|
||||
keyBindingDialogHeader.requestFocus();
|
||||
}
|
||||
|
||||
/**
|
||||
* When a key is pressed, check if the new binding conflicts to any existed settings, if not
|
||||
* assign the selected action with the new key binding to GameKeyBind.
|
||||
* @param event
|
||||
* @param button
|
||||
*/
|
||||
private void keyPressed(KeyEvent event, Button button) {
|
||||
event.consume();
|
||||
KeyAction buttonAction = buttonActionMap.get(button);
|
||||
if (gameKeyBind.bindKeyToAction(event.getCode(), buttonAction)) {
|
||||
showSnackBar(button.getId() + " is set to " + event.getCode().getName(), false);
|
||||
button.setText(gameKeyBind.getKeyCode(buttonAction).getName());
|
||||
} else {
|
||||
loadKeyBind();
|
||||
showSnackBar(event.getCode().getName() + " is already in use", true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* When the turning mode is toggled, update gameKeyBind and send out packet to notify the server
|
||||
*/
|
||||
private void toggleTurningMode() {
|
||||
gameKeyBind.toggleTurningMode();
|
||||
gameClient.sendToggleTurningModePacket();
|
||||
loadKeyBind();
|
||||
}
|
||||
|
||||
public void setGameClient(GameClient gameClient) {
|
||||
this.gameClient = gameClient;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package seng302.visualiser.controllers.dialogs;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXDialog;
|
||||
import java.net.URL;
|
||||
import java.util.ResourceBundle;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.Initializable;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
|
||||
public class PopupDialogController implements Initializable {
|
||||
|
||||
@FXML
|
||||
private Label headerLabel;
|
||||
@FXML
|
||||
private Label contentLabel;
|
||||
@FXML
|
||||
private Label closeLabel;
|
||||
@FXML
|
||||
private JFXButton optionButton;
|
||||
|
||||
@FXML
|
||||
private JFXDialog popupDialog;
|
||||
|
||||
@Override
|
||||
public void initialize(URL location, ResourceBundle resources) {
|
||||
|
||||
}
|
||||
|
||||
public void setContent(String content) {
|
||||
this.contentLabel.setText(content);
|
||||
}
|
||||
|
||||
public void setHeader(String header) {
|
||||
this.headerLabel.setText(header);
|
||||
}
|
||||
|
||||
public void setOptionButton(JFXButton jfxButton) {
|
||||
this.optionButton = jfxButton;
|
||||
}
|
||||
|
||||
public void setOptionButtonText(String text) {
|
||||
this.optionButton.setText(text);
|
||||
}
|
||||
|
||||
public void setOptionButtonEventHandler(EventHandler<? super MouseEvent> eventHandler) {
|
||||
this.optionButton.setOnMouseClicked(eventHandler);
|
||||
}
|
||||
|
||||
public void setPopupDialog(JFXDialog popupDialog) {
|
||||
this.popupDialog = popupDialog;
|
||||
this.closeLabel.setOnMouseClicked(event -> this.popupDialog.close());
|
||||
}
|
||||
}
|
||||
+99
-10
@@ -1,18 +1,21 @@
|
||||
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;
|
||||
import java.net.URL;
|
||||
import java.util.List;
|
||||
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 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;
|
||||
import seng302.visualiser.validators.ValidationTools;
|
||||
@@ -25,16 +28,46 @@ public class ServerCreationController implements Initializable {
|
||||
@FXML
|
||||
private JFXSlider maxPlayersSlider;
|
||||
@FXML
|
||||
private JFXButton submitBtn;
|
||||
@FXML
|
||||
private Label closeLabel;
|
||||
@FXML
|
||||
private Label maxPlayersLabel;
|
||||
@FXML
|
||||
private JFXButton submitBtn;
|
||||
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.");
|
||||
@@ -49,6 +82,20 @@ 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());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -68,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());
|
||||
@@ -80,11 +133,47 @@ 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;
|
||||
}
|
||||
|
||||
public void notifyListeners() {
|
||||
for (ServerCreationDialogListener serverCreationDialogListener : serverCreationDialogListeners) {
|
||||
serverCreationDialogListener.notifyClosure();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+103
-20
@@ -1,10 +1,19 @@
|
||||
package seng302.visualiser.fxObjects.assets_2D;
|
||||
package seng302.visualiser.fxObjects;
|
||||
|
||||
import javafx.geometry.Point3D;
|
||||
import javafx.scene.Group;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.paint.Paint;
|
||||
import javafx.scene.shape.*;
|
||||
import javafx.scene.shape.Arc;
|
||||
import javafx.scene.shape.ArcType;
|
||||
import javafx.scene.shape.Polygon;
|
||||
import javafx.scene.shape.Polyline;
|
||||
import javafx.scene.shape.StrokeLineCap;
|
||||
import javafx.scene.transform.Rotate;
|
||||
import javafx.scene.transform.Translate;
|
||||
import seng302.visualiser.fxObjects.assets_3D.Model;
|
||||
import seng302.visualiser.fxObjects.assets_3D.ModelFactory;
|
||||
import seng302.visualiser.fxObjects.assets_3D.ModelType;
|
||||
|
||||
// TODO: 16/08/17 this class used to be well written... FeelsBadMan. Maybe lose the ternary operators.
|
||||
/**
|
||||
@@ -20,11 +29,52 @@ 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) {
|
||||
Model entryArrow = ModelFactory.importModel(type);
|
||||
|
||||
double angleDeg = angle;
|
||||
angle = 180 - angle;
|
||||
angle = Math.toRadians(angle);
|
||||
|
||||
int multiplier = roundingSide == RoundingSide.STARBOARD ? 1 : -1;
|
||||
double relativeX = multiplier * 5.7 * Math.sin(angle + Math.PI / 2);
|
||||
double relativeY = multiplier * 5.7 * Math.cos(angle + Math.PI / 2);
|
||||
double xStart = relativeX + -10 * Math.sin(angle);
|
||||
double yStart = relativeY + -10 * Math.cos(angle);
|
||||
entryArrow.getAssets().getTransforms().addAll(
|
||||
new Translate(xStart, yStart, 0),
|
||||
new Rotate(angleDeg, new Point3D(0,0,1))
|
||||
);
|
||||
return entryArrow;
|
||||
}
|
||||
|
||||
public static Model constructExitArrow3D (
|
||||
RoundingSide roundingSide, double angle, ModelType type) {
|
||||
Model exitArrow = ModelFactory.importModel(type);
|
||||
|
||||
double angleDeg = angle;
|
||||
angle = 180 - angle;
|
||||
angle = Math.toRadians(angle);
|
||||
|
||||
int multiplier = roundingSide == RoundingSide.STARBOARD ? 1 : -1;
|
||||
double xStart = multiplier * 5.7 * Math.sin(angle + Math.PI / 2);
|
||||
double yStart = multiplier * 5.7 * Math.cos(angle + Math.PI / 2);
|
||||
|
||||
exitArrow.getAssets().getTransforms().addAll(
|
||||
new Translate(xStart, yStart, 0),
|
||||
new Rotate(angleDeg, new Point3D(0,0,1))
|
||||
);
|
||||
|
||||
return exitArrow;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates an entry arrow group showing an arrow into and out of the rounding area. It is centered on (0, 0).
|
||||
@@ -35,48 +85,64 @@ public class MarkArrowFactory {
|
||||
* @return The group containing all JavaFX objects.
|
||||
*/
|
||||
public static Group constructEntryArrow (RoundingSide roundingSide, double angleOfEntry,
|
||||
double angleOfExit, Paint colour) {
|
||||
if (roundingSide == RoundingSide.PORT && angleOfEntry < angleOfExit && Math.abs(angleOfExit - angleOfEntry) < 180) {
|
||||
return makeInteriorAngle(roundingSide, angleOfExit, angleOfEntry, colour);
|
||||
} else if (roundingSide == RoundingSide.STARBOARD && angleOfEntry > angleOfExit && -Math.abs(angleOfEntry - angleOfExit) > -180) {
|
||||
return makeInteriorAngle(roundingSide, angleOfExit, angleOfEntry, colour);
|
||||
}
|
||||
|
||||
angleOfEntry = 180 - angleOfEntry;
|
||||
double angleOfExit, Paint colour) {
|
||||
//Create regular exit arrow.
|
||||
Group arrow = new Group();
|
||||
Group exitSection = constructExitArrow(roundingSide, angleOfExit, colour);
|
||||
//Reverse angles to make arc
|
||||
angleOfEntry = 180 - angleOfEntry;
|
||||
angleOfExit = 180 - angleOfExit;
|
||||
//Maker the arc
|
||||
Arc roundSection = new Arc(
|
||||
0, 0, MARK_ARROW_SEPARATION, MARK_ARROW_SEPARATION,
|
||||
(roundingSide == RoundingSide.PORT ? -180 : 0) + angleOfEntry,
|
||||
//Where to start drawing arc from
|
||||
(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)
|
||||
);
|
||||
roundSection.setStrokeWidth(STROKE_WIDTH);
|
||||
roundSection.setType(ArcType.OPEN);
|
||||
roundSection.setStroke(colour);
|
||||
roundSection.setFill(new Color(0,0,0,0));
|
||||
//Revert angle to normal for line segment. Invert Port/Starboard since it is an entry arrow.
|
||||
Polygon entrySection = constructLineSegment(
|
||||
roundingSide == RoundingSide.PORT ? RoundingSide.STARBOARD : RoundingSide.PORT, 180 + angleOfEntry, colour
|
||||
roundingSide == RoundingSide.PORT ? RoundingSide.STARBOARD : RoundingSide.PORT,
|
||||
180 + angleOfEntry, colour
|
||||
);
|
||||
arrow.getChildren().addAll(exitSection, roundSection, entrySection);
|
||||
return arrow;
|
||||
}
|
||||
|
||||
private static Group makeInteriorAngle (RoundingSide roundingSide, double angleOfExit, double angleOfEntry, Paint colour) {
|
||||
/**
|
||||
* Make an arrow when the turning is not around the outside of the mark.
|
||||
*
|
||||
* @param roundingSide side to round on.
|
||||
* @param angleOfExit angle of entry
|
||||
* @param angleOfEntry angle of exit
|
||||
* @param colour colour of arrow
|
||||
* @return the arrow.
|
||||
*/
|
||||
public static Group constructInteriorArrow(RoundingSide roundingSide, double angleOfExit,
|
||||
double angleOfEntry, Paint colour) {
|
||||
|
||||
Group arrow = new Group();
|
||||
Polygon lineSegment;
|
||||
//Reverse angle of exit/entry to find position between them
|
||||
angleOfEntry = Math.toRadians(360 - angleOfEntry);
|
||||
angleOfExit = Math.toRadians(180 - angleOfExit);
|
||||
//Find start of entry arrow if it was a regular arrow.
|
||||
int multiplier = roundingSide == RoundingSide.STARBOARD ? -1 : 1;
|
||||
double xStart = multiplier * MARK_ARROW_SEPARATION * Math.sin(angleOfEntry + Math.PI / 2);
|
||||
double yStart = multiplier * MARK_ARROW_SEPARATION * Math.cos(angleOfEntry + Math.PI / 2);
|
||||
xStart = xStart + (ARROW_LENGTH * Math.sin(angleOfEntry));
|
||||
yStart = yStart + (ARROW_LENGTH * Math.cos(angleOfEntry));
|
||||
//Find of end exit arrow if it was a regular arrow.
|
||||
multiplier = roundingSide == RoundingSide.STARBOARD ? 1 : -1;
|
||||
double xEnd = multiplier * MARK_ARROW_SEPARATION * Math.sin(angleOfExit + Math.PI / 2);
|
||||
double yEnd = multiplier * MARK_ARROW_SEPARATION * Math.cos(angleOfExit + Math.PI / 2);
|
||||
xEnd = xEnd + (ARROW_LENGTH * Math.sin(angleOfExit));
|
||||
yEnd = yEnd + (ARROW_LENGTH * Math.cos(angleOfExit));
|
||||
//Make line between these points.
|
||||
lineSegment = new Polygon(
|
||||
xStart, yStart,
|
||||
xEnd, yEnd
|
||||
@@ -85,12 +151,14 @@ public class MarkArrowFactory {
|
||||
lineSegment.setFill(Color.BLUE);
|
||||
lineSegment.setStrokeWidth(STROKE_WIDTH);
|
||||
lineSegment.setStrokeLineCap(StrokeLineCap.ROUND);
|
||||
//Make arrow head at the angle between these points.
|
||||
Polyline arrowHead = constructArrowHead(
|
||||
90 + Math.toDegrees(Math.atan2(yStart - yEnd, xEnd - xStart)),
|
||||
colour
|
||||
);
|
||||
arrowHead.setLayoutX(xEnd);
|
||||
arrowHead.setLayoutY(yEnd);
|
||||
//Construct arrow.
|
||||
arrow.getChildren().addAll(lineSegment, arrowHead);
|
||||
return arrow;
|
||||
}
|
||||
@@ -113,6 +181,15 @@ public class MarkArrowFactory {
|
||||
return arrow;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a line rotated to the correct angle and and in the correct position for a mark at
|
||||
* position 0,0. Note that a line segment is assumed to be facing away from the mark so for
|
||||
* entry Starboard make the RoundingSide Port and vice versa.
|
||||
* @param roundingSide Rounding side of an exit arrow. (Reversed for entry)
|
||||
* @param angle Angle of line segment.
|
||||
* @param colour the desired colour of the line.
|
||||
* @return Line segmented at correct rotation centered at (0,0)
|
||||
*/
|
||||
private static Polygon constructLineSegment (RoundingSide roundingSide, double angle, Paint colour) {
|
||||
Polygon lineSegment;
|
||||
angle = Math.toRadians(angle);
|
||||
@@ -122,8 +199,8 @@ public class MarkArrowFactory {
|
||||
double xEnd = xStart + (ARROW_LENGTH * Math.sin(angle));
|
||||
double yEnd = yStart + (ARROW_LENGTH * Math.cos(angle));
|
||||
lineSegment = new Polygon(
|
||||
xStart, yStart,
|
||||
xEnd, yEnd
|
||||
xStart, yStart,
|
||||
xEnd, yEnd
|
||||
);
|
||||
lineSegment.setStroke(colour);
|
||||
lineSegment.setFill(Color.BLUE);
|
||||
@@ -132,6 +209,12 @@ public class MarkArrowFactory {
|
||||
return lineSegment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a PolyLine in the shape of an arrow head.
|
||||
* @param rotation direction for the arrow head to point.
|
||||
* @param colour colour of the arrow head
|
||||
* @return the arrowhead shaped PolyLine.
|
||||
*/
|
||||
private static Polyline constructArrowHead (double rotation, Paint colour) {
|
||||
Polyline arrow = new Polyline(
|
||||
-ARROW_HEAD_WIDTH, -ARROW_HEAD_DEPTH,
|
||||
@@ -0,0 +1,41 @@
|
||||
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);
|
||||
|
||||
public abstract void addFinishArrow(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,460 +0,0 @@
|
||||
package seng302.visualiser.fxObjects.assets_2D;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.ReadOnlyDoubleWrapper;
|
||||
import javafx.beans.property.SimpleDoubleProperty;
|
||||
import javafx.geometry.Point2D;
|
||||
import javafx.geometry.Point3D;
|
||||
import javafx.scene.AmbientLight;
|
||||
import javafx.scene.Group;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.PointLight;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.paint.PhongMaterial;
|
||||
import javafx.scene.shape.Line;
|
||||
import javafx.scene.shape.Polygon;
|
||||
import javafx.scene.shape.Polyline;
|
||||
import javafx.scene.shape.Shape3D;
|
||||
import javafx.scene.transform.Rotate;
|
||||
import javafx.scene.transform.Scale;
|
||||
import javafx.scene.transform.Translate;
|
||||
import seng302.visualiser.fxObjects.assets_3D.BoatMeshType;
|
||||
import seng302.visualiser.fxObjects.assets_3D.BoatModel;
|
||||
import seng302.visualiser.fxObjects.assets_3D.ModelFactory;
|
||||
import seng302.visualiser.fxObjects.assets_3D.ModelType;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* BoatGroup is a javafx group that by default contains a graphical objects for representing a 2
|
||||
* dimensional boat. It contains a single polygon for the boat, a group of lines to show it's path,
|
||||
* a wake object and two text labels to annotate the boat teams name and the boats velocity. The
|
||||
* boat will update it's position onscreen everytime UpdatePosition is called unless the window is
|
||||
* minimized in which case it attempts to store animations and apply them when the window is
|
||||
* maximised.
|
||||
*/
|
||||
public class BoatObject extends Group {
|
||||
|
||||
|
||||
private static final double MODEL_SCALE_FACTOR = 400;
|
||||
private static final double MODEL_X_OFFSET = 0; // standard
|
||||
private static final double MODEL_Y_OFFSET = 0; // standard
|
||||
|
||||
private static final int VIEWPORT_SIZE = 800;
|
||||
|
||||
private static final Color lightColor = Color.rgb(244, 255, 250);
|
||||
private static final Color jewelColor = Color.rgb(0, 190, 222);
|
||||
|
||||
|
||||
@FunctionalInterface
|
||||
public interface SelectedBoatListener {
|
||||
|
||||
void notifySelected(BoatObject boatObject, Boolean isSelected);
|
||||
}
|
||||
|
||||
//Constants for drawing
|
||||
private static final float BOAT_HEIGHT = 15f;
|
||||
private static final float BOAT_WIDTH = 10f;
|
||||
|
||||
private double xVelocity;
|
||||
private double yVelocity;
|
||||
private double lastHeading;
|
||||
private double sailState;
|
||||
//Graphical objects
|
||||
private Polyline trail = new Polyline();
|
||||
private BoatModel boatAssets;
|
||||
private Polygon sail;
|
||||
private Group wake;
|
||||
private Line leftLayLine;
|
||||
private Line rightLayline;
|
||||
private double distanceTravelled, lastRotation;
|
||||
private Point2D lastPoint;
|
||||
private Color colour = Color.BLACK;
|
||||
private Boolean isSelected = false, destinationSet; //All boats are initialised as selected
|
||||
private boolean isPlayer = false;
|
||||
private Rotate rotation = new Rotate(0,0,1);
|
||||
|
||||
private List<SelectedBoatListener> selectedBoatListenerListeners = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Creates a BoatGroup with the default triangular boat polygon.
|
||||
*/
|
||||
public BoatObject() {
|
||||
this(-BOAT_WIDTH / 2, BOAT_HEIGHT / 2,
|
||||
0.0, -BOAT_HEIGHT / 2,
|
||||
BOAT_WIDTH / 2, BOAT_HEIGHT / 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a BoatGroup with the boat being the default polygon. The head of the boat should be
|
||||
* at point (0,0).
|
||||
*
|
||||
* @param points An array of co-ordinates x1,y1,x2,y2,x3,y3... that will make up the boat
|
||||
* polygon.
|
||||
*/
|
||||
public BoatObject(double... points) {
|
||||
initChildren(points);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the javafx objects that will be the in the group by default.
|
||||
*
|
||||
* @param points An array of co-ordinates x1,y1,x2,y2,x3,y3... that will make up the boat
|
||||
* polygon.
|
||||
*/
|
||||
private void initChildren(double... points) {
|
||||
boatAssets = ModelFactory.boatGameView(BoatMeshType.DINGHY, colour);
|
||||
boatAssets.hideSail();
|
||||
boatAssets.getAssets().getTransforms().addAll(
|
||||
// new Rotate(-40, new Point3D(1,0,0)),
|
||||
rotation
|
||||
// new Rotate(-90, new Point3D(0,0,1))
|
||||
);
|
||||
boatAssets.getAssets().getTransforms().add(new Scale(5, 5, 5));
|
||||
// boatAssets.setDrawMode(DrawMode.FILL);
|
||||
// boatAssets.setFill(colour);
|
||||
// boatAssets.setFill(this.colour);
|
||||
// boatAssets.setMaterial(new PhongMaterial(this.colour));
|
||||
boatAssets.getAssets().setOnMouseEntered(event -> {
|
||||
// boatAssets.setFill(Color.FLORALWHITE);
|
||||
// boatAssets.setStroke(Color.RED);
|
||||
// boatAssets.setMaterial(new PhongMaterial(Color.FLORALWHITE));
|
||||
});
|
||||
boatAssets.getAssets().setOnMouseExited(event -> {
|
||||
// boatAssets.setMaterial(new PhongMaterial(this.colour));
|
||||
// boatAssets.setFill(colour);
|
||||
// boatAssets.setFill(this.colour);
|
||||
// boatAssets.setStroke(Color.BLACK);
|
||||
});
|
||||
boatAssets.getAssets().setOnMouseClicked(event -> setIsSelected(!isSelected));
|
||||
boatAssets.getAssets().setCache(true);
|
||||
// boatAssets.setCacheHint(CacheHint.SPEED);
|
||||
|
||||
// annotationBox = new AnnotationBox();
|
||||
// annotationBox.setFill(colour);
|
||||
|
||||
leftLayLine = new Line();
|
||||
rightLayline = new Line();
|
||||
trail.getStrokeDashArray().setAll(5d, 10d);
|
||||
trail.setCache(true);
|
||||
|
||||
wake = ModelFactory.importModel(ModelType.WAKE).getAssets();
|
||||
|
||||
sail = new Polygon(0.0,BOAT_HEIGHT / 4,
|
||||
0.0, BOAT_HEIGHT);
|
||||
sailState = 0;
|
||||
sail.setStrokeWidth(2.0);
|
||||
sail.setStroke(Color.BLACK);
|
||||
sail.setFill(Color.TRANSPARENT);
|
||||
sail.setCache(true);
|
||||
super.getChildren().clear();
|
||||
PointLight pointLight = new PointLight(Color.WHITE);
|
||||
// pointLight.setLightOn(true);
|
||||
pointLight.getTransforms().add(new Translate(0, 0, -30));
|
||||
super.getChildren().add(pointLight);
|
||||
// pointLight = new PointLight(Color.WHITE);
|
||||
//// pointLight.setLightOn(true);
|
||||
// pointLight.getTransforms().add(new Translate(50, 50, -20));
|
||||
// super.getChildren().add(pointLight);
|
||||
// pointLight = new PointLight(Color.WHITE);
|
||||
//// pointLight.setLightOn(true);
|
||||
// pointLight.getTransforms().add(new Translate(50, -50, -20));
|
||||
// super.getChildren().add(pointLight);
|
||||
// pointLight = new PointLight(Color.WHITE);
|
||||
//// pointLight.setLightOn(true);
|
||||
// pointLight.getTransforms().add(new Translate(-50, -50, -20));
|
||||
// super.getChildren().add(pointLight);
|
||||
AmbientLight light = new AmbientLight(new Color(0.5,0.5,0.5,1));
|
||||
super.getChildren().add(light);
|
||||
super.getChildren().addAll(boatAssets.getAssets());
|
||||
}
|
||||
|
||||
public void setFill (Color value) {
|
||||
this.colour = value;
|
||||
PhongMaterial pm = new PhongMaterial(this.colour);
|
||||
pm.setSpecularPower(0.5);
|
||||
pm.setSpecularColor(Color.BLACK);
|
||||
for (int i=0;i<2;i++) {
|
||||
Shape3D s = (Shape3D) boatAssets.getAssets().getChildren().get(i);
|
||||
s.setMaterial(pm);
|
||||
}
|
||||
trail.setStroke(colour);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Moves the boat and its children annotations to coordinates specified
|
||||
* @param x The X coordinate to move the boat to
|
||||
* @param y The Y coordinate to move the boat to
|
||||
* @param rotation The rotation by which the boat moves
|
||||
* @param velocity The velocity the boat is moving
|
||||
* @param sailIn Boolean to toggle sail state.
|
||||
* @param windDir .
|
||||
*/
|
||||
public void moveTo(double x, double y, double rotation, double velocity, Boolean sailIn, double windDir) {
|
||||
Double dx = Math.abs(boatAssets.getAssets().getLayoutX() - x);
|
||||
Double dy = Math.abs(boatAssets.getAssets().getLayoutY() - y);
|
||||
Platform.runLater(() -> {
|
||||
rotateTo(rotation, sailIn, windDir);
|
||||
boatAssets.getAssets().setLayoutX(x);
|
||||
boatAssets.getAssets().setLayoutY(y);
|
||||
// if (sailIn) {
|
||||
//// sail.getPoints().clear();
|
||||
//// sail.getPoints().addAll(0.0, 0.0, 4.0, 1.5, 8.0, 3.0, 12.0, 3.5, 16.0, 3.0, 20.0, 1.5, 24.0, 0.0);
|
||||
//// sail.getPoints().addAll(0.0, 0.0, 24.0, 0.0);
|
||||
// sail.setLayoutX(x);
|
||||
// sail.setLayoutY(y);
|
||||
// } else {
|
||||
//// animateSail();
|
||||
// sail.setLayoutX(x);
|
||||
// sail.setLayoutY(y);
|
||||
// }
|
||||
wake.setLayoutX(x);
|
||||
wake.setLayoutY(y);
|
||||
});
|
||||
// wake.setRotation(rotation, velocity);
|
||||
// rotateTo(rotation);
|
||||
// boatAssets.setLayoutX(x);
|
||||
// boatAssets.setLayoutY(y);
|
||||
// wake.setLayoutX(x);
|
||||
// wake.setLayoutY(y);
|
||||
// wake.rotate(rotation);
|
||||
|
||||
// wake.setRotation(rotation, groundSpeed);
|
||||
// isStopped = false;
|
||||
// destinationSet = true;
|
||||
lastRotation = rotation;
|
||||
|
||||
distanceTravelled += Math.sqrt((dx * dx) + (dy * dy));
|
||||
|
||||
if (distanceTravelled > 15 && isPlayer) {
|
||||
distanceTravelled = 0d;
|
||||
Platform.runLater(() -> trail.getPoints().addAll(x, y));
|
||||
}
|
||||
}
|
||||
|
||||
private Double normalizeHeading(double heading, double windDirection) {
|
||||
Double normalizedHeading = heading - windDirection;
|
||||
normalizedHeading = (double) Math.floorMod(normalizedHeading.longValue(), 360L);
|
||||
return normalizedHeading;
|
||||
}
|
||||
|
||||
|
||||
private void rotateTo(double heading, boolean sailsIn, double windDir) {
|
||||
rotation.setAngle(heading);
|
||||
wake.getTransforms().setAll(new Rotate(heading, new Point3D(0,0,1)));
|
||||
if (sailsIn) {
|
||||
boatAssets.showSail();
|
||||
Double sailWindOffset = 30.0;
|
||||
Double upwindAngleLimit = 15.0;
|
||||
Double downwindAngleLimit = 10.0; //Upwind from normalised horizontal
|
||||
Double normalizedHeading = normalizeHeading(heading, windDir);
|
||||
if (normalizedHeading < 180) {
|
||||
if (normalizedHeading < sailWindOffset + upwindAngleLimit){
|
||||
boatAssets.rotateSail(-heading + 90 - upwindAngleLimit);
|
||||
} else if (normalizedHeading > 90 + sailWindOffset){
|
||||
boatAssets.rotateSail(-heading + downwindAngleLimit);
|
||||
} else {
|
||||
boatAssets.rotateSail(-heading + 90 + sailWindOffset);
|
||||
}
|
||||
} else {
|
||||
// if (normalizedHeading > 360 - (sailWindOffset + upwindAngleLimit)){
|
||||
// boatAssets.rotateSail(-heading + 90 + upwindAngleLimit);
|
||||
// } else if (normalizedHeading < 270 - sailWindOffset){
|
||||
// boatAssets.rotateSail(-heading + 180 - downwindAngleLimit);
|
||||
// } else {
|
||||
// boatAssets.rotateSail(-heading + 90 - sailWindOffset);
|
||||
// }
|
||||
}
|
||||
} else {
|
||||
boatAssets.hideSail();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void animateSail(){
|
||||
Double[] points = new Double[200];
|
||||
double amplitude = 2.0;
|
||||
double period = 10;
|
||||
for (int i = 0; i < 50; i++) {
|
||||
points[i * 2] = amplitude * Math.sin(((Math.PI * i) / period + sailState));
|
||||
points[i * 2 + 1] = (double) (BOAT_HEIGHT * i) / BOAT_HEIGHT / 2;
|
||||
points[199 - (i * 2)] = (double) (BOAT_HEIGHT * i) / BOAT_HEIGHT / 2;
|
||||
points[199 - (i * 2 + 1)] = amplitude * Math.sin(((Math.PI * i) / period + sailState));
|
||||
}
|
||||
if (sailState == - 2 * Math.PI) {
|
||||
sailState = 0;
|
||||
} else {
|
||||
sailState = sailState - Math.PI / 5;
|
||||
}
|
||||
sail.getPoints().clear();
|
||||
sail.getPoints().addAll(points);
|
||||
|
||||
}
|
||||
|
||||
public void updateLocation() {
|
||||
// boatAssets.getTransforms().add(new Rotate(2, new Point3D(1,1,1)));
|
||||
// double dx = xVelocity / 60;
|
||||
// double dy = yVelocity / 60;
|
||||
//
|
||||
// distanceTravelled += Math.abs(dx) + Math.abs(dy);
|
||||
// moveGroupBy(dx, dy);
|
||||
//
|
||||
// if (distanceTravelled > 70) {
|
||||
// distanceTravelled = 0d;
|
||||
//
|
||||
// if (lastPoint != null) {
|
||||
// Line l = new Line(
|
||||
// lastPoint.getX(),
|
||||
// lastPoint.getY(),
|
||||
// boatAssets.getLayoutX(),
|
||||
// boatAssets.getLayoutY()
|
||||
// );
|
||||
// l.getStrokeDashArray().setAll(3d, 7d);
|
||||
// l.setStroke(colour);
|
||||
// l.setCache(true);
|
||||
// l.setCacheHint(CacheHint.SPEED);
|
||||
// lineGroup.getChildren().add(l);
|
||||
// }
|
||||
// lastPoint = new Point2D(boatAssets.getLayoutX(), boatAssets.getLayoutY());
|
||||
// }
|
||||
// wake.updatePosition();
|
||||
}
|
||||
|
||||
// /**
|
||||
// * This function works out if a boat is going upwind or down wind. It looks at the boats current position, the next
|
||||
// * gates position and the current wind
|
||||
// * If bot the wind vector from the next gate and the boat from the next gate lay on the same side, then the boat is
|
||||
// * going up wind, if they are on different sides of the gate, then the boat is going downwind
|
||||
// * @param canvasController
|
||||
// */
|
||||
// public Boolean isUpwindLeg(GameViewController canvasController, Mark nextMark) {
|
||||
//
|
||||
// Double windAngle = StreamParser.getWindDirection();
|
||||
// GateMark thisGateMark = (GateMark) nextMark;
|
||||
// SingleMark nextMark1 = thisGateMark.getSingleMark1();
|
||||
// SingleMark nextMark2 = thisGateMark.getSingleMark2();
|
||||
// Point2D nextMarkPoint1 = canvasController.findScaledXY(nextMark1.getLatitude(), nextMark1.getLongitude());
|
||||
// Point2D nextMarkPoint2 = canvasController.findScaledXY(nextMark2.getLatitude(), nextMark2.getLongitude());
|
||||
//
|
||||
// Point2D boatCurrentPoint = new Point2D(boatAssets.getLayoutX(), boatAssets.getLayoutY());
|
||||
// Point2D windTestPoint = GeoUtility.makeArbitraryVectorPoint(nextMarkPoint1, windAngle, 10d);
|
||||
//
|
||||
//
|
||||
// Integer boatLineFuncResult = GeoUtility.lineFunction(nextMarkPoint1, nextMarkPoint2, boatCurrentPoint);
|
||||
// Integer windLineFuncResult = GeoUtility.lineFunction(nextMarkPoint1, nextMarkPoint2, windTestPoint);
|
||||
//
|
||||
//
|
||||
// /*
|
||||
// If both the wind vector from the gate and the boat from the gate are on the same side of that gate, then the
|
||||
// boat is travelling into the wind. thus upwind. Otherwise if they are on different sides, then the boat is going
|
||||
// with the wind.
|
||||
// */
|
||||
// return boatLineFuncResult.equals(windLineFuncResult);
|
||||
// return true;
|
||||
// }
|
||||
|
||||
public void setIsSelected(Boolean isSelected) {
|
||||
updateListener(isSelected);
|
||||
this.isSelected = isSelected;
|
||||
setLineGroupVisible(isSelected);
|
||||
setWakeVisible(isSelected);
|
||||
setLayLinesVisible(isSelected);
|
||||
}
|
||||
|
||||
public void setVisibility (boolean teamName, boolean velocity, boolean estTime, boolean legTime,
|
||||
boolean trail, boolean wake) {
|
||||
// boatAnnotations.setVisible(teamName, velocity, estTime, legTime);
|
||||
// this.wake.setVisible(wake);
|
||||
this.trail.setVisible(trail);
|
||||
}
|
||||
|
||||
public void setLineGroupVisible(Boolean visible) {
|
||||
trail.setVisible(visible);
|
||||
}
|
||||
|
||||
public void setWakeVisible(Boolean visible) {
|
||||
// wake.setVisible(visible);
|
||||
}
|
||||
|
||||
public void setLayLinesVisible(Boolean visible) {
|
||||
leftLayLine.setVisible(visible);
|
||||
rightLayline.setVisible(visible);
|
||||
}
|
||||
|
||||
public void setLaylines(Line line1, Line line2) {
|
||||
this.leftLayLine = line1;
|
||||
this.rightLayline = line2;
|
||||
}
|
||||
|
||||
public ArrayList<Line> getLaylines() {
|
||||
ArrayList<Line> laylines = new ArrayList<>();
|
||||
laylines.add(leftLayLine);
|
||||
laylines.add(rightLayline);
|
||||
return laylines;
|
||||
}
|
||||
|
||||
public Group getWake () {
|
||||
return wake;
|
||||
}
|
||||
|
||||
public Node getTrail() {
|
||||
return trail;
|
||||
}
|
||||
|
||||
public Double getBoatLayoutX() {
|
||||
return boatAssets.getAssets().getLayoutX();
|
||||
}
|
||||
|
||||
|
||||
public Double getBoatLayoutY() {
|
||||
return boatAssets.getAssets().getLayoutY();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets this boat to appear highlighted
|
||||
*/
|
||||
public void setAsPlayer() {
|
||||
// boatAssets.getPoints().setAll(
|
||||
// -BOAT_WIDTH / 1.75, BOAT_HEIGHT / 1.75,
|
||||
// 0.0, -BOAT_HEIGHT / 1.75,
|
||||
// BOAT_WIDTH / 1.75, BOAT_HEIGHT / 1.75
|
||||
// );
|
||||
// boatAssets.setStroke(Color.BLACK);
|
||||
// boatAssets.setStrokeWidth(2);
|
||||
// boatAssets.setStrokeLineCap(StrokeLineCap.ROUND);
|
||||
isPlayer = true;
|
||||
animateSail();
|
||||
}
|
||||
|
||||
public void setTrajectory(double heading, double velocity, double windDir) {
|
||||
// wake.r(lastHeading - heading, velocity);
|
||||
// rotateTo(heading, false, windDir);
|
||||
xVelocity = Math.cos(Math.toRadians(heading)) * velocity;
|
||||
yVelocity = Math.sin(Math.toRadians(heading)) * velocity;
|
||||
lastHeading = heading;
|
||||
}
|
||||
|
||||
public Boolean getSelected() {
|
||||
return isSelected;
|
||||
}
|
||||
|
||||
public void setTrajectory(double heading, double velocity, double scaleFactorX, double scaleFactorY) {
|
||||
// wake.setRotation(lastHeading - heading, velocity);
|
||||
// rotateTo(heading);
|
||||
// xVelocity = Math.cos(Math.toRadians(heading)) * velocity * scaleFactorX;
|
||||
// yVelocity = Math.sin(Math.toRadians(heading)) * velocity * scaleFactorY;
|
||||
lastHeading = heading;
|
||||
}
|
||||
|
||||
private void updateListener(Boolean isSelected) {
|
||||
for (SelectedBoatListener sbl : selectedBoatListenerListeners) {
|
||||
sbl.notifySelected(this, isSelected);
|
||||
}
|
||||
}
|
||||
|
||||
public void addSelectedBoatListener(SelectedBoatListener sbl) {
|
||||
selectedBoatListenerListeners.add(sbl);
|
||||
}
|
||||
}
|
||||
@@ -1,24 +1,22 @@
|
||||
package seng302.visualiser.fxObjects.assets_2D;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.Group;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.paint.Paint;
|
||||
import javafx.scene.shape.Circle;
|
||||
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 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.
|
||||
@@ -27,8 +25,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));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -61,6 +58,14 @@ public class Marker2D extends Group {
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addFinishArrow(RoundingSide roundingSide, double entryAngle, double exitAngle){
|
||||
enterArrows.add(
|
||||
MarkArrowFactory.constructInteriorArrow(roundingSide, entryAngle, exitAngle, Color.RED)
|
||||
);
|
||||
exitArrows.add(new Group());
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the next EnterArrow. Does nothing if there are no more enter arrows. Other arrows
|
||||
* become hidden.
|
||||
@@ -79,15 +84,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);
|
||||
}
|
||||
}
|
||||
@@ -2,21 +2,62 @@ package seng302.visualiser.fxObjects.assets_3D;
|
||||
|
||||
/**
|
||||
* Enum for boat meshes. Enum values should be of the form :
|
||||
* ENUM_VALUE (hull file, mast file, X offset of mast CoR from origin, sail file, X offset of sail CoR from origin)
|
||||
* ENUM_VALUE (hull file, mast file, Y offset of mast CoR from origin, sail file, Y offset of sail CoR from origin, jib file, fixed sail)
|
||||
* Files must be valid .stl files.
|
||||
*/
|
||||
public enum BoatMeshType {
|
||||
|
||||
DINGHY ("dinghy_hull.stl", "dinghy_mast.stl", -1.36653, "dinghy_sail.stl", -1.36653);
|
||||
DINGHY("dinghy_hull.stl", "dinghy_mast.stl", 1.36653, "dinghy_sail.stl", 1.36653, null, false, 1.8, 1.0, 1.0),
|
||||
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),
|
||||
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;
|
||||
final String hullFile, mastFile, sailFile, jibFile;
|
||||
final double mastOffset, sailOffset;
|
||||
public final double maxSpeedMultiplier;
|
||||
public final double accelerationMultiplier;
|
||||
public final double turnStep;
|
||||
final boolean fixedSail;
|
||||
final static BoatMeshType[] boatTypes = new BoatMeshType[]{DINGHY, CATAMARAN, PIRATE_SHIP, DUCKY, PARROT, WAKA};
|
||||
|
||||
BoatMeshType(String hullFile, String mastFile, double mastOffset, String sailFile, double sailOffset) {
|
||||
BoatMeshType(String hullFile, String mastFile, double mastOffset, String sailFile,
|
||||
double sailOffset, String jibFile, boolean fixedSail, double maxSpeedMultiplier, double accelerationMultiplier, double turnStep) {
|
||||
this.hullFile = hullFile;
|
||||
this.mastFile = mastFile;
|
||||
this.mastOffset = mastOffset;
|
||||
this.sailFile = sailFile;
|
||||
this.sailOffset = sailOffset;
|
||||
this.jibFile = jibFile;
|
||||
this.fixedSail = fixedSail;
|
||||
this.maxSpeedMultiplier = maxSpeedMultiplier;
|
||||
this.accelerationMultiplier = accelerationMultiplier;
|
||||
this.turnStep = turnStep;
|
||||
}
|
||||
|
||||
|
||||
public static BoatMeshType getNextBoatType(BoatMeshType boatType) {
|
||||
for (int i = 0; i < boatTypes.length; i++) {
|
||||
if (i == boatTypes.length -1) {
|
||||
return boatTypes[0];
|
||||
} else if (boatType == boatTypes[i]) {
|
||||
return boatTypes[i+1];
|
||||
}
|
||||
}
|
||||
return boatType;
|
||||
}
|
||||
|
||||
public static BoatMeshType getPrevBoatType(BoatMeshType boatType) {
|
||||
for (int i = 0; i < boatTypes.length; i++) {
|
||||
if (i == 0 && boatType == boatTypes[i]) {
|
||||
return boatTypes[boatTypes.length -1];
|
||||
} else if (boatType == boatTypes[i]) {
|
||||
return boatTypes[i-1];
|
||||
}
|
||||
}
|
||||
return boatType;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,14 +34,16 @@ public class BoatModel extends Model {
|
||||
* @param degrees The rotation of the sail in degrees
|
||||
*/
|
||||
public void rotateSail(double degrees) {
|
||||
MeshView mast = getMeshViewChild(MAST_INDEX);
|
||||
MeshView sail = getMeshViewChild(SAIL_INDEX);
|
||||
mast.getTransforms().setAll(
|
||||
new Rotate(degrees, -meshType.mastOffset, 0,0, new Point3D(0, 0, 1))
|
||||
);
|
||||
sail.getTransforms().setAll(
|
||||
new Rotate(degrees, -meshType.sailOffset, 0,0, new Point3D(0, 0, 1))
|
||||
);
|
||||
if (!meshType.fixedSail) {
|
||||
MeshView mast = getMeshViewChild(MAST_INDEX);
|
||||
MeshView sail = getMeshViewChild(SAIL_INDEX);
|
||||
mast.getTransforms().setAll(
|
||||
new Rotate(degrees, 0, -meshType.mastOffset, 0, new Point3D(0, 0, 1))
|
||||
);
|
||||
sail.getTransforms().setAll(
|
||||
new Rotate(degrees, 0, -meshType.sailOffset,0, new Point3D(0, 0, 1))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public void hideSail() {
|
||||
@@ -58,7 +60,9 @@ public class BoatModel extends Model {
|
||||
*/
|
||||
public void changeColour(Color newColour) {
|
||||
changeColourChild(HULL_INDEX, newColour);
|
||||
changeColourChild(SAIL_INDEX, newColour);
|
||||
if (meshType != BoatMeshType.PARROT) {
|
||||
changeColourChild(MAST_INDEX, newColour);
|
||||
}
|
||||
}
|
||||
|
||||
private void changeColourChild(int index, Color newColour) {
|
||||
@@ -69,4 +73,8 @@ public class BoatModel extends Model {
|
||||
private MeshView getMeshViewChild(int index) {
|
||||
return (MeshView) assets.getChildren().get(index);
|
||||
}
|
||||
|
||||
public BoatMeshType getMeshType() {
|
||||
return meshType;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
package seng302.visualiser.fxObjects.assets_3D;
|
||||
|
||||
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
|
||||
* dimensional boat. It contains a single polygon for the boat, a group of lines to show it's path,
|
||||
* a wake object and two text labels to annotate the boat teams name and the boatTypes velocity. The
|
||||
* boat will update it's position onscreen everytime UpdatePosition is called unless the window is
|
||||
* minimized in which case it attempts to store animations and apply them when the window is
|
||||
* maximised.
|
||||
*/
|
||||
public class BoatObject extends Group {
|
||||
|
||||
@FunctionalInterface
|
||||
public interface SelectedBoatListener {
|
||||
|
||||
void notifySelected(BoatObject boatObject, Boolean isSelected);
|
||||
}
|
||||
|
||||
private BoatModel boatAssets;
|
||||
private Group wake;
|
||||
private Color colour = Color.BLACK;
|
||||
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<>();
|
||||
|
||||
/**
|
||||
* Creates a BoatGroup with the default triangular boat polygon.
|
||||
*/
|
||||
public BoatObject(BoatMeshType boatMeshType) {
|
||||
rotationProperty = new ReadOnlyDoubleWrapper(0.0);
|
||||
boatAssets = ModelFactory.boatGameView(boatMeshType, colour);
|
||||
boatAssets.hideSail();
|
||||
boatAssets.getAssets().getTransforms().addAll(
|
||||
rotation
|
||||
);
|
||||
boatAssets.getAssets().setOnMouseClicked(event -> {
|
||||
setIsSelected(!isSelected);
|
||||
updateListeners();
|
||||
});
|
||||
boatAssets.getAssets().setCache(true);
|
||||
wake = ModelFactory.importModel(ModelType.WAKE).getAssets();
|
||||
super.getChildren().addAll(boatAssets.getAssets());
|
||||
}
|
||||
|
||||
public void setFill (Color value) {
|
||||
this.colour = value;
|
||||
boatAssets.changeColour(colour);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Moves the boat and its children annotations to coordinates specified
|
||||
* @param x The X coordinate to move the boat to
|
||||
* @param y The Y coordinate to move the boat to
|
||||
* @param rotation The rotation by which the boat moves
|
||||
* @param velocity The velocity the boat is moving
|
||||
* @param sailIn Boolean to toggle sail state.
|
||||
* @param windDir .
|
||||
*/
|
||||
public void moveTo(double x, double y, double rotation, double velocity, Boolean sailIn, double windDir) {
|
||||
Platform.runLater(() -> {
|
||||
rotateTo(rotation, sailIn, windDir);
|
||||
this.layoutXProperty().setValue(x);
|
||||
this.layoutYProperty().setValue(y);
|
||||
wake.setLayoutX(x);
|
||||
wake.setLayoutY(y);
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
return normalizedHeading;
|
||||
}
|
||||
|
||||
|
||||
private void rotateTo(double heading, boolean sailsIn, double windDir) {
|
||||
rotationProperty.set(heading);
|
||||
rotation.setAngle(heading);
|
||||
wake.getTransforms().setAll(new Rotate(heading, new Point3D(0,0,1)));
|
||||
if (sailsIn) {
|
||||
boatAssets.showSail();
|
||||
Double sailWindOffset = 30.0;
|
||||
Double upwindAngleLimit = 15.0;
|
||||
Double downwindAngleLimit = 10.0; //Upwind from normalised horizontal
|
||||
Double normalizedHeading = normalizeHeading(heading, windDir);
|
||||
if (normalizedHeading < 180) {
|
||||
if (normalizedHeading < sailWindOffset + upwindAngleLimit){
|
||||
boatAssets.rotateSail(-upwindAngleLimit);
|
||||
} else if (normalizedHeading > 90 + sailWindOffset){
|
||||
boatAssets.rotateSail(-90 + downwindAngleLimit);
|
||||
} else {
|
||||
boatAssets.rotateSail(-heading + windDir + sailWindOffset);
|
||||
}
|
||||
} else {
|
||||
if (normalizedHeading > 360 - (sailWindOffset + upwindAngleLimit)) {
|
||||
boatAssets.rotateSail(upwindAngleLimit);
|
||||
} else if (normalizedHeading < 270 - sailWindOffset) {
|
||||
boatAssets.rotateSail(90 - downwindAngleLimit);
|
||||
} else {
|
||||
boatAssets.rotateSail(-heading + windDir - sailWindOffset);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
boatAssets.hideSail();
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public void setIsSelected(Boolean isSelected) {
|
||||
this.isSelected = isSelected;
|
||||
}
|
||||
|
||||
private void updateListeners() {
|
||||
for (SelectedBoatListener sbl : selectedBoatListenerListeners) {
|
||||
sbl.notifySelected(this, this.isSelected);
|
||||
}
|
||||
}
|
||||
|
||||
public void addSelectedBoatListener(SelectedBoatListener sbl) {
|
||||
selectedBoatListenerListeners.add(sbl);
|
||||
}
|
||||
|
||||
public ReadOnlyDoubleWrapper getRotationProperty() {
|
||||
return rotationProperty;
|
||||
}
|
||||
}
|
||||
@@ -1,48 +1,40 @@
|
||||
package seng302.visualiser.fxObjects.assets_3D;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.Group;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.paint.Paint;
|
||||
import javafx.scene.transform.Scale;
|
||||
import seng302.visualiser.fxObjects.assets_2D.MarkArrowFactory;
|
||||
import seng302.visualiser.fxObjects.assets_2D.MarkArrowFactory.RoundingSide;
|
||||
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 Group mark = ModelFactory.importModel(ModelType.PLAIN_MARKER).getAssets();
|
||||
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;
|
||||
private Model mark;
|
||||
private ModelType markType;
|
||||
private ModelType arrowType;
|
||||
|
||||
/**
|
||||
* Creates a new Marker containing only a circle. The default colour is black.
|
||||
*/
|
||||
public Marker3D() {
|
||||
// mark.setRadius(5);
|
||||
// mark.setCenterX(0);
|
||||
// mark.setCenterY(0);
|
||||
Platform.runLater(() -> {
|
||||
mark.getTransforms().add(new Scale(5,5,5));
|
||||
this.getChildren().addAll(mark, new Group());
|
||||
}); //Empty group placeholder or arrows.
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Marker containing only a circle of the given colour.
|
||||
* @param colour the desired colour for the marker.
|
||||
*/
|
||||
public Marker3D(Paint colour) {
|
||||
this();
|
||||
this.colour = colour;
|
||||
// mark.setFill(colour);
|
||||
public Marker3D(ModelType modelType) {
|
||||
markType = modelType;
|
||||
switch (markType) {
|
||||
case PLAIN_MARKER:
|
||||
arrowType = ModelType.PLAIN_ARROW;
|
||||
break;
|
||||
case FINISH_MARKER:
|
||||
arrowType = ModelType.FINISH_ARROW;
|
||||
break;
|
||||
case START_MARKER:
|
||||
arrowType = ModelType.START_ARROW;
|
||||
break;
|
||||
}
|
||||
mark = ModelFactory.importModel(modelType);
|
||||
Platform.runLater(() ->
|
||||
this.getChildren().addAll(mark.getAssets())
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -53,40 +45,32 @@ public class Marker3D extends Group {
|
||||
* @param exitAngle The angle the arrow wil point from the marker.
|
||||
*/
|
||||
public void addArrows(RoundingSide roundingSide, double entryAngle,
|
||||
double exitAngle) {
|
||||
double exitAngle) {
|
||||
//Change Color.GRAY to this.colour to revert all gray arrows.
|
||||
enterArrows.add(
|
||||
MarkArrowFactory.constructEntryArrow(roundingSide, entryAngle, exitAngle, Color.GRAY)
|
||||
MarkArrowFactory.constructEntryArrow3D(roundingSide, entryAngle, arrowType).getAssets()
|
||||
);
|
||||
exitArrows.add(
|
||||
MarkArrowFactory.constructExitArrow(roundingSide, exitAngle, Color.GRAY)
|
||||
MarkArrowFactory.constructExitArrow3D(roundingSide, exitAngle, arrowType).getAssets()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the next EnterArrow. Does nothing if there are no more enter arrows. Other arrows become hidden.
|
||||
*/
|
||||
public void showNextEnterArrow() {
|
||||
showArrow(enterArrows, enterArrowIndex);
|
||||
enterArrowIndex++;
|
||||
public void addFinishArrow(RoundingSide roundingSide, double entryAngle,
|
||||
double exitAngle) {
|
||||
enterArrows.add(
|
||||
MarkArrowFactory.constructEntryArrow3D(roundingSide, entryAngle, ModelType.FINISH_ARROW).getAssets()
|
||||
);
|
||||
exitArrows.add(
|
||||
MarkArrowFactory.constructExitArrow3D(roundingSide, exitAngle, ModelType.FINISH_ARROW).getAssets()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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()) {
|
||||
if (arrowListIndex == 1) {;
|
||||
}
|
||||
Platform.runLater(() -> {
|
||||
this.getChildren().remove(1);
|
||||
this.getChildren().add(arrowList.get(arrowListIndex));
|
||||
});
|
||||
Platform.runLater(() ->
|
||||
this.getChildren().setAll(mark.getAssets(), arrowList.get(arrowListIndex))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,6 +78,6 @@ public class Marker3D extends Group {
|
||||
* Hides all arrows.
|
||||
*/
|
||||
public void hideAllArrows() {
|
||||
Platform.runLater(() -> this.getChildren().setAll(mark, new Group()));
|
||||
Platform.runLater(() -> this.getChildren().setAll(mark.getAssets()));
|
||||
}
|
||||
}
|
||||
@@ -8,10 +8,10 @@ import javafx.scene.Group;
|
||||
*/
|
||||
public class Model {
|
||||
|
||||
AnimationTimer animationTimer;
|
||||
Group assets;
|
||||
protected AnimationTimer animationTimer;
|
||||
protected Group assets;
|
||||
|
||||
Model (Group assets, AnimationTimer animation) {
|
||||
public Model (Group assets, AnimationTimer animation) {
|
||||
this.assets = assets;
|
||||
this.animationTimer = animation;
|
||||
if (animation != null) {
|
||||
@@ -25,7 +25,7 @@ public class Model {
|
||||
}
|
||||
}
|
||||
|
||||
public void setAnimation(AnimationTimer animation) {
|
||||
void setAnimation(AnimationTimer animation) {
|
||||
animationTimer = animation;
|
||||
if (animation != null) {
|
||||
animation.start();
|
||||
|
||||
@@ -9,15 +9,15 @@ import javafx.scene.CacheHint;
|
||||
import javafx.scene.Group;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.paint.PhongMaterial;
|
||||
import javafx.scene.shape.Circle;
|
||||
import javafx.scene.shape.MeshView;
|
||||
import javafx.scene.transform.Rotate;
|
||||
import javafx.scene.transform.Scale;
|
||||
import javafx.scene.transform.Translate;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Factory class for creating 3D models of boats.
|
||||
* Factory class for creating 3D models of boatTypes.
|
||||
*/
|
||||
public class ModelFactory {
|
||||
|
||||
@@ -50,52 +50,80 @@ public class ModelFactory {
|
||||
return bo;
|
||||
}
|
||||
|
||||
public static BoatModel boatRotatingView(BoatMeshType boatType, Color primaryColour) {
|
||||
public static BoatModel boatCustomiseView(BoatMeshType boatType, Color primaryColour) {
|
||||
Group boatAssets = getUnmodifiedBoatModel(boatType, primaryColour);
|
||||
final Rotate animationRotate = new Rotate(0, new Point3D(0,0,1));
|
||||
boatAssets.getTransforms().addAll(
|
||||
new Scale(40, 40, 40),
|
||||
new Rotate(90, new Point3D(0,0,1)),
|
||||
new Rotate(90, new Point3D(0, 1, 0))
|
||||
new Scale(8.0, 8.0, 8.0),
|
||||
new Rotate(-70, new Point3D(1,0,0)),
|
||||
new Translate(16,50, 1),
|
||||
animationRotate
|
||||
);
|
||||
|
||||
final Rotate animationRotate = new Rotate(0, new Point3D(1,1,1));
|
||||
boatAssets.getTransforms().add(animationRotate);
|
||||
BoatModel bo = new BoatModel(boatAssets, null, boatType);
|
||||
bo.rotateSail(45);
|
||||
|
||||
return new BoatModel(boatAssets, new AnimationTimer() {
|
||||
|
||||
private double rotation = 0;
|
||||
private Rotate rotate = animationRotate;
|
||||
|
||||
bo.setAnimation(new AnimationTimer() {
|
||||
double boatAngle = 0;
|
||||
Rotate rotate = animationRotate;
|
||||
@Override
|
||||
public void handle(long now) {
|
||||
rotation += 0.5;
|
||||
rotate.setAngle(rotation);
|
||||
boatAngle += 0.5;
|
||||
rotate.setAngle(boatAngle);
|
||||
}
|
||||
}, boatType);
|
||||
});
|
||||
boatAssets.getChildren().addAll(
|
||||
new AmbientLight()
|
||||
);
|
||||
return bo;
|
||||
}
|
||||
|
||||
public static BoatModel boatGameView(BoatMeshType boatType, Color primaryColour) {
|
||||
Group boatAssets = getUnmodifiedBoatModel(boatType, primaryColour);
|
||||
boatAssets.getTransforms().setAll(
|
||||
new Rotate(-90, new Point3D(0,0,1)),
|
||||
new Scale(0.06, 0.06, 0.06)
|
||||
new Scale(0.3, 0.3, 0.3)
|
||||
);
|
||||
return new BoatModel(boatAssets, null, boatType);
|
||||
}
|
||||
|
||||
private static Group getUnmodifiedBoatModel(BoatMeshType boatType, Color primaryColour) {
|
||||
|
||||
Group boatAssets = new Group();
|
||||
MeshView hull = importFile(boatType.hullFile);
|
||||
MeshView hull = importBoatSTL(boatType.hullFile);
|
||||
hull.setMaterial(new PhongMaterial(primaryColour));
|
||||
MeshView mast = importFile(boatType.mastFile);
|
||||
mast.setMaterial(new PhongMaterial(primaryColour));
|
||||
MeshView sail = importFile(boatType.sailFile);
|
||||
sail.setMaterial(new PhongMaterial(Color.WHITE));
|
||||
boatAssets.getChildren().addAll(hull, mast, sail);
|
||||
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 = importBoatSTL(boatType.jibFile);
|
||||
jib.setMaterial(
|
||||
new PhongMaterial(boatType == BoatMeshType.PARROT ? Color.DARKGRAY : Color.WHITE)
|
||||
);
|
||||
boatAssets.getChildren().add(jib);
|
||||
}
|
||||
|
||||
return boatAssets;
|
||||
}
|
||||
|
||||
private static MeshView importFile(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/" + fileName));
|
||||
MeshView importedFile = new MeshView(importer.getImport());
|
||||
@@ -116,8 +144,16 @@ 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:
|
||||
return makeCoinPickup(assets);
|
||||
case BUMPER_PICKUP:
|
||||
case RANDOM_PICKUP:
|
||||
case HANDLING_PICKUP:
|
||||
case WIND_WALKER_PICKUP:
|
||||
return makeTokenPickup(assets);
|
||||
case FINISH_MARKER:
|
||||
case PLAIN_MARKER:
|
||||
case START_MARKER:
|
||||
@@ -137,29 +173,41 @@ public class ModelFactory {
|
||||
return makeTrail(assets);
|
||||
case PLAYER_IDENTIFIER:
|
||||
return makeIdentifierIcon(assets);
|
||||
case START_ARROW:
|
||||
case FINISH_ARROW:
|
||||
case PLAIN_ARROW:
|
||||
makeArrow(assets);
|
||||
default:
|
||||
return new Model(new Group(assets), null);
|
||||
}
|
||||
}
|
||||
|
||||
private static Model makeCoinPickup(Group assets){
|
||||
assets.setRotationAxis(new Point3D(1,0,0));
|
||||
assets.setRotate(90);
|
||||
assets.setTranslateX(0.2);
|
||||
assets.setTranslateY(1);
|
||||
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(
|
||||
new Translate(0,-1,0),
|
||||
new Rotate(0 ,new Point3D(1,1,1))
|
||||
animationRotate,
|
||||
new Translate(0, 0, -1)
|
||||
);
|
||||
|
||||
return new Model(new Group(assets), new AnimationTimer() {
|
||||
|
||||
private double rotation = 0;
|
||||
private Group group = assets;
|
||||
private Rotate rotate = animationRotate;
|
||||
|
||||
@Override
|
||||
public void handle(long now) {
|
||||
rotation += 1;
|
||||
((Rotate) group.getTransforms().get(1)).setAngle(rotation);
|
||||
rotate.setAngle(rotation);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -174,23 +222,17 @@ public class ModelFactory {
|
||||
}
|
||||
|
||||
private static Model makeOcean(Group group) {
|
||||
// group.setScaleY(Double.MAX_VALUE);
|
||||
// group.setScaleX(Double.MAX_VALUE);
|
||||
group.getTransforms().addAll(
|
||||
new Rotate(90, new Point3D(1, 0, 0)),
|
||||
new Scale(10,4,10)
|
||||
Circle ocean = new Circle(
|
||||
0,0,250, Color.SKYBLUE
|
||||
);
|
||||
// group.getChildren().add(new AmbientLight());
|
||||
// Circle ocean = new Circle(0,0,500, Color.SKYBLUE);
|
||||
// ocean.setStroke(Color.TRANSPARENT);
|
||||
// group.getChildren().add(ocean);
|
||||
ocean.setStroke(Color.TRANSPARENT);
|
||||
group.getChildren().add(ocean);
|
||||
return new Model(new Group(group), null);
|
||||
}
|
||||
|
||||
private static Model makeBarrier(Group assets) {
|
||||
assets.getTransforms().addAll(
|
||||
new Rotate(90, new Point3D(1,0,0)),
|
||||
new Scale(1.5,1.5,1.5)
|
||||
new Rotate(90, new Point3D(1,0,0))
|
||||
);
|
||||
return new Model(new Group(assets), null);
|
||||
}
|
||||
@@ -213,7 +255,8 @@ public class ModelFactory {
|
||||
|
||||
private static Model makeTrail(Group trailPiece) {
|
||||
trailPiece.getTransforms().addAll(
|
||||
new Rotate(90, new Point3D(0,0,1))
|
||||
new Rotate(-90, new Point3D(0,0,1)),
|
||||
new Rotate(90, new Point3D(1,0,0))
|
||||
);
|
||||
return new Model(new Group(trailPiece), null);
|
||||
}
|
||||
@@ -225,4 +268,38 @@ public class ModelFactory {
|
||||
);
|
||||
return new Model(assets, null);
|
||||
}
|
||||
|
||||
private static Model makeArrow(Group assets) {
|
||||
assets.getTransforms().addAll(
|
||||
new Rotate(90, new Point3D(1,0,0))
|
||||
);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,11 +7,15 @@ package seng302.visualiser.fxObjects.assets_3D;
|
||||
public enum ModelType {
|
||||
|
||||
VELOCITY_PICKUP("velocity_pickup.dae"),
|
||||
HANDLING_PICKUP("turning_pickup.dae"),
|
||||
WIND_WALKER_PICKUP("wind_walker_pickup.dae"),
|
||||
BUMPER_PICKUP("bumper_pickup.dae"),
|
||||
RANDOM_PICKUP("random_pickup.dae"),
|
||||
FINISH_MARKER ("finish_marker.dae"),
|
||||
START_MARKER ("start_marker.dae"),
|
||||
PLAIN_MARKER ("plain_marker.dae"),
|
||||
MARK_AREA ("mark_area.dae"),
|
||||
OCEAN ("ocean.dae"),
|
||||
OCEAN (null),
|
||||
BORDER_PYLON ("barrier_pole.dae"),
|
||||
BORDER_BARRIER ("barrier_segment.dae"),
|
||||
FINISH_LINE ("finish_line.dae"),
|
||||
@@ -19,12 +23,19 @@ public enum ModelType {
|
||||
GATE_LINE ("gate_line.dae"),
|
||||
WAKE ("wake.dae"),
|
||||
TRAIL_SEGMENT ("trail_segment.dae"),
|
||||
PLAYER_IDENTIFIER ("player_identifier.dae");
|
||||
PLAYER_IDENTIFIER ("player_identifier.dae"),
|
||||
PLAIN_ARROW ("arrow.dae"),
|
||||
START_ARROW ("start_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;
|
||||
|
||||
ModelType(String filename) {
|
||||
this.filename = 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,108 +0,0 @@
|
||||
package seng302.visualiser;
|
||||
|
||||
import com.interactivemesh.jfx.importer.stl.StlMeshImporter;
|
||||
import javafx.animation.AnimationTimer;
|
||||
import javafx.application.Application;
|
||||
import javafx.geometry.Point3D;
|
||||
import javafx.scene.Camera;
|
||||
import javafx.scene.Group;
|
||||
import javafx.scene.PerspectiveCamera;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.SceneAntialiasing;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.paint.PhongMaterial;
|
||||
import javafx.scene.shape.MeshView;
|
||||
import javafx.scene.transform.Rotate;
|
||||
import javafx.scene.transform.Scale;
|
||||
import javafx.scene.transform.Translate;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
/**
|
||||
* Created by cir27 on 7/09/17.
|
||||
*/
|
||||
public class test3d extends Application {
|
||||
|
||||
Group root = new Group();
|
||||
Scene scene;
|
||||
|
||||
@Override
|
||||
public void start(Stage primaryStage) throws Exception {
|
||||
// camera = new PerspectiveCamera();
|
||||
// gameObjects = new Group();
|
||||
// root3D = new Group(camera, gameObjects);
|
||||
scene = new Scene(
|
||||
root, 1000, 1000, true, SceneAntialiasing.BALANCED
|
||||
);
|
||||
gameView3DTest();
|
||||
primaryStage.setScene(scene);
|
||||
primaryStage.show();
|
||||
// scene.setCamera(camera);
|
||||
// primaryStage.setScene(scene);
|
||||
// primaryStage.show();
|
||||
//
|
||||
// StlMeshImporter importer = new StlMeshImporter();
|
||||
// importer.read(test3d.class.getResource("/meshes/dinghy_hull.stl").toString());
|
||||
// MeshView boat = new MeshView(importer.getImport());
|
||||
// boat.setMaterial(new PhongMaterial(Color.GREENYELLOW));
|
||||
//
|
||||
// importer = new StlMeshImporter();
|
||||
// importer.read(getClass().getResource("/meshes/dinghy_mast.stl").toString());
|
||||
// MeshView mast = new MeshView(importer.getImport());
|
||||
// mast.setMaterial(new PhongMaterial(Color.GREENYELLOW));
|
||||
//
|
||||
// importer = new StlMeshImporter();
|
||||
// importer.read(getClass().getResource("/meshes/dinghy_sail.stl").toString());
|
||||
// MeshView sail = new MeshView(importer.getImport());
|
||||
// sail.setMaterial(new PhongMaterial(Color.LIGHTGREY));
|
||||
//
|
||||
// gameObjects.getChildren().addAll(boat, mast, sail);
|
||||
//
|
||||
// gameObjects.getTransforms().add(new Scale(25, 25,25));
|
||||
// gameObjects.getTransforms().add(new Translate(15, 20,0));
|
||||
// gameObjects.getTransforms().addAll(
|
||||
// new Rotate(90, new Point3D(0,0,1)),
|
||||
// new Rotate(90, new Point3D(0, 1, 0))
|
||||
// );
|
||||
//
|
||||
//// PointLight light = new PointLight();
|
||||
//// light.setLightOn(true);
|
||||
//// light.getTransforms().add(new Translate(15, 20, 0));
|
||||
////
|
||||
//// PointLight light2 = new PointLight();
|
||||
//// light2.setLightOn(true);
|
||||
//// light2.getTransforms().add(new Translate(30, 40, 0));
|
||||
//
|
||||
//// root3D.getChildren().addAll(light);
|
||||
//
|
||||
// scene.setOnKeyPressed(event -> {
|
||||
// switch (event.getCode()) {
|
||||
// case UP:
|
||||
// gameObjects.getTransforms().add(new Rotate(5, new Point3D(0,0,1)));
|
||||
// break;
|
||||
// case DOWN:
|
||||
// gameObjects.getTransforms().add(new Rotate(-5, new Point3D(0,0,1)));
|
||||
// break;
|
||||
// case LEFT:
|
||||
// gameObjects.getTransforms().add(new Rotate(-5, new Point3D(0,1,0)));
|
||||
// break;
|
||||
// case RIGHT:
|
||||
// gameObjects.getTransforms().add(new Rotate(5, new Point3D(0,1,0)));
|
||||
// break;
|
||||
// }
|
||||
// });
|
||||
//
|
||||
// AnimationTimer animationTimer = new AnimationTimer() {
|
||||
// @Override
|
||||
// public void handle(long now) {
|
||||
// sail.getTransforms().add(new Rotate(0.5, 0, -1.36653, 0, new Point3D(0, 0, 1)));
|
||||
// }
|
||||
// };
|
||||
//
|
||||
//// animationTimer.start();
|
||||
}
|
||||
|
||||
private void gameView3DTest() {
|
||||
GameView3D gameView3D = new GameView3D();
|
||||
root.getChildren().add(gameView3D.getAssets());
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 92 KiB |
@@ -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>
|
||||
Binary file not shown.
@@ -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;
|
||||
}
|
||||
@@ -44,8 +44,18 @@
|
||||
-fx-border-color: -fx-decorator-color;
|
||||
-fx-border-width: 0 4 4 4;
|
||||
}
|
||||
|
||||
.jfx-decorator-button {
|
||||
-fx-focus-traversable: false; /* so decorator button will not be focused */
|
||||
}
|
||||
|
||||
/********* customised scroll bar for scroll pane ***********/
|
||||
|
||||
.scroll-pane {
|
||||
-fx-focus-traversable: false;
|
||||
-fx-border-style: none;
|
||||
}
|
||||
|
||||
/* The main scrollbar **track** CSS class */
|
||||
.scroll-bar:horizontal .track,
|
||||
.scroll-bar:vertical .track {
|
||||
@@ -99,4 +109,16 @@
|
||||
|
||||
.slider .track {
|
||||
-fx-background-color: -fx-pp-dark-text-color;
|
||||
}
|
||||
|
||||
.jfx-snackbar-content {
|
||||
-fx-background-color: -fx-pp-front-color;
|
||||
-fx-padding: 0 5 0 5;
|
||||
-fx-spacing: 0 5 0 5;
|
||||
-fx-font-size: 15;
|
||||
}
|
||||
|
||||
.jfx-snackbar-toast {
|
||||
-fx-text-fill: -fx-pp-theme-color;
|
||||
-fx-font-size: 15;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user