mirror of
https://github.com/michaelrausch/Party-Parrots-At-Sea.git
synced 2026-05-09 14:28:43 +00:00
Compare commits
368 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 81b2d285e9 | |||
| 5d7f307260 | |||
| 00e2af9c14 | |||
| 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 | |||
| 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 | |||
| 47798b19fe | |||
| d9247eb031 | |||
| 7b0d31438e | |||
| 1e74321aab | |||
| f0fc75feec | |||
| c8dc448a52 | |||
| e518d13fd5 | |||
| 167545cbef | |||
| 20b656b16d | |||
| 90a54beb8d | |||
| e375efb8e9 | |||
| 06e9c55d2c | |||
| 9cba76f979 | |||
| d5ce61a0ff | |||
| 634322de3f | |||
| 6f534a430d | |||
| 17b0864815 | |||
| 3455321f01 | |||
| 7e9e96c091 | |||
| b25c3367a9 | |||
| ec7ee34305 | |||
| 9dad88e56a | |||
| 6a4547f3f9 | |||
| 71f626f57e | |||
| 1c343ec02d | |||
| 5046ca6ffa | |||
| bd31bae586 | |||
| 2f973deacc | |||
| f4c5e483ce | |||
| bb3ee424cc | |||
| 99f38fa05f | |||
| 6a8c77c670 | |||
| 659a521cc9 | |||
| da3613fe36 | |||
| 8dc3e54186 | |||
| 8fd35392b0 | |||
| bc9f0ea924 | |||
| 6d9864e677 | |||
| 8c2125276e | |||
| 235d6c9cef | |||
| 5dd936f8f1 | |||
| d223d393fa | |||
| aa098569f3 | |||
| d49e84e6d2 | |||
| 75bf92a67f | |||
| 0d7201e235 | |||
| 0197de6fe3 | |||
| d79ff0f1cf | |||
| 18f36d7ea4 | |||
| 131cd80e02 | |||
| 3d0209300e | |||
| 6b4f7eb42b | |||
| 8b9b3a31bd | |||
| 0bf83aa858 | |||
| dff261cf41 | |||
| 83ee217cef | |||
| d4826739e3 | |||
| fdfb5959fa | |||
| 878c0e0f43 | |||
| 1ce8df976c | |||
| 6fcd4ae4cc | |||
| 534eaee8ce | |||
| ab35e3506d | |||
| 1fec620427 | |||
| 00100eb991 | |||
| 58512fdbdf | |||
| 76a1a3c7a0 | |||
| 1b8c503712 | |||
| 334e13295f | |||
| 0c4d001510 | |||
| 24a3a54ccb | |||
| 40408cff27 | |||
| 9fcb8915c2 | |||
| caf910c4c5 | |||
| 4f07786449 | |||
| 2b53e0d5b4 | |||
| 1210f9342b | |||
| f136a970db | |||
| 800ae2864f | |||
| e764caee60 | |||
| 78b4786482 | |||
| e3ccb570ed | |||
| 470bf121a5 | |||
| f077486e22 | |||
| 717f7558d9 | |||
| 05236337ba | |||
| 1b76d59acb | |||
| 02e6d2a98b | |||
| 06a4dde216 | |||
| 1516e817b7 | |||
| bd7ea920b6 | |||
| 4a170f8179 | |||
| 1f9e6154ae | |||
| 5595e0439b | |||
| 08f7127b69 | |||
| 8ba28ffda0 | |||
| edd52a07a7 | |||
| 875a6b4e98 | |||
| faefcc7938 | |||
| 0c956f93c8 | |||
| 0e2946f20b | |||
| f66ef3c208 | |||
| cf4f8813d2 | |||
| e2bc613c9d | |||
| c2c3c9eb53 | |||
| cadf995bf7 | |||
| 62f139c604 | |||
| 0bf6dd9e6b | |||
| eed5f56690 | |||
| b35126ff4e | |||
| c39499cee7 | |||
| cff15ba07d | |||
| 6be7c17c40 | |||
| 64811354e3 | |||
| 3364f8b516 | |||
| 8cc725616c | |||
| 0feccdc8b9 | |||
| 1a755cec33 | |||
| d565552fcc | |||
| 26a8d76f8b | |||
| d87dcaa4fe | |||
| 0b61dffe71 | |||
| 4e69157c09 | |||
| d67566e217 | |||
| 56f1dd2efb | |||
| 302bc91461 | |||
| 488ab47c8d | |||
| 0650ba30e2 | |||
| 04518c35b0 | |||
| f33e4cc137 | |||
| 7a4b3f0ad9 | |||
| 1619c95098 | |||
| 396098e009 | |||
| e4dbbd05c7 | |||
| a1e3ec54c7 | |||
| 47f3f6e27d | |||
| fce7aa4f9a | |||
| 81afad1bcc | |||
| 5026c568a7 | |||
| 88d1e91b6f | |||
| 67f39e9049 | |||
| eb1d3f1a60 | |||
| 54ca12f3b6 | |||
| 3f910b8db7 | |||
| b346d5a706 | |||
| 45fa73595f | |||
| 01ca3f3453 | |||
| ba8e333d81 | |||
| 24d4c1df15 | |||
| 0c5d661995 | |||
| 262f27fa8a | |||
| 0fcdf41419 | |||
| 4ebf7d6104 | |||
| 353dd48829 | |||
| 4bd7291a4a | |||
| 0d0b2e59d5 | |||
| ace48a8404 | |||
| 201405d070 | |||
| dc19310849 | |||
| 1c866ea8c2 | |||
| 23027705da | |||
| c15f13bc2c | |||
| 6ee2517f74 | |||
| 75155fe481 | |||
| 2fcff65dd6 | |||
| 4c730ea890 |
@@ -7,7 +7,6 @@
|
|||||||
.mtj.tmp/
|
.mtj.tmp/
|
||||||
|
|
||||||
# Package Files #
|
# Package Files #
|
||||||
*.jar
|
|
||||||
*.war
|
*.war
|
||||||
*.ear
|
*.ear
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
bc00cae65d030845973151123fd0f2b1
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
de6c72cb03b2216bbe03ac7b882f0c146fb76bc8
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<groupId>lib.com.interactivemesh</groupId>
|
||||||
|
<artifactId>jimColModelImporter</artifactId>
|
||||||
|
<version>0.7</version>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
8fc884a64856917671745720acc6048c
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
4b35131587917ed1a16acb1eff8cd7a213a26edc
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<metadata>
|
||||||
|
<groupId>lib.com.interactivemesh</groupId>
|
||||||
|
<artifactId>jimColModelImporter</artifactId>
|
||||||
|
<versioning>
|
||||||
|
<release>0.7</release>
|
||||||
|
<versions>
|
||||||
|
<version>0.7</version>
|
||||||
|
</versions>
|
||||||
|
<lastUpdated>20170912024010</lastUpdated>
|
||||||
|
</versioning>
|
||||||
|
</metadata>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
3132c3f88de1a942ac37930b8cdaa764
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
20847be06b0d11b70f1fbfb1527c5efee4e9f49e
|
||||||
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
deec04fc74e1115465598d342810df18
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
ea31eabe6384ae965cd8180920f7ba0248717313
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<groupId>lib.com.interactivemesh</groupId>
|
||||||
|
<artifactId>jimStlMeshImporter</artifactId>
|
||||||
|
<version>0.7</version>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
82a485ac9a76d6587b1b23b7fbd8f5a0
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
2bac29a6598a88b2f115b72433181c13fc6201d2
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<metadata>
|
||||||
|
<groupId>lib.com.interactivemesh</groupId>
|
||||||
|
<artifactId>jimStlMeshImporter</artifactId>
|
||||||
|
<versioning>
|
||||||
|
<release>0.7</release>
|
||||||
|
<versions>
|
||||||
|
<version>0.7</version>
|
||||||
|
</versions>
|
||||||
|
<lastUpdated>20170912024122</lastUpdated>
|
||||||
|
</versioning>
|
||||||
|
</metadata>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
cad88c5c501f771bc8d1fc085decb3c4
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
c6cd4fae002dbbe4246c8eac4b35de07d921fd51
|
||||||
@@ -11,6 +11,7 @@
|
|||||||
<properties>
|
<properties>
|
||||||
<maven.compiler.source>1.8</maven.compiler.source>
|
<maven.compiler.source>1.8</maven.compiler.source>
|
||||||
<maven.compiler.target>1.8</maven.compiler.target>
|
<maven.compiler.target>1.8</maven.compiler.target>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
@@ -70,6 +71,50 @@
|
|||||||
<version>1.4</version>
|
<version>1.4</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- https://mvnrepository.com/artifact/org.lwjgl/lwjgl -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.lwjgl</groupId>
|
||||||
|
<artifactId>lwjgl</artifactId>
|
||||||
|
<version>3.1.2</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>de.javagl</groupId>
|
||||||
|
<artifactId>obj</artifactId>
|
||||||
|
<version>0.2.1</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.interactivemesh</groupId>
|
||||||
|
<artifactId>jimStlMeshImporter</artifactId>
|
||||||
|
<version>0.7</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.interactivemesh</groupId>
|
||||||
|
<artifactId>jimColModelImporter</artifactId>
|
||||||
|
<version>0.7</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.jfoenix</groupId>
|
||||||
|
<artifactId>jfoenix</artifactId>
|
||||||
|
<version>1.8.0</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- https://mvnrepository.com/artifact/javax.jmdns/jmdns -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>javax.jmdns</groupId>
|
||||||
|
<artifactId>jmdns</artifactId>
|
||||||
|
<version>3.4.2</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.fxyz3d</groupId>
|
||||||
|
<artifactId>fxyz3d</artifactId>
|
||||||
|
<version>0.1.1</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
@@ -152,4 +197,18 @@
|
|||||||
</plugin>
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
</reporting>
|
</reporting>
|
||||||
|
|
||||||
|
<repositories>
|
||||||
|
<repository>
|
||||||
|
<id>lib</id>
|
||||||
|
<name>third party libraries</name>
|
||||||
|
<url>file://${basedir}/lib</url>
|
||||||
|
</repository>
|
||||||
|
|
||||||
|
<repository>
|
||||||
|
<id>Homer-Core</id>
|
||||||
|
<name>Homer-core-repo</name>
|
||||||
|
<url>https://nexus.arcsmed.at/content/repositories/homer.core</url>
|
||||||
|
</repository>
|
||||||
|
</repositories>
|
||||||
</project>
|
</project>
|
||||||
@@ -2,10 +2,6 @@ package seng302;
|
|||||||
|
|
||||||
import ch.qos.logback.classic.Level;
|
import ch.qos.logback.classic.Level;
|
||||||
import javafx.application.Application;
|
import javafx.application.Application;
|
||||||
import javafx.fxml.FXMLLoader;
|
|
||||||
import javafx.scene.Parent;
|
|
||||||
import javafx.scene.Scene;
|
|
||||||
import javafx.scene.image.Image;
|
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
import org.apache.commons.cli.CommandLine;
|
import org.apache.commons.cli.CommandLine;
|
||||||
import org.apache.commons.cli.CommandLineParser;
|
import org.apache.commons.cli.CommandLineParser;
|
||||||
@@ -14,11 +10,13 @@ import org.apache.commons.cli.Options;
|
|||||||
import org.apache.commons.cli.ParseException;
|
import org.apache.commons.cli.ParseException;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import seng302.model.PolarTable;
|
import seng302.discoveryServer.DiscoveryServer;
|
||||||
|
import seng302.visualiser.controllers.ViewManager;
|
||||||
|
|
||||||
public class App extends Application {
|
public class App extends Application {
|
||||||
|
|
||||||
private static Logger logger = LoggerFactory.getLogger(App.class);
|
private static Logger logger = LoggerFactory.getLogger(App.class);
|
||||||
|
private static boolean isRunningAsCache = false;
|
||||||
|
|
||||||
public static void parseArgs(String[] args) throws ParseException {
|
public static void parseArgs(String[] args) throws ParseException {
|
||||||
Options options = new Options();
|
Options options = new Options();
|
||||||
@@ -29,9 +27,21 @@ public class App extends Application {
|
|||||||
.getLogger(Logger.ROOT_LOGGER_NAME);
|
.getLogger(Logger.ROOT_LOGGER_NAME);
|
||||||
|
|
||||||
options.addOption("debugLevel", true, "Set the application debug level");
|
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);
|
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")) {
|
if (cmd.hasOption("debugLevel")) {
|
||||||
|
|
||||||
switch (cmd.getOptionValue("debugLevel")) {
|
switch (cmd.getOptionValue("debugLevel")) {
|
||||||
@@ -67,36 +77,34 @@ public class App extends Application {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void start(Stage primaryStage) throws Exception {
|
public void start(Stage primaryStage) throws Exception {
|
||||||
Parent root = FXMLLoader.load(getClass().getResource("/views/StartScreenView.fxml"));
|
ViewManager.getInstance().initialiseSplashScreen(primaryStage);
|
||||||
primaryStage.setTitle("RaceVision");
|
|
||||||
Scene scene = new Scene(root, 1530, 960);
|
|
||||||
scene.getStylesheets().add(getClass().getResource("/css/master.css").toString());
|
|
||||||
primaryStage.setScene(scene);
|
|
||||||
// primaryStage.setMaxWidth(1530);
|
|
||||||
// primaryStage.setMaxHeight(960);
|
|
||||||
primaryStage.getIcons().add(new Image(getClass().getResourceAsStream("/PP.png")));
|
|
||||||
// primaryStage.setMaximized(true);
|
|
||||||
|
|
||||||
primaryStage.show();
|
|
||||||
|
|
||||||
primaryStage.setOnCloseRequest(e -> {
|
|
||||||
// ClientPacketParser.appClose();
|
|
||||||
// ClientPacketParser.appClose();
|
|
||||||
System.exit(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
// ClientState.primaryStage = primaryStage;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void main(String[] args) {
|
private static void runDiscoveryServer() throws Exception {
|
||||||
|
while (true){
|
||||||
|
try {
|
||||||
|
new DiscoveryServer();
|
||||||
|
}
|
||||||
|
catch (Exception ignored){
|
||||||
|
;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
try {
|
try {
|
||||||
parseArgs(args);
|
parseArgs(args);
|
||||||
} catch (ParseException e) {
|
} catch (ParseException e) {
|
||||||
logger.error("Could not parse command line arguments");
|
logger.error("Could not parse command line arguments");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!isRunningAsCache){
|
||||||
launch(args);
|
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 v0.1 " + selectedColor +"\n" +
|
||||||
|
" .loc'.'lxxxxxxxxocc;''''';ccoxxxxxxxxx:..oc \n" +
|
||||||
|
"olc,..',:cccccccccccc:;;;;;;;;:ccccccccc,.'c, \n" +
|
||||||
|
"Ol;......................................;l' ");
|
||||||
|
System.out.println(ANSI_RESET);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DiscoveryServer() throws Exception {
|
||||||
|
displayHeader();
|
||||||
|
serverTable = new ServerTable();
|
||||||
|
|
||||||
|
try{
|
||||||
|
serverSocket = new ServerSocket(PORT_NUMBER);
|
||||||
|
}
|
||||||
|
catch(java.net.BindException e){
|
||||||
|
logger.error("FATAL - Could not bind socket, are you sure there isn't already an instance running?");
|
||||||
|
System.exit(1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info("Started successfully - Now accepting connections");
|
||||||
|
|
||||||
|
try{
|
||||||
|
while (true){
|
||||||
|
Socket clientSocket = serverSocket.accept();
|
||||||
|
|
||||||
|
parseRequest(clientSocket);
|
||||||
|
|
||||||
|
clientSocket.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e){
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void parseRequest(Socket clientSocket) throws Exception {
|
||||||
|
ServerRepoStreamParser parser = new ServerRepoStreamParser(clientSocket.getInputStream());
|
||||||
|
|
||||||
|
if (clientSocket.isConnected() && !clientSocket.isClosed()){
|
||||||
|
PacketType parsePacketResult = parser.parse();
|
||||||
|
|
||||||
|
switch (parsePacketResult){
|
||||||
|
case SERVER_REGISTRATION:
|
||||||
|
ServerListing listing = parser.getServerListing();
|
||||||
|
|
||||||
|
if (!serverTable.getAllServers().contains(listing)){
|
||||||
|
listing.setRoomCode(serverTable.getNextRoomCode().toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
serverTable.addServer(listing);
|
||||||
|
|
||||||
|
Message serverRegMessage = new RoomCodeRequest(listing.getRoomCode());
|
||||||
|
clientSocket.getOutputStream().write(serverRegMessage.getBuffer());
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ROOM_CODE_REQUEST:
|
||||||
|
String desiredRoomCode = parser.getRoomCode();
|
||||||
|
ServerListing serverListing;
|
||||||
|
|
||||||
|
if (desiredRoomCode.equals("0000")){
|
||||||
|
serverListing = getRandomFreeServer();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
serverListing = serverTable.getServerByRoomCode(desiredRoomCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
Message response;
|
||||||
|
|
||||||
|
if (serverListing != null){
|
||||||
|
response = new ServerRegistrationMessage(serverListing.getServerName(), serverListing.getMapName(), serverListing.getAddress(), serverListing.getPortNumber(), 0, 0, desiredRoomCode);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
response = ServerRegistrationMessage.getEmptyRegistration();
|
||||||
|
}
|
||||||
|
|
||||||
|
clientSocket.getOutputStream().write(response.getBuffer());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServerListing getRandomFreeServer() {
|
||||||
|
ServerListing serverToJoin;
|
||||||
|
|
||||||
|
List<ServerListing> servers = serverTable.getAllServers();
|
||||||
|
|
||||||
|
if (servers.size() <= 0){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (servers.size() == 1){
|
||||||
|
return servers.get(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
serverToJoin = servers.get(new Random().nextInt(servers.size()));
|
||||||
|
|
||||||
|
int tries = 0;
|
||||||
|
|
||||||
|
while (serverToJoin != null && serverToJoin.isMaxPlayersReached() && tries < MAX_SERVER_TRIES){
|
||||||
|
serverToJoin = servers.get(new Random().nextInt(servers.size()));
|
||||||
|
tries++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (serverToJoin != null && serverToJoin.isMaxPlayersReached()){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return serverToJoin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void close(){
|
||||||
|
try {
|
||||||
|
serverSocket.close();
|
||||||
|
} catch (IOException ignored) {
|
||||||
|
;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,195 @@
|
|||||||
|
package seng302.discoveryServer;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import seng302.discoveryServer.util.ServerListing;
|
||||||
|
import seng302.discoveryServer.util.ServerRepoStreamParser;
|
||||||
|
import seng302.gameServer.messages.Message;
|
||||||
|
import seng302.gameServer.messages.RoomCodeRequest;
|
||||||
|
import seng302.gameServer.messages.ServerRegistrationMessage;
|
||||||
|
import seng302.model.stream.packets.PacketType;
|
||||||
|
import seng302.visualiser.controllers.ViewManager;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.Timer;
|
||||||
|
import java.util.TimerTask;
|
||||||
|
|
||||||
|
public class DiscoveryServerClient {
|
||||||
|
private final Integer UPDATE_INTERVAL_MS = 5000;
|
||||||
|
|
||||||
|
private static String roomCode = null;
|
||||||
|
private Timer serverListingUpdateTimer;
|
||||||
|
private Logger logger = LoggerFactory.getLogger(DiscoveryServerClient.class);
|
||||||
|
private String ip = "";
|
||||||
|
private Boolean isInInvalidState = false;
|
||||||
|
|
||||||
|
public DiscoveryServerClient() {
|
||||||
|
try {
|
||||||
|
ip = getInetIpAddr();
|
||||||
|
} catch (Exception e) {
|
||||||
|
failError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getInetIp(){
|
||||||
|
return ip;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void failError() {
|
||||||
|
isInInvalidState = true;
|
||||||
|
ViewManager.getInstance().showErrorSnackBar("You do not appear to be able to connect to the internet. Matchmaking will be unavailable.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean didFail(){
|
||||||
|
return isInInvalidState;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register the server with the discovery server
|
||||||
|
* @param serverListing The listing to register
|
||||||
|
*/
|
||||||
|
public void register(ServerListing serverListing){
|
||||||
|
if (isInInvalidState) return;
|
||||||
|
|
||||||
|
if (serverListingUpdateTimer != null){
|
||||||
|
serverListingUpdateTimer.cancel();
|
||||||
|
serverListingUpdateTimer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
serverListingUpdateTimer = new Timer();
|
||||||
|
|
||||||
|
serverListingUpdateTimer.schedule(new TimerTask() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
sendRegistrationUpdate(serverListing);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.debug("Could not update server listing");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 0, UPDATE_INTERVAL_MS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop updating the server registration updates
|
||||||
|
*/
|
||||||
|
public void unregister(){
|
||||||
|
if (serverListingUpdateTimer != null)
|
||||||
|
serverListingUpdateTimer.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the connection information for a server given a room code
|
||||||
|
*
|
||||||
|
* @param roomCode The room code to search for
|
||||||
|
* @return The ServerListing, or null if there was an error
|
||||||
|
* @throws Exception .
|
||||||
|
*/
|
||||||
|
public ServerListing getServerForRoomCode(String roomCode) throws Exception {
|
||||||
|
Socket socket = new Socket(DiscoveryServer.DISCOVERY_SERVER, DiscoveryServer.PORT_NUMBER);
|
||||||
|
ServerRepoStreamParser parser = new ServerRepoStreamParser(socket.getInputStream());
|
||||||
|
|
||||||
|
Message request = new RoomCodeRequest(roomCode); //roomCode);
|
||||||
|
socket.getOutputStream().write(request.getBuffer());
|
||||||
|
|
||||||
|
PacketType packetType = parser.parse();
|
||||||
|
|
||||||
|
if (packetType != PacketType.SERVER_REGISTRATION){
|
||||||
|
logger.debug("Wrong packet received in response to a room code request");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.close();
|
||||||
|
|
||||||
|
return parser.getServerListing();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServerListing getRandomServer() throws Exception {
|
||||||
|
Socket socket = new Socket(DiscoveryServer.DISCOVERY_SERVER, DiscoveryServer.PORT_NUMBER);
|
||||||
|
ServerRepoStreamParser parser = new ServerRepoStreamParser(socket.getInputStream());
|
||||||
|
|
||||||
|
Message request = new RoomCodeRequest("0000");
|
||||||
|
socket.getOutputStream().write(request.getBuffer());
|
||||||
|
|
||||||
|
PacketType packetType = parser.parse();
|
||||||
|
|
||||||
|
if (packetType != PacketType.SERVER_REGISTRATION){
|
||||||
|
logger.error("Incorrect packet type received");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.close();
|
||||||
|
|
||||||
|
ServerListing serverListing = parser.getServerListing();
|
||||||
|
|
||||||
|
if (serverListing == null || serverListing.equals(ServerRegistrationMessage.getEmptyRegistration())){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return serverListing;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a registration update to the discovery server.
|
||||||
|
*
|
||||||
|
* @param serverListing The server listing to send
|
||||||
|
* @throws Exception IF there was an error sending the update
|
||||||
|
*/
|
||||||
|
private void sendRegistrationUpdate(ServerListing serverListing) throws Exception {
|
||||||
|
Socket socket = new Socket(DiscoveryServer.DISCOVERY_SERVER, DiscoveryServer.PORT_NUMBER);
|
||||||
|
ServerRepoStreamParser parser = new ServerRepoStreamParser(socket.getInputStream());
|
||||||
|
|
||||||
|
Message req = new ServerRegistrationMessage(serverListing);
|
||||||
|
|
||||||
|
socket.getOutputStream().write(req.getBuffer());
|
||||||
|
|
||||||
|
PacketType packetType = parser.parse();
|
||||||
|
|
||||||
|
if (packetType != PacketType.ROOM_CODE_REQUEST){
|
||||||
|
socket.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String roomCode = parser.getRoomCode();
|
||||||
|
|
||||||
|
if (roomCode.length() != 0){
|
||||||
|
DiscoveryServerClient.roomCode = roomCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The last room code received by the client
|
||||||
|
*/
|
||||||
|
public static String getRoomCode(){
|
||||||
|
return roomCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getInetIpAddr() throws Exception {
|
||||||
|
URL myIp = new URL("http://checkip.amazonaws.com");
|
||||||
|
BufferedReader in = null;
|
||||||
|
try {
|
||||||
|
in = new BufferedReader(new InputStreamReader(
|
||||||
|
myIp.openStream()));
|
||||||
|
String ip = in.readLine();
|
||||||
|
return ip;
|
||||||
|
} finally {
|
||||||
|
if (in != null) {
|
||||||
|
try {
|
||||||
|
in.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package seng302.discoveryServer.util;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
public class ReadableByteInputStream {
|
||||||
|
private InputStream is;
|
||||||
|
|
||||||
|
public ReadableByteInputStream(InputStream is){
|
||||||
|
this.is = is;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get n bytes from the input stream
|
||||||
|
* @param n number of bytes
|
||||||
|
* @return the bytes read
|
||||||
|
* @throws Exception .
|
||||||
|
*/
|
||||||
|
public byte[] getBytes(int n) throws Exception {
|
||||||
|
byte[] bytes = new byte[n];
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
bytes[i] = (byte) readByte();
|
||||||
|
}
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Skip n bytes
|
||||||
|
* @param n number of bytes to skip
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public void skipBytes(long n) throws Exception {
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
readByte();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read the next byte from the stream
|
||||||
|
* @return The byte that was read
|
||||||
|
* @throws Exception .
|
||||||
|
*/
|
||||||
|
public int readByte() throws Exception {
|
||||||
|
int currentByte = is.read();
|
||||||
|
|
||||||
|
if (currentByte == -1) {
|
||||||
|
throw new Exception();
|
||||||
|
}
|
||||||
|
return currentByte;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
package seng302.discoveryServer.util;
|
||||||
|
|
||||||
|
public class ServerListing {
|
||||||
|
public final static int SERVER_TTL_DEFAULT = 10;
|
||||||
|
|
||||||
|
private String serverName = "";
|
||||||
|
private String mapName = "";
|
||||||
|
private String address = "";
|
||||||
|
private int portNumber = 0;
|
||||||
|
private int capacity = 0;
|
||||||
|
private int players = 0;
|
||||||
|
private String roomCode = "";
|
||||||
|
private int ttl = SERVER_TTL_DEFAULT;
|
||||||
|
|
||||||
|
|
||||||
|
public ServerListing(String serverName, String mapName, String address, int portNumber, int capacity){
|
||||||
|
this.serverName = serverName;
|
||||||
|
this.mapName = mapName;
|
||||||
|
this.address = address;
|
||||||
|
this.portNumber = portNumber;
|
||||||
|
this.capacity = capacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServerListing setNumberOfPlayers(int players){
|
||||||
|
this.players = players;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServerListing setRoomCode(String roomCode){
|
||||||
|
this.roomCode = roomCode;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void refreshTtl(){
|
||||||
|
ttl = SERVER_TTL_DEFAULT;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void decrementTtl(){
|
||||||
|
ttl--;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasTtlExpired(){
|
||||||
|
return ttl < 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (obj == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!ServerListing.class.isAssignableFrom(obj.getClass())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
final ServerListing other = (ServerListing) obj;
|
||||||
|
|
||||||
|
if (this.getPortNumber() != other.getPortNumber()){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.getMapName().equals(other.getMapName())){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.getServerName().equals(other.getServerName())){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.getCapacity() != other.getCapacity()){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.getAddress().equals(other.getAddress())){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return this.getServerName().hashCode() +
|
||||||
|
this.getAddress().hashCode() + this.getMapName().hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRoomCode() {
|
||||||
|
return roomCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPortNumber() {
|
||||||
|
return portNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMapName() {
|
||||||
|
return mapName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getServerName() {
|
||||||
|
return serverName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCapacity() {
|
||||||
|
return capacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAddress() {
|
||||||
|
return address;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTtl(Integer ttl){
|
||||||
|
this.ttl = ttl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isMaxPlayersReached() {
|
||||||
|
return players >= capacity;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
package seng302.discoveryServer.util;
|
||||||
|
|
||||||
|
|
||||||
|
import seng302.gameServer.messages.Message;
|
||||||
|
import seng302.model.stream.packets.PacketType;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
public class ServerRepoStreamParser {
|
||||||
|
private ReadableByteInputStream inputStream;
|
||||||
|
|
||||||
|
private String roomCode;
|
||||||
|
private String mapName;
|
||||||
|
private ServerListing serverListing;
|
||||||
|
|
||||||
|
public ServerRepoStreamParser(InputStream is){
|
||||||
|
inputStream = new ReadableByteInputStream(is);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PacketType parse() throws Exception {
|
||||||
|
int sync1 = inputStream.readByte();
|
||||||
|
int sync2 = inputStream.readByte();
|
||||||
|
|
||||||
|
PacketType packetType = null;
|
||||||
|
|
||||||
|
if (sync1 == 0x47 && sync2 == 0x83) {
|
||||||
|
int type = inputStream.readByte();
|
||||||
|
inputStream.skipBytes(10);
|
||||||
|
long payloadLength = Message.bytesToLong(inputStream.getBytes(2));
|
||||||
|
byte[] payload = inputStream.getBytes((int) payloadLength);
|
||||||
|
inputStream.skipBytes(4);
|
||||||
|
|
||||||
|
packetType = PacketType.assignPacketType(type, payload);
|
||||||
|
|
||||||
|
switch (packetType) {
|
||||||
|
case ROOM_CODE_REQUEST:
|
||||||
|
roomCode = parseRoomCodeRequest(payload);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LOBBY_REQUEST:
|
||||||
|
mapName = parseLobbyRequest(payload);
|
||||||
|
|
||||||
|
case SERVER_REGISTRATION:
|
||||||
|
serverListing = parseServerRegistration(payload);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return packetType;
|
||||||
|
}
|
||||||
|
private String parseLobbyRequest(byte[] payload) {
|
||||||
|
int mapNameLength = (int) Message.bytesToLong(Arrays.copyOfRange(payload, 0 ,4));
|
||||||
|
|
||||||
|
return new String(Arrays.copyOfRange(payload, 4, 4+mapNameLength));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String parseRoomCodeRequest(byte[] payload) {
|
||||||
|
int roomCodeLength = (int) Message.bytesToLong(Arrays.copyOfRange(payload, 0 ,6));
|
||||||
|
|
||||||
|
return new String(Arrays.copyOfRange(payload, 6, 6+roomCodeLength));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ServerListing parseServerRegistration(byte[] payload) {
|
||||||
|
int nameLength = (int) Message.bytesToLong(Arrays.copyOfRange(payload, 0, 6));
|
||||||
|
int mapNameLength = (int) Message.bytesToLong(Arrays.copyOfRange(payload, 6, 12));
|
||||||
|
int addressLength = (int) Message.bytesToLong(Arrays.copyOfRange(payload, 12, 18));
|
||||||
|
int roomCodeLength = (int) Message.bytesToLong(Arrays.copyOfRange(payload, 18, 24));
|
||||||
|
|
||||||
|
int portNumber = (int) Message.bytesToLong(Arrays.copyOfRange(payload, 24, 28));
|
||||||
|
int players = (int) Message.bytesToLong(Arrays.copyOfRange(payload, 28, 32));
|
||||||
|
int capacity = (int) Message.bytesToLong(Arrays.copyOfRange(payload, 32, 36));
|
||||||
|
|
||||||
|
int currentPos = 36;
|
||||||
|
int nextPos = currentPos + nameLength;
|
||||||
|
String serverName = new String(Arrays.copyOfRange(payload, currentPos, nextPos));
|
||||||
|
|
||||||
|
currentPos = nextPos;
|
||||||
|
nextPos = currentPos + mapNameLength;
|
||||||
|
String mapName = new String(Arrays.copyOfRange(payload, currentPos, nextPos));
|
||||||
|
|
||||||
|
currentPos = nextPos;
|
||||||
|
nextPos = currentPos + addressLength;
|
||||||
|
String address = new String(Arrays.copyOfRange(payload, currentPos, nextPos));
|
||||||
|
|
||||||
|
currentPos = nextPos;
|
||||||
|
nextPos = currentPos + roomCodeLength;
|
||||||
|
String roomCode = new String(Arrays.copyOfRange(payload, currentPos, nextPos));
|
||||||
|
|
||||||
|
ServerListing serverListing = new ServerListing(serverName, mapName, address, portNumber, capacity);
|
||||||
|
serverListing.setNumberOfPlayers(players);
|
||||||
|
serverListing.setRoomCode(roomCode);
|
||||||
|
|
||||||
|
return serverListing;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRoomCode() {
|
||||||
|
return roomCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMapName() {
|
||||||
|
return mapName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServerListing getServerListing() {
|
||||||
|
return serverListing;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
package seng302.discoveryServer.util;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
public class ServerTable {
|
||||||
|
private List<ServerListing> servers;
|
||||||
|
private int lastRoomCode = 4020;
|
||||||
|
private Logger logger = LoggerFactory.getLogger(ServerTable.class);
|
||||||
|
|
||||||
|
public ServerTable(){
|
||||||
|
servers = new ArrayList<>();
|
||||||
|
|
||||||
|
new Timer().schedule(new TimerTask() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
updateServers();
|
||||||
|
}
|
||||||
|
}, 0, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the servers TTL values, and then remove expired servers
|
||||||
|
*/
|
||||||
|
private void updateServers() {
|
||||||
|
List<ServerListing> serversToRemove = new ArrayList<>();
|
||||||
|
|
||||||
|
for (ServerListing server : servers){
|
||||||
|
server.decrementTtl();
|
||||||
|
|
||||||
|
if (server.hasTtlExpired()){
|
||||||
|
logger.debug("Removed expired server - " + server.getServerName());
|
||||||
|
serversToRemove.add(server);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
servers.removeAll(serversToRemove);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a server to the table
|
||||||
|
* @param server The server to add
|
||||||
|
*/
|
||||||
|
public void addServer(ServerListing server){
|
||||||
|
if (servers.contains(server)){
|
||||||
|
updateTtlForServer(server);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
logger.debug("Added new server - " + server.getServerName() + " at address: " + server.getAddress() + ":" + server.getPortNumber());
|
||||||
|
servers.add(server);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the TTL for a given server to the default TTL value
|
||||||
|
* @param server The server to update
|
||||||
|
*/
|
||||||
|
private void updateTtlForServer(ServerListing server) {
|
||||||
|
for (ServerListing serverListing : servers){
|
||||||
|
if (server.equals(serverListing)){
|
||||||
|
serverListing.refreshTtl();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return All the servers in the table
|
||||||
|
*/
|
||||||
|
public List<ServerListing> getAllServers(){
|
||||||
|
return Collections.unmodifiableList(servers);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a server from the table given its room code
|
||||||
|
* @param roomCode The room code to search for
|
||||||
|
* @return The ServerListing of the found server, or null
|
||||||
|
* the server wasn't found
|
||||||
|
*/
|
||||||
|
public ServerListing getServerByRoomCode(String roomCode){
|
||||||
|
for (ServerListing serverListing : servers){
|
||||||
|
if (serverListing.getRoomCode().equals(roomCode)){
|
||||||
|
return serverListing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The next available room code
|
||||||
|
*/
|
||||||
|
public Integer getNextRoomCode(){
|
||||||
|
lastRoomCode += 1;
|
||||||
|
return lastRoomCode;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,26 +1,27 @@
|
|||||||
package seng302.gameServer;
|
package seng302.gameServer;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Random;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.Timer;
|
||||||
|
import java.util.TimerTask;
|
||||||
import javafx.scene.paint.Color;
|
import javafx.scene.paint.Color;
|
||||||
import javax.xml.parsers.DocumentBuilder;
|
|
||||||
import javax.xml.parsers.DocumentBuilderFactory;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.w3c.dom.Document;
|
|
||||||
import org.xml.sax.InputSource;
|
|
||||||
import seng302.gameServer.messages.BoatAction;
|
import seng302.gameServer.messages.BoatAction;
|
||||||
import seng302.gameServer.messages.BoatStatus;
|
import seng302.gameServer.messages.BoatStatus;
|
||||||
|
import seng302.gameServer.messages.ChatterMessage;
|
||||||
import seng302.gameServer.messages.CustomizeRequestType;
|
import seng302.gameServer.messages.CustomizeRequestType;
|
||||||
import seng302.gameServer.messages.MarkRoundingMessage;
|
import seng302.gameServer.messages.MarkRoundingMessage;
|
||||||
import seng302.gameServer.messages.MarkType;
|
import seng302.gameServer.messages.MarkType;
|
||||||
import seng302.gameServer.messages.Message;
|
import seng302.gameServer.messages.Message;
|
||||||
import seng302.gameServer.messages.RoundingBoatStatus;
|
import seng302.gameServer.messages.RoundingBoatStatus;
|
||||||
import seng302.gameServer.messages.YachtEventCodeMessage;
|
|
||||||
import seng302.model.GeoPoint;
|
import seng302.model.GeoPoint;
|
||||||
import seng302.model.Limit;
|
import seng302.model.Limit;
|
||||||
import seng302.model.Player;
|
import seng302.model.Player;
|
||||||
@@ -29,8 +30,12 @@ import seng302.model.ServerYacht;
|
|||||||
import seng302.model.mark.CompoundMark;
|
import seng302.model.mark.CompoundMark;
|
||||||
import seng302.model.mark.Mark;
|
import seng302.model.mark.Mark;
|
||||||
import seng302.model.mark.MarkOrder;
|
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.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)
|
* A Static class to hold information about the current state of the game (model)
|
||||||
@@ -41,102 +46,105 @@ public class GameState implements Runnable {
|
|||||||
|
|
||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
interface NewMessageListener {
|
interface NewMessageListener {
|
||||||
|
|
||||||
void notify(Message message);
|
void notify(Message message);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Logger logger = LoggerFactory.getLogger(GameState.class);
|
private static Logger logger = LoggerFactory.getLogger(GameState.class);
|
||||||
|
|
||||||
|
|
||||||
private static final Integer STATE_UPDATES_PER_SECOND = 60;
|
private static final Integer STATE_UPDATES_PER_SECOND = 60;
|
||||||
public static Integer MAX_PLAYERS = 8;
|
|
||||||
public static Double ROUNDING_DISTANCE = 50d; // TODO: 14/08/17 wmu16 - Look into this value further
|
//Scheduling constants
|
||||||
public static final Double MARK_COLLISION_DISTANCE = 15d;
|
static final int WARNING_TIME = 10 * -1000;
|
||||||
|
static final int PREPATORY_TIME = 5 * -1000;
|
||||||
|
private static final int TIME_TILL_START = 10 * 1000;
|
||||||
|
|
||||||
|
//Wind Constants
|
||||||
|
private static final int MAX_WIND_SPEED = 12000;
|
||||||
|
private static final int MIN_WIND_SPEED = 8000;
|
||||||
|
|
||||||
|
//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 = 25.0;
|
||||||
public static final Double BOUNCE_DISTANCE_MARK = 20.0;
|
private static final Double BOUNCE_DISTANCE_MARK = 20.0;
|
||||||
public static final Double BOUNCE_DISTANCE_YACHT = 30.0;
|
public static final Double BOUNCE_DISTANCE_YACHT = 30.0;
|
||||||
public static final Double COLLISION_VELOCITY_PENALTY = 0.3;
|
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;
|
private static Long previousUpdateTime;
|
||||||
public static Double windDirection;
|
public static Double windDirection;
|
||||||
private static Double windSpeed;
|
private static Double windSpeed;
|
||||||
|
private static Double serverSpeedMultiplier;
|
||||||
|
|
||||||
private static Boolean customizationFlag; // dirty flag to tell if a player has customized their boat.
|
private static Boolean customizationFlag; // dirty flag to tell if a player has customized their boat.
|
||||||
|
private static Boolean playerHasLeftFlag;
|
||||||
|
|
||||||
private static String hostIpAddress;
|
private static String hostIpAddress;
|
||||||
private static List<Player> players;
|
private static List<Player> players;
|
||||||
private static Map<Integer, ServerYacht> yachts;
|
private static Map<Integer, ServerYacht> yachts;
|
||||||
private static Boolean isRaceStarted;
|
|
||||||
private static GameStages currentStage;
|
private static GameStages currentStage;
|
||||||
private static MarkOrder markOrder;
|
private static MarkOrder markOrder;
|
||||||
private static long startTime;
|
private static long startTime;
|
||||||
private static Set<Mark> marks;
|
private static Set<Mark> marks = new HashSet<>();
|
||||||
private static List<Limit> courseLimit;
|
private static List<Limit> courseLimit = new ArrayList<>();
|
||||||
|
private static Integer maxPlayers = 8;
|
||||||
|
|
||||||
private static List<NewMessageListener> markListeners;
|
private static List<Token> tokensInPlay;
|
||||||
|
private static RandomSpawn randomSpawn;
|
||||||
|
|
||||||
|
private static List<NewMessageListener> newMessageListeners;
|
||||||
|
|
||||||
private static Map<Player, String> playerStringMap = new HashMap<>();
|
private static Map<Player, String> playerStringMap = new HashMap<>();
|
||||||
/*
|
private static boolean tokensEnabled = false;
|
||||||
Ideally I would like to make this class an object instantiated by the server and given to
|
|
||||||
it's created threads if necessary. Outside of that I think the dependencies on it
|
|
||||||
(atm only Yacht & GameClient) can be removed from most other classes. The observable list of
|
|
||||||
players could be pulled directly from the server by the GameClient since it instantiates it
|
|
||||||
and it is reasonable for it to pull data. The current setup of publicly available statics is
|
|
||||||
pretty meh IMO because anything can change it making it unreliable and like people did with
|
|
||||||
the old ServerParser class everything that needs shared just gets thrown in the static
|
|
||||||
collections and things become a real mess.
|
|
||||||
*/
|
|
||||||
|
|
||||||
public GameState(String hostIpAddress) {
|
public GameState() {
|
||||||
windDirection = 180d;
|
windDirection = 180d;
|
||||||
windSpeed = 10000d;
|
windSpeed = 10000d;
|
||||||
this.hostIpAddress = hostIpAddress;
|
|
||||||
yachts = new HashMap<>();
|
yachts = new HashMap<>();
|
||||||
|
tokensInPlay = new ArrayList<>();
|
||||||
players = new ArrayList<>();
|
players = new ArrayList<>();
|
||||||
GameState.hostIpAddress = hostIpAddress;
|
|
||||||
customizationFlag = false;
|
customizationFlag = false;
|
||||||
|
playerHasLeftFlag = false;
|
||||||
|
serverSpeedMultiplier = 1.0;
|
||||||
currentStage = GameStages.LOBBYING;
|
currentStage = GameStages.LOBBYING;
|
||||||
isRaceStarted = false;
|
|
||||||
//set this when game stage changes to prerace
|
|
||||||
previousUpdateTime = System.currentTimeMillis();
|
previousUpdateTime = System.currentTimeMillis();
|
||||||
markOrder = new MarkOrder(); //This could be instantiated at some point with a select map?
|
newMessageListeners = new ArrayList<>();
|
||||||
markListeners = new ArrayList<>();
|
|
||||||
|
|
||||||
resetStartTime();
|
resetStartTime();
|
||||||
|
//setCourseLimit("/server_config/race.xml");
|
||||||
new Thread(this, "GameState").start(); //Run the auto updates on the game state
|
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) {
|
public static void setRace(RaceXMLData raceXMLData) {
|
||||||
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
|
markOrder = new MarkOrder(raceXMLData);
|
||||||
documentBuilderFactory.setNamespaceAware(true);
|
for (CompoundMark compoundMark : raceXMLData.getCompoundMarks().values()){
|
||||||
DocumentBuilder documentBuilder;
|
marks.addAll(compoundMark.getMarks());
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
courseLimit = XMLParser.parseRace(document).getCourseLimit();
|
randomSpawn = new RandomSpawn(markOrder.getOrderedUniqueCompoundMarks());
|
||||||
}
|
courseLimit = raceXMLData.getCourseLimit();
|
||||||
|
|
||||||
public static String getHostIpAddress() {
|
|
||||||
return hostIpAddress;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Set<Mark> getMarks() {
|
|
||||||
return Collections.unmodifiableSet(marks);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<Player> getPlayers() {
|
public static List<Player> getPlayers() {
|
||||||
return players;
|
return players;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static List<Token> getTokensInPlay() {
|
||||||
|
return tokensInPlay;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Set<Mark> getMarks() {
|
||||||
|
return Collections.unmodifiableSet(marks);
|
||||||
|
}
|
||||||
|
|
||||||
public static void addPlayer(Player player) {
|
public static void addPlayer(Player player) {
|
||||||
players.add(player);
|
players.add(player);
|
||||||
String playerText = player.getYacht().getSourceId() + " " + player.getYacht().getBoatName()
|
String playerText = player.getYacht().getSourceId() + " " + player.getYacht().getBoatName()
|
||||||
@@ -157,10 +165,6 @@ public class GameState implements Runnable {
|
|||||||
yachts.remove(yachtId);
|
yachts.remove(yachtId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Boolean getIsRaceStarted() {
|
|
||||||
return isRaceStarted;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static GameStages getCurrentStage() {
|
public static GameStages getCurrentStage() {
|
||||||
return currentStage;
|
return currentStage;
|
||||||
}
|
}
|
||||||
@@ -178,7 +182,7 @@ public class GameState implements Runnable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void resetStartTime(){
|
public static void resetStartTime(){
|
||||||
startTime = System.currentTimeMillis() + MainServerThread.TIME_TILL_START;
|
startTime = System.currentTimeMillis() + TIME_TILL_START;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Double getWindDirection() {
|
public static Double getWindDirection() {
|
||||||
@@ -229,16 +233,77 @@ public class GameState implements Runnable {
|
|||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
System.out.println("[GameState] interrupted exception");
|
System.out.println("[GameState] interrupted exception");
|
||||||
}
|
}
|
||||||
if (currentStage == GameStages.PRE_RACE || currentStage == GameStages.RACING) {
|
if (currentStage == GameStages.PRE_RACE) {
|
||||||
update();
|
update();
|
||||||
|
if (System.currentTimeMillis() > startTime) {
|
||||||
|
startSpawningTokens();
|
||||||
|
startUpdatingWind();
|
||||||
|
GameState.setCurrentStage(GameStages.RACING);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentStage == GameStages.RACING) {
|
if (currentStage == GameStages.RACING) {
|
||||||
update();
|
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.setWindSpeed(Double.valueOf(windSpeed));
|
||||||
|
GameState.setWindDirection(direction.doubleValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public static void updateBoat(Integer sourceId, BoatAction actionType) {
|
public static void updateBoat(Integer sourceId, BoatAction actionType) {
|
||||||
ServerYacht playerYacht = yachts.get(sourceId);
|
ServerYacht playerYacht = yachts.get(sourceId);
|
||||||
switch (actionType) {
|
switch (actionType) {
|
||||||
@@ -260,31 +325,53 @@ public class GameState implements Runnable {
|
|||||||
case DOWNWIND:
|
case DOWNWIND:
|
||||||
playerYacht.turnDownwind();
|
playerYacht.turnDownwind();
|
||||||
break;
|
break;
|
||||||
|
case CONTINUOUSLY_TURNING:
|
||||||
|
playerYacht.setContinuouslyTurning(true);
|
||||||
|
break;
|
||||||
|
case DEFAULT_TURNING:
|
||||||
|
playerYacht.setContinuouslyTurning(false);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called periodically in this GameState thread to update the GameState values
|
* Randomly select a subset of tokensInPlay from a pre defined superset
|
||||||
|
* Broadasts a new race status message to show this update
|
||||||
|
*/
|
||||||
|
private void spawnNewToken() {
|
||||||
|
tokensInPlay.clear();
|
||||||
|
Token token = randomSpawn.getRandomToken();
|
||||||
|
// token.assignType(TokenType.WIND_WALKER);
|
||||||
|
logger.debug("Spawned token of type " + token.getTokenType());
|
||||||
|
tokensInPlay.add(token);
|
||||||
|
MessageFactory.updateTokens(tokensInPlay);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called periodically in this GameState thread to update the GameState values.
|
||||||
|
* -Updates yachts velocity
|
||||||
|
* -Updates locations
|
||||||
|
* -Checks for collisions
|
||||||
|
* -Checks for progression
|
||||||
|
*
|
||||||
|
* -Also checks things like the end of the race and race start time etc
|
||||||
*/
|
*/
|
||||||
public void update() {
|
public void update() {
|
||||||
Boolean raceFinished = true;
|
Boolean raceFinished = true;
|
||||||
|
|
||||||
Double timeInterval = (System.currentTimeMillis() - previousUpdateTime) / 1000000.0;
|
Double timeInterval = (System.currentTimeMillis() - previousUpdateTime) / 1000000.0;
|
||||||
previousUpdateTime = System.currentTimeMillis();
|
previousUpdateTime = System.currentTimeMillis();
|
||||||
if (System.currentTimeMillis() > startTime) {
|
|
||||||
GameState.setCurrentStage(GameStages.RACING);
|
|
||||||
}
|
|
||||||
for (ServerYacht yacht : yachts.values()) {
|
for (ServerYacht yacht : yachts.values()) {
|
||||||
updateVelocity(yacht);
|
updateVelocity(yacht);
|
||||||
yacht.runAutoPilot();
|
yacht.runAutoPilot();
|
||||||
yacht.updateLocation(timeInterval);
|
yacht.updateLocation(timeInterval);
|
||||||
if (yacht.getBoatStatus() != BoatStatus.FINISHED) {
|
preformTokenUpdates(yacht); //This update must be done before collision. Sorta hacky
|
||||||
checkCollision(yacht);
|
checkCollision(yacht);
|
||||||
|
if (yacht.getBoatStatus() != BoatStatus.FINISHED) {
|
||||||
checkForLegProgression(yacht);
|
checkForLegProgression(yacht);
|
||||||
raceFinished = false;
|
raceFinished = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (raceFinished) {
|
if (raceFinished) {
|
||||||
@@ -292,6 +379,139 @@ public class GameState implements Runnable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the yacht has crossed the course limit
|
* Check if the yacht has crossed the course limit
|
||||||
*
|
*
|
||||||
@@ -305,6 +525,7 @@ public class GameState implements Runnable {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (GeoUtility.checkCrossedLine(courseLimit.get(courseLimit.size() - 1), courseLimit.get(0),
|
if (GeoUtility.checkCrossedLine(courseLimit.get(courseLimit.size() - 1), courseLimit.get(0),
|
||||||
yacht.getLastLocation(), yacht.getLocation()) != 0) {
|
yacht.getLastLocation(), yacht.getLocation()) != 0) {
|
||||||
return true;
|
return true;
|
||||||
@@ -312,8 +533,41 @@ public class GameState implements Runnable {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 Token checkTokenPickUp(ServerYacht yacht) {
|
||||||
|
for (Token token : tokensInPlay) {
|
||||||
|
Double distance = GeoUtility.getDistance(token, yacht.getLocation());
|
||||||
|
if (distance < YACHT_COLLISION_DISTANCE) {
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks for collision with other in game objects for the given serverYacht. To be called each
|
||||||
|
* update. If there is a collision, Notifies the server to send the appropriate messages out.
|
||||||
|
* Checks for these items in turn:
|
||||||
|
* - Other yachts
|
||||||
|
* - Marks
|
||||||
|
* - Boundary
|
||||||
|
* - Tokens
|
||||||
|
*
|
||||||
|
* @param serverYacht The server yacht to check collisions with
|
||||||
|
*/
|
||||||
public static void checkCollision(ServerYacht serverYacht) {
|
public static void checkCollision(ServerYacht serverYacht) {
|
||||||
ServerYacht collidedYacht = checkYachtCollision(serverYacht);
|
//Yacht Collision
|
||||||
|
ServerYacht collidedYacht = checkYachtCollision(serverYacht, false);
|
||||||
|
Mark collidedMark = checkMarkCollision(serverYacht);
|
||||||
|
|
||||||
if (collidedYacht != null) {
|
if (collidedYacht != null) {
|
||||||
GeoPoint originalLocation = serverYacht.getLocation();
|
GeoPoint originalLocation = serverYacht.getLocation();
|
||||||
serverYacht.setLocation(
|
serverYacht.setLocation(
|
||||||
@@ -328,60 +582,64 @@ public class GameState implements Runnable {
|
|||||||
collidedYacht.setCurrentVelocity(
|
collidedYacht.setCurrentVelocity(
|
||||||
collidedYacht.getCurrentVelocity() * COLLISION_VELOCITY_PENALTY
|
collidedYacht.getCurrentVelocity() * COLLISION_VELOCITY_PENALTY
|
||||||
);
|
);
|
||||||
notifyMessageListeners(
|
notifyMessageListeners(MessageFactory.makeCollisionMessage(serverYacht));
|
||||||
new YachtEventCodeMessage(serverYacht.getSourceId())
|
}
|
||||||
);
|
|
||||||
} else {
|
//Mark Collision
|
||||||
Mark collidedMark = checkMarkCollision(serverYacht);
|
else if (collidedMark != null) {
|
||||||
if (collidedMark != null) {
|
|
||||||
serverYacht.setLocation(
|
serverYacht.setLocation(
|
||||||
calculateBounceBack(serverYacht, collidedMark, BOUNCE_DISTANCE_MARK)
|
calculateBounceBack(serverYacht, collidedMark, BOUNCE_DISTANCE_MARK)
|
||||||
);
|
);
|
||||||
|
|
||||||
serverYacht.setCurrentVelocity(
|
serverYacht.setCurrentVelocity(
|
||||||
serverYacht.getCurrentVelocity() * COLLISION_VELOCITY_PENALTY
|
serverYacht.getCurrentVelocity() * COLLISION_VELOCITY_PENALTY
|
||||||
);
|
);
|
||||||
notifyMessageListeners(
|
notifyMessageListeners(MessageFactory.makeCollisionMessage(serverYacht));
|
||||||
new YachtEventCodeMessage(serverYacht.getSourceId())
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
else{
|
|
||||||
if (checkBoundaryCollision(serverYacht)) {
|
//Boundary Collision
|
||||||
|
else if (checkBoundaryCollision(serverYacht)) {
|
||||||
serverYacht.setLocation(
|
serverYacht.setLocation(
|
||||||
calculateBounceBack(serverYacht, serverYacht.getLocation(),
|
calculateBounceBack(serverYacht, serverYacht.getLocation(),
|
||||||
BOUNCE_DISTANCE_YACHT)
|
BOUNCE_DISTANCE_YACHT)
|
||||||
);
|
);
|
||||||
|
|
||||||
serverYacht.setCurrentVelocity(
|
serverYacht.setCurrentVelocity(
|
||||||
serverYacht.getCurrentVelocity() * COLLISION_VELOCITY_PENALTY
|
serverYacht.getCurrentVelocity() * COLLISION_VELOCITY_PENALTY
|
||||||
);
|
);
|
||||||
notifyMessageListeners(
|
notifyMessageListeners(MessageFactory.makeCollisionMessage(serverYacht));
|
||||||
new YachtEventCodeMessage(serverYacht.getSourceId())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void updateVelocity(ServerYacht yacht) {
|
private void updateVelocity(ServerYacht yacht) {
|
||||||
Double velocity = yacht.getCurrentVelocity();
|
|
||||||
Double trueWindAngle = Math.abs(windDirection - yacht.getHeading());
|
Double trueWindAngle = Math.abs(windDirection - yacht.getHeading());
|
||||||
Double boatSpeedInKnots = PolarTable.getBoatSpeed(getWindSpeedKnots(), trueWindAngle);
|
Double boatSpeedInKnots = PolarTable.getBoatSpeed(getWindSpeedKnots(), trueWindAngle);
|
||||||
Double maxBoatSpeed = GeoUtility.knotsToMMS(boatSpeedInKnots);
|
Double maxBoatSpeed =
|
||||||
|
GeoUtility.knotsToMMS(boatSpeedInKnots) * serverSpeedMultiplier * yacht
|
||||||
|
.getPowerUpSpeedMultiplier() * yacht.getBoatTypeSpeedMultiplier();
|
||||||
|
|
||||||
|
Double currentVelocity = yacht.getCurrentVelocity();
|
||||||
// TODO: 15/08/17 remove magic numbers from these equations.
|
// TODO: 15/08/17 remove magic numbers from these equations.
|
||||||
if (yacht.getSailIn()) {
|
if (yacht.getSailIn()) {
|
||||||
if (velocity < maxBoatSpeed - 500) {
|
if (currentVelocity < maxBoatSpeed - 500) {
|
||||||
yacht.changeVelocity(maxBoatSpeed / 100);
|
yacht.changeVelocity(
|
||||||
} else if (velocity > maxBoatSpeed + 500) {
|
(maxBoatSpeed / 100) * yacht.getBoatTypeAccelerationMultiplier());
|
||||||
yacht.changeVelocity(-velocity / 200);
|
} else if (currentVelocity > maxBoatSpeed + 500) {
|
||||||
|
yacht.changeVelocity(
|
||||||
|
(-currentVelocity / 200) * yacht.getBoatTypeAccelerationMultiplier());
|
||||||
} else {
|
} else {
|
||||||
yacht.setCurrentVelocity(maxBoatSpeed);
|
yacht
|
||||||
|
.setCurrentVelocity((maxBoatSpeed) * yacht.getBoatTypeAccelerationMultiplier());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (velocity > 3000) {
|
if (currentVelocity > 3000) {
|
||||||
yacht.changeVelocity(-velocity / 200);
|
yacht.changeVelocity(
|
||||||
} else if (velocity > 100) {
|
(-currentVelocity / 200) * yacht.getBoatTypeAccelerationMultiplier());
|
||||||
yacht.changeVelocity(-velocity / 50);
|
} else if (currentVelocity > 100) {
|
||||||
} else if (velocity <= 100) {
|
yacht.changeVelocity(
|
||||||
|
(-currentVelocity / 50) * yacht.getBoatTypeAccelerationMultiplier());
|
||||||
|
} else if (currentVelocity <= 100) {
|
||||||
yacht.setCurrentVelocity(0d);
|
yacht.setCurrentVelocity(0d);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -442,6 +700,12 @@ public class GameState implements Runnable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (hasProgressed) {
|
if (hasProgressed) {
|
||||||
|
if (currentMarkSeqID != 0 && !markOrder.isLastMark(currentMarkSeqID)) {
|
||||||
|
|
||||||
|
String logMessage = yacht.getBoatName() + " passed leg " + yacht.getLegNumber();
|
||||||
|
notifyMessageListeners(
|
||||||
|
MessageFactory.makeChatterMessage(yacht.getSourceId(), logMessage));
|
||||||
|
}
|
||||||
yacht.incrementLegNumber();
|
yacht.incrementLegNumber();
|
||||||
sendMarkRoundingMessage(yacht);
|
sendMarkRoundingMessage(yacht);
|
||||||
logMarkRounding(yacht);
|
logMarkRounding(yacht);
|
||||||
@@ -461,6 +725,10 @@ public class GameState implements Runnable {
|
|||||||
* @param yacht The current yacht to check for
|
* @param yacht The current yacht to check for
|
||||||
*/
|
*/
|
||||||
private Boolean checkStartLineCrossing(ServerYacht yacht) {
|
private Boolean checkStartLineCrossing(ServerYacht yacht) {
|
||||||
|
long timeTillStart = System.currentTimeMillis() - this.getStartTime();
|
||||||
|
if (timeTillStart < 0){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
Integer currentMarkSeqID = yacht.getCurrentMarkSeqID();
|
Integer currentMarkSeqID = yacht.getCurrentMarkSeqID();
|
||||||
CompoundMark currentMark = markOrder.getCurrentMark(currentMarkSeqID);
|
CompoundMark currentMark = markOrder.getCurrentMark(currentMarkSeqID);
|
||||||
GeoPoint lastLocation = yacht.getLastLocation();
|
GeoPoint lastLocation = yacht.getLastLocation();
|
||||||
@@ -476,6 +744,9 @@ public class GameState implements Runnable {
|
|||||||
if (crossedLine == 2 && isClockwiseCross || crossedLine == 1 && !isClockwiseCross) {
|
if (crossedLine == 2 && isClockwiseCross || crossedLine == 1 && !isClockwiseCross) {
|
||||||
yacht.setClosestCurrentMark(mark1);
|
yacht.setClosestCurrentMark(mark1);
|
||||||
yacht.setBoatStatus(BoatStatus.RACING);
|
yacht.setBoatStatus(BoatStatus.RACING);
|
||||||
|
String logMessage = yacht.getBoatName() + " passed start line";
|
||||||
|
notifyMessageListeners(
|
||||||
|
MessageFactory.makeChatterMessage(yacht.getSourceId(), logMessage));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -579,6 +850,10 @@ public class GameState implements Runnable {
|
|||||||
if (crossedLine == 1 && isClockwiseCross || crossedLine == 2 && !isClockwiseCross) {
|
if (crossedLine == 1 && isClockwiseCross || crossedLine == 2 && !isClockwiseCross) {
|
||||||
yacht.setClosestCurrentMark(mark1);
|
yacht.setClosestCurrentMark(mark1);
|
||||||
yacht.setBoatStatus(BoatStatus.FINISHED);
|
yacht.setBoatStatus(BoatStatus.FINISHED);
|
||||||
|
|
||||||
|
String logMessage = yacht.getBoatName() + " passed finish line";
|
||||||
|
notifyMessageListeners(
|
||||||
|
MessageFactory.makeChatterMessage(yacht.getSourceId(), logMessage));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -601,16 +876,20 @@ public class GameState implements Runnable {
|
|||||||
String name = new String(customizeData);
|
String name = new String(customizeData);
|
||||||
playerYacht.setBoatName(name);
|
playerYacht.setBoatName(name);
|
||||||
} else if (requestType.equals(CustomizeRequestType.COLOR)) {
|
} 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 red = customizeData[0] & 0xFF;
|
||||||
int green = customizeData[1] & 0xFF;
|
int green = customizeData[1] & 0xFF;
|
||||||
int blue = customizeData[2] & 0xFF;
|
int blue = customizeData[2] & 0xFF;
|
||||||
Color yachtColor = Color.rgb(red, green, blue);
|
Color yachtColor = Color.rgb(red, green, blue);
|
||||||
playerYacht.setBoatColor(yachtColor);
|
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) {
|
private static Mark checkMarkCollision(ServerYacht yacht) {
|
||||||
Set<Mark> marksInRace = GameState.getMarks();
|
Set<Mark> marksInRace = new HashSet<>(marks);
|
||||||
for (Mark mark : marksInRace) {
|
for (Mark mark : marksInRace) {
|
||||||
if (GeoUtility.getDistance(yacht.getLocation(), mark)
|
if (GeoUtility.getDistance(yacht.getLocation(), mark)
|
||||||
<= MARK_COLLISION_DISTANCE) {
|
<= MARK_COLLISION_DISTANCE) {
|
||||||
@@ -639,15 +918,22 @@ public class GameState implements Runnable {
|
|||||||
* Collision detection which iterates through all the yachts and check if any yacht collided
|
* 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.
|
* 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.
|
* @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()) {
|
for (ServerYacht otherYacht : GameState.getYachts().values()) {
|
||||||
if (otherYacht != yacht) {
|
if (otherYacht != yacht) {
|
||||||
Double distance = GeoUtility
|
Double distance = GeoUtility
|
||||||
.getDistance(otherYacht.getLocation(), yacht.getLocation());
|
.getDistance(otherYacht.getLocation(), yacht.getLocation());
|
||||||
if (distance < YACHT_COLLISION_DISTANCE) {
|
;
|
||||||
|
if (distance < collisionDistance) {
|
||||||
return otherYacht;
|
return otherYacht;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -671,8 +957,8 @@ public class GameState implements Runnable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void notifyMessageListeners(Message message) {
|
private static void notifyMessageListeners(Message message) {
|
||||||
for (NewMessageListener mpl : markListeners) {
|
for (NewMessageListener ml : newMessageListeners) {
|
||||||
mpl.notify(message);
|
ml.notify(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -683,9 +969,35 @@ public class GameState implements Runnable {
|
|||||||
roundingMark.getSourceID()));
|
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":
|
||||||
|
try {
|
||||||
|
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":
|
||||||
|
String logMessage = "Game will now finish";
|
||||||
|
notifyMessageListeners(MessageFactory
|
||||||
|
.makeChatterMessage(chatterMessage.getMessageType(), logMessage));
|
||||||
|
endRace();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
notifyMessageListeners(chatterMessage);
|
||||||
|
}
|
||||||
|
|
||||||
public static void addMarkPassListener(NewMessageListener listener) {
|
public static void addMessageEventListener(NewMessageListener listener) {
|
||||||
markListeners.add(listener);
|
newMessageListeners.add(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void setCustomizationFlag() {
|
public static void setCustomizationFlag() {
|
||||||
@@ -699,4 +1011,51 @@ public class GameState implements Runnable {
|
|||||||
public static void resetCustomizationFlag() {
|
public static void resetCustomizationFlag() {
|
||||||
customizationFlag = false;
|
customizationFlag = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void setPlayerHasLeftFlag(Boolean flag) {
|
||||||
|
playerHasLeftFlag = flag;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Boolean getPlayerHasLeftFlag() {
|
||||||
|
return playerHasLeftFlag;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Integer getNumberOfPlayers(){
|
||||||
|
Integer numPlayers = 1;
|
||||||
|
|
||||||
|
for(Player p : getPlayers()){
|
||||||
|
if(p.getSocket().isConnected()){
|
||||||
|
numPlayers++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return numPlayers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Integer getCapacity(){
|
||||||
|
return maxPlayers;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 () {
|
||||||
|
yachts.forEach((id, yacht) -> yacht.setBoatStatus(BoatStatus.FINISHED));
|
||||||
|
currentStage = GameStages.FINISHED;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double getServerSpeedMultiplier() {
|
||||||
|
return serverSpeedMultiplier;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setTokensEnabled (boolean tokensEnabled) {
|
||||||
|
GameState.tokensEnabled = tokensEnabled;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import java.io.IOException;
|
|||||||
import java.util.Stack;
|
import java.util.Stack;
|
||||||
import java.util.Timer;
|
import java.util.Timer;
|
||||||
import java.util.TimerTask;
|
import java.util.TimerTask;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
import seng302.model.Player;
|
import seng302.model.Player;
|
||||||
import seng302.gameServer.messages.Heartbeat;
|
import seng302.gameServer.messages.Heartbeat;
|
||||||
import seng302.gameServer.messages.Message;
|
import seng302.gameServer.messages.Message;
|
||||||
@@ -14,6 +16,9 @@ import seng302.gameServer.messages.Message;
|
|||||||
* cannot be sent to a player
|
* cannot be sent to a player
|
||||||
*/
|
*/
|
||||||
public class HeartbeatThread implements Runnable {
|
public class HeartbeatThread implements Runnable {
|
||||||
|
|
||||||
|
private Logger logger = LoggerFactory.getLogger(HeartbeatThread.class);
|
||||||
|
|
||||||
private final int HEARTBEAT_PERIOD = 200;
|
private final int HEARTBEAT_PERIOD = 200;
|
||||||
private ClientConnectionDelegate delegate;
|
private ClientConnectionDelegate delegate;
|
||||||
private Integer seqNum;
|
private Integer seqNum;
|
||||||
@@ -44,12 +49,12 @@ public class HeartbeatThread implements Runnable {
|
|||||||
* The delegate is notified if a player has disconnected
|
* The delegate is notified if a player has disconnected
|
||||||
*/
|
*/
|
||||||
private void sendHeartbeatToAllPlayers(){
|
private void sendHeartbeatToAllPlayers(){
|
||||||
|
try {
|
||||||
Message heartbeat = new Heartbeat(seqNum);
|
Message heartbeat = new Heartbeat(seqNum);
|
||||||
for (Player player : GameState.getPlayers()){
|
for (Player player : GameState.getPlayers()) {
|
||||||
if (!player.getSocket().isConnected()) {
|
if (!player.getSocket().isConnected()) {
|
||||||
playerLostConnection(player);
|
playerLostConnection(player);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
player.getSocket().getOutputStream().write(heartbeat.getBuffer());
|
player.getSocket().getOutputStream().write(heartbeat.getBuffer());
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
@@ -58,6 +63,9 @@ public class HeartbeatThread implements Runnable {
|
|||||||
}
|
}
|
||||||
updateDelegate();
|
updateDelegate();
|
||||||
seqNum++;
|
seqNum++;
|
||||||
|
} catch (NullPointerException ne) {
|
||||||
|
logger.debug("Socket closed between checking for connection and sending heartbeat");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,17 +1,30 @@
|
|||||||
package seng302.gameServer;
|
package seng302.gameServer;
|
||||||
|
|
||||||
import seng302.gameServer.messages.*;
|
import java.io.IOException;
|
||||||
|
import java.net.ServerSocket;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.Timer;
|
||||||
|
import java.util.TimerTask;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import seng302.gameServer.messages.Message;
|
||||||
import seng302.model.GeoPoint;
|
import seng302.model.GeoPoint;
|
||||||
import seng302.model.Player;
|
import seng302.model.Player;
|
||||||
import seng302.model.PolarTable;
|
import seng302.model.PolarTable;
|
||||||
import seng302.model.ServerYacht;
|
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.GeoUtility;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.ServerSocket;
|
import java.net.ServerSocket;
|
||||||
import java.time.LocalDateTime;
|
import java.util.ArrayList;
|
||||||
import java.util.*;
|
import java.util.Random;
|
||||||
|
import java.util.Timer;
|
||||||
|
import java.util.TimerTask;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A class describing the overall server, which creates and collects server threads for each client
|
* A class describing the overall server, which creates and collects server threads for each client
|
||||||
@@ -19,148 +32,153 @@ import java.util.*;
|
|||||||
*/
|
*/
|
||||||
public class MainServerThread implements Runnable, ClientConnectionDelegate {
|
public class MainServerThread implements Runnable, ClientConnectionDelegate {
|
||||||
|
|
||||||
|
private Logger logger = LoggerFactory.getLogger(MainServerThread.class);
|
||||||
|
|
||||||
private static final int PORT = 4942;
|
private static final int PORT = 4942;
|
||||||
private static final Integer CLIENT_UPDATES_PER_SECOND = 60;
|
private static final Integer CLIENT_UPDATES_PER_SECOND = 60;
|
||||||
private static final int LOG_LEVEL = 1;
|
|
||||||
private static final int WARNING_TIME = 10 * -1000;
|
|
||||||
private static final int PREPATORY_TIME = 5 * -1000;
|
|
||||||
public static final int TIME_TILL_START = 10 * 1000;
|
|
||||||
|
|
||||||
private static final int MAX_WIND_SPEED = 12000;
|
|
||||||
private static final int MIN_WIND_SPEED = 8000;
|
|
||||||
|
|
||||||
public static int windSpeed = 1000;
|
|
||||||
|
|
||||||
private boolean terminated;
|
private boolean terminated;
|
||||||
|
|
||||||
private Thread thread;
|
private Thread thread;
|
||||||
|
private boolean hasStarted = false;
|
||||||
|
|
||||||
private ServerSocket serverSocket = null;
|
private ServerSocket serverSocket = null;
|
||||||
private ArrayList<ServerToClientThread> serverToClientThreads = new ArrayList<>();
|
private ArrayList<ServerToClientThread> serverToClientThreads = new ArrayList<>();
|
||||||
|
private static Integer capacity;
|
||||||
|
private RaceXMLData raceXMLData;
|
||||||
|
private RegattaXMLData regattaXMLData;
|
||||||
|
private boolean serverStarted = false;
|
||||||
|
|
||||||
|
private void startAdvertisingServer() {
|
||||||
|
Integer capacity = GameState.getCapacity();
|
||||||
|
Integer numPlayers = GameState.getNumberOfPlayers();
|
||||||
|
Integer spacesLeft = capacity - numPlayers;
|
||||||
|
|
||||||
|
// No spaces left on server
|
||||||
|
if (spacesLeft < 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start advertising server
|
||||||
|
try {
|
||||||
|
ServerAdvertiser.getInstance()
|
||||||
|
.setMapName(regattaXMLData.getCourseName())
|
||||||
|
.setCapacity(capacity)
|
||||||
|
.setNumberOfPlayers(numPlayers - 1)
|
||||||
|
.registerGame(PORT, regattaXMLData.getRegattaName());
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.warn("Could not register server");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public MainServerThread() {
|
public MainServerThread() {
|
||||||
new GameState("localhost");
|
new GameState();
|
||||||
try {
|
try {
|
||||||
serverSocket = new ServerSocket(PORT);
|
serverSocket = new ServerSocket(PORT);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
serverLog("IO error in server thread handler upon trying to make new server socket", 0);
|
logger.trace("IO error in server thread handler upon trying to make new server socket",
|
||||||
|
0);
|
||||||
}
|
}
|
||||||
PolarTable.parsePolarFile(getClass().getResourceAsStream("/config/acc_polars.csv"));
|
|
||||||
GameState.addMarkPassListener(this::broadcastMessage);
|
|
||||||
terminated = false;
|
terminated = false;
|
||||||
thread = new Thread(this, "MainServer");
|
thread = new Thread(this, "MainServer");
|
||||||
startUpdatingWind();
|
|
||||||
thread.start();
|
thread.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void startServer() {
|
||||||
|
PolarTable.parsePolarFile(getClass().getResourceAsStream("/server_config/acc_polars.csv"));
|
||||||
|
MessageFactory.updateXMLGenerator(raceXMLData, regattaXMLData);
|
||||||
|
GameState.setRace(raceXMLData);
|
||||||
|
MessageFactory.updateBoats(new ArrayList<>(GameState.getYachts().values()));
|
||||||
|
startAdvertisingServer();
|
||||||
|
GameState.addMessageEventListener(this::broadcastMessage);
|
||||||
|
sendSetupMessages();
|
||||||
|
}
|
||||||
|
|
||||||
public void run() {
|
public void run() {
|
||||||
|
|
||||||
new HeartbeatThread(this);
|
new HeartbeatThread(this);
|
||||||
new ServerListenThread(serverSocket, 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.
|
//You should handle interrupts in some way, so that the thread won't keep on forever if you exit the app.
|
||||||
while (!terminated) {
|
while (!terminated) {
|
||||||
|
if (GameState.getPlayerHasLeftFlag()) {
|
||||||
|
for (ServerToClientThread stc : serverToClientThreads) {
|
||||||
|
if (!stc.isSocketOpen()) {
|
||||||
|
GameState.getYachts().remove(stc.getSourceId());
|
||||||
|
sendSetupMessages();
|
||||||
|
try {
|
||||||
|
stc.getSocket().close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
GameState.setPlayerHasLeftFlag(false);
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
Thread.sleep(1000 / CLIENT_UPDATES_PER_SECOND);
|
Thread.sleep(1000 / CLIENT_UPDATES_PER_SECOND);
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
serverLog("Interrupted exception in Main Server Thread thread sleep", 1);
|
logger.trace("Interrupted exception in Main Server Thread thread sleep", 1);
|
||||||
}
|
|
||||||
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.
|
|
||||||
for (ServerToClientThread thread : serverToClientThreads) {
|
|
||||||
thread.sendSetupMessages();
|
|
||||||
}
|
}
|
||||||
|
if (GameState.getCurrentStage() == GameStages.LOBBYING && GameState.getCustomizationFlag()) {
|
||||||
|
MessageFactory.updateBoats(new ArrayList<>(GameState.getYachts().values()));
|
||||||
|
sendSetupMessages();
|
||||||
GameState.resetCustomizationFlag();
|
GameState.resetCustomizationFlag();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (GameState.getCurrentStage() == GameStages.PRE_RACE) {
|
if (GameState.getCurrentStage() == GameStages.PRE_RACE) {
|
||||||
updateClients();
|
sendBoatLocations();
|
||||||
}
|
}
|
||||||
|
|
||||||
//RACING
|
//RACING
|
||||||
if (GameState.getCurrentStage() == GameStages.RACING) {
|
if (GameState.getCurrentStage() == GameStages.RACING) {
|
||||||
updateClients();
|
sendBoatLocations();
|
||||||
}
|
}
|
||||||
|
|
||||||
//FINISHED
|
//FINISHED
|
||||||
else if (GameState.getCurrentStage() == GameStages.FINISHED) {
|
else if (GameState.getCurrentStage() == GameStages.FINISHED) {
|
||||||
terminate();
|
broadcastMessage(MessageFactory.getRaceStatusMessage());
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: 14/07/17 wmu16 - Send out disconnect packet to clients
|
|
||||||
try {
|
try {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
synchronized (this){
|
||||||
for (ServerToClientThread serverToClientThread : serverToClientThreads) {
|
for (ServerToClientThread serverToClientThread : serverToClientThreads) {
|
||||||
serverToClientThread.terminate();
|
serverToClientThread.terminate();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
serverSocket.close();
|
serverSocket.close();
|
||||||
return;
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
System.out.println("IO error in server thread handler upon closing socket");
|
System.out.println("IO error in server thread handler upon closing socket");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateClients() {
|
private void sendBoatLocations() {
|
||||||
for (ServerToClientThread serverToClientThread : serverToClientThreads) {
|
for (ServerYacht serverYacht : GameState.getYachts().values()) {
|
||||||
serverToClientThread.sendBoatLocationPackets();
|
broadcastMessage(MessageFactory.getBoatLocationMessage(serverYacht));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void sendSetupMessages() {
|
||||||
|
MessageFactory.updateBoats(new ArrayList<>(GameState.getYachts().values()));
|
||||||
|
broadcastMessage(MessageFactory.getRaceXML());
|
||||||
|
broadcastMessage(MessageFactory.getRegattaXML());
|
||||||
|
broadcastMessage(MessageFactory.getBoatXML());
|
||||||
|
}
|
||||||
|
|
||||||
private void broadcastMessage(Message message) {
|
private void broadcastMessage(Message message) {
|
||||||
for (ServerToClientThread serverToClientThread : serverToClientThreads) {
|
for (ServerToClientThread serverToClientThread : serverToClientThreads) {
|
||||||
serverToClientThread.sendMessage(message);
|
serverToClientThread.sendMessage(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void startUpdatingWind(){
|
|
||||||
Timer timer = new Timer();
|
|
||||||
timer.schedule(new TimerTask() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
updateWind();
|
|
||||||
}
|
|
||||||
}, 0, 500);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static void serverLog(String message, int logLevel) {
|
|
||||||
if (logLevel <= LOG_LEVEL) {
|
|
||||||
System.out.println(
|
|
||||||
"[SERVER " + LocalDateTime.now().toLocalTime().toString() + "] " + message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A client has tried to connect to the server
|
* A client has tried to connect to the server
|
||||||
*
|
*
|
||||||
@@ -168,13 +186,45 @@ public class MainServerThread implements Runnable, ClientConnectionDelegate {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void clientConnected(ServerToClientThread serverToClientThread) {
|
public void clientConnected(ServerToClientThread serverToClientThread) {
|
||||||
serverLog("Player Connected From " + serverToClientThread.getThread().getName(), 0);
|
logger.debug("Player Connected From " + serverToClientThread.getThread().getName(), 0);
|
||||||
serverToClientThreads.add(serverToClientThread);
|
if (serverToClientThreads.size() == 0) { //Sets first client as host.
|
||||||
serverToClientThread.addConnectionListener(() -> {
|
serverToClientThread.setAsHost();
|
||||||
for (ServerToClientThread thread : serverToClientThreads) {
|
serverToClientThread.raceXMLProperty().addListener((obs, oldVal, race) -> {
|
||||||
thread.sendSetupMessages();
|
if (race != null) {
|
||||||
|
raceXMLData = race;
|
||||||
|
}
|
||||||
|
if (regattaXMLData != null) {
|
||||||
|
startServer();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
serverToClientThread.regattaXMLProperty().addListener((obs, oldVal, regatta) -> {
|
||||||
|
if (regatta != null) {
|
||||||
|
regattaXMLData = regatta;
|
||||||
|
}
|
||||||
|
if (raceXMLData != null) {
|
||||||
|
startServer();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
//serverToClientThread.addConnectionListener(this::sendSetupMessages);
|
||||||
|
}
|
||||||
|
serverToClientThreads.add(serverToClientThread);
|
||||||
|
|
||||||
|
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);
|
serverToClientThread.addDisconnectListener(this::clientDisconnected);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,12 +235,7 @@ public class MainServerThread implements Runnable, ClientConnectionDelegate {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void clientDisconnected(Player player) {
|
public void clientDisconnected(Player player) {
|
||||||
// try {
|
logger.debug("Player " + player.getYacht().getSourceId() + "'s socket disconnected", 0);
|
||||||
// player.getSocket().close();
|
|
||||||
// } catch (Exception e) {
|
|
||||||
// serverLog("Cannot disconnect the socket for the disconnected player.", 0);
|
|
||||||
// }
|
|
||||||
serverLog("Player " + player.getYacht().getSourceId() + "'s socket disconnected", 0);
|
|
||||||
GameState.removeYacht(player.getYacht().getSourceId());
|
GameState.removeYacht(player.getYacht().getSourceId());
|
||||||
GameState.removePlayer(player);
|
GameState.removePlayer(player);
|
||||||
ServerToClientThread closedConnection = null;
|
ServerToClientThread closedConnection = null;
|
||||||
@@ -201,70 +246,43 @@ public class MainServerThread implements Runnable, ClientConnectionDelegate {
|
|||||||
serverToClientThread.sendSetupMessages();
|
serverToClientThread.sendSetupMessages();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
serverToClientThreads.remove(closedConnection);
|
serverToClientThreads.remove(closedConnection);
|
||||||
|
|
||||||
|
try {
|
||||||
|
ServerAdvertiser.getInstance().setNumberOfPlayers(GameState.getNumberOfPlayers());
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.warn("Couldn't update advertisement");
|
||||||
|
}
|
||||||
|
|
||||||
closedConnection.terminate();
|
closedConnection.terminate();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void startGame() {
|
public void startGame() {
|
||||||
|
try {
|
||||||
|
ServerAdvertiser.getInstance().unregister();
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.warn("Error unregistering server");
|
||||||
|
}
|
||||||
|
|
||||||
initialiseBoatPositions();
|
initialiseBoatPositions();
|
||||||
Timer t = new Timer();
|
Timer t = new Timer();
|
||||||
|
|
||||||
t.schedule(new TimerTask() {
|
t.schedule(new TimerTask() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
broadcastMessage(makeRaceStatusMessage());
|
broadcastMessage(MessageFactory.getRaceStatusMessage());
|
||||||
if (GameState.getCurrentStage() == GameStages.PRE_RACE || GameState.getCurrentStage() == GameStages.LOBBYING) {
|
if (GameState.getCurrentStage() == GameStages.PRE_RACE
|
||||||
broadcastMessage(makeRaceStartMessage());
|
|| GameState.getCurrentStage() == GameStages.LOBBYING) {
|
||||||
|
broadcastMessage(MessageFactory.getRaceStartStatusMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, 0, 500);
|
}, 0, 500);
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private RaceStartStatusMessage makeRaceStartMessage() {
|
// if (GameState.getCurrentStage() == GameStages.LOBBYING) {
|
||||||
Long raceStartTime = GameState.getStartTime();
|
// sendSetupMessages();
|
||||||
|
// }
|
||||||
return new RaceStartStatusMessage(1, raceStartTime ,
|
|
||||||
1, RaceStartNotificationType.SET_RACE_START_TIME);
|
|
||||||
}
|
|
||||||
|
|
||||||
private RaceStatusMessage makeRaceStatusMessage() {
|
|
||||||
// variables taken from GameServerThread
|
|
||||||
|
|
||||||
List<BoatSubMessage> boatSubMessages = new ArrayList<>();
|
|
||||||
RaceStatus raceStatus;
|
|
||||||
|
|
||||||
for (Player player : GameState.getPlayers()) {
|
|
||||||
ServerYacht y = player.getYacht();
|
|
||||||
BoatSubMessage m = new BoatSubMessage(y.getSourceId(), y.getBoatStatus(),
|
|
||||||
y.getLegNumber(),
|
|
||||||
0, 0, 1234L,
|
|
||||||
1234L);
|
|
||||||
boatSubMessages.add(m);
|
|
||||||
}
|
|
||||||
|
|
||||||
long timeTillStart = System.currentTimeMillis() - GameState.getStartTime();
|
|
||||||
|
|
||||||
if (GameState.getCurrentStage() == GameStages.LOBBYING) {
|
|
||||||
raceStatus = RaceStatus.PRESTART;
|
|
||||||
} else if (GameState.getCurrentStage() == GameStages.PRE_RACE) {
|
|
||||||
raceStatus = RaceStatus.PRESTART;
|
|
||||||
|
|
||||||
if (timeTillStart > WARNING_TIME) {
|
|
||||||
raceStatus = RaceStatus.WARNING;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (timeTillStart > PREPATORY_TIME) {
|
|
||||||
raceStatus = RaceStatus.PREPARATORY;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
raceStatus = RaceStatus.STARTED;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new RaceStatusMessage(1, raceStatus, GameState.getStartTime(),
|
|
||||||
GameState.getWindDirection(),
|
|
||||||
GameState.getWindSpeedMMS().longValue(), GameState.getPlayers().size(),
|
|
||||||
RaceType.MATCH_RACE, 1, boatSubMessages);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void terminate() {
|
public void terminate() {
|
||||||
@@ -275,43 +293,170 @@ public class MainServerThread implements Runnable, ClientConnectionDelegate {
|
|||||||
* Initialise boats to specific spaced out geopoints behind starting line.
|
* Initialise boats to specific spaced out geopoints behind starting line.
|
||||||
*/
|
*/
|
||||||
private void initialiseBoatPositions() {
|
private void initialiseBoatPositions() {
|
||||||
// Getting the start line compound marks
|
// CompoundMark cm = GameState.getMarkOrder().getMarkOrder().get(0);
|
||||||
// if (gameClient== null) {
|
// GeoPoint startMark1 = cm.getSubMark(1);
|
||||||
// return;
|
// GeoPoint startMark2 = cm.getSubMark(2);
|
||||||
|
//
|
||||||
|
// // Calculating midpoint
|
||||||
|
// Double perpendicularAngle = GeoUtility.getBearing(startMark1, startMark2);
|
||||||
|
// Double length = GeoUtility.getDistance(startMark1, startMark2);
|
||||||
|
// GeoPoint midpoint = GeoUtility.getGeoCoordinate(startMark1, perpendicularAngle, length / 2);
|
||||||
|
//
|
||||||
|
// // Setting each boats position side by side
|
||||||
|
// final double SEPARATION = 50.0; // distance apart in meters
|
||||||
|
//
|
||||||
|
// int boatIndex = 0;
|
||||||
|
// for (ServerYacht yacht : GameState.getYachts().values()) {
|
||||||
|
// int distanceApart = boatIndex / 2;
|
||||||
|
//
|
||||||
|
// if (boatIndex % 2 == 1 && boatIndex != 0) {
|
||||||
|
// distanceApart++;
|
||||||
|
// distanceApart *= -1;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// GeoPoint spawnMark = GeoUtility
|
||||||
|
// .getGeoCoordinate(midpoint, perpendicularAngle, distanceApart * SEPARATION);
|
||||||
|
//
|
||||||
|
// if (yacht.getHeading() < perpendicularAngle) {
|
||||||
|
// spawnMark = GeoUtility
|
||||||
|
// .getGeoCoordinate(spawnMark, perpendicularAngle + 90, SEPARATION);
|
||||||
|
// } else {
|
||||||
|
// spawnMark = GeoUtility
|
||||||
|
// .getGeoCoordinate(spawnMark, perpendicularAngle + 270, SEPARATION);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// yacht.setLocation(spawnMark);
|
||||||
|
// boatIndex++;
|
||||||
// }
|
// }
|
||||||
CompoundMark cm = GameState.getMarkOrder().getMarkOrder().get(0);
|
|
||||||
GeoPoint startMark1 = cm.getSubMark(1);
|
|
||||||
GeoPoint startMark2 = cm.getSubMark(2);
|
|
||||||
|
|
||||||
// Calculating midpoint
|
// final double SEPARATION = 50.0; // distance apart in meters
|
||||||
Double perpendicularAngle = GeoUtility.getBearing(startMark1, startMark2);
|
//
|
||||||
Double length = GeoUtility.getDistance(startMark1, startMark2);
|
// //Reverse of the angle from start to first mark
|
||||||
GeoPoint midpoint = GeoUtility.getGeoCoordinate(startMark1, perpendicularAngle, length / 2);
|
// double angleToFirstMark = 360 - GeoUtility.getBearing(
|
||||||
|
// GameState.getMarkOrder().getMarkOrder().get(0).getMidPoint(),
|
||||||
|
// GameState.getMarkOrder().getMarkOrder().get(1).getMidPoint()
|
||||||
|
// );
|
||||||
|
//
|
||||||
|
// //Length of start line
|
||||||
|
// double startLineLength = GeoUtility.getDistance(
|
||||||
|
// GameState.getMarkOrder().getMarkOrder().get(0).getSubMark(1),
|
||||||
|
// GameState.getMarkOrder().getMarkOrder().get(0).getSubMark(2)
|
||||||
|
// );
|
||||||
|
//
|
||||||
|
// //Angle of start line
|
||||||
|
// double startMarkToMarkAngle = GeoUtility.getBearing(
|
||||||
|
// GameState.getMarkOrder().getMarkOrder().get(0).getSubMark(1),
|
||||||
|
// GameState.getMarkOrder().getMarkOrder().get(0).getSubMark(2)
|
||||||
|
// );
|
||||||
|
//
|
||||||
|
// //How many yachts can fit along the start line
|
||||||
|
// int spacesAlongLine = (int) Math.round(startLineLength / SEPARATION);
|
||||||
|
// //The free space left by the boats.
|
||||||
|
// double buffer = (startLineLength % SEPARATION) / 2;
|
||||||
|
//
|
||||||
|
// //Randomize starting order.
|
||||||
|
// List<ServerYacht> serverYachtList = new ArrayList<>(GameState.getYachts().values());
|
||||||
|
// Collections.shuffle(serverYachtList);
|
||||||
|
//
|
||||||
|
// //set the starting point away from start line.
|
||||||
|
// GeoPoint startingPoint = GeoUtility.getGeoCoordinate(
|
||||||
|
// GameState.getMarkOrder().getMarkOrder().get(0).getSubMark(1),
|
||||||
|
// angleToFirstMark, SEPARATION
|
||||||
|
// );
|
||||||
|
//
|
||||||
|
// //Move it along the start line
|
||||||
|
// startingPoint = GeoUtility.getGeoCoordinate(
|
||||||
|
// startingPoint, startMarkToMarkAngle, buffer
|
||||||
|
// );
|
||||||
|
//
|
||||||
|
// int yachtCount = 0;
|
||||||
|
// int repeats = 0;
|
||||||
|
//
|
||||||
|
// GeoPoint yachtLocation;
|
||||||
|
//
|
||||||
|
// for (ServerYacht serverYacht : serverYachtList) {
|
||||||
|
//
|
||||||
|
// //Move away from start line
|
||||||
|
// yachtLocation = GeoUtility.getGeoCoordinate(
|
||||||
|
// startingPoint, angleToFirstMark,repeats * SEPARATION
|
||||||
|
// );
|
||||||
|
// //Move along start line
|
||||||
|
// yachtLocation = GeoUtility.getGeoCoordinate(
|
||||||
|
// yachtLocation, startMarkToMarkAngle, yachtCount * SEPARATION
|
||||||
|
// );
|
||||||
|
// serverYacht.setLocation(yachtLocation);
|
||||||
|
// serverYacht.setHeading(GeoUtility.getBearing(
|
||||||
|
// yachtLocation, GameState.getMarkOrder().getMarkOrder().get(1).getMidPoint()
|
||||||
|
// ));
|
||||||
|
// //Set location for next yacht
|
||||||
|
// yachtCount++;
|
||||||
|
// if (yachtCount > spacesAlongLine) {
|
||||||
|
// yachtCount = 0;
|
||||||
|
// repeats++;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
// Setting each boats position side by side
|
final double DISTANCE_TO_START = 75d;
|
||||||
double DISTANCE_FACTOR = 50.0; // distance apart in meters
|
final double YACHT_SEPARATION = 20d;
|
||||||
int boatIndex = 0;
|
|
||||||
for (ServerYacht yacht : GameState.getYachts().values()) {
|
|
||||||
int distanceApart = boatIndex / 2;
|
|
||||||
|
|
||||||
if (boatIndex % 2 == 1 && boatIndex != 0) {
|
//Length of start line
|
||||||
distanceApart++;
|
double startLineLength = GeoUtility.getDistance(
|
||||||
distanceApart *= -1;
|
GameState.getMarkOrder().getMarkOrder().get(0).getSubMark(1),
|
||||||
|
GameState.getMarkOrder().getMarkOrder().get(0).getSubMark(2)
|
||||||
|
);
|
||||||
|
|
||||||
|
//How many yachts can fit along the start line
|
||||||
|
int spacesAlongLine = (int) Math.round(startLineLength / YACHT_SEPARATION);
|
||||||
|
|
||||||
|
//Angle of start line
|
||||||
|
double startMarkToMarkAngle = GeoUtility.getBearing(
|
||||||
|
GameState.getMarkOrder().getMarkOrder().get(0).getSubMark(1),
|
||||||
|
GameState.getMarkOrder().getMarkOrder().get(0).getSubMark(2)
|
||||||
|
);
|
||||||
|
|
||||||
|
//angle from first mark to the start
|
||||||
|
double angleToStart = GeoUtility.getBearing(
|
||||||
|
GameState.getMarkOrder().getMarkOrder().get(1).getMidPoint(),
|
||||||
|
GameState.getMarkOrder().getMarkOrder().get(0).getMidPoint()
|
||||||
|
);
|
||||||
|
|
||||||
|
double angleFromStart = GeoUtility.getBearing(
|
||||||
|
GameState.getMarkOrder().getMarkOrder().get(0).getMidPoint(),
|
||||||
|
GameState.getMarkOrder().getMarkOrder().get(1).getMidPoint()
|
||||||
|
);
|
||||||
|
|
||||||
|
GeoPoint startingPoint = GeoUtility.getGeoCoordinate(
|
||||||
|
GameState.getMarkOrder().getMarkOrder().get(0).getMidPoint(),
|
||||||
|
angleToStart, DISTANCE_TO_START
|
||||||
|
);
|
||||||
|
|
||||||
|
List<ServerYacht> randomisedYachts = new ArrayList<>(GameState.getYachts().values());
|
||||||
|
Collections.shuffle(randomisedYachts);
|
||||||
|
while (randomisedYachts.size() > 0) {
|
||||||
|
|
||||||
|
int numYachtsInLine = spacesAlongLine > randomisedYachts.size() ? randomisedYachts.size() : spacesAlongLine;
|
||||||
|
double yachtSpace = numYachtsInLine * YACHT_SEPARATION / 2;
|
||||||
|
|
||||||
|
GeoPoint firstYachtPoint = GeoUtility.getGeoCoordinate(
|
||||||
|
startingPoint, startMarkToMarkAngle + 180, yachtSpace
|
||||||
|
);
|
||||||
|
|
||||||
|
for (int i=0; i<numYachtsInLine; i++){
|
||||||
|
randomisedYachts.get(0).setHeading(angleFromStart);
|
||||||
|
randomisedYachts.get(0).setLocation(firstYachtPoint);
|
||||||
|
firstYachtPoint = GeoUtility.getGeoCoordinate(
|
||||||
|
firstYachtPoint, startMarkToMarkAngle, yachtSpace
|
||||||
|
);
|
||||||
|
randomisedYachts.remove(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
GeoPoint spawnMark = GeoUtility
|
startingPoint = GeoUtility.getGeoCoordinate(
|
||||||
.getGeoCoordinate(midpoint, perpendicularAngle, distanceApart * DISTANCE_FACTOR);
|
startingPoint, angleToStart, DISTANCE_TO_START
|
||||||
|
);
|
||||||
if (yacht.getHeading() < perpendicularAngle) {
|
}
|
||||||
spawnMark = GeoUtility
|
|
||||||
.getGeoCoordinate(spawnMark, perpendicularAngle + 90, DISTANCE_FACTOR);
|
|
||||||
} else {
|
|
||||||
spawnMark = GeoUtility
|
|
||||||
.getGeoCoordinate(spawnMark, perpendicularAngle + 270, DISTANCE_FACTOR);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
yacht.setLocation(spawnMark);
|
public boolean hasStarted() {
|
||||||
boatIndex++;
|
return hasStarted;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,232 @@
|
|||||||
|
package seng302.gameServer;
|
||||||
|
|
||||||
|
import seng302.gameServer.messages.*;
|
||||||
|
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;
|
||||||
|
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;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Class for interfacing between the data we have in the GameState to the messages we need to send
|
||||||
|
* through the MainServerThread.
|
||||||
|
*
|
||||||
|
* WARNING DO NOT USE THIS CLASS IF GAMESTATE HAS NOT BEEN INSTANTIATED. (Main Server has not started)
|
||||||
|
* // TODO: 29/08/17 wmu16 - Make GameState non static to fix this ¯\_(ツ)_/¯
|
||||||
|
* Created by wmu16 on 29/08/17.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
Ideally this class would be created with an instance of the GameState (I tried implementing this for
|
||||||
|
a bit) but it was too difficult to properly make GameState non static without doing some proper
|
||||||
|
re working. To do later.
|
||||||
|
*/
|
||||||
|
public class MessageFactory {
|
||||||
|
|
||||||
|
private static XMLGenerator xmlGenerator = new XMLGenerator();
|
||||||
|
private static XMLMessage race;
|
||||||
|
private static XMLMessage regatta;
|
||||||
|
private static XMLMessage boats;
|
||||||
|
|
||||||
|
public static void updateXMLGenerator(RaceXMLData race, RegattaXMLData regatta) {
|
||||||
|
xmlGenerator.setRegattaTemplate(
|
||||||
|
new RegattaXMLTemplate(
|
||||||
|
regatta.getRegattaName(),
|
||||||
|
regatta.getCourseName(),
|
||||||
|
regatta.getCentralLat(),
|
||||||
|
regatta.getCentralLng()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
xmlGenerator.setRaceTemplate(
|
||||||
|
new RaceXMLTemplate(
|
||||||
|
new ArrayList<>(),
|
||||||
|
new ArrayList<>(),
|
||||||
|
race.getMarkSequence(),
|
||||||
|
race.getCourseLimit(),
|
||||||
|
new ArrayList<>(race.getCompoundMarks().values()),
|
||||||
|
GameState.getCapacity(), true
|
||||||
|
)
|
||||||
|
);
|
||||||
|
String xmlStr = xmlGenerator.getRaceAsXml();
|
||||||
|
MessageFactory.race = new XMLMessage(xmlStr, XMLMessageSubType.RACE, xmlStr.length());
|
||||||
|
xmlStr = xmlGenerator.getRegattaAsXml();
|
||||||
|
MessageFactory.regatta = new XMLMessage(xmlStr, XMLMessageSubType.REGATTA, xmlStr.length());
|
||||||
|
xmlStr = xmlGenerator.getBoatsAsXml();
|
||||||
|
MessageFactory.boats = new XMLMessage(xmlStr, XMLMessageSubType.BOAT, xmlStr.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void updateBoats(List<ServerYacht> yachts) {
|
||||||
|
// for (ServerYacht serverYacht : yachts) {
|
||||||
|
// System.out.println(serverYacht);
|
||||||
|
// }
|
||||||
|
xmlGenerator.getRace().setBoats(yachts);
|
||||||
|
String xmlStr = xmlGenerator.getBoatsAsXml();
|
||||||
|
MessageFactory.boats = new XMLMessage(xmlStr, XMLMessageSubType.BOAT, xmlStr.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void updateTokens(List<Token> tokens) {
|
||||||
|
xmlGenerator.getRace().setTokens(tokens);
|
||||||
|
String xmlStr = xmlGenerator.getRaceAsXml();
|
||||||
|
MessageFactory.race = new XMLMessage(xmlStr, XMLMessageSubType.RACE, xmlStr.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static RaceStartStatusMessage getRaceStartStatusMessage() {
|
||||||
|
return new RaceStartStatusMessage(
|
||||||
|
1,
|
||||||
|
GameState.getStartTime(),
|
||||||
|
1,
|
||||||
|
RaceStartNotificationType.SET_RACE_START_TIME);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RaceStatusMessage getRaceStatusMessage() {
|
||||||
|
// variables taken from GameServerThread
|
||||||
|
|
||||||
|
List<BoatSubMessage> boatSubMessages = new ArrayList<>();
|
||||||
|
RaceStatus raceStatus;
|
||||||
|
|
||||||
|
for (Player player : GameState.getPlayers()) {
|
||||||
|
ServerYacht y = player.getYacht();
|
||||||
|
BoatSubMessage m = new BoatSubMessage(y.getSourceId(), y.getBoatStatus(),
|
||||||
|
y.getLegNumber(),
|
||||||
|
0, 0, 1234L,
|
||||||
|
1234L);
|
||||||
|
boatSubMessages.add(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
long timeTillStart = System.currentTimeMillis() - GameState.getStartTime();
|
||||||
|
|
||||||
|
if (GameState.getCurrentStage() == GameStages.LOBBYING) {
|
||||||
|
raceStatus = RaceStatus.PRESTART;
|
||||||
|
} else if (GameState.getCurrentStage() == GameStages.PRE_RACE) {
|
||||||
|
raceStatus = RaceStatus.PRESTART;
|
||||||
|
|
||||||
|
if (timeTillStart > GameState.WARNING_TIME) {
|
||||||
|
raceStatus = RaceStatus.WARNING;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timeTillStart > GameState.PREPATORY_TIME) {
|
||||||
|
raceStatus = RaceStatus.PREPARATORY;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
raceStatus = RaceStatus.STARTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new RaceStatusMessage(1, raceStatus, GameState.getStartTime(),
|
||||||
|
GameState.getWindDirection(),
|
||||||
|
GameState.getWindSpeedMMS().longValue(), GameState.getPlayers().size(),
|
||||||
|
RaceType.MATCH_RACE, 1, boatSubMessages);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BoatLocationMessage getBoatLocationMessage(ServerYacht yacht) {
|
||||||
|
return new BoatLocationMessage(
|
||||||
|
yacht.getSourceId(),
|
||||||
|
0, // TODO: 29/08/17 wmu16 - Work out what to do with seqNo. Currently not used
|
||||||
|
yacht.getLocation().getLat(),
|
||||||
|
yacht.getLocation().getLng(),
|
||||||
|
yacht.getHeading(),
|
||||||
|
yacht.getCurrentVelocity().longValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static XMLMessage getRaceXML() {
|
||||||
|
return race;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static XMLMessage getRegattaXML() {
|
||||||
|
return regatta;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static XMLMessage getBoatXML() {
|
||||||
|
return boats;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,185 @@
|
|||||||
|
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;
|
||||||
|
import java.net.Inet4Address;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.NetworkInterface;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
import java.util.Hashtable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Advertises the game server on the local network
|
||||||
|
*/
|
||||||
|
public class ServerAdvertiser {
|
||||||
|
/*
|
||||||
|
Our service name & protocol
|
||||||
|
|
||||||
|
This must be in the format _Service._Proto.Name as per http://www.ietf.org/rfc/rfc2782.txt
|
||||||
|
Where Service is unique on the network, and protocol is usually _tcp.
|
||||||
|
|
||||||
|
The pseudo-domain 'local.' must end in a full-stop. This is used to indicate that
|
||||||
|
the lookup should be performed using an IP multicast query on the local IP network.
|
||||||
|
|
||||||
|
Read this before changing any of the following values
|
||||||
|
https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/NetServices/Articles/domainnames.html#//apple_ref/doc/uid/TP40002460-SW1
|
||||||
|
*/
|
||||||
|
private static String SERVICE = "_partyatsea";
|
||||||
|
private static String PROTOCOL = "_tcp";
|
||||||
|
public static String SERVICE_TYPE = SERVICE + "." + PROTOCOL + ".local.";
|
||||||
|
|
||||||
|
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");
|
||||||
|
props.put("capacity", "0");
|
||||||
|
props.put("players", "0");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an instance of the ServerAdvertiser, create an instance if there isn't already one
|
||||||
|
* @return A ServerAdvertiser Instance
|
||||||
|
* @throws IOException If there was an exception creating the instance
|
||||||
|
*/
|
||||||
|
public static ServerAdvertiser getInstance() throws IOException {
|
||||||
|
if (instance == null){
|
||||||
|
instance = new ServerAdvertiser();
|
||||||
|
}
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the map name and broadcast an update on the network
|
||||||
|
* @param mapName The new map name
|
||||||
|
* @return The current ServerAdvertiser instance
|
||||||
|
*/
|
||||||
|
public ServerAdvertiser setMapName(String mapName){
|
||||||
|
props.replace("map", mapName);
|
||||||
|
|
||||||
|
if (serviceInfo != null){
|
||||||
|
serviceInfo.setText(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the number of players on the server and broadcast an update on the network
|
||||||
|
* @param numPlayers The number of players on the server
|
||||||
|
* @return The current ServerAdvertiser instance
|
||||||
|
*/
|
||||||
|
public ServerAdvertiser setNumberOfPlayers(Integer numPlayers){
|
||||||
|
props.replace("players", numPlayers.toString());
|
||||||
|
|
||||||
|
if (serviceInfo != null){
|
||||||
|
serviceInfo.setText(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the max capacity of the server and broadcast an update on the network
|
||||||
|
* @param capacity The maximum capacity of the server
|
||||||
|
* @return The current ServerAdvertiser instance
|
||||||
|
*/
|
||||||
|
public ServerAdvertiser setCapacity(Integer capacity){
|
||||||
|
props.replace("capacity", capacity.toString());
|
||||||
|
|
||||||
|
if (serviceInfo != null){
|
||||||
|
serviceInfo.setText(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register this service on the network
|
||||||
|
*
|
||||||
|
* Note: other parameters (map name/spaces left etc) are set after the
|
||||||
|
* service has been registered
|
||||||
|
* @param portNo The servers port number
|
||||||
|
* @param serverName The servers name
|
||||||
|
*/
|
||||||
|
public void registerGame(Integer portNo, String serverName) {
|
||||||
|
|
||||||
|
serviceInfo = ServiceInfo.create(SERVICE_TYPE, serverName, portNo, 0, 0, props);
|
||||||
|
|
||||||
|
new java.util.Timer().schedule(
|
||||||
|
new java.util.TimerTask() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
jmdnsInstance.registerService(serviceInfo);
|
||||||
|
} catch (IOException e) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unregister the service
|
||||||
|
*/
|
||||||
|
public void unregister(){
|
||||||
|
if (serviceInfo != null)
|
||||||
|
jmdnsInstance.unregisterService(serviceInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the local host ip address.
|
||||||
|
*
|
||||||
|
* @return the localhost ip address
|
||||||
|
*/
|
||||||
|
public static String getLocalHostIp() {
|
||||||
|
String ipAddress = null;
|
||||||
|
try {
|
||||||
|
Enumeration<NetworkInterface> e = NetworkInterface.getNetworkInterfaces();
|
||||||
|
while (e.hasMoreElements()) {
|
||||||
|
NetworkInterface ni = e.nextElement();
|
||||||
|
if (ni.isLoopback())
|
||||||
|
continue;
|
||||||
|
if(ni.isPointToPoint())
|
||||||
|
continue;
|
||||||
|
if(ni.isVirtual())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
Enumeration<InetAddress> addresses = ni.getInetAddresses();
|
||||||
|
while(addresses.hasMoreElements()) {
|
||||||
|
InetAddress address = addresses.nextElement();
|
||||||
|
if(address instanceof Inet4Address) { // skip all ipv6
|
||||||
|
ipAddress = address.getHostAddress();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
if (ipAddress == null) {
|
||||||
|
System.out.println("[HOST] Cannot obtain local host ip address.");
|
||||||
|
}
|
||||||
|
return ipAddress;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
package seng302.gameServer;
|
||||||
|
|
||||||
|
public class ServerDescription {
|
||||||
|
private Integer capacity;
|
||||||
|
private String address;
|
||||||
|
private Integer portNum;
|
||||||
|
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;
|
||||||
|
this.mapName = mapName;
|
||||||
|
this.numPlayers = numPlayers;
|
||||||
|
this.address = address;
|
||||||
|
this.portNum = portNum;
|
||||||
|
this.capacity = capacity;
|
||||||
|
lastUpdated = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return serverName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMapName() {
|
||||||
|
return mapName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer portNumber() {
|
||||||
|
return portNum;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAddress(){
|
||||||
|
return address;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getNumPlayers() {
|
||||||
|
return numPlayers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getCapacity(){
|
||||||
|
return capacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (obj == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!ServerDescription.class.isAssignableFrom(obj.getClass())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final ServerDescription other = (ServerDescription) obj;
|
||||||
|
|
||||||
|
if (!this.getAddress().equals(other.getAddress()) ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.portNumber().equals(other.portNumber())){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.getMapName().equals(other.getMapName())){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.getName().equals(other.getName())){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.getCapacity().equals(other.getCapacity())){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ package seng302.gameServer;
|
|||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import seng302.gameServer.messages.BoatAction;
|
import seng302.gameServer.messages.BoatAction;
|
||||||
|
import seng302.gameServer.messages.ChatterMessage;
|
||||||
import seng302.gameServer.messages.ClientType;
|
import seng302.gameServer.messages.ClientType;
|
||||||
import seng302.gameServer.messages.CustomizeRequestType;
|
import seng302.gameServer.messages.CustomizeRequestType;
|
||||||
import seng302.gameServer.messages.Message;
|
import seng302.gameServer.messages.Message;
|
||||||
@@ -28,5 +29,18 @@ public class ServerPacketParser {
|
|||||||
long type = Message.bytesToLong(Arrays.copyOfRange(payload, 4, 5));
|
long type = Message.bytesToLong(Arrays.copyOfRange(payload, 4, 5));
|
||||||
return CustomizeRequestType.getRequestType((int) type);
|
return CustomizeRequestType.getRequestType((int) type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static ChatterMessage extractChatterText(byte[] payload) {
|
||||||
|
return new ChatterMessage(
|
||||||
|
payload[1], new String(Arrays.copyOfRange(payload, 3, payload.length))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ChatterMessage extractChatterText(StreamPacket packet) {
|
||||||
|
byte[] payload = packet.getPayload();
|
||||||
|
return new ChatterMessage(
|
||||||
|
payload[1], new String(Arrays.copyOfRange(payload, 3, payload.length))
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,66 +1,47 @@
|
|||||||
package seng302.gameServer;
|
package seng302.gameServer;
|
||||||
|
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
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;
|
||||||
|
import seng302.gameServer.messages.CustomizeRequestType;
|
||||||
|
import seng302.gameServer.messages.Message;
|
||||||
|
import seng302.gameServer.messages.RegistrationResponseMessage;
|
||||||
|
import seng302.gameServer.messages.RegistrationResponseStatus;
|
||||||
|
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.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.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
import java.net.SocketException;
|
import java.net.SocketException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Observable;
|
|
||||||
import java.util.Observer;
|
|
||||||
import java.util.concurrent.ThreadLocalRandom;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import java.util.zip.CRC32;
|
import java.util.zip.CRC32;
|
||||||
import java.util.zip.Checksum;
|
import java.util.zip.Checksum;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import seng302.gameServer.messages.BoatAction;
|
|
||||||
import seng302.gameServer.messages.BoatLocationMessage;
|
|
||||||
import seng302.gameServer.messages.ClientType;
|
|
||||||
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.gameServer.messages.YachtEventCodeMessage;
|
|
||||||
import seng302.gameServer.messages.YachtEventCodeMessage;
|
|
||||||
import seng302.model.Player;
|
|
||||||
import seng302.model.ServerYacht;
|
|
||||||
import seng302.model.stream.packets.PacketType;
|
|
||||||
import seng302.model.stream.packets.StreamPacket;
|
|
||||||
import seng302.model.stream.xml.generator.Race;
|
|
||||||
import seng302.model.stream.xml.generator.Regatta;
|
|
||||||
import seng302.utilities.XMLGenerator;
|
|
||||||
import seng302.gameServer.messages.BoatAction;
|
|
||||||
import seng302.gameServer.messages.BoatLocationMessage;
|
|
||||||
import seng302.gameServer.messages.ClientType;
|
|
||||||
import seng302.gameServer.messages.Message;
|
|
||||||
import seng302.gameServer.messages.RegistrationResponseMessage;
|
|
||||||
import seng302.gameServer.messages.RegistrationResponseStatus;
|
|
||||||
import seng302.gameServer.messages.XMLMessage;
|
|
||||||
import seng302.gameServer.messages.XMLMessageSubType;
|
|
||||||
import seng302.gameServer.messages.YachtEventCodeMessage;
|
|
||||||
import seng302.model.Player;
|
|
||||||
import seng302.model.ServerYacht;
|
|
||||||
import seng302.model.stream.packets.PacketType;
|
|
||||||
import seng302.model.stream.packets.StreamPacket;
|
|
||||||
import seng302.model.stream.xml.generator.Race;
|
|
||||||
import seng302.model.stream.xml.generator.Regatta;
|
|
||||||
import seng302.utilities.XMLGenerator;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A class describing a single connection to a Client for the purposes of sending and receiving on
|
* A class describing a single connection to a Client for the purposes of sending and receiving on
|
||||||
* its own thread. All server threads created and owned by the server thread handler which can
|
* its own thread. All server threads created and owned by the server thread handler which can
|
||||||
* trigger client updates on its threads Created by wmu16 on 13/07/17.
|
* trigger client updates on its threads Created by wmu16 on 13/07/17.
|
||||||
*/
|
*/
|
||||||
public class ServerToClientThread implements Runnable, Observer {
|
public class ServerToClientThread implements Runnable {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called to notify listeners when this thread receives a connection correctly.
|
* Called to notify listeners when this thread receives a connection correctly.
|
||||||
@@ -91,15 +72,18 @@ public class ServerToClientThread implements Runnable, Observer {
|
|||||||
|
|
||||||
private ClientType clientType;
|
private ClientType clientType;
|
||||||
private Boolean isRegistered = false;
|
private Boolean isRegistered = false;
|
||||||
|
private Boolean isHost = false;
|
||||||
|
|
||||||
private XMLGenerator xml;
|
private XMLGenerator xmlGenerator;
|
||||||
|
|
||||||
private List<ConnectionListener> connectionListeners = new ArrayList<>();
|
private List<ConnectionListener> connectionListeners = new ArrayList<>();
|
||||||
private DisconnectListener disconnectListener;
|
private DisconnectListener disconnectListener;
|
||||||
|
|
||||||
private ServerYacht yacht;
|
|
||||||
private Player player;
|
private Player player;
|
||||||
|
|
||||||
|
private SimpleObjectProperty<RaceXMLData> raceXMLProperty = new SimpleObjectProperty<>();
|
||||||
|
private SimpleObjectProperty<RegattaXMLData> regattaXMLProperty = new SimpleObjectProperty<>();
|
||||||
|
|
||||||
public ServerToClientThread(Socket socket) {
|
public ServerToClientThread(Socket socket) {
|
||||||
this.socket = socket;
|
this.socket = socket;
|
||||||
seqNo = 0;
|
seqNo = 0;
|
||||||
@@ -115,50 +99,22 @@ public class ServerToClientThread implements Runnable, Observer {
|
|||||||
thread.start();
|
thread.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setUpPlayer(){
|
public Integer getSourceId() {
|
||||||
BufferedReader fn;
|
return sourceId;
|
||||||
String fName = "";
|
}
|
||||||
BufferedReader ln;
|
|
||||||
String lName = "";
|
|
||||||
|
|
||||||
fn = new BufferedReader(
|
private void setUpPlayer(){
|
||||||
new InputStreamReader(
|
String shortName = "P" + sourceId;
|
||||||
ServerToClientThread.class.getResourceAsStream(
|
String longName = "Player " + sourceId;
|
||||||
"/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()));
|
|
||||||
|
|
||||||
ServerYacht yacht = new ServerYacht(
|
ServerYacht yacht = new ServerYacht(
|
||||||
"Yacht", sourceId, sourceId.toString(), fName, fName + " " + lName, "NZ"
|
BoatMeshType.DINGHY, sourceId, sourceId.toString(), shortName, longName, "NZ");
|
||||||
);
|
|
||||||
|
|
||||||
yacht.addObserver(this); // TODO: yacht can notify mark rounding message hyi25 13/8/17
|
|
||||||
player = new Player(socket, yacht);
|
player = new Player(socket, yacht);
|
||||||
GameState.addYacht(sourceId, yacht);
|
GameState.addYacht(sourceId, yacht);
|
||||||
GameState.addPlayer(player);
|
GameState.addPlayer(player);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void update(Observable o, Object arg) {
|
|
||||||
if (arg != null) {
|
|
||||||
sendMessage((Message) arg);
|
|
||||||
} else {
|
|
||||||
sendSetupMessages();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void completeRegistration(ClientType clientType) throws IOException {
|
private void completeRegistration(ClientType clientType) throws IOException {
|
||||||
// Fail if not a player
|
// Fail if not a player
|
||||||
if (!clientType.equals(ClientType.PLAYER)){
|
if (!clientType.equals(ClientType.PLAYER)){
|
||||||
@@ -167,7 +123,7 @@ public class ServerToClientThread implements Runnable, Observer {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (GameState.getPlayers().size() >= GameState.MAX_PLAYERS){
|
if (GameState.getPlayers().size() >= GameState.getCapacity()){
|
||||||
RegistrationResponseMessage responseMessage = new RegistrationResponseMessage(0, RegistrationResponseStatus.FAILURE_FULL);
|
RegistrationResponseMessage responseMessage = new RegistrationResponseMessage(0, RegistrationResponseStatus.FAILURE_FULL);
|
||||||
os.write(responseMessage.getBuffer());
|
os.write(responseMessage.getBuffer());
|
||||||
return;
|
return;
|
||||||
@@ -211,32 +167,52 @@ public class ServerToClientThread implements Runnable, Observer {
|
|||||||
long computedCrc = checksum.getValue();
|
long computedCrc = checksum.getValue();
|
||||||
long packetCrc = Message.bytesToLong(getBytes(4));
|
long packetCrc = Message.bytesToLong(getBytes(4));
|
||||||
if (computedCrc == packetCrc) {
|
if (computedCrc == packetCrc) {
|
||||||
|
StreamPacket packet = new StreamPacket(type, payloadLength, timeStamp, payload);
|
||||||
switch (PacketType.assignPacketType(type, payload)) {
|
switch (PacketType.assignPacketType(type, payload)) {
|
||||||
case BOAT_ACTION:
|
case BOAT_ACTION:
|
||||||
BoatAction actionType = ServerPacketParser
|
BoatAction actionType = ServerPacketParser.extractBoatAction(packet);
|
||||||
.extractBoatAction(
|
|
||||||
new StreamPacket(type, payloadLength, timeStamp, payload));
|
|
||||||
GameState.updateBoat(sourceId, actionType);
|
GameState.updateBoat(sourceId, actionType);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case RACE_REGISTRATION_REQUEST:
|
case RACE_REGISTRATION_REQUEST:
|
||||||
ClientType requestedType = ServerPacketParser.extractClientType(
|
ClientType requestedType = ServerPacketParser
|
||||||
new StreamPacket(type, payloadLength, timeStamp, payload));
|
.extractClientType(packet);
|
||||||
|
|
||||||
completeRegistration(requestedType);
|
completeRegistration(requestedType);
|
||||||
break;
|
break;
|
||||||
|
case CHATTER_TEXT:
|
||||||
|
ChatterMessage chatterMessage = ServerPacketParser
|
||||||
|
.extractChatterText(packet);
|
||||||
|
GameState.processChatter(chatterMessage, isHost);
|
||||||
|
break;
|
||||||
case RACE_CUSTOMIZATION_REQUEST:
|
case RACE_CUSTOMIZATION_REQUEST:
|
||||||
Long sourceID = Message
|
Long sourceID = Message.bytesToLong(
|
||||||
.bytesToLong(Arrays.copyOfRange(payload, 0, 3));
|
Arrays.copyOfRange(payload, 0, 3)
|
||||||
|
);
|
||||||
CustomizeRequestType requestType = ServerPacketParser
|
CustomizeRequestType requestType = ServerPacketParser
|
||||||
.extractCustomizationType(
|
.extractCustomizationType(packet);
|
||||||
new StreamPacket(type, payloadLength, timeStamp, payload));
|
|
||||||
GameState.customizePlayer(sourceID, requestType,
|
GameState.customizePlayer(sourceID, requestType,
|
||||||
Arrays.copyOfRange(payload, 6, payload.length));
|
Arrays.copyOfRange(payload, 6, payload.length)
|
||||||
|
);
|
||||||
GameState.setCustomizationFlag();
|
GameState.setCustomizationFlag();
|
||||||
// TODO: 17/08/2017 ajm412: Send a response packet here, not really necessary until we do shapes.
|
// TODO: 17/08/2017 ajm412: Send a response packet here, not really necessary until we do shapes.
|
||||||
break;
|
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 {
|
} else {
|
||||||
logger.warn("Packet has been dropped", 1);
|
logger.warn("Packet has been dropped", 1);
|
||||||
@@ -244,40 +220,18 @@ public class ServerToClientThread implements Runnable, Observer {
|
|||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
closeSocket();
|
closeSocket();
|
||||||
|
GameState.setPlayerHasLeftFlag(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
GameState.setPlayerHasLeftFlag(true);
|
||||||
logger.warn("Closed serverToClientThread" + thread, 1);
|
logger.warn("Closed serverToClientThread" + thread, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void sendSetupMessages() {
|
public void sendSetupMessages() {
|
||||||
xml = new XMLGenerator();
|
sendMessage(MessageFactory.getRegattaXML());
|
||||||
Race race = new Race();
|
sendMessage(MessageFactory.getBoatXML());
|
||||||
|
sendMessage(MessageFactory.getRaceXML());
|
||||||
for (ServerYacht yacht : GameState.getYachts().values()) {
|
|
||||||
race.addBoat(yacht);
|
|
||||||
}
|
|
||||||
|
|
||||||
//@TODO calculate lat/lng values
|
|
||||||
xml.setRegatta(
|
|
||||||
new Regatta(
|
|
||||||
"Party Parrot Test Server", "Bermuda Test Course",
|
|
||||||
57.6679590, 11.8503233)
|
|
||||||
);
|
|
||||||
xml.setRace(race);
|
|
||||||
|
|
||||||
XMLMessage xmlMessage;
|
|
||||||
xmlMessage = new XMLMessage(xml.getRegattaAsXml(), XMLMessageSubType.REGATTA,
|
|
||||||
xml.getRegattaAsXml().length());
|
|
||||||
sendMessage(xmlMessage);
|
|
||||||
|
|
||||||
xmlMessage = new XMLMessage(xml.getBoatsAsXml(), XMLMessageSubType.BOAT,
|
|
||||||
xml.getBoatsAsXml().length());
|
|
||||||
sendMessage(xmlMessage);
|
|
||||||
|
|
||||||
xmlMessage = new XMLMessage(xml.getRaceAsXml(), XMLMessageSubType.RACE,
|
|
||||||
xml.getRaceAsXml().length());
|
|
||||||
sendMessage(xmlMessage);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void closeSocket() {
|
private void closeSocket() {
|
||||||
@@ -288,6 +242,10 @@ public class ServerToClientThread implements Runnable, Observer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Boolean isSocketOpen() {
|
||||||
|
return !socket.isClosed();
|
||||||
|
}
|
||||||
|
|
||||||
private int readByte() throws Exception {
|
private int readByte() throws Exception {
|
||||||
int currentByte = -1;
|
int currentByte = -1;
|
||||||
try {
|
try {
|
||||||
@@ -334,23 +292,6 @@ public class ServerToClientThread implements Runnable, Observer {
|
|||||||
return seqNo;
|
return seqNo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void sendBoatLocationPackets() {
|
|
||||||
ArrayList<ServerYacht> yachts = new ArrayList<>(GameState.getYachts().values());
|
|
||||||
for (ServerYacht yacht : yachts) {
|
|
||||||
BoatLocationMessage boatLocationMessage =
|
|
||||||
new BoatLocationMessage(
|
|
||||||
yacht.getSourceId(),
|
|
||||||
getSeqNo(),
|
|
||||||
yacht.getLocation().getLat(),
|
|
||||||
yacht.getLocation().getLng(),
|
|
||||||
yacht.getHeading(),
|
|
||||||
yacht.getCurrentVelocity().longValue());
|
|
||||||
|
|
||||||
sendMessage(boatLocationMessage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Thread getThread() {
|
public Thread getThread() {
|
||||||
return thread;
|
return thread;
|
||||||
}
|
}
|
||||||
@@ -359,14 +300,6 @@ public class ServerToClientThread implements Runnable, Observer {
|
|||||||
return socket;
|
return socket;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ServerYacht getYacht() {
|
|
||||||
return yacht;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void sendCollisionMessage(Integer yachtId) {
|
|
||||||
sendMessage(new YachtEventCodeMessage(yachtId));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addConnectionListener(ConnectionListener listener) {
|
public void addConnectionListener(ConnectionListener listener) {
|
||||||
connectionListeners.add(listener);
|
connectionListeners.add(listener);
|
||||||
}
|
}
|
||||||
@@ -386,4 +319,16 @@ public class ServerToClientThread implements Runnable, Observer {
|
|||||||
public void addDisconnectListener(DisconnectListener disconnectListener) {
|
public void addDisconnectListener(DisconnectListener disconnectListener) {
|
||||||
this.disconnectListener = disconnectListener;
|
this.disconnectListener = disconnectListener;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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),
|
TACK_GYBE(4),
|
||||||
UPWIND(5),
|
UPWIND(5),
|
||||||
DOWNWIND(6),
|
DOWNWIND(6),
|
||||||
MAINTAIN_HEADING(7);
|
MAINTAIN_HEADING(7),
|
||||||
|
CONTINUOUSLY_TURNING(8),
|
||||||
|
DEFAULT_TURNING(9);
|
||||||
|
|
||||||
private final int type;
|
private final int type;
|
||||||
private static final Map<Integer, BoatAction> intToTypeMap = new HashMap<>();
|
private static final Map<Integer, BoatAction> intToTypeMap = new HashMap<>();
|
||||||
|
|||||||
@@ -5,19 +5,19 @@ package seng302.gameServer.messages;
|
|||||||
*/
|
*/
|
||||||
public class BoatActionMessage extends Message{
|
public class BoatActionMessage extends Message{
|
||||||
private final MessageType MESSAGE_TYPE = MessageType.BOAT_ACTION;
|
private final MessageType MESSAGE_TYPE = MessageType.BOAT_ACTION;
|
||||||
private final int MESSAGE_SIZE = 1;
|
private final int MESSAGE_SIZE = 5;
|
||||||
private BoatAction actionType;
|
private BoatAction actionType;
|
||||||
|
|
||||||
public BoatActionMessage(BoatAction actionType) {
|
public BoatActionMessage(BoatAction actionType, int sourceId) {
|
||||||
this.actionType = actionType;
|
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();
|
allocateBuffer();
|
||||||
writeHeaderToBuffer();
|
writeHeaderToBuffer();
|
||||||
// Write message fields
|
// Write message fields
|
||||||
putInt(actionType.getValue(), 1);
|
putInt(actionType.getValue(), 1);
|
||||||
|
putInt(sourceId, 4);
|
||||||
writeCRC();
|
writeCRC();
|
||||||
rewind();
|
rewind();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -11,9 +11,11 @@ public class ChatterMessage extends Message {
|
|||||||
private int message_size = 21;
|
private int message_size = 21;
|
||||||
private String message;
|
private String message;
|
||||||
|
|
||||||
public ChatterMessage(int message_type, int message_size, String message) {
|
public ChatterMessage(int message_type, String message) {
|
||||||
|
byte[] byteMessage = message.getBytes();
|
||||||
|
|
||||||
this.message_type = message_type;
|
this.message_type = message_type;
|
||||||
this.message_size = message_size;
|
this.message_size = byteMessage.length;
|
||||||
this.message = message;
|
this.message = message;
|
||||||
|
|
||||||
setHeader(new Header(MessageType.CHATTER_TEXT, 1, (short) getSize()));
|
setHeader(new Header(MessageType.CHATTER_TEXT, 1, (short) getSize()));
|
||||||
@@ -23,7 +25,7 @@ public class ChatterMessage extends Message {
|
|||||||
putByte((byte) MESSAGE_VERSION_NUMBER);
|
putByte((byte) MESSAGE_VERSION_NUMBER);
|
||||||
putInt(message_type, 1);
|
putInt(message_type, 1);
|
||||||
putInt(message_size, 1);
|
putInt(message_size, 1);
|
||||||
putBytes(message.getBytes());
|
putBytes(byteMessage);
|
||||||
|
|
||||||
writeCRC();
|
writeCRC();
|
||||||
rewind();
|
rewind();
|
||||||
@@ -34,5 +36,11 @@ public class ChatterMessage extends Message {
|
|||||||
return MESSAGE_SIZE + message_size;
|
return MESSAGE_SIZE + message_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getMessage() {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMessageType() {
|
||||||
|
return message_type;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ public class MarkRoundingMessage extends Message{
|
|||||||
* @param roundingBoatStatus roundingBoatStatus
|
* @param roundingBoatStatus roundingBoatStatus
|
||||||
* @param roundingSide roundingSide
|
* @param roundingSide roundingSide
|
||||||
* @param markId markId
|
* @param markId markId
|
||||||
|
* @param markType .
|
||||||
*/
|
*/
|
||||||
public MarkRoundingMessage(int ackNumber, int raceId, int sourceId, RoundingBoatStatus roundingBoatStatus,
|
public MarkRoundingMessage(int ackNumber, int raceId, int sourceId, RoundingBoatStatus roundingBoatStatus,
|
||||||
RoundingSide roundingSide, MarkType markType, int markId) {
|
RoundingSide roundingSide, MarkType markType, int markId) {
|
||||||
|
|||||||
@@ -21,7 +21,10 @@ public enum MessageType {
|
|||||||
REGISTRATION_REQUEST(101),
|
REGISTRATION_REQUEST(101),
|
||||||
REGISTRATION_RESPONSE(102),
|
REGISTRATION_RESPONSE(102),
|
||||||
CUSTOMIZATION_REQUEST(103),
|
CUSTOMIZATION_REQUEST(103),
|
||||||
CUSTOMIZATION_RESPONSE(104);
|
CUSTOMIZATION_RESPONSE(104),
|
||||||
|
REPO_REGISTRATION_REQUEST(201),
|
||||||
|
ROOM_CODE_REQUEST(202),
|
||||||
|
LOBBY_REQUEST(203);
|
||||||
|
|
||||||
|
|
||||||
private int code;
|
private int code;
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ package seng302.gameServer.messages;
|
|||||||
public class RegistrationRequestMessage extends Message {
|
public class RegistrationRequestMessage extends Message {
|
||||||
private static int MESSAGE_LENGTH = 2;
|
private static int MESSAGE_LENGTH = 2;
|
||||||
|
|
||||||
public RegistrationRequestMessage(ClientType type){
|
public RegistrationRequestMessage(ClientType type, int clientID){
|
||||||
setHeader(new Header(MessageType.REGISTRATION_REQUEST, 1, (short) getSize()));
|
setHeader(new Header(MessageType.REGISTRATION_REQUEST, clientID, (short) getSize()));
|
||||||
|
|
||||||
allocateBuffer();
|
allocateBuffer();
|
||||||
writeHeaderToBuffer();
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -18,13 +18,13 @@ public class YachtEventCodeMessage extends Message {
|
|||||||
private int eventId;
|
private int eventId;
|
||||||
|
|
||||||
|
|
||||||
public YachtEventCodeMessage(Integer subjectId) {
|
public YachtEventCodeMessage(Integer subjectId, YachtEventType yachtEventType) {
|
||||||
timeStamp = System.currentTimeMillis() / 1000L;
|
timeStamp = System.currentTimeMillis() / 1000L;
|
||||||
ack = 0;
|
ack = 0;
|
||||||
raceId = 1;
|
raceId = 1;
|
||||||
destSourceId = subjectId; // collision boat source id
|
destSourceId = subjectId; // collision boat source id
|
||||||
incidentId = 0;
|
incidentId = 0;
|
||||||
eventId = 33;
|
eventId = yachtEventType.getCode();
|
||||||
|
|
||||||
setHeader(new Header(MESSAGE_TYPE, 0x01, (short) getSize()));
|
setHeader(new Header(MESSAGE_TYPE, 0x01, (short) getSize()));
|
||||||
allocateBuffer();
|
allocateBuffer();
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package seng302.gameServer.messages;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enum for different event types for the yacht
|
||||||
|
*/
|
||||||
|
public enum YachtEventType {
|
||||||
|
COLLISION(33),
|
||||||
|
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;
|
||||||
|
|
||||||
|
YachtEventType(int code) {
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCode() {
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,16 +6,23 @@ import java.util.ArrayList;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Observable;
|
import java.util.Observable;
|
||||||
import java.util.Observer;
|
import java.util.Observer;
|
||||||
|
import java.util.Timer;
|
||||||
import javafx.beans.property.ReadOnlyDoubleProperty;
|
import javafx.beans.property.ReadOnlyDoubleProperty;
|
||||||
import javafx.beans.property.ReadOnlyDoubleWrapper;
|
import javafx.beans.property.ReadOnlyDoubleWrapper;
|
||||||
import javafx.beans.property.ReadOnlyIntegerProperty;
|
import javafx.beans.property.ReadOnlyIntegerProperty;
|
||||||
import javafx.beans.property.ReadOnlyIntegerWrapper;
|
import javafx.beans.property.ReadOnlyIntegerWrapper;
|
||||||
import javafx.beans.property.ReadOnlyLongProperty;
|
import javafx.beans.property.ReadOnlyLongProperty;
|
||||||
import javafx.beans.property.ReadOnlyLongWrapper;
|
import javafx.beans.property.ReadOnlyLongWrapper;
|
||||||
|
import javafx.beans.value.ObservableObjectValue;
|
||||||
|
import javafx.collections.FXCollections;
|
||||||
import javafx.scene.paint.Color;
|
import javafx.scene.paint.Color;
|
||||||
|
import jdk.nashorn.internal.objects.annotations.Function;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import seng302.model.mark.CompoundMark;
|
import seng302.model.token.TokenType;
|
||||||
|
import seng302.visualiser.fxObjects.assets_3D.BoatMeshType;
|
||||||
|
import seng302.model.token.TokenType;
|
||||||
|
import seng302.visualiser.fxObjects.assets_3D.BoatObject;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Yacht class for the racing boat. <p> Class created to store more variables (eg. boat statuses)
|
* Yacht class for the racing boat. <p> Class created to store more variables (eg. boat statuses)
|
||||||
@@ -32,19 +39,40 @@ public class ClientYacht extends Observable {
|
|||||||
|
|
||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
public interface MarkRoundingListener {
|
public interface MarkRoundingListener {
|
||||||
void notifyRounding(ClientYacht yacht, CompoundMark markPassed, int legNumber);
|
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 Logger logger = LoggerFactory.getLogger(ClientYacht.class);
|
||||||
|
|
||||||
|
|
||||||
private String boatType;
|
private BoatMeshType boatType;
|
||||||
private Integer sourceId;
|
private Integer sourceId;
|
||||||
private String hullID; //matches HullNum in the XML spec.
|
private String hullID; //matches HullNum in the XML spec.
|
||||||
private String shortName;
|
private String shortName;
|
||||||
private String boatName;
|
private String boatName;
|
||||||
private String country;
|
private String country;
|
||||||
private Integer position;
|
private Integer position;
|
||||||
|
private TokenType powerUp;
|
||||||
|
|
||||||
private Long estimateTimeAtFinish;
|
private Long estimateTimeAtFinish;
|
||||||
private Boolean sailIn = true;
|
private Boolean sailIn = true;
|
||||||
@@ -57,16 +85,24 @@ public class ClientYacht extends Observable {
|
|||||||
private Integer boatStatus;
|
private Integer boatStatus;
|
||||||
private Double currentVelocity;
|
private Double currentVelocity;
|
||||||
|
|
||||||
|
Timer t;
|
||||||
|
|
||||||
|
private BoatObject boatObject;
|
||||||
|
|
||||||
private List<YachtLocationListener> locationListeners = new ArrayList<>();
|
private List<YachtLocationListener> locationListeners = new ArrayList<>();
|
||||||
private List<MarkRoundingListener> markRoundingListeners = 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 ReadOnlyDoubleWrapper velocityProperty = new ReadOnlyDoubleWrapper();
|
||||||
private ReadOnlyLongWrapper timeTillNextProperty = new ReadOnlyLongWrapper();
|
private ReadOnlyLongWrapper timeTillNextProperty = new ReadOnlyLongWrapper();
|
||||||
private ReadOnlyLongWrapper timeSinceLastMarkProperty = new ReadOnlyLongWrapper();
|
private ReadOnlyLongWrapper timeSinceLastMarkProperty = new ReadOnlyLongWrapper();
|
||||||
private ReadOnlyIntegerWrapper placingProperty = new ReadOnlyIntegerWrapper();
|
private ReadOnlyIntegerWrapper placingProperty = new ReadOnlyIntegerWrapper();
|
||||||
private CompoundMark lastMarkRounded;
|
private ReadOnlyDoubleWrapper headingProperty = new ReadOnlyDoubleWrapper();
|
||||||
private Color colour;
|
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) {
|
String boatName, String country) {
|
||||||
this.boatType = boatType;
|
this.boatType = boatType;
|
||||||
this.sourceId = sourceId;
|
this.sourceId = sourceId;
|
||||||
@@ -76,6 +112,7 @@ public class ClientYacht extends Observable {
|
|||||||
this.country = country;
|
this.country = country;
|
||||||
this.location = new GeoPoint(57.670341, 11.826856);
|
this.location = new GeoPoint(57.670341, 11.826856);
|
||||||
this.heading = 120.0; //In degrees
|
this.heading = 120.0; //In degrees
|
||||||
|
this.headingProperty.set(this.heading);
|
||||||
this.currentVelocity = 0d;
|
this.currentVelocity = 0d;
|
||||||
this.boatStatus = 1;
|
this.boatStatus = 1;
|
||||||
this.colour = Color.rgb(0, 0, 0, 1.0);
|
this.colour = Color.rgb(0, 0, 0, 1.0);
|
||||||
@@ -90,7 +127,7 @@ public class ClientYacht extends Observable {
|
|||||||
super.addObserver(o);
|
super.addObserver(o);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getBoatType() {
|
public BoatMeshType getBoatType() {
|
||||||
return boatType;
|
return boatType;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,14 +226,6 @@ public class ClientYacht extends Observable {
|
|||||||
return markRoundTime;
|
return markRoundTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
public CompoundMark getLastMarkRounded() {
|
|
||||||
return lastMarkRounded;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setLastMarkRounded(CompoundMark lastMarkRounded) {
|
|
||||||
this.lastMarkRounded = lastMarkRounded;
|
|
||||||
}
|
|
||||||
|
|
||||||
public GeoPoint getLocation() {
|
public GeoPoint getLocation() {
|
||||||
return location;
|
return location;
|
||||||
}
|
}
|
||||||
@@ -209,6 +238,32 @@ public class ClientYacht extends Observable {
|
|||||||
this.position = position;
|
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() {
|
public void toggleSail() {
|
||||||
sailIn = !sailIn;
|
sailIn = !sailIn;
|
||||||
}
|
}
|
||||||
@@ -231,6 +286,7 @@ public class ClientYacht extends Observable {
|
|||||||
|
|
||||||
public void setHeading(Double heading) {
|
public void setHeading(Double heading) {
|
||||||
this.heading = heading;
|
this.heading = heading;
|
||||||
|
setHeadingProperty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -257,19 +313,26 @@ public class ClientYacht extends Observable {
|
|||||||
|
|
||||||
public void setColour(Color colour) {
|
public void setColour(Color colour) {
|
||||||
this.colour = colour;
|
this.colour = colour;
|
||||||
|
for (ColorChangeListener listener : colorChangeListeners) {
|
||||||
|
listener.notifyColorChange(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void updateLocation(double lat, double lng, double heading, double velocity) {
|
public void updateLocation(double lat, double lng, double heading, double velocity) {
|
||||||
setLocation(lat, lng);
|
setLocation(lat, lng);
|
||||||
this.heading = heading;
|
this.heading = heading;
|
||||||
// this.currentVelocity = velocity;
|
setHeadingProperty();
|
||||||
|
this.currentVelocity = velocity;
|
||||||
updateVelocityProperty(velocity);
|
updateVelocityProperty(velocity);
|
||||||
for (YachtLocationListener yll : locationListeners) {
|
for (YachtLocationListener yll : locationListeners) {
|
||||||
yll.notifyLocation(this, lat, lng, heading, sailIn, velocity);
|
yll.notifyLocation(this, lat, lng, heading, sailIn, velocity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setHeadingProperty() {
|
||||||
|
headingProperty.set(heading);
|
||||||
|
}
|
||||||
|
|
||||||
public void addLocationListener(YachtLocationListener listener) {
|
public void addLocationListener(YachtLocationListener listener) {
|
||||||
locationListeners.add(listener);
|
locationListeners.add(listener);
|
||||||
}
|
}
|
||||||
@@ -278,6 +341,18 @@ public class ClientYacht extends Observable {
|
|||||||
markRoundingListeners.add(listener);
|
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) {
|
public void removeMarkRoundingListener(MarkRoundingListener listener) {
|
||||||
markRoundingListeners.remove(listener);
|
markRoundingListeners.remove(listener);
|
||||||
}
|
}
|
||||||
@@ -286,13 +361,29 @@ public class ClientYacht extends Observable {
|
|||||||
return sailIn;
|
return sailIn;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void roundMark(CompoundMark mark, long markRoundTime, long timeSinceLastMark) {
|
public void roundMark(long markRoundTime, long timeSinceLastMark) {
|
||||||
this.markRoundTime = markRoundTime;
|
this.markRoundTime = markRoundTime;
|
||||||
timeSinceLastMarkProperty.set(timeSinceLastMark);
|
timeSinceLastMarkProperty.set(timeSinceLastMark);
|
||||||
lastMarkRounded = mark;
|
|
||||||
legNumber++;
|
legNumber++;
|
||||||
for (MarkRoundingListener listener : markRoundingListeners) {
|
for (MarkRoundingListener listener : markRoundingListeners) {
|
||||||
listener.notifyRounding(this, lastMarkRounded, legNumber);
|
listener.notifyRounding(this, legNumber);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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() {
|
public Integer getSeqID() {
|
||||||
return seqID;
|
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.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
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
|
* 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>> polarTable;
|
||||||
private static HashMap<Double, HashMap<Double, Double>> upwindOptimal;
|
private static HashMap<Double, HashMap<Double, Double>> upwindOptimal;
|
||||||
private static HashMap<Double, HashMap<Double, Double>> downwindOptimal;
|
private static HashMap<Double, HashMap<Double, Double>> downwindOptimal;
|
||||||
|
private static Double optimalAngle;
|
||||||
|
|
||||||
private static int upTwaIndex;
|
private static int upTwaIndex;
|
||||||
private static int dnTwaIndex;
|
private static int dnTwaIndex;
|
||||||
@@ -33,11 +36,13 @@ public final class PolarTable {
|
|||||||
upwindOptimal = new HashMap<>();
|
upwindOptimal = new HashMap<>();
|
||||||
downwindOptimal = new HashMap<>();
|
downwindOptimal = new HashMap<>();
|
||||||
|
|
||||||
String line;
|
String line = null;
|
||||||
|
String check;
|
||||||
Boolean isHeaderLine = true;
|
Boolean isHeaderLine = true;
|
||||||
|
|
||||||
try (BufferedReader br = new BufferedReader(new InputStreamReader(polarFile))) {
|
try (BufferedReader br = new BufferedReader(new InputStreamReader(polarFile))) {
|
||||||
while ((line = br.readLine()) != null) {
|
while ((check = br.readLine()) != null) {
|
||||||
|
line = check;
|
||||||
String[] thisLine = line.split(",");
|
String[] thisLine = line.split(",");
|
||||||
|
|
||||||
//Initial line in file
|
//Initial line in file
|
||||||
@@ -66,7 +71,10 @@ public final class PolarTable {
|
|||||||
upwindOptimal.put(thisWindSpeed, thisUpWindPolar);
|
upwindOptimal.put(thisWindSpeed, thisUpWindPolar);
|
||||||
downwindOptimal.put(thisWindSpeed, thisDnWindPolar);
|
downwindOptimal.put(thisWindSpeed, thisDnWindPolar);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
getMaxSpeedAngle(line);
|
||||||
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
System.out.println("[PolarTable] IO exception");
|
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
|
* Parses the header line of a polar file
|
||||||
@@ -85,14 +114,18 @@ public final class PolarTable {
|
|||||||
String thisItem = thisLine[i];
|
String thisItem = thisLine[i];
|
||||||
if (thisItem.toLowerCase().startsWith("uptwa")) {
|
if (thisItem.toLowerCase().startsWith("uptwa")) {
|
||||||
upTwaIndex = i;
|
upTwaIndex = i;
|
||||||
}
|
} else if (thisItem.toLowerCase().startsWith("dntwa")) {
|
||||||
else if (thisItem.toLowerCase().startsWith("dntwa")) {
|
|
||||||
dnTwaIndex = i;
|
dnTwaIndex = i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static Double getOptimalAngle() {
|
||||||
|
return optimalAngle;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return The entire polar table
|
* @return The entire polar table
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -3,11 +3,8 @@ package seng302.model;
|
|||||||
import java.text.DateFormat;
|
import java.text.DateFormat;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Observable;
|
|
||||||
import java.util.TimeZone;
|
import java.util.TimeZone;
|
||||||
import javafx.beans.property.ReadOnlyDoubleProperty;
|
import javafx.beans.property.ReadOnlyDoubleProperty;
|
||||||
import javafx.beans.property.ReadOnlyDoubleWrapper;
|
import javafx.beans.property.ReadOnlyDoubleWrapper;
|
||||||
@@ -15,6 +12,7 @@ import javafx.collections.FXCollections;
|
|||||||
import javafx.collections.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
import seng302.model.stream.parser.RaceStartData;
|
import seng302.model.stream.parser.RaceStartData;
|
||||||
import seng302.model.stream.parser.RaceStatusData;
|
import seng302.model.stream.parser.RaceStatusData;
|
||||||
|
import seng302.utilities.Sounds;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class for storing race data that does not relate to specific vessels or marks such as time or wind.
|
* Class for storing race data that does not relate to specific vessels or marks such as time or wind.
|
||||||
@@ -33,7 +31,9 @@ public class RaceState {
|
|||||||
private ReadOnlyDoubleWrapper windDirection = new ReadOnlyDoubleWrapper();
|
private ReadOnlyDoubleWrapper windDirection = new ReadOnlyDoubleWrapper();
|
||||||
private long serverSystemTime;
|
private long serverSystemTime;
|
||||||
private long expectedStartTime;
|
private long expectedStartTime;
|
||||||
private boolean isRaceStarted = false;
|
private boolean raceRunning = false;
|
||||||
|
private boolean gunFired = false;
|
||||||
|
private boolean raceFinished = false;
|
||||||
long timeTillStart;
|
long timeTillStart;
|
||||||
private ObservableList<ClientYacht> playerPositions;
|
private ObservableList<ClientYacht> playerPositions;
|
||||||
private List<ClientYacht> collisions = new ArrayList<>();
|
private List<ClientYacht> collisions = new ArrayList<>();
|
||||||
@@ -48,7 +48,7 @@ public class RaceState {
|
|||||||
this.windDirection.set(data.getWindDirection());
|
this.windDirection.set(data.getWindDirection());
|
||||||
this.serverSystemTime = data.getCurrentTime();
|
this.serverSystemTime = data.getCurrentTime();
|
||||||
this.expectedStartTime = data.getExpectedStartTime();
|
this.expectedStartTime = data.getExpectedStartTime();
|
||||||
this.isRaceStarted = data.isRaceStarted();
|
this.raceRunning = data.isRaceStarted();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setTimeZone (TimeZone timeZone) {
|
public void setTimeZone (TimeZone timeZone) {
|
||||||
@@ -64,6 +64,10 @@ public class RaceState {
|
|||||||
if (raceTime < 0) {
|
if (raceTime < 0) {
|
||||||
return "-" + DATE_TIME_FORMAT.format(-1 * (raceTime - 1000));
|
return "-" + DATE_TIME_FORMAT.format(-1 * (raceTime - 1000));
|
||||||
} else {
|
} else {
|
||||||
|
if (!gunFired) {
|
||||||
|
gunFired = true;
|
||||||
|
Sounds.playCapGunSound();
|
||||||
|
}
|
||||||
return DATE_TIME_FORMAT.format(serverSystemTime - expectedStartTime);
|
return DATE_TIME_FORMAT.format(serverSystemTime - expectedStartTime);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -89,9 +93,12 @@ public class RaceState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isRaceStarted () {
|
public boolean isRaceStarted () {
|
||||||
return isRaceStarted;
|
return raceRunning;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setRaceStarted(Boolean value) {
|
||||||
|
this.raceRunning = value;
|
||||||
|
}
|
||||||
public void setBoats(Collection<ClientYacht> clientYachts) {
|
public void setBoats(Collection<ClientYacht> clientYachts) {
|
||||||
playerPositions.setAll(clientYachts);
|
playerPositions.setAll(clientYachts);
|
||||||
}
|
}
|
||||||
@@ -119,4 +126,12 @@ public class RaceState {
|
|||||||
public void removeCollisionListener(CollisionListener collisionListener) {
|
public void removeCollisionListener(CollisionListener collisionListener) {
|
||||||
collisionListeners.remove(collisionListener);
|
collisionListeners.remove(collisionListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setRaceFinished() {
|
||||||
|
raceFinished = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getRaceFinished() {
|
||||||
|
return raceFinished;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,39 +1,39 @@
|
|||||||
package seng302.model;
|
package seng302.model;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Observable;
|
|
||||||
import java.util.Observer;
|
|
||||||
import javafx.scene.paint.Color;
|
import javafx.scene.paint.Color;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import seng302.gameServer.GameState;
|
import seng302.gameServer.GameState;
|
||||||
import seng302.gameServer.messages.BoatStatus;
|
import seng302.gameServer.messages.BoatStatus;
|
||||||
import seng302.model.mark.Mark;
|
import seng302.model.mark.Mark;
|
||||||
|
import seng302.model.token.TokenType;
|
||||||
import seng302.utilities.GeoUtility;
|
import seng302.utilities.GeoUtility;
|
||||||
|
import seng302.visualiser.fxObjects.assets_3D.BoatMeshType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Yacht class for the racing boat. <p> Class created to store more variables (eg. boat statuses)
|
* Yacht class for the racing boat. <p> Class created to store more variables (eg. boat statuses)
|
||||||
* compared to the XMLParser boat class, also done outside Boat class because some old variables are
|
* compared to the XMLParser boat class, also done outside Boat class because some old variables are
|
||||||
* not used anymore.
|
* not used anymore.
|
||||||
*/
|
*/
|
||||||
public class ServerYacht extends Observable {
|
public class ServerYacht {
|
||||||
|
|
||||||
private Logger logger = LoggerFactory.getLogger(ClientYacht.class);
|
private Logger logger = LoggerFactory.getLogger(ServerYacht.class);
|
||||||
|
|
||||||
public static final Double TURN_STEP = 5.0;
|
|
||||||
|
|
||||||
//Boat info
|
//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 Integer sourceId;
|
||||||
private String hullID; //matches HullNum in the XML spec.
|
private String hullID; //matches HullNum in the XML spec.
|
||||||
private String shortName;
|
private String shortName;
|
||||||
private String boatName;
|
private String boatName;
|
||||||
private String country;
|
private String country;
|
||||||
private BoatStatus boatStatus;
|
private BoatStatus boatStatus;
|
||||||
|
|
||||||
private Color boatColor;
|
private Color boatColor;
|
||||||
|
|
||||||
|
|
||||||
//Location
|
//Location
|
||||||
private Double lastHeading;
|
private Double lastHeading;
|
||||||
private Boolean sailIn;
|
private Boolean sailIn;
|
||||||
@@ -52,10 +52,18 @@ public class ServerYacht extends Observable {
|
|||||||
private Boolean hasPassedLine;
|
private Boolean hasPassedLine;
|
||||||
private Boolean hasPassedThroughGate;
|
private Boolean hasPassedThroughGate;
|
||||||
|
|
||||||
|
//PowerUp
|
||||||
|
private TokenType powerUp;
|
||||||
|
private Long powerUpStartTime;
|
||||||
|
private Double powerUpSpeedMultiplier;
|
||||||
|
private Integer powerUpHandlingMultiplier;
|
||||||
|
|
||||||
public ServerYacht(String boatType, Integer sourceId, String hullID, String shortName,
|
//turning mode
|
||||||
|
private Boolean continuouslyTurning;
|
||||||
|
|
||||||
|
public ServerYacht(BoatMeshType boatType, Integer sourceId, String hullID, String shortName,
|
||||||
String boatName, String country) {
|
String boatName, String country) {
|
||||||
this.boatType = boatType;
|
setBoatType(boatType);
|
||||||
this.boatStatus = BoatStatus.PRESTART;
|
this.boatStatus = BoatStatus.PRESTART;
|
||||||
this.sourceId = sourceId;
|
this.sourceId = sourceId;
|
||||||
this.hullID = hullID;
|
this.hullID = hullID;
|
||||||
@@ -71,10 +79,13 @@ public class ServerYacht extends Observable {
|
|||||||
this.currentMarkSeqID = 0;
|
this.currentMarkSeqID = 0;
|
||||||
this.legNumber = 0;
|
this.legNumber = 0;
|
||||||
this.boatColor = Colors.getColor(sourceId - 1);
|
this.boatColor = Colors.getColor(sourceId - 1);
|
||||||
|
this.powerUp = null;
|
||||||
|
this.powerUpSpeedMultiplier = 1d;
|
||||||
|
this.powerUpHandlingMultiplier = 1;
|
||||||
this.hasEnteredRoundingZone = false;
|
this.hasEnteredRoundingZone = false;
|
||||||
this.hasPassedLine = false;
|
this.hasPassedLine = false;
|
||||||
this.hasPassedThroughGate = false;
|
this.hasPassedThroughGate = false;
|
||||||
|
this.continuouslyTurning = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -102,12 +113,40 @@ public class ServerYacht extends Observable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add ServerToClientThread as the observer, this observer pattern mainly server for the boat
|
* Powers up a yacht with a given yacht, only after powering it down first to avoid double power
|
||||||
* rounding package.
|
* ups
|
||||||
|
*
|
||||||
|
* @param powerUp The given power up
|
||||||
*/
|
*/
|
||||||
@Override
|
public void powerUp(TokenType powerUp) {
|
||||||
public void addObserver(Observer o) {
|
powerDown();
|
||||||
super.addObserver(o);
|
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() {
|
||||||
|
return powerUpStartTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TokenType getPowerUp() {
|
||||||
|
return powerUp;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -116,13 +155,14 @@ public class ServerYacht extends Observable {
|
|||||||
* @param amount the amount by which to adjust the boat heading.
|
* @param amount the amount by which to adjust the boat heading.
|
||||||
*/
|
*/
|
||||||
public void adjustHeading(Double amount) {
|
public void adjustHeading(Double amount) {
|
||||||
Double newVal = heading + amount;
|
Double newVal = heading + amount * powerUpHandlingMultiplier * boatTypeTurnStepMultiplier;
|
||||||
lastHeading = heading;
|
lastHeading = heading;
|
||||||
heading = (double) Math.floorMod(newVal.longValue(), 360L);
|
heading = (double) Math.floorMod(newVal.longValue(), 360L);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Swaps the boats direction from one side of the wind to the other.
|
* Swaps the boats direction from one side of the wind to the other.
|
||||||
|
* @param windDirection .
|
||||||
*/
|
*/
|
||||||
public void tackGybe(Double windDirection) {
|
public void tackGybe(Double windDirection) {
|
||||||
if (isAuto) {
|
if (isAuto) {
|
||||||
@@ -138,11 +178,11 @@ public class ServerYacht extends Observable {
|
|||||||
/**
|
/**
|
||||||
* Enables the boats auto pilot feature, which will move the boat towards a given heading.
|
* 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;
|
isAuto = true;
|
||||||
autoHeading = thisHeading;
|
autoHeading = newHeading;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -160,8 +200,9 @@ public class ServerYacht extends Observable {
|
|||||||
if (isAuto) {
|
if (isAuto) {
|
||||||
turnTowardsHeading(autoHeading);
|
turnTowardsHeading(autoHeading);
|
||||||
if (Math.abs(heading - autoHeading)
|
if (Math.abs(heading - autoHeading)
|
||||||
<= TURN_STEP) { //Cancel when within 1 turn step of target.
|
<= turnStep*1.5) {
|
||||||
isAuto = false;
|
isAuto = false;
|
||||||
|
setHeading(autoHeading);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -173,44 +214,52 @@ public class ServerYacht extends Observable {
|
|||||||
public void turnUpwind() {
|
public void turnUpwind() {
|
||||||
disableAutoPilot();
|
disableAutoPilot();
|
||||||
Double normalizedHeading = normalizeHeading();
|
Double normalizedHeading = normalizeHeading();
|
||||||
|
if (continuouslyTurning) {
|
||||||
|
adjustHeading(turnStep);
|
||||||
|
} else {
|
||||||
if (normalizedHeading == 0) {
|
if (normalizedHeading == 0) {
|
||||||
if (lastHeading < 180) {
|
if (lastHeading < 180) {
|
||||||
adjustHeading(-TURN_STEP);
|
adjustHeading(-turnStep);
|
||||||
} else {
|
} else {
|
||||||
adjustHeading(TURN_STEP);
|
adjustHeading(turnStep);
|
||||||
}
|
}
|
||||||
} else if (normalizedHeading == 180) {
|
} else if (normalizedHeading == 180) {
|
||||||
if (lastHeading < 180) {
|
if (lastHeading < 180) {
|
||||||
adjustHeading(TURN_STEP);
|
adjustHeading(turnStep);
|
||||||
} else {
|
} else {
|
||||||
adjustHeading(-TURN_STEP);
|
adjustHeading(-turnStep);
|
||||||
}
|
}
|
||||||
} else if (normalizedHeading < 180) {
|
} else if (normalizedHeading < 180) {
|
||||||
adjustHeading(-TURN_STEP);
|
adjustHeading(-turnStep);
|
||||||
} else {
|
} else {
|
||||||
adjustHeading(TURN_STEP);
|
adjustHeading(turnStep);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void turnDownwind() {
|
public void turnDownwind() {
|
||||||
disableAutoPilot();
|
disableAutoPilot();
|
||||||
Double normalizedHeading = normalizeHeading();
|
Double normalizedHeading = normalizeHeading();
|
||||||
|
if (continuouslyTurning) {
|
||||||
|
adjustHeading(-turnStep);
|
||||||
|
} else {
|
||||||
if (normalizedHeading == 0) {
|
if (normalizedHeading == 0) {
|
||||||
if (lastHeading < 180) {
|
if (lastHeading < 180) {
|
||||||
adjustHeading(TURN_STEP);
|
adjustHeading(turnStep);
|
||||||
} else {
|
} else {
|
||||||
adjustHeading(-TURN_STEP);
|
adjustHeading(-turnStep);
|
||||||
}
|
}
|
||||||
} else if (normalizedHeading == 180) {
|
} else if (normalizedHeading == 180) {
|
||||||
if (lastHeading < 180) {
|
if (lastHeading < 180) {
|
||||||
adjustHeading(-TURN_STEP);
|
adjustHeading(-turnStep);
|
||||||
} else {
|
} else {
|
||||||
adjustHeading(TURN_STEP);
|
adjustHeading(turnStep);
|
||||||
}
|
}
|
||||||
} else if (normalizedHeading < 180) {
|
} else if (normalizedHeading < 180) {
|
||||||
adjustHeading(TURN_STEP);
|
adjustHeading(turnStep);
|
||||||
} else {
|
} else {
|
||||||
adjustHeading(-TURN_STEP);
|
adjustHeading(-turnStep);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -239,7 +288,7 @@ public class ServerYacht extends Observable {
|
|||||||
|
|
||||||
// Take optimal heading and turn into a boat heading rather than a wind heading.
|
// Take optimal heading and turn into a boat heading rather than a wind heading.
|
||||||
optimalHeading =
|
optimalHeading =
|
||||||
optimalHeading + GameState.getWindDirection();
|
(optimalHeading + GameState.getWindDirection()) % 360;
|
||||||
|
|
||||||
setAutoPilot(optimalHeading);
|
setAutoPilot(optimalHeading);
|
||||||
}
|
}
|
||||||
@@ -254,9 +303,9 @@ public class ServerYacht extends Observable {
|
|||||||
private void turnTowardsHeading(Double newHeading) {
|
private void turnTowardsHeading(Double newHeading) {
|
||||||
Double newVal = heading - newHeading;
|
Double newVal = heading - newHeading;
|
||||||
if (Math.floorMod(newVal.longValue(), 360L) > 180) {
|
if (Math.floorMod(newVal.longValue(), 360L) > 180) {
|
||||||
adjustHeading(TURN_STEP / 5);
|
adjustHeading(turnStep / 5);
|
||||||
} else {
|
} else {
|
||||||
adjustHeading(-TURN_STEP / 5);
|
adjustHeading(-turnStep / 5);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -407,4 +456,43 @@ public class ServerYacht extends Observable {
|
|||||||
return boatColor;
|
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;
|
package seng302.model.mark;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import seng302.gameServer.messages.RoundingSide;
|
import seng302.gameServer.messages.RoundingSide;
|
||||||
import seng302.model.GeoPoint;
|
import seng302.model.GeoPoint;
|
||||||
@@ -10,13 +10,13 @@ public class CompoundMark {
|
|||||||
|
|
||||||
private int compoundMarkId;
|
private int compoundMarkId;
|
||||||
private String name;
|
private String name;
|
||||||
private List<Mark> marks = new ArrayList<>();
|
private List<Mark> marks;
|
||||||
private GeoPoint midPoint;
|
private GeoPoint midPoint;
|
||||||
|
|
||||||
public CompoundMark(int markID, String name, List<Mark> marks) {
|
public CompoundMark(int markID, String name, List<Mark> marks) {
|
||||||
this.compoundMarkId = markID;
|
this.compoundMarkId = markID;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.marks.addAll(marks);
|
this.marks = Collections.unmodifiableList(marks);
|
||||||
if (marks.size() > 1) {
|
if (marks.size() > 1) {
|
||||||
this.midPoint = GeoUtility.getDirtyMidPoint(marks.get(0), marks.get(1));
|
this.midPoint = GeoUtility.getDirtyMidPoint(marks.get(0), marks.get(1));
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -32,4 +32,10 @@ public class Corner {
|
|||||||
public Integer getZoneSize() {
|
public Integer getZoneSize() {
|
||||||
return zoneSize;
|
return zoneSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Corner = {seqID=" + seqID + ", compoundMarkID=" + compoundMarkID + ", rounding="
|
||||||
|
+ rounding +", zoneSize=" + zoneSize + "}";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,32 +1,33 @@
|
|||||||
package seng302.model.mark;
|
package seng302.model.mark;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.util.ArrayList;
|
||||||
import java.io.StringReader;
|
import java.util.Collections;
|
||||||
import javax.xml.parsers.DocumentBuilder;
|
import java.util.List;
|
||||||
import javax.xml.parsers.DocumentBuilderFactory;
|
|
||||||
import javax.xml.parsers.ParserConfigurationException;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
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.gameServer.messages.RoundingSide;
|
||||||
import seng302.model.stream.xml.generator.Race;
|
|
||||||
import seng302.model.stream.xml.parser.RaceXMLData;
|
import seng302.model.stream.xml.parser.RaceXMLData;
|
||||||
import seng302.utilities.XMLGenerator;
|
|
||||||
import seng302.utilities.XMLParser;
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class to hold the order of the marks in the race.
|
* Class to hold the order of the marks in the race.
|
||||||
*/
|
*/
|
||||||
public class MarkOrder {
|
public class MarkOrder {
|
||||||
private List<CompoundMark> raceMarkOrder;
|
private List<CompoundMark> raceMarkOrder;
|
||||||
|
private List<CompoundMark> orderedUniqueCompoundMarks;
|
||||||
private Logger logger = LoggerFactory.getLogger(MarkOrder.class);
|
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());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -38,10 +39,13 @@ public class MarkOrder {
|
|||||||
logger.warn("Race order accessed but not instantiated");
|
logger.warn("Race order accessed but not instantiated");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Collections.unmodifiableList(raceMarkOrder);
|
return Collections.unmodifiableList(raceMarkOrder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<CompoundMark> getOrderedUniqueCompoundMarks() {
|
||||||
|
return orderedUniqueCompoundMarks;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param seqID The seqID of the current mark the boat is heading to
|
* @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)
|
* @return A Boolean indicating if this coming mark is the last one (finish line)
|
||||||
@@ -72,67 +76,4 @@ public class MarkOrder {
|
|||||||
public CompoundMark getNextMark(Integer currentSeqID) throws IndexOutOfBoundsException {
|
public CompoundMark getNextMark(Integer currentSeqID) throws IndexOutOfBoundsException {
|
||||||
return raceMarkOrder.get(currentSeqID + 1);
|
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();
|
|
||||||
|
|
||||||
generator.setRace(new Race());
|
|
||||||
|
|
||||||
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_REQUEST,
|
||||||
RACE_REGISTRATION_RESPONSE,
|
RACE_REGISTRATION_RESPONSE,
|
||||||
RACE_CUSTOMIZATION_REQUEST,
|
RACE_CUSTOMIZATION_REQUEST,
|
||||||
RACE_CUSTOMIZATION_RESPONSE;
|
RACE_CUSTOMIZATION_RESPONSE,
|
||||||
|
|
||||||
|
SERVER_REGISTRATION, ROOM_CODE_REQUEST, LOBBY_REQUEST;
|
||||||
|
|
||||||
public static PacketType assignPacketType(int packetType, byte[] payload){
|
public static PacketType assignPacketType(int packetType, byte[] payload){
|
||||||
switch(packetType){
|
switch(packetType){
|
||||||
@@ -65,6 +67,10 @@ public enum PacketType {
|
|||||||
return RACE_CUSTOMIZATION_REQUEST;
|
return RACE_CUSTOMIZATION_REQUEST;
|
||||||
case 104:
|
case 104:
|
||||||
return RACE_CUSTOMIZATION_RESPONSE;
|
return RACE_CUSTOMIZATION_RESPONSE;
|
||||||
|
case 201:
|
||||||
|
return SERVER_REGISTRATION;
|
||||||
|
case 202:
|
||||||
|
return ROOM_CODE_REQUEST;
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
return OTHER;
|
return OTHER;
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ public class RaceStatusData {
|
|||||||
* Returns the data for boats collected form race status packets.
|
* Returns the data for boats collected form race status packets.
|
||||||
*
|
*
|
||||||
* @return A list of boat data. Boat data is in the form
|
* @return A list of boat data. Boat data is in the form
|
||||||
* [boatID, estTimeToNextMark, estTimeToFinish, legNumber].
|
* [boatID, estTimeToNextMark, estTimeToFinish, legNumber, status].
|
||||||
*/
|
*/
|
||||||
public List<long[]> getBoatData () {
|
public List<long[]> getBoatData () {
|
||||||
return boatData;
|
return boatData;
|
||||||
|
|||||||
@@ -1,53 +0,0 @@
|
|||||||
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.ServerYacht;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A Race object that can be parsed into XML
|
|
||||||
*/
|
|
||||||
public class Race {
|
|
||||||
|
|
||||||
private List<ServerYacht> yachts;
|
|
||||||
private LocalDateTime startTime;
|
|
||||||
|
|
||||||
public Race(){
|
|
||||||
yachts = new ArrayList<>();
|
|
||||||
startTime = LocalDateTime.now();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a boat to the race
|
|
||||||
* @param yacht The boat to add
|
|
||||||
*/
|
|
||||||
public void addBoat(ServerYacht yacht) {
|
|
||||||
yachts.add(yacht);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a list of boats in the race
|
|
||||||
* @return A List of boats
|
|
||||||
*/
|
|
||||||
public List<ServerYacht> getBoats() {
|
|
||||||
return Collections.unmodifiableList(yachts);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the time until the race starts
|
|
||||||
* @param seconds The time in seconds until the race starts
|
|
||||||
*/
|
|
||||||
public void setRaceStartDelay(Integer seconds){
|
|
||||||
startTime = startTime.plusMinutes(seconds);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the time the race starts
|
|
||||||
* @return The time the race starts
|
|
||||||
*/
|
|
||||||
public String getRaceStartTime(){
|
|
||||||
return startTime.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
package seng302.model.stream.xml.generator;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Race object that can be parsed into XML
|
||||||
|
*/
|
||||||
|
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, 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a list of boats in the race
|
||||||
|
* @return A List of boats
|
||||||
|
*/
|
||||||
|
public List<ServerYacht> getBoats() {
|
||||||
|
return Collections.unmodifiableList(yachts);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a list of tokens in the race
|
||||||
|
*
|
||||||
|
* @return A list of tokens
|
||||||
|
*/
|
||||||
|
public List<Token> getTokens() {
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
public void setRaceStartDelay(Integer seconds){
|
||||||
|
startTime = startTime.plusMinutes(seconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the time the race starts
|
||||||
|
* @return The time the race starts
|
||||||
|
*/
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
+10
-2
@@ -3,7 +3,7 @@ package seng302.model.stream.xml.generator;
|
|||||||
/**
|
/**
|
||||||
* A Race regatta that can be parsed into XML
|
* A Race regatta that can be parsed into XML
|
||||||
*/
|
*/
|
||||||
public class Regatta {
|
public class RegattaXMLTemplate {
|
||||||
private final Double DEFAULT_ALTITUDE = 0d;
|
private final Double DEFAULT_ALTITUDE = 0d;
|
||||||
private final Integer DEFAULT_REGATTA_ID = 0;
|
private final Integer DEFAULT_REGATTA_ID = 0;
|
||||||
|
|
||||||
@@ -18,7 +18,7 @@ public class Regatta {
|
|||||||
private Integer utcOffset;
|
private Integer utcOffset;
|
||||||
private Double magneticVariation;
|
private Double magneticVariation;
|
||||||
|
|
||||||
public Regatta(String name, String courseName, Double latitude, Double longitude) {
|
public RegattaXMLTemplate(String name, String courseName, Double latitude, Double longitude) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.id = DEFAULT_REGATTA_ID;
|
this.id = DEFAULT_REGATTA_ID;
|
||||||
this.courseName = courseName;
|
this.courseName = courseName;
|
||||||
@@ -74,4 +74,12 @@ public class Regatta {
|
|||||||
public Double getMagneticVariation(){
|
public Double getMagneticVariation(){
|
||||||
return magneticVariation;
|
return magneticVariation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setCourseName(String courseName) {
|
||||||
|
this.courseName = courseName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRegattaName(String regattaName) {
|
||||||
|
this.name = regattaName;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -6,6 +6,7 @@ import java.util.Map;
|
|||||||
import seng302.model.Limit;
|
import seng302.model.Limit;
|
||||||
import seng302.model.mark.CompoundMark;
|
import seng302.model.mark.CompoundMark;
|
||||||
import seng302.model.mark.Corner;
|
import seng302.model.mark.Corner;
|
||||||
|
import seng302.model.token.Token;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Process a Document object containing race data in XML format and stores the data.
|
* Process a Document object containing race data in XML format and stores the data.
|
||||||
@@ -13,13 +14,16 @@ import seng302.model.mark.Corner;
|
|||||||
public class RaceXMLData {
|
public class RaceXMLData {
|
||||||
|
|
||||||
private List<Integer> participants;
|
private List<Integer> participants;
|
||||||
|
private List<Token> tokens;
|
||||||
private Map<Integer, CompoundMark> compoundMarks;
|
private Map<Integer, CompoundMark> compoundMarks;
|
||||||
private List<Corner> markSequence;
|
private List<Corner> markSequence;
|
||||||
private List<Limit> courseLimit;
|
private List<Limit> courseLimit;
|
||||||
|
|
||||||
public RaceXMLData(List<Integer> participants, List<CompoundMark> compoundMarks,
|
public RaceXMLData(List<Integer> participants, List<Token> tokens,
|
||||||
|
List<CompoundMark> compoundMarks,
|
||||||
List<Corner> markSequence, List<Limit> courseLimit) {
|
List<Corner> markSequence, List<Limit> courseLimit) {
|
||||||
this.participants = participants;
|
this.participants = participants;
|
||||||
|
this.tokens = tokens;
|
||||||
this.markSequence = markSequence;
|
this.markSequence = markSequence;
|
||||||
this.courseLimit = courseLimit;
|
this.courseLimit = courseLimit;
|
||||||
this.compoundMarks = new HashMap<>();
|
this.compoundMarks = new HashMap<>();
|
||||||
@@ -32,6 +36,10 @@ public class RaceXMLData {
|
|||||||
return participants;
|
return participants;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<Token> getTokens() {
|
||||||
|
return tokens;
|
||||||
|
}
|
||||||
|
|
||||||
public Map<Integer, CompoundMark> getCompoundMarks() {
|
public Map<Integer, CompoundMark> getCompoundMarks() {
|
||||||
return compoundMarks;
|
return compoundMarks;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
package seng302.model.token;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Random;
|
||||||
|
import seng302.model.GeoPoint;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class describing a game token
|
||||||
|
* Created by wmu16 on 28/08/17.
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package seng302.model.token;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An enum describing the different types of game objects
|
||||||
|
* Created by wmu16 on 28/08/17.
|
||||||
|
*/
|
||||||
|
public enum TokenType {
|
||||||
|
|
||||||
|
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, String name, int timeout) {
|
||||||
|
this.value = value;
|
||||||
|
this.name = name;
|
||||||
|
this.timeout = timeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTimeout() {
|
||||||
|
return timeout;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package seng302.utilities;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import java.net.URL;
|
||||||
|
|
||||||
|
public class BonjourInstallChecker {
|
||||||
|
private static String INSTALL_URL = "https://support.apple.com/kb/DL999?locale=en_US";
|
||||||
|
private static String[] INSTALL_DIRECTORIES = {"C:/Program Files/Bonjour", "C:/Program Files (x86)/Bonjour"};
|
||||||
|
|
||||||
|
private static Boolean isWindows(){
|
||||||
|
return System.getProperty("os.name").startsWith("Windows");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Boolean isBonjourInstalled(){
|
||||||
|
for (String dir : INSTALL_DIRECTORIES){
|
||||||
|
File file = new File(dir);
|
||||||
|
|
||||||
|
if (file.isDirectory()){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Boolean isBonjourSupported(){
|
||||||
|
if (isWindows()){
|
||||||
|
return isBonjourInstalled();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void openInstallUrl(){
|
||||||
|
Runtime rt = Runtime.getRuntime();
|
||||||
|
|
||||||
|
try {
|
||||||
|
rt.exec("rundll32 url.dll,FileProtocolHandler " + INSTALL_URL);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ import seng302.model.GeoPoint;
|
|||||||
public class GeoUtility {
|
public class GeoUtility {
|
||||||
|
|
||||||
private static double EARTH_RADIUS = 6378.137;
|
private static double EARTH_RADIUS = 6378.137;
|
||||||
|
// private static double EARTH_RADIUS = 6378.13712121212121212121212121212121212121;
|
||||||
private static Double MS_TO_KNOTS = 1.943844492;
|
private static Double MS_TO_KNOTS = 1.943844492;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,205 @@
|
|||||||
|
package seng302.utilities;
|
||||||
|
|
||||||
|
import javafx.scene.media.Media;
|
||||||
|
import javafx.scene.media.MediaPlayer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Static class for playing sounds throughout the program
|
||||||
|
*
|
||||||
|
* Created by kre39 on 28/08/17.
|
||||||
|
*/
|
||||||
|
public class Sounds {
|
||||||
|
|
||||||
|
private static MediaPlayer musicPlayer;
|
||||||
|
private static MediaPlayer soundEffect;
|
||||||
|
private static MediaPlayer soundPlayer;
|
||||||
|
private static 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;
|
||||||
|
|
||||||
|
|
||||||
|
public static void stopMusic() {
|
||||||
|
if (musicPlayer != null) {
|
||||||
|
musicPlayer.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
static void setMutes() {
|
||||||
|
if (soundPlayer != null) {
|
||||||
|
soundPlayer.setMute(soundEffectsMuted);
|
||||||
|
}
|
||||||
|
if (soundEffect != null) {
|
||||||
|
soundEffect.setMute(soundEffectsMuted);
|
||||||
|
}
|
||||||
|
if (musicPlayer != null) {
|
||||||
|
musicPlayer.setMute(musicMuted);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void stopSoundEffects() {
|
||||||
|
if (soundEffect != null) {
|
||||||
|
soundEffect.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void toggleAllSounds() {
|
||||||
|
toggleMuteEffects();
|
||||||
|
toggleMuteMusic();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void toggleMuteMusic() {
|
||||||
|
musicMuted = !musicMuted;
|
||||||
|
if (musicPlayer != null) {
|
||||||
|
musicPlayer.setMute(musicMuted);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void toggleMuteEffects() {
|
||||||
|
soundEffectsMuted = !soundEffectsMuted;
|
||||||
|
if (soundPlayer != null) {
|
||||||
|
soundPlayer.setMute(soundEffectsMuted);
|
||||||
|
}
|
||||||
|
if (soundEffect != null) {
|
||||||
|
soundEffect.setMute(soundEffectsMuted);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isMusicMuted() {
|
||||||
|
return musicMuted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isSoundEffectsMuted() {
|
||||||
|
return soundEffectsMuted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void playRaceMusic() {
|
||||||
|
// Media menuMusic = new Media(Sounds.class.getClassLoader().getResource("sounds/Chill-house-music-loop-116-bpm.wav").toString());
|
||||||
|
Media raceMusic = new Media(Sounds.class.getClassLoader().getResource("sounds/Music-loop-120-bpm.mp3").toString());
|
||||||
|
musicPlayer = new MediaPlayer(raceMusic);
|
||||||
|
musicPlayer.setCycleCount(MediaPlayer.INDEFINITE);
|
||||||
|
musicPlayer.setVolume(0.3);
|
||||||
|
musicPlayer.play();
|
||||||
|
raceMusic = new Media(Sounds.class.getClassLoader().getResource("sounds/Sounds-of-the-ocean.mp3").toString());
|
||||||
|
soundEffect = new MediaPlayer(raceMusic);
|
||||||
|
soundEffect.setCycleCount(MediaPlayer.INDEFINITE);
|
||||||
|
soundEffect.setVolume(0.3);
|
||||||
|
soundEffect.play();
|
||||||
|
musicPlayer.setMute(musicMuted);
|
||||||
|
soundEffect.setMute(soundEffectsMuted);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void playMenuMusic() {
|
||||||
|
Media menuMusic = new Media(
|
||||||
|
Sounds.class.getClassLoader().getResource("sounds/Elevator-music.mp3").toString());
|
||||||
|
musicPlayer = new MediaPlayer(menuMusic);
|
||||||
|
musicPlayer.setCycleCount(MediaPlayer.INDEFINITE);
|
||||||
|
musicPlayer.setVolume(0.3);
|
||||||
|
musicPlayer.play();
|
||||||
|
musicPlayer.setMute(musicMuted);
|
||||||
|
if (soundEffect != null) {
|
||||||
|
soundEffect.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static void playFinishMusic() {
|
||||||
|
Media finishMusic = new Media(Sounds.class.getClassLoader().getResource("sounds/Happy-birthday-song.mp3").toString());
|
||||||
|
musicPlayer = new MediaPlayer(finishMusic);
|
||||||
|
musicPlayer.setCycleCount(MediaPlayer.INDEFINITE);
|
||||||
|
musicPlayer.setVolume(0.3);
|
||||||
|
musicPlayer.play();
|
||||||
|
musicPlayer.setMute(musicMuted);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void playButtonClick() {
|
||||||
|
if (!soundEffectsMuted) {
|
||||||
|
Media buttonClick = new Media(
|
||||||
|
Sounds.class.getClassLoader().getResource("sounds/Button-click-sound.mp3")
|
||||||
|
.toString());
|
||||||
|
soundPlayer = new MediaPlayer(buttonClick);
|
||||||
|
soundPlayer.setVolume(0.5);
|
||||||
|
soundPlayer.play();
|
||||||
|
soundPlayer.setMute(soundEffectsMuted);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void playFinishSound() {
|
||||||
|
if (!soundEffectsMuted) {
|
||||||
|
Media finishSound = new Media(
|
||||||
|
Sounds.class.getClassLoader().getResource("sounds/Sms-notification.mp3")
|
||||||
|
.toString());
|
||||||
|
soundPlayer = new MediaPlayer(finishSound);
|
||||||
|
soundPlayer.setVolume(0.5);
|
||||||
|
soundPlayer.play();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static void playMarkRoundingSound() {
|
||||||
|
if (!soundEffectsMuted) {
|
||||||
|
Media markRoundingSound = new Media(
|
||||||
|
Sounds.class.getClassLoader().getResource("sounds/sms-tone.mp3").toString());
|
||||||
|
soundPlayer = new MediaPlayer(markRoundingSound);
|
||||||
|
soundPlayer.play();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void playCapGunSound() {
|
||||||
|
if (!soundEffectsMuted) {
|
||||||
|
Media gunSound = new Media(
|
||||||
|
Sounds.class.getClassLoader().getResource("sounds/Gunshot-sound.mp3").toString());
|
||||||
|
soundPlayer = new MediaPlayer(gunSound);
|
||||||
|
soundPlayer.play();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void playCrashSound() {
|
||||||
|
if (!soundEffectsMuted) {
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void playTokenPickupSound() {
|
||||||
|
if (!soundEffectsMuted) {
|
||||||
|
Media pickupSound = new Media(
|
||||||
|
Sounds.class.getClassLoader().getResource("sounds/Coin-pick-up-sound-effect.mp3")
|
||||||
|
.toString());
|
||||||
|
soundPlayer = new MediaPlayer(pickupSound);
|
||||||
|
soundPlayer.play();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void playHoverSound() {
|
||||||
|
if (!soundEffectsMuted) {
|
||||||
|
if (!hoverInitialized) {
|
||||||
|
Media hoverSound = new Media(
|
||||||
|
Sounds.class.getClassLoader().getResource("sounds/Error-sound-effect.mp3")
|
||||||
|
.toString());
|
||||||
|
hoverSoundPlayer = new MediaPlayer(hoverSound);
|
||||||
|
hoverInitialized = true;
|
||||||
|
}
|
||||||
|
hoverSoundPlayer.setVolume(0.5);
|
||||||
|
if (hoverSoundPlayer != null) {
|
||||||
|
hoverSoundPlayer.stop();
|
||||||
|
}
|
||||||
|
hoverSoundPlayer.play();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import java.io.StringReader;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import javafx.util.Pair;
|
||||||
import javax.xml.parsers.DocumentBuilder;
|
import javax.xml.parsers.DocumentBuilder;
|
||||||
import javax.xml.parsers.DocumentBuilderFactory;
|
import javax.xml.parsers.DocumentBuilderFactory;
|
||||||
import javax.xml.parsers.ParserConfigurationException;
|
import javax.xml.parsers.ParserConfigurationException;
|
||||||
@@ -13,8 +14,12 @@ import org.xml.sax.InputSource;
|
|||||||
import org.xml.sax.SAXException;
|
import org.xml.sax.SAXException;
|
||||||
import seng302.model.stream.packets.PacketType;
|
import seng302.model.stream.packets.PacketType;
|
||||||
import seng302.model.stream.packets.StreamPacket;
|
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.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
|
* StreamParser is a utilities class for taking byte data, formatted according to the AC35 streaming
|
||||||
@@ -35,7 +40,6 @@ public class StreamParser {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
long heartbeat = bytesToLong(packet.getPayload());
|
long heartbeat = bytesToLong(packet.getPayload());
|
||||||
System.out.println("heartbeat = " + heartbeat);
|
|
||||||
return heartbeat;
|
return heartbeat;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,31 +66,10 @@ public class StreamParser {
|
|||||||
long windDir = bytesToLong(Arrays.copyOfRange(payload, 18, 20));
|
long windDir = bytesToLong(Arrays.copyOfRange(payload, 18, 20));
|
||||||
long rawWindSpeed = bytesToLong(Arrays.copyOfRange(payload, 20, 22));
|
long rawWindSpeed = bytesToLong(Arrays.copyOfRange(payload, 20, 22));
|
||||||
|
|
||||||
// DateFormat format = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
|
|
||||||
// currentTime = format.format((new Date(currentTime)))
|
|
||||||
|
|
||||||
RaceStatusData data = new RaceStatusData(
|
RaceStatusData data = new RaceStatusData(
|
||||||
windDir, rawWindSpeed, raceStatus, currentTime, expectedStartTime
|
windDir, rawWindSpeed, raceStatus, currentTime, expectedStartTime
|
||||||
);
|
);
|
||||||
|
|
||||||
// long timeTillStart =
|
|
||||||
// ((new Date(expectedStartTime)).getTime() - (new Date(currentTime)).getTime()) / 1000;
|
|
||||||
//
|
|
||||||
// if (timeTillStart > 0) {
|
|
||||||
// timeSinceStart = timeTillStart;
|
|
||||||
// } else {
|
|
||||||
// if (raceStatus == 4 || raceStatus == 8) {
|
|
||||||
// raceFinished = true;
|
|
||||||
// raceStarted = false;
|
|
||||||
// } else if (!raceStarted) {
|
|
||||||
// raceStarted = true;
|
|
||||||
// raceFinished = false;
|
|
||||||
// }
|
|
||||||
// timeSinceStart = timeTillStart;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
|
|
||||||
//
|
|
||||||
int noBoats = payload[22];
|
int noBoats = payload[22];
|
||||||
int raceType = payload[23];
|
int raceType = payload[23];
|
||||||
long boatID, estTimeAtNextMark, estTimeAtFinish;
|
long boatID, estTimeAtNextMark, estTimeAtFinish;
|
||||||
@@ -106,24 +89,6 @@ public class StreamParser {
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
// private static void setBoatLegPosition(Yacht updatingBoat, Integer leg){
|
|
||||||
// Integer placing = 1;
|
|
||||||
// if (leg != updatingBoat.getLegNumber() && (raceStarted || raceFinished)) {
|
|
||||||
// for (Yacht boat : boats.values()) {
|
|
||||||
// if (boat.getLegNumber() != null && leg <= boat.getLegNumber()){
|
|
||||||
// placing += 1;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// updatingBoat.setPlacing(placing.toString());
|
|
||||||
// updatingBoat.setLegNumber(leg);
|
|
||||||
// boatsPos.putIfAbsent(placing, updatingBoat);
|
|
||||||
// boatsPos.replace(placing, updatingBoat);
|
|
||||||
// } else if(updatingBoat.getLegNumber() == null){
|
|
||||||
// updatingBoat.setPlacing("1");
|
|
||||||
// updatingBoat.setLegNumber(leg);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses and returns the text from a StreamPacket containing text data for display.
|
* Parses and returns the text from a StreamPacket containing text data for display.
|
||||||
*
|
*
|
||||||
@@ -171,7 +136,6 @@ public class StreamParser {
|
|||||||
long messageLength = bytesToLong(Arrays.copyOfRange(payload, 12, 14));
|
long messageLength = bytesToLong(Arrays.copyOfRange(payload, 12, 14));
|
||||||
String xmlMessage = new String(
|
String xmlMessage = new String(
|
||||||
(Arrays.copyOfRange(payload, 14, (int) (14 + messageLength)))).trim();
|
(Arrays.copyOfRange(payload, 14, (int) (14 + messageLength)))).trim();
|
||||||
|
|
||||||
//Create XML document Object
|
//Create XML document Object
|
||||||
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
|
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
|
||||||
DocumentBuilder db;
|
DocumentBuilder db;
|
||||||
@@ -255,15 +219,15 @@ public class StreamParser {
|
|||||||
* @return Chatter text message as a string. Returns null if the packet is not of type
|
* @return Chatter text message as a string. Returns null if the packet is not of type
|
||||||
* CHATTER_TEXT.
|
* CHATTER_TEXT.
|
||||||
*/
|
*/
|
||||||
public static String extractChatterText(StreamPacket packet) {
|
public static Pair<Integer, String> extractChatterText(StreamPacket packet) {
|
||||||
if (packet.getType() != PacketType.CHATTER_TEXT) {
|
if (packet.getType() != PacketType.CHATTER_TEXT) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
byte[] payload = packet.getPayload();
|
byte[] payload = packet.getPayload();
|
||||||
int messageVersionNo = payload[0];
|
int messageVersionNo = payload[0];
|
||||||
int messageType = payload[1];
|
int messageType = payload[1];
|
||||||
int length = payload[2];
|
int length = (int) bytesToLong(new byte[]{payload[2]});
|
||||||
return new String(Arrays.copyOfRange(payload, 3, 3 + length));
|
return new Pair<>(messageType, new String(Arrays.copyOfRange(payload, 3, 3 + length)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -392,26 +356,6 @@ public class StreamParser {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static void extractBoatAction(StreamPacket packet) {
|
|
||||||
byte[] payload = packet.getPayload();
|
|
||||||
int messageVersionNo = payload[0];
|
|
||||||
long actionType = bytesToLong(Arrays.copyOfRange(payload, 0, 1));
|
|
||||||
if (actionType == 1) {
|
|
||||||
System.out.println("VMG");
|
|
||||||
} else if (actionType == 2) {
|
|
||||||
System.out.println("SAILS IN");
|
|
||||||
} else if (actionType == 3) {
|
|
||||||
System.out.println("SAILS OUT");
|
|
||||||
} else if (actionType == 4) {
|
|
||||||
System.out.println("TACK/GYBE");
|
|
||||||
} else if (actionType == 5) {
|
|
||||||
System.out.println("UPWIND");
|
|
||||||
} else if (actionType == 6) {
|
|
||||||
System.out.println("DOWNWIND");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* takes an array of up to 7 bytes and returns a positive long constructed from the input bytes
|
* takes an array of up to 7 bytes and returns a positive long constructed from the input bytes
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -3,13 +3,15 @@ package seng302.utilities;
|
|||||||
import freemarker.template.Configuration;
|
import freemarker.template.Configuration;
|
||||||
import freemarker.template.Template;
|
import freemarker.template.Template;
|
||||||
import freemarker.template.TemplateException;
|
import freemarker.template.TemplateException;
|
||||||
|
import seng302.gameServer.messages.XMLMessageSubType;
|
||||||
|
import seng302.model.stream.xml.generator.RaceXMLTemplate;
|
||||||
|
import seng302.model.stream.xml.generator.RegattaXMLTemplate;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStreamWriter;
|
import java.io.OutputStreamWriter;
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
import seng302.model.stream.xml.generator.Race;
|
import java.util.Random;
|
||||||
import seng302.model.stream.xml.generator.Regatta;
|
|
||||||
import seng302.gameServer.messages.XMLMessageSubType;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An XML generator to generate the Race, Boat, and Regatta XML dynamically
|
* An XML generator to generate the Race, Boat, and Regatta XML dynamically
|
||||||
@@ -20,8 +22,13 @@ public class XMLGenerator {
|
|||||||
private static final String BOATS_TEMPLATE_NAME = "boats.ftlh";
|
private static final String BOATS_TEMPLATE_NAME = "boats.ftlh";
|
||||||
private static final String RACE_TEMPLATE_NAME = "race.ftlh";
|
private static final String RACE_TEMPLATE_NAME = "race.ftlh";
|
||||||
private Configuration configuration;
|
private Configuration configuration;
|
||||||
private Regatta regatta;
|
private RegattaXMLTemplate regatta;
|
||||||
private Race race;
|
private RaceXMLTemplate race;
|
||||||
|
|
||||||
|
public static RegattaXMLTemplate DEFAULT_REGATTA = new RegattaXMLTemplate("Party Parrot Test Server " + new Random().nextInt(100),
|
||||||
|
"Bermuda",
|
||||||
|
57.6679590,
|
||||||
|
11.8503233);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set up a configuration instance for Apache Freemake
|
* Set up a configuration instance for Apache Freemake
|
||||||
@@ -39,7 +46,7 @@ public class XMLGenerator {
|
|||||||
/**
|
/**
|
||||||
* Create an instance of the XML Generator
|
* Create an instance of the XML Generator
|
||||||
*/
|
*/
|
||||||
public XMLGenerator(){
|
public XMLGenerator() {
|
||||||
setupConfiguration();
|
setupConfiguration();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,7 +55,7 @@ public class XMLGenerator {
|
|||||||
* Note: This must be set before a regatta message can be generated
|
* Note: This must be set before a regatta message can be generated
|
||||||
* @param regatta The race regatta
|
* @param regatta The race regatta
|
||||||
*/
|
*/
|
||||||
public void setRegatta(Regatta regatta){
|
public void setRegattaTemplate(RegattaXMLTemplate regatta) {
|
||||||
this.regatta = regatta;
|
this.regatta = regatta;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,7 +64,7 @@ public class XMLGenerator {
|
|||||||
* Note: This must be set before a boat or race message can be generated
|
* Note: This must be set before a boat or race message can be generated
|
||||||
* @param race The race
|
* @param race The race
|
||||||
*/
|
*/
|
||||||
public void setRace(Race race){
|
public void setRaceTemplate(RaceXMLTemplate race) {
|
||||||
this.race = race;
|
this.race = race;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,7 +113,7 @@ public class XMLGenerator {
|
|||||||
public String getRegattaAsXml(){
|
public String getRegattaAsXml(){
|
||||||
String result = null;
|
String result = null;
|
||||||
|
|
||||||
if (regatta == null) return null;
|
if (regatta == null) regatta = DEFAULT_REGATTA;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
result = parseToXmlString(REGATTA_TEMPLATE_NAME, XMLMessageSubType.REGATTA);
|
result = parseToXmlString(REGATTA_TEMPLATE_NAME, XMLMessageSubType.REGATTA);
|
||||||
@@ -160,4 +167,20 @@ public class XMLGenerator {
|
|||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void setDefaultRaceName(String raceName){
|
||||||
|
DEFAULT_REGATTA.setRegattaName(raceName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setDefaultMapName(String mapName){
|
||||||
|
DEFAULT_REGATTA.setCourseName(mapName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public RegattaXMLTemplate getRegatta() {
|
||||||
|
return regatta;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RaceXMLTemplate getRace() {
|
||||||
|
return race;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,27 +1,44 @@
|
|||||||
package seng302.utilities;
|
package seng302.utilities;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Random;
|
||||||
import javafx.scene.paint.Color;
|
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.Document;
|
||||||
import org.w3c.dom.Element;
|
import org.w3c.dom.Element;
|
||||||
|
import org.w3c.dom.NamedNodeMap;
|
||||||
import org.w3c.dom.Node;
|
import org.w3c.dom.Node;
|
||||||
import org.w3c.dom.NodeList;
|
import org.w3c.dom.NodeList;
|
||||||
|
import org.xml.sax.SAXException;
|
||||||
import seng302.model.ClientYacht;
|
import seng302.model.ClientYacht;
|
||||||
|
import seng302.model.Colors;
|
||||||
import seng302.model.Limit;
|
import seng302.model.Limit;
|
||||||
import seng302.model.mark.CompoundMark;
|
import seng302.model.mark.CompoundMark;
|
||||||
import seng302.model.mark.Corner;
|
import seng302.model.mark.Corner;
|
||||||
import seng302.model.mark.Mark;
|
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.RaceXMLData;
|
||||||
import seng302.model.stream.xml.parser.RegattaXMLData;
|
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
|
* Utilities for parsing XML documents
|
||||||
*/
|
*/
|
||||||
public class XMLParser {
|
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.
|
* Returns the text content of a given child element tag, assuming it exists, as an Integer.
|
||||||
*
|
*
|
||||||
@@ -32,7 +49,7 @@ public class XMLParser {
|
|||||||
private static Integer getElementInt(Element ele, String tag) {
|
private static Integer getElementInt(Element ele, String tag) {
|
||||||
NodeList tagList = ele.getElementsByTagName(tag);
|
NodeList tagList = ele.getElementsByTagName(tag);
|
||||||
if (tagList.getLength() > 0) {
|
if (tagList.getLength() > 0) {
|
||||||
return Integer.parseInt(tagList.item(0).getTextContent());
|
return Integer.parseInt(tagList.item(0).getTextContent().replaceAll("\\s+",""));
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -64,7 +81,7 @@ public class XMLParser {
|
|||||||
private static Double getElementDouble(Element ele, String tag) {
|
private static Double getElementDouble(Element ele, String tag) {
|
||||||
NodeList tagList = ele.getElementsByTagName(tag);
|
NodeList tagList = ele.getElementsByTagName(tag);
|
||||||
if (tagList.getLength() > 0) {
|
if (tagList.getLength() > 0) {
|
||||||
return Double.parseDouble(tagList.item(0).getTextContent());
|
return Double.parseDouble(tagList.item(0).getTextContent().replaceAll("\\s+",""));
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -136,19 +153,29 @@ public class XMLParser {
|
|||||||
Node currentBoat = boatsList.item(i);
|
Node currentBoat = boatsList.item(i);
|
||||||
if (currentBoat.getNodeName().equals("Boat")) {
|
if (currentBoat.getNodeName().equals("Boat")) {
|
||||||
// Boat boat = new Boat(currentBoat);
|
// 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(
|
ClientYacht yacht = new ClientYacht(
|
||||||
XMLParser.getNodeAttributeString(currentBoat, "Type"),
|
boatMeshType,
|
||||||
XMLParser.getNodeAttributeInt(currentBoat, "SourceID"),
|
XMLParser.getNodeAttributeInt(currentBoat, "SourceID"),
|
||||||
XMLParser.getNodeAttributeString(currentBoat, "HullNum"),
|
XMLParser.getNodeAttributeString(currentBoat, "HullNum"),
|
||||||
XMLParser.getNodeAttributeString(currentBoat, "ShortName"),
|
XMLParser.getNodeAttributeString(currentBoat, "ShortName"),
|
||||||
XMLParser.getNodeAttributeString(currentBoat, "BoatName"),
|
XMLParser.getNodeAttributeString(currentBoat, "BoatName"),
|
||||||
XMLParser.getNodeAttributeString(currentBoat, "Country"));
|
XMLParser.getNodeAttributeString(currentBoat, "Country"));
|
||||||
yacht.setColour(Color.web(getNodeAttributeString(currentBoat, "Color")));
|
yacht.setColour(color);
|
||||||
if (yacht.getBoatType().equals("Yacht")) {
|
|
||||||
competingBoats.put(yacht.getSourceId(), yacht);
|
competingBoats.put(yacht.getSourceId(), yacht);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return competingBoats;
|
return competingBoats;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,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
|
* Returns an object containing the data extracted from the given xml formatted document
|
||||||
*
|
*
|
||||||
@@ -181,13 +238,36 @@ public class XMLParser {
|
|||||||
public static RaceXMLData parseRace(Document doc) {
|
public static RaceXMLData parseRace(Document doc) {
|
||||||
Element docEle = doc.getDocumentElement();
|
Element docEle = doc.getDocumentElement();
|
||||||
return new RaceXMLData(
|
return new RaceXMLData(
|
||||||
extractParticpantIDs(docEle),
|
extractParticipantIDs(docEle),
|
||||||
|
extractTokens(docEle),
|
||||||
extractCompoundMarks(docEle),
|
extractCompoundMarks(docEle),
|
||||||
extractMarkOrder(docEle),
|
extractMarkOrder(docEle),
|
||||||
extractCourseLimit(docEle)
|
extractCourseLimit(docEle)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts token data
|
||||||
|
*/
|
||||||
|
private static List<Token> extractTokens(Element docEle) {
|
||||||
|
List<Token> tokens = new ArrayList<>();
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extracts course limit data
|
* Extracts course limit data
|
||||||
*/
|
*/
|
||||||
@@ -197,13 +277,11 @@ public class XMLParser {
|
|||||||
for (int i = 0; i < limitList.getLength(); i++) {
|
for (int i = 0; i < limitList.getLength(); i++) {
|
||||||
Node limitNode = limitList.item(i);
|
Node limitNode = limitList.item(i);
|
||||||
if (limitNode.getNodeName().equals("Limit")) {
|
if (limitNode.getNodeName().equals("Limit")) {
|
||||||
courseLimit.add(
|
courseLimit.add(new Limit(
|
||||||
new Limit(
|
|
||||||
XMLParser.getNodeAttributeInt(limitNode, "SeqID"),
|
XMLParser.getNodeAttributeInt(limitNode, "SeqID"),
|
||||||
XMLParser.getNodeAttributeDouble(limitNode, "Lat"),
|
XMLParser.getNodeAttributeDouble(limitNode, "Lat"),
|
||||||
XMLParser.getNodeAttributeDouble(limitNode, "Lon")
|
XMLParser.getNodeAttributeDouble(limitNode, "Lon")
|
||||||
)
|
));
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return courseLimit;
|
return courseLimit;
|
||||||
@@ -235,7 +313,7 @@ public class XMLParser {
|
|||||||
/**
|
/**
|
||||||
* Extracts course participants data
|
* Extracts course participants data
|
||||||
*/
|
*/
|
||||||
private static List<Integer> extractParticpantIDs (Element docEle) {
|
private static List<Integer> extractParticipantIDs(Element docEle) {
|
||||||
List<Integer> boatIDs = new ArrayList<>();
|
List<Integer> boatIDs = new ArrayList<>();
|
||||||
NodeList pList = docEle.getElementsByTagName("Participants").item(0).getChildNodes();
|
NodeList pList = docEle.getElementsByTagName("Participants").item(0).getChildNodes();
|
||||||
for (int i = 0; i < pList.getLength(); i++) {
|
for (int i = 0; i < pList.getLength(); i++) {
|
||||||
@@ -257,10 +335,11 @@ public class XMLParser {
|
|||||||
for (int i = 0; i < cMarkList.getLength(); i++) {
|
for (int i = 0; i < cMarkList.getLength(); i++) {
|
||||||
Node cMarkNode = cMarkList.item(i);
|
Node cMarkNode = cMarkList.item(i);
|
||||||
if (cMarkNode.getNodeName().equals("CompoundMark")) {
|
if (cMarkNode.getNodeName().equals("CompoundMark")) {
|
||||||
|
String name = XMLParser.getNodeAttributeString(cMarkNode, "Name");
|
||||||
|
name = (name == null || name.equals("")) ? "Mark " + i+1: name;
|
||||||
cMark = new CompoundMark(
|
cMark = new CompoundMark(
|
||||||
XMLParser.getNodeAttributeInt(cMarkNode, "CompoundMarkID"),
|
XMLParser.getNodeAttributeInt(cMarkNode, "CompoundMarkID"),
|
||||||
XMLParser.getNodeAttributeString(cMarkNode, "Name"),
|
name, createMarks(cMarkNode)
|
||||||
createMarks(cMarkNode)
|
|
||||||
);
|
);
|
||||||
allMarks.add(cMark);
|
allMarks.add(cMark);
|
||||||
}
|
}
|
||||||
@@ -281,14 +360,169 @@ public class XMLParser {
|
|||||||
Node markNode = childMarks.item(i);
|
Node markNode = childMarks.item(i);
|
||||||
if (markNode.getNodeName().equals("Mark")) {
|
if (markNode.getNodeName().equals("Mark")) {
|
||||||
Integer seqID = XMLParser.getNodeAttributeInt(markNode, "SeqID");
|
Integer seqID = XMLParser.getNodeAttributeInt(markNode, "SeqID");
|
||||||
|
seqID = (seqID == null) ? i+1 : seqID;
|
||||||
|
|
||||||
Integer sourceID = XMLParser.getNodeAttributeInt(markNode, "SourceID");
|
Integer sourceID = XMLParser.getNodeAttributeInt(markNode, "SourceID");
|
||||||
|
sourceID = (sourceID == null) ? i+1 : sourceID;
|
||||||
|
|
||||||
String markName = XMLParser.getNodeAttributeString(markNode, "Name");
|
String markName = XMLParser.getNodeAttributeString(markNode, "Name");
|
||||||
|
markName = (markName == null || markName.equals("")) ? cMarkName + " " + i+1: markName;
|
||||||
|
|
||||||
Double targetLat = XMLParser.getNodeAttributeDouble(markNode, "TargetLat");
|
Double targetLat = XMLParser.getNodeAttributeDouble(markNode, "TargetLat");
|
||||||
Double targetLng = XMLParser.getNodeAttributeDouble(markNode, "TargetLng");
|
Double targetLng = XMLParser.getNodeAttributeDouble(markNode, "TargetLng");
|
||||||
|
|
||||||
Mark mark = new Mark(markName, seqID, targetLat, targetLng, sourceID);
|
Mark mark = new Mark(markName, seqID, targetLat, targetLng, sourceID);
|
||||||
subMarks.add(mark);
|
subMarks.add(mark);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return subMarks;
|
return subMarks;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This ungodly combination of existing methods and code blocks parses a race definition file.
|
||||||
|
* Look upon it and despair.
|
||||||
|
* @param url The input file path
|
||||||
|
* @param serverName the name of the server
|
||||||
|
* @param repetitions the repetitions of a segment of the race def file.
|
||||||
|
* @param maxPlayers max number of players. uses the default race max if null or greater than the actual max.
|
||||||
|
* @param tokensEnabled if tokens are enabled
|
||||||
|
* @return a pair which contains regatta string, race string as key, value pair.
|
||||||
|
*/
|
||||||
|
public static Pair<RegattaXMLTemplate, RaceXMLTemplate> parseRaceDef(
|
||||||
|
String url, String serverName, Integer repetitions, Integer maxPlayers, Boolean tokensEnabled
|
||||||
|
) {
|
||||||
|
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
|
||||||
|
DocumentBuilder db;
|
||||||
|
Document doc = null;
|
||||||
|
try {
|
||||||
|
db = dbf.newDocumentBuilder();
|
||||||
|
doc = db.parse(XMLParser.class.getResourceAsStream(url));
|
||||||
|
} catch (ParserConfigurationException | IOException | SAXException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
Element docEle = doc.getDocumentElement();
|
||||||
|
|
||||||
|
RegattaXMLTemplate regattaXMLTemplate = new RegattaXMLTemplate(
|
||||||
|
serverName, XMLParser.getElementString(docEle, "CourseName"),
|
||||||
|
XMLParser.getElementDouble(docEle, "CentralLat"),
|
||||||
|
XMLParser.getElementDouble(docEle, "CentralLng")
|
||||||
|
);
|
||||||
|
|
||||||
|
XMLGenerator xmlGenerator = new XMLGenerator();
|
||||||
|
xmlGenerator.setRegattaTemplate(regattaXMLTemplate);
|
||||||
|
|
||||||
|
if (maxPlayers == null) {
|
||||||
|
maxPlayers = XMLParser.getElementInt(docEle, "MaxPlayers");
|
||||||
|
} else if (maxPlayers > XMLParser.getElementInt(docEle, "MaxPlayers")) {
|
||||||
|
maxPlayers = XMLParser.getElementInt(docEle, "MaxPlayers");
|
||||||
|
}
|
||||||
|
|
||||||
|
RaceXMLTemplate raceXMLTemplate = new RaceXMLTemplate(
|
||||||
|
new ArrayList<>(), new ArrayList<>(),
|
||||||
|
XMLParser.extractMarkOrderRaceDef(docEle, repetitions),
|
||||||
|
XMLParser.extractCourseLimitRaceDef(docEle),
|
||||||
|
XMLParser.extractCompoundMarksRaceDef(docEle),
|
||||||
|
maxPlayers, tokensEnabled
|
||||||
|
);
|
||||||
|
xmlGenerator.setRaceTemplate(raceXMLTemplate);
|
||||||
|
return new Pair<>(regattaXMLTemplate, raceXMLTemplate);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<Corner> extractMarkOrderRaceDef(Element docEle, int repitions){
|
||||||
|
List<Corner> compoundMarkSequence = new ArrayList<>();
|
||||||
|
NodeList cornerList = docEle.getElementsByTagName("Course").item(0).getChildNodes();
|
||||||
|
|
||||||
|
int seqId = 1;
|
||||||
|
final int zoneSize = 3;
|
||||||
|
|
||||||
|
for (int i=0; i<cornerList.getLength(); i++) {
|
||||||
|
Node segment = cornerList.item(i);
|
||||||
|
if (segment.getNodeName().equals("OpeningSegment") ||
|
||||||
|
segment.getNodeName().equals("ClosingSegment")) {
|
||||||
|
|
||||||
|
seqId = parseCourseSegment(segment, seqId, compoundMarkSequence);
|
||||||
|
|
||||||
|
} else if (segment.getNodeName().equals("RepeatingSegment")) {
|
||||||
|
for (int k = 0; k < repitions; k++) {
|
||||||
|
seqId = parseCourseSegment(segment, seqId, compoundMarkSequence);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return compoundMarkSequence;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses a segment of the course adding new Corners to the given list.
|
||||||
|
* @param segment Segment to parse
|
||||||
|
* @param seqID initial sequence ID
|
||||||
|
* @param course course to add corners to
|
||||||
|
* @return the last sequence id.
|
||||||
|
*/
|
||||||
|
private static int parseCourseSegment(Node segment, int seqID, List<Corner> course) {
|
||||||
|
NodeList segmentList = segment.getChildNodes();
|
||||||
|
for (int j = 0; j < segmentList.getLength(); j++) {
|
||||||
|
Node corner = segmentList.item(j);
|
||||||
|
if (corner.getNodeName().equals("Corner")) {
|
||||||
|
String rounding = XMLParser.getNodeAttributeString(corner, "Rounding");
|
||||||
|
rounding = //Converting "P" to "Port" and "S" to "Stbd"
|
||||||
|
rounding.equals("P") ? "Port" :
|
||||||
|
rounding.equals("S") ? "Stbd" : rounding;
|
||||||
|
course.add(new Corner(
|
||||||
|
seqID++, XMLParser.getNodeAttributeInt(corner, "CompoundMarkID"),
|
||||||
|
rounding, 3
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return seqID;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<Limit> extractCourseLimitRaceDef(Element docEle) {
|
||||||
|
List<Limit> courseLimit = new ArrayList<>();
|
||||||
|
NodeList limitList = docEle.getElementsByTagName("CourseLimit").item(0).getChildNodes();
|
||||||
|
int seqId = 1;
|
||||||
|
for (int i = 0; i < limitList.getLength(); i++) {
|
||||||
|
Node limitNode = limitList.item(i);
|
||||||
|
if (limitNode.getNodeName().equals("Limit")) {
|
||||||
|
courseLimit.add(new Limit(
|
||||||
|
seqId++, XMLParser.getNodeAttributeDouble(limitNode, "Lat"),
|
||||||
|
XMLParser.getNodeAttributeDouble(limitNode, "Lng")
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return courseLimit;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<CompoundMark> extractCompoundMarksRaceDef(Element docEle){
|
||||||
|
List<CompoundMark> allMarks = new ArrayList<>();
|
||||||
|
NodeList cMarkList = docEle.getElementsByTagName("Marks").item(0).getChildNodes();
|
||||||
|
CompoundMark cMark;
|
||||||
|
int markCount = 200;
|
||||||
|
for (int i = 0; i < cMarkList.getLength(); i++) {
|
||||||
|
Node cMarkNode = cMarkList.item(i);
|
||||||
|
if (cMarkNode.getNodeName().equals("CompoundMark")) {
|
||||||
|
Integer id = XMLParser.getNodeAttributeInt(cMarkNode, "CompoundMarkID");
|
||||||
|
List<Mark> subMarks = createMarksRaceDef(cMarkNode, markCount,"Mark " + id);
|
||||||
|
markCount += subMarks.size();
|
||||||
|
allMarks.add(new CompoundMark(id, "Mark " + id, subMarks));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return allMarks;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<Mark> createMarksRaceDef(Node compoundMark, int markCount, String markName) {
|
||||||
|
List<Mark> subMarks = new ArrayList<>();
|
||||||
|
NodeList childMarks = compoundMark.getChildNodes();
|
||||||
|
int seqID = 1;
|
||||||
|
for (int i = 0; i < childMarks.getLength(); i++) {
|
||||||
|
Node markNode = childMarks.item(i);
|
||||||
|
if (markNode.getNodeName().equals("Mark")) {
|
||||||
|
Double targetLat = XMLParser.getNodeAttributeDouble(markNode, "Lat");
|
||||||
|
Double targetLng = XMLParser.getNodeAttributeDouble(markNode, "Lng");
|
||||||
|
Mark mark = new Mark(markName + " subMark " + seqID, seqID, targetLat, targetLng, markCount++);
|
||||||
|
subMarks.add(mark);
|
||||||
|
seqID += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return subMarks;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,14 @@
|
|||||||
package seng302.visualiser;
|
package seng302.visualiser;
|
||||||
|
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.util.Pair;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import seng302.gameServer.messages.*;
|
||||||
|
import seng302.model.stream.packets.PacketType;
|
||||||
|
import seng302.model.stream.packets.StreamPacket;
|
||||||
|
import seng302.utilities.XMLParser;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
@@ -14,18 +23,27 @@ import java.util.TimerTask;
|
|||||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||||
import java.util.zip.CRC32;
|
import java.util.zip.CRC32;
|
||||||
import java.util.zip.Checksum;
|
import java.util.zip.Checksum;
|
||||||
|
import javafx.util.Pair;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import seng302.gameServer.messages.BoatAction;
|
import seng302.gameServer.messages.BoatAction;
|
||||||
import seng302.gameServer.messages.BoatActionMessage;
|
import seng302.gameServer.messages.BoatActionMessage;
|
||||||
|
import seng302.gameServer.messages.ChatterMessage;
|
||||||
import seng302.gameServer.messages.ClientType;
|
import seng302.gameServer.messages.ClientType;
|
||||||
import seng302.gameServer.messages.CustomizeRequestMessage;
|
import seng302.gameServer.messages.CustomizeRequestMessage;
|
||||||
import seng302.gameServer.messages.CustomizeRequestType;
|
import seng302.gameServer.messages.CustomizeRequestType;
|
||||||
import seng302.gameServer.messages.Message;
|
import seng302.gameServer.messages.Message;
|
||||||
import seng302.gameServer.messages.RegistrationRequestMessage;
|
import seng302.gameServer.messages.RegistrationRequestMessage;
|
||||||
import seng302.gameServer.messages.RegistrationResponseStatus;
|
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.PacketType;
|
||||||
import seng302.model.stream.packets.StreamPacket;
|
import seng302.model.stream.packets.StreamPacket;
|
||||||
|
import seng302.model.stream.xml.generator.RaceXMLTemplate;
|
||||||
|
import seng302.model.stream.xml.generator.RegattaXMLTemplate;
|
||||||
|
import seng302.utilities.XMLGenerator;
|
||||||
|
import seng302.utilities.XMLParser;
|
||||||
|
import seng302.visualiser.controllers.ViewManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A class describing a single connection to a Server for the purposes of sending and receiving on
|
* A class describing a single connection to a Server for the purposes of sending and receiving on
|
||||||
@@ -33,7 +51,7 @@ import seng302.model.stream.packets.StreamPacket;
|
|||||||
*/
|
*/
|
||||||
public class ClientToServerThread implements Runnable {
|
public class ClientToServerThread implements Runnable {
|
||||||
|
|
||||||
|
private boolean isStarted = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Functional interface for receiving packets from client socket.
|
* Functional interface for receiving packets from client socket.
|
||||||
@@ -45,7 +63,12 @@ public class ClientToServerThread implements Runnable {
|
|||||||
|
|
||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
public interface DisconnectedFromHostListener {
|
public interface DisconnectedFromHostListener {
|
||||||
void notifYDisconnection (String message);
|
void notifyDisconnection(String message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface ConnectionErrorListener {
|
||||||
|
void notifyConnectionError(String message);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ByteReadException extends Exception {
|
private class ByteReadException extends Exception {
|
||||||
@@ -57,6 +80,7 @@ public class ClientToServerThread implements Runnable {
|
|||||||
private Queue<StreamPacket> streamPackets = new ConcurrentLinkedQueue<>();
|
private Queue<StreamPacket> streamPackets = new ConcurrentLinkedQueue<>();
|
||||||
private List<ClientSocketListener> listeners = new ArrayList<>();
|
private List<ClientSocketListener> listeners = new ArrayList<>();
|
||||||
private List<DisconnectedFromHostListener> disconnectionListeners = new ArrayList<>();
|
private List<DisconnectedFromHostListener> disconnectionListeners = new ArrayList<>();
|
||||||
|
private ConnectionErrorListener connectionErrorListener = null;
|
||||||
private Thread thread;
|
private Thread thread;
|
||||||
|
|
||||||
private Socket socket;
|
private Socket socket;
|
||||||
@@ -69,7 +93,7 @@ public class ClientToServerThread implements Runnable {
|
|||||||
private Timer upWindPacketTimer = new Timer();
|
private Timer upWindPacketTimer = new Timer();
|
||||||
private Timer downWindPacketTimer = new Timer();
|
private Timer downWindPacketTimer = new Timer();
|
||||||
private boolean upwindTimerFlag = false, downwindTimerFlag = false;
|
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;
|
private int clientId = -1;
|
||||||
|
|
||||||
@@ -95,7 +119,7 @@ public class ClientToServerThread implements Runnable {
|
|||||||
|
|
||||||
sendRegistrationRequest();
|
sendRegistrationRequest();
|
||||||
|
|
||||||
thread = new Thread(this);
|
thread = new Thread(this, "ClientToServer");
|
||||||
thread.start();
|
thread.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,6 +128,8 @@ public class ClientToServerThread implements Runnable {
|
|||||||
* variable is false.
|
* variable is false.
|
||||||
*/
|
*/
|
||||||
public void run() {
|
public void run() {
|
||||||
|
isStarted = true;
|
||||||
|
|
||||||
int sync1;
|
int sync1;
|
||||||
int sync2;
|
int sync2;
|
||||||
// TODO: 14/07/17 wmu16 - Work out how to fix this while loop
|
// TODO: 14/07/17 wmu16 - Work out how to fix this while loop
|
||||||
@@ -134,10 +160,12 @@ public class ClientToServerThread implements Runnable {
|
|||||||
else {
|
else {
|
||||||
if (clientId == -1) continue; // Do not continue if not registered
|
if (clientId == -1) continue; // Do not continue if not registered
|
||||||
streamPackets.add(new StreamPacket(type, payloadLength, timeStamp, payload));
|
streamPackets.add(new StreamPacket(type, payloadLength, timeStamp, payload));
|
||||||
|
synchronized (this) {
|
||||||
for (ClientSocketListener csl : listeners)
|
for (ClientSocketListener csl : listeners)
|
||||||
csl.newPacket();
|
csl.newPacket();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
logger.warn("Packet has been dropped", 1);
|
logger.warn("Packet has been dropped", 1);
|
||||||
}
|
}
|
||||||
@@ -151,6 +179,11 @@ public class ClientToServerThread implements Runnable {
|
|||||||
logger.warn("Closed connection to server", 1);
|
logger.warn("Closed connection to server", 1);
|
||||||
notifyDisconnectListeners("Connection to server was terminated");
|
notifyDisconnectListeners("Connection to server was terminated");
|
||||||
closeSocket();
|
closeSocket();
|
||||||
|
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
ViewManager.getInstance().showErrorSnackBar("Server rejected connection.");
|
||||||
|
ViewManager.getInstance().goToStartView();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void sendCustomizationRequest(CustomizeRequestType reqType, byte[] payload) {
|
public void sendCustomizationRequest(CustomizeRequestType reqType, byte[] payload) {
|
||||||
@@ -167,16 +200,22 @@ public class ClientToServerThread implements Runnable {
|
|||||||
private void notifyDisconnectListeners (String message) {
|
private void notifyDisconnectListeners (String message) {
|
||||||
if (socketOpen) {
|
if (socketOpen) {
|
||||||
for (DisconnectedFromHostListener listener : disconnectionListeners) {
|
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
|
* Sends a request to the server asking for a source ID
|
||||||
*/
|
*/
|
||||||
private void sendRegistrationRequest() {
|
private void sendRegistrationRequest() {
|
||||||
RegistrationRequestMessage requestMessage = new RegistrationRequestMessage(ClientType.PLAYER);
|
RegistrationRequestMessage requestMessage = new RegistrationRequestMessage(ClientType.PLAYER, clientId);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
os.write(requestMessage.getBuffer());
|
os.write(requestMessage.getBuffer());
|
||||||
@@ -192,9 +231,8 @@ public class ClientToServerThread implements Runnable {
|
|||||||
* @param packet The registration requests packet
|
* @param packet The registration requests packet
|
||||||
*/
|
*/
|
||||||
private void processRegistrationResponse(StreamPacket 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));
|
int statusCode = (int) Message.bytesToLong(Arrays.copyOfRange(packet.getPayload(), 4,5));
|
||||||
|
|
||||||
RegistrationResponseStatus status = RegistrationResponseStatus.getResponseStatus(statusCode);
|
RegistrationResponseStatus status = RegistrationResponseStatus.getResponseStatus(statusCode);
|
||||||
|
|
||||||
if (status.equals(RegistrationResponseStatus.SUCCESS_PLAYING)){
|
if (status.equals(RegistrationResponseStatus.SUCCESS_PLAYING)){
|
||||||
@@ -212,8 +250,10 @@ public class ClientToServerThread implements Runnable {
|
|||||||
else{
|
else{
|
||||||
alertErrorText = "Could not connect to server";
|
alertErrorText = "Could not connect to server";
|
||||||
}
|
}
|
||||||
|
handleConnectionError("Server no longer available.");
|
||||||
notifyDisconnectListeners(alertErrorText);
|
notifyDisconnectListeners(alertErrorText);
|
||||||
closeSocket();
|
|
||||||
|
System.out.println();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -244,7 +284,7 @@ public class ClientToServerThread implements Runnable {
|
|||||||
new TimerTask() {
|
new TimerTask() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
sendBoatActionMessage(new BoatActionMessage(BoatAction.DOWNWIND));
|
sendBoatActionMessage(new BoatActionMessage(BoatAction.DOWNWIND, clientId));
|
||||||
}
|
}
|
||||||
}, 0, PACKET_SENDING_INTERVAL_MS
|
}, 0, PACKET_SENDING_INTERVAL_MS
|
||||||
);
|
);
|
||||||
@@ -257,14 +297,14 @@ public class ClientToServerThread implements Runnable {
|
|||||||
new TimerTask() {
|
new TimerTask() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
sendBoatActionMessage(new BoatActionMessage(BoatAction.UPWIND));
|
sendBoatActionMessage(new BoatActionMessage(BoatAction.UPWIND, clientId));
|
||||||
}
|
}
|
||||||
}, 0, PACKET_SENDING_INTERVAL_MS
|
}, 0, PACKET_SENDING_INTERVAL_MS
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
sendBoatActionMessage(new BoatActionMessage(actionType));
|
sendBoatActionMessage(new BoatActionMessage(actionType, clientId));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -283,9 +323,17 @@ public class ClientToServerThread implements Runnable {
|
|||||||
* @param message The given message type.
|
* @param message The given message type.
|
||||||
*/
|
*/
|
||||||
private void sendBoatActionMessage(BoatActionMessage message) {
|
private void sendBoatActionMessage(BoatActionMessage message) {
|
||||||
|
sendByteBuffer(message.getBuffer());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendChatterMessage(String message) {
|
||||||
|
sendByteBuffer(new ChatterMessage(clientId, message).getBuffer());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendByteBuffer(byte[] bytes) {
|
||||||
if (clientId != -1) {
|
if (clientId != -1) {
|
||||||
try {
|
try {
|
||||||
os.write(message.getBuffer());
|
os.write(bytes);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
logger.warn("IOException on attempting to sendBoatAction from Client");
|
logger.warn("IOException on attempting to sendBoatAction from Client");
|
||||||
notifyDisconnectListeners("Cannot communicate with server");
|
notifyDisconnectListeners("Cannot communicate with server");
|
||||||
@@ -294,7 +342,7 @@ public class ClientToServerThread implements Runnable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void closeSocket() {
|
public void closeSocket() {
|
||||||
try {
|
try {
|
||||||
socket.close();
|
socket.close();
|
||||||
socketOpen = false;
|
socketOpen = false;
|
||||||
@@ -312,20 +360,32 @@ public class ClientToServerThread implements Runnable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void addStreamObserver (ClientSocketListener streamListener) {
|
public void addStreamObserver (ClientSocketListener streamListener) {
|
||||||
|
synchronized (this){
|
||||||
listeners.add(streamListener);
|
listeners.add(streamListener);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void removeStreamObserver (ClientSocketListener streamListener) {
|
public void removeStreamObserver (ClientSocketListener streamListener) {
|
||||||
listeners.remove(streamListener);
|
listeners.remove(streamListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addDisconnectionListener (DisconnectedFromHostListener listener) {
|
public void addDisconnectionListener (DisconnectedFromHostListener listener) {
|
||||||
|
synchronized (this){
|
||||||
disconnectionListeners.add(listener);
|
disconnectionListeners.add(listener);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void removeDisconnectionListener (DisconnectedFromHostListener listener) {
|
public void removeDisconnectionListener (DisconnectedFromHostListener listener) {
|
||||||
|
synchronized (this){
|
||||||
disconnectionListeners.remove(listener);
|
disconnectionListeners.remove(listener);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setConnectionErrorListener(ConnectionErrorListener listener){
|
||||||
|
synchronized (this){
|
||||||
|
connectionErrorListener = listener;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private int readByte() throws ByteReadException {
|
private int readByte() throws ByteReadException {
|
||||||
int currentByte = -1;
|
int currentByte = -1;
|
||||||
@@ -339,8 +399,9 @@ public class ClientToServerThread implements Runnable {
|
|||||||
}
|
}
|
||||||
if (currentByte == -1) {
|
if (currentByte == -1) {
|
||||||
notifyDisconnectListeners("Cannot read from server.");
|
notifyDisconnectListeners("Cannot read from server.");
|
||||||
closeSocket();
|
|
||||||
logger.warn("InputStream reach end of stream", 1);
|
logger.warn("InputStream reach end of stream", 1);
|
||||||
|
handleConnectionError("Could not connect to server. Server is no longer available.");
|
||||||
|
closeSocket();
|
||||||
}
|
}
|
||||||
return currentByte;
|
return currentByte;
|
||||||
}
|
}
|
||||||
@@ -359,7 +420,32 @@ public class ClientToServerThread implements Runnable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int getClientId () {
|
public int getClientId () {
|
||||||
return clientId;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +1,35 @@
|
|||||||
package seng302.visualiser;
|
package seng302.visualiser;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
import java.time.ZoneId;
|
import java.time.ZoneId;
|
||||||
import java.time.ZoneOffset;
|
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.Map;
|
||||||
import java.util.TimeZone;
|
import java.util.TimeZone;
|
||||||
|
import java.util.Timer;
|
||||||
|
import java.util.TimerTask;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.collections.FXCollections;
|
import javafx.collections.FXCollections;
|
||||||
import javafx.collections.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
import javafx.fxml.FXMLLoader;
|
import javafx.scene.input.KeyCode;
|
||||||
import javafx.scene.Node;
|
|
||||||
import javafx.scene.control.Alert;
|
|
||||||
import javafx.scene.control.Alert.AlertType;
|
|
||||||
import javafx.scene.input.KeyEvent;
|
import javafx.scene.input.KeyEvent;
|
||||||
import javafx.scene.layout.Pane;
|
import javafx.scene.layout.Pane;
|
||||||
|
import javafx.scene.paint.Color;
|
||||||
|
import javafx.util.Pair;
|
||||||
|
import seng302.gameServer.GameStages;
|
||||||
import seng302.gameServer.GameState;
|
import seng302.gameServer.GameState;
|
||||||
import seng302.gameServer.MainServerThread;
|
import seng302.gameServer.MainServerThread;
|
||||||
|
import seng302.gameServer.ServerDescription;
|
||||||
import seng302.gameServer.messages.BoatAction;
|
import seng302.gameServer.messages.BoatAction;
|
||||||
import seng302.gameServer.messages.BoatStatus;
|
import seng302.gameServer.messages.BoatStatus;
|
||||||
|
import seng302.gameServer.messages.YachtEventType;
|
||||||
import seng302.model.ClientYacht;
|
import seng302.model.ClientYacht;
|
||||||
|
import seng302.model.GameKeyBind;
|
||||||
|
import seng302.model.KeyAction;
|
||||||
import seng302.model.RaceState;
|
import seng302.model.RaceState;
|
||||||
import seng302.model.stream.packets.StreamPacket;
|
import seng302.model.stream.packets.StreamPacket;
|
||||||
import seng302.model.stream.parser.MarkRoundingData;
|
import seng302.model.stream.parser.MarkRoundingData;
|
||||||
@@ -28,12 +39,21 @@ import seng302.model.stream.parser.RaceStatusData;
|
|||||||
import seng302.model.stream.parser.YachtEventData;
|
import seng302.model.stream.parser.YachtEventData;
|
||||||
import seng302.model.stream.xml.parser.RaceXMLData;
|
import seng302.model.stream.xml.parser.RaceXMLData;
|
||||||
import seng302.model.stream.xml.parser.RegattaXMLData;
|
import seng302.model.stream.xml.parser.RegattaXMLData;
|
||||||
|
import seng302.model.token.TokenType;
|
||||||
|
import seng302.utilities.Sounds;
|
||||||
import seng302.utilities.StreamParser;
|
import seng302.utilities.StreamParser;
|
||||||
|
import seng302.utilities.XMLGenerator;
|
||||||
import seng302.utilities.XMLParser;
|
import seng302.utilities.XMLParser;
|
||||||
import seng302.visualiser.controllers.FinishScreenViewController;
|
|
||||||
import seng302.visualiser.controllers.LobbyController;
|
import seng302.visualiser.controllers.LobbyController;
|
||||||
import seng302.visualiser.controllers.LobbyController.CloseStatus;
|
|
||||||
import seng302.visualiser.controllers.RaceViewController;
|
import seng302.visualiser.controllers.RaceViewController;
|
||||||
|
import seng302.visualiser.controllers.ViewManager;
|
||||||
|
import seng302.visualiser.controllers.dialogs.PopupDialogController;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
import java.time.ZoneOffset;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class is a client side instance of a yacht racing game in JavaFX. The game is instantiated
|
* This class is a client side instance of a yacht racing game in JavaFX. The game is instantiated
|
||||||
@@ -52,6 +72,11 @@ public class GameClient {
|
|||||||
private RaceXMLData courseData;
|
private RaceXMLData courseData;
|
||||||
private RaceState raceState = new RaceState();
|
private RaceState raceState = new RaceState();
|
||||||
private LobbyController lobbyController;
|
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();
|
private ObservableList<String> clientLobbyList = FXCollections.observableArrayList();
|
||||||
|
|
||||||
@@ -62,6 +87,7 @@ public class GameClient {
|
|||||||
*/
|
*/
|
||||||
public GameClient(Pane holder) {
|
public GameClient(Pane holder) {
|
||||||
this.holderPane = holder;
|
this.holderPane = holder;
|
||||||
|
this.gameKeyBind = GameKeyBind.getInstance();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -74,95 +100,117 @@ public class GameClient {
|
|||||||
startClientToServerThread(ipAddress, portNumber);
|
startClientToServerThread(ipAddress, portNumber);
|
||||||
socketThread.addDisconnectionListener((cause) -> {
|
socketThread.addDisconnectionListener((cause) -> {
|
||||||
showConnectionError(cause);
|
showConnectionError(cause);
|
||||||
Platform.runLater(this::loadStartScreen);
|
tearDownConnection();
|
||||||
});
|
});
|
||||||
socketThread.addStreamObserver(this::parsePackets);
|
socketThread.addStreamObserver(this::parsePackets);
|
||||||
LobbyController lobbyController = loadLobby();
|
|
||||||
lobbyController.setSocketThread(socketThread);
|
ViewManager.getInstance().setPlayerList(clientLobbyList);
|
||||||
lobbyController.setPlayerID(socketThread.getClientId());
|
|
||||||
lobbyController.setPlayerListSource(clientLobbyList);
|
while (regattaData == null){
|
||||||
lobbyController.disableReadyButton();
|
try {
|
||||||
if (regattaData != null){
|
Thread.sleep(100);
|
||||||
lobbyController.setTitle(regattaData.getRegattaName());
|
} catch (InterruptedException e) {
|
||||||
lobbyController.setCourseName(regattaData.getCourseName());
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
else{
|
|
||||||
lobbyController.setTitle(ipAddress);
|
|
||||||
lobbyController.setCourseName("");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
lobbyController.addCloseListener((exitCause) -> this.loadStartScreen());
|
ViewManager.getInstance().setProperty("serverName", regattaData.getRegattaName());
|
||||||
this.lobbyController = lobbyController;
|
ViewManager.getInstance().setProperty("mapName", regattaData.getCourseName());
|
||||||
|
|
||||||
|
getServerThread().setConnectionErrorListener((eMessage) -> {
|
||||||
|
ViewManager.getInstance().showErrorSnackBar(eMessage);
|
||||||
|
//destroyClientToServerThread();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.lobbyController = ViewManager.getInstance().goToLobby(true);
|
||||||
|
|
||||||
} catch (IOException ioe) {
|
} catch (IOException ioe) {
|
||||||
showConnectionError("Unable to find server");
|
ViewManager.getInstance().showErrorSnackBar("There are no servers currently available.");
|
||||||
Platform.runLater(this::loadStartScreen);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void destroyClientToServerThread() {
|
||||||
|
socketThread.closeSocket();
|
||||||
|
socketThread = null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Connect to a game as the host at the given address and starts the visualiser.
|
* Connect to a game as the host at the given address and starts the visualiser.
|
||||||
* @param ipAddress IP to connect to.
|
* @param ipAddress IP to connect to.
|
||||||
* @param portNumber Port to connect to.
|
* @param portNumber Port to connect to.
|
||||||
*/
|
*/
|
||||||
public void runAsHost(String ipAddress, Integer portNumber) {
|
public ServerDescription runAsHost(
|
||||||
|
String ipAddress, Integer portNumber, String serverName, Integer maxPlayers, String race,
|
||||||
|
Integer numLegs, Boolean tokensEnabled
|
||||||
|
) {
|
||||||
|
XMLGenerator.setDefaultRaceName(serverName);
|
||||||
|
|
||||||
server = new MainServerThread();
|
server = new MainServerThread();
|
||||||
|
|
||||||
|
while (!server.hasStarted()){
|
||||||
try {
|
try {
|
||||||
startClientToServerThread(ipAddress, portNumber);
|
Thread.sleep(10);
|
||||||
socketThread.addDisconnectionListener((cause) -> {
|
} catch (InterruptedException e) {
|
||||||
Platform.runLater(this::loadStartScreen);
|
e.printStackTrace();
|
||||||
});
|
}
|
||||||
LobbyController lobbyController = loadLobby();
|
|
||||||
lobbyController.setSocketThread(socketThread);
|
|
||||||
lobbyController.setPlayerID(socketThread.getClientId());
|
|
||||||
lobbyController.setPlayerListSource(clientLobbyList);
|
|
||||||
if (regattaData != null) {
|
|
||||||
lobbyController.setTitle("Hosting: " + regattaData.getRegattaName());
|
|
||||||
lobbyController.setCourseName(regattaData.getCourseName());
|
|
||||||
} else {
|
|
||||||
lobbyController.setTitle("Hosting: " + ipAddress);
|
|
||||||
lobbyController.setCourseName("");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
lobbyController.addCloseListener(exitCause -> {
|
try {
|
||||||
if (exitCause == CloseStatus.READY) {
|
startClientToServerThread(ipAddress, 4942);
|
||||||
GameState.resetStartTime();
|
} catch (IOException e) {
|
||||||
lobbyController.disableReadyButton();
|
|
||||||
server.startGame();
|
|
||||||
} else if (exitCause == CloseStatus.LEAVE) {
|
|
||||||
server.terminate();
|
|
||||||
server = null;
|
|
||||||
loadStartScreen();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.lobbyController = lobbyController;
|
|
||||||
} catch (IOException ioe) {
|
|
||||||
showConnectionError("Cannot connect to server as host");
|
showConnectionError("Cannot connect to server as host");
|
||||||
Platform.runLater(this::loadStartScreen);
|
}
|
||||||
|
|
||||||
|
// Wait for C2S thread
|
||||||
|
while (!socketThread.hasStarted()){
|
||||||
|
try {
|
||||||
|
Thread.sleep(10);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadStartScreen() {
|
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);
|
||||||
|
|
||||||
|
ViewManager.getInstance().setPlayerList(clientLobbyList);
|
||||||
|
return new ServerDescription(serverName, regattaData.getCourseName(), GameState.getNumberOfPlayers(), GameState.getCapacity(), ipAddress, 4942);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void tearDownConnection() {
|
||||||
socketThread.setSocketToClose();
|
socketThread.setSocketToClose();
|
||||||
if (server != null) {
|
if (server != null) {
|
||||||
server.terminate();
|
server.terminate();
|
||||||
server = null;
|
server = null;
|
||||||
}
|
}
|
||||||
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) {
|
private void showConnectionError (String message) {
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
Alert alert = new Alert(AlertType.ERROR);
|
PopupDialogController controller = ViewManager.getInstance().showPopupDialog();
|
||||||
alert.setHeaderText("Connection Error");
|
controller.setHeader("Oops");
|
||||||
alert.setContentText(message);
|
controller.setContent(message);
|
||||||
alert.showAndWait();
|
controller.setOptionButtonText("GO HOME");
|
||||||
|
controller
|
||||||
|
.setOptionButtonEventHandler(event -> ViewManager.getInstance().goToStartView());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,52 +219,8 @@ public class GameClient {
|
|||||||
socketThread.addStreamObserver(this::parsePackets);
|
socketThread.addStreamObserver(this::parsePackets);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public void setRaceViewController(RaceViewController controller) {
|
||||||
* Loads a view of the lobby into the clients pane
|
this.raceViewController = controller;
|
||||||
*
|
|
||||||
* @return the lobby controller.
|
|
||||||
*/
|
|
||||||
private LobbyController loadLobby() {
|
|
||||||
FXMLLoader fxmlLoader = new FXMLLoader(
|
|
||||||
GameClient.class.getResource("/views/LobbyView.fxml"));
|
|
||||||
try {
|
|
||||||
holderPane.getChildren().clear();
|
|
||||||
holderPane.getChildren().add(fxmlLoader.load());
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
return fxmlLoader.getController();
|
|
||||||
}
|
|
||||||
|
|
||||||
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() {
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void parsePackets() {
|
private void parsePackets() {
|
||||||
@@ -245,11 +249,14 @@ public class GameClient {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case RACE_XML:
|
case RACE_XML:
|
||||||
courseData = XMLParser.parseRace(
|
RaceXMLData raceXMLData = XMLParser.parseRace(
|
||||||
StreamParser.extractXmlMessage(packet)
|
StreamParser.extractXmlMessage(packet)
|
||||||
);
|
);
|
||||||
if (raceView != null) {
|
if (courseData == null) { //workaround for object comparisons. Avoid recreating
|
||||||
raceView.updateRaceData(courseData);
|
courseData = raceXMLData;
|
||||||
|
}
|
||||||
|
if (raceView != null) { //Token update
|
||||||
|
raceView.updateTokens(raceXMLData);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -262,11 +269,14 @@ public class GameClient {
|
|||||||
clientLobbyList.add(boat.getBoatName())
|
clientLobbyList.add(boat.getBoatName())
|
||||||
);
|
);
|
||||||
raceState.setBoats(allBoatsMap.values());
|
raceState.setBoats(allBoatsMap.values());
|
||||||
|
if (lobbyController != null) {
|
||||||
|
lobbyController.setBoats(allBoatsMap);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case RACE_START_STATUS:
|
case RACE_START_STATUS:
|
||||||
raceState.updateState(StreamParser.extractRaceStartStatus(packet));
|
raceState.updateState(StreamParser.extractRaceStartStatus(packet));
|
||||||
if (lobbyController != null) lobbyController.updateRaceState(raceState);
|
if (lobbyController != null) Platform.runLater(() -> lobbyController.updateRaceState(raceState));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case BOAT_LOCATION:
|
case BOAT_LOCATION:
|
||||||
@@ -278,15 +288,33 @@ public class GameClient {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case YACHT_EVENT_CODE:
|
case YACHT_EVENT_CODE:
|
||||||
showCollisionAlert(StreamParser.extractYachtEventCode(packet));
|
processYachtEvent(StreamParser.extractYachtEventCode(packet));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case CHATTER_TEXT:
|
||||||
|
Pair<Integer, String> playerIdMessagePair = StreamParser
|
||||||
|
.extractChatterText(packet);
|
||||||
|
raceView.updateChatHistory(
|
||||||
|
allBoatsMap.get(playerIdMessagePair.getKey()).getColour(),
|
||||||
|
playerIdMessagePair.getValue()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void startRaceIfAllDataReceived() {
|
private void startRaceIfAllDataReceived() {
|
||||||
if (allXMLReceived() && raceView == null) {
|
if (allXMLReceived() && raceView == null) {
|
||||||
loadRaceView();
|
raceView = ViewManager.getInstance().loadRaceView();
|
||||||
|
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
sendToggleTurningModePacket(); // notify the server about player's steering mode
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -320,7 +348,6 @@ public class GameClient {
|
|||||||
if (allXMLReceived()) {
|
if (allXMLReceived()) {
|
||||||
ClientYacht clientYacht = allBoatsMap.get(roundingData.getBoatId());
|
ClientYacht clientYacht = allBoatsMap.get(roundingData.getBoatId());
|
||||||
clientYacht.roundMark(
|
clientYacht.roundMark(
|
||||||
courseData.getCompoundMarks().get(roundingData.getMarkId()),
|
|
||||||
roundingData.getTimeStamp(),
|
roundingData.getTimeStamp(),
|
||||||
raceState.getRaceTime() - roundingData.getTimeStamp()
|
raceState.getRaceTime() - roundingData.getTimeStamp()
|
||||||
);
|
);
|
||||||
@@ -335,6 +362,9 @@ public class GameClient {
|
|||||||
for (ClientYacht yacht : allBoatsMap.values()) {
|
for (ClientYacht yacht : allBoatsMap.values()) {
|
||||||
if (yacht.getBoatStatus() != BoatStatus.FINISHED.getCode()) {
|
if (yacht.getBoatStatus() != BoatStatus.FINISHED.getCode()) {
|
||||||
raceFinished = false;
|
raceFinished = false;
|
||||||
|
} else if (!finishedBoats.contains(yacht)) {
|
||||||
|
finishedBoats.add(yacht);
|
||||||
|
Sounds.playFinishSound();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -350,9 +380,13 @@ public class GameClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (raceFinished) {
|
if (raceFinished) {
|
||||||
|
raceViewController.showFinishDialog(finishedBoats);
|
||||||
|
Sounds.playFinishSound();
|
||||||
close();
|
close();
|
||||||
loadFinishScreenView();
|
ViewManager.getInstance().getGameClient().stopGame();
|
||||||
|
//loadFinishScreenView();
|
||||||
}
|
}
|
||||||
|
raceState.setRaceFinished();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -367,35 +401,46 @@ public class GameClient {
|
|||||||
socketThread.setSocketToClose();
|
socketThread.setSocketToClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle the key-pressed event from the text field.
|
* Handle the key-pressed event from the text field.
|
||||||
* @param e The key event triggering this call
|
* @param e The key event triggering this call
|
||||||
*/
|
*/
|
||||||
private void keyPressed(KeyEvent e) {
|
public void keyPressed(KeyEvent e) {
|
||||||
switch (e.getCode()) {
|
if (raceView.isChatInputFocused()) {
|
||||||
case SPACE: // align with vmg
|
if (e.getCode() == KeyCode.ENTER) {
|
||||||
socketThread.sendBoatAction(BoatAction.VMG); break;
|
formatAndSendChatMessage(raceView.readChatInput());
|
||||||
case PAGE_UP: // upwind
|
}
|
||||||
socketThread.sendBoatAction(BoatAction.UPWIND); break;
|
return;
|
||||||
case PAGE_DOWN: // downwind
|
}
|
||||||
socketThread.sendBoatAction(BoatAction.DOWNWIND); break;
|
|
||||||
case ENTER: // tack/gybe
|
if (gameKeyBind.getKeyCode(KeyAction.VMG) == e.getCode()) { // align with vmg
|
||||||
socketThread.sendBoatAction(BoatAction.TACK_GYBE); break;
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void keyReleased(KeyEvent e) {
|
public void keyReleased(KeyEvent e) {
|
||||||
switch (e.getCode()) {
|
if (raceView.isChatInputFocused()) {
|
||||||
//TODO 12/07/17 Determine the sail state and send the appropriate packet (eg. if sails are in, send a sail out packet)
|
return;
|
||||||
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);
|
socketThread.sendBoatAction(BoatAction.SAILS_IN);
|
||||||
|
}
|
||||||
allBoatsMap.get(socketThread.getClientId()).toggleSail();
|
allBoatsMap.get(socketThread.getClientId()).toggleSail();
|
||||||
break;
|
} else if (gameKeyBind.getKeyCode(KeyAction.UPWIND) == e.getCode()
|
||||||
case PAGE_UP:
|
|| gameKeyBind.getKeyCode(KeyAction.DOWNWIND) == e.getCode()) {
|
||||||
case PAGE_DOWN:
|
socketThread.sendBoatAction(BoatAction.MAINTAIN_HEADING);
|
||||||
socketThread.sendBoatAction(BoatAction.MAINTAIN_HEADING); break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -403,17 +448,108 @@ public class GameClient {
|
|||||||
return courseData;
|
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.
|
* Tells race view to show a collision animation.
|
||||||
*/
|
*/
|
||||||
private void showCollisionAlert(YachtEventData yachtEventData) {
|
private void showCollisionAlert(ClientYacht yacht) {
|
||||||
// 33 is the agreed code to show collision
|
Sounds.playCrashSound();
|
||||||
if (yachtEventData.getEventId() == 33) {
|
raceState.storeCollision(yacht);
|
||||||
raceState.storeCollision(
|
}
|
||||||
allBoatsMap.get(
|
|
||||||
yachtEventData.getSubjectId().intValue()
|
private void formatAndSendChatMessage(String rawChat) {
|
||||||
)
|
if (rawChat.length() > 0) {
|
||||||
|
socketThread.sendChatterMessage(
|
||||||
|
new SimpleDateFormat("[HH:mm:ss] ").format(new Date()) +
|
||||||
|
allBoatsMap.get(socketThread.getClientId()).getShortName() + ": " + rawChat
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void startGame(){
|
||||||
|
server.startGame();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClientToServerThread getServerThread() {
|
||||||
|
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,847 +1,31 @@
|
|||||||
package seng302.visualiser;
|
package seng302.visualiser;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
import javafx.animation.AnimationTimer;
|
|
||||||
import javafx.animation.KeyFrame;
|
|
||||||
import javafx.animation.KeyValue;
|
|
||||||
import javafx.animation.Timeline;
|
|
||||||
import javafx.application.Platform;
|
|
||||||
import javafx.collections.ObservableList;
|
|
||||||
import javafx.geometry.Point2D;
|
|
||||||
import javafx.scene.Group;
|
import javafx.scene.Group;
|
||||||
import javafx.scene.Node;
|
import javafx.scene.Node;
|
||||||
import javafx.scene.image.ImageView;
|
|
||||||
import javafx.scene.input.KeyCode;
|
|
||||||
import javafx.scene.input.KeyEvent;
|
|
||||||
import javafx.scene.layout.AnchorPane;
|
|
||||||
import javafx.scene.layout.Pane;
|
|
||||||
import javafx.scene.paint.Color;
|
|
||||||
import javafx.scene.paint.Paint;
|
|
||||||
import javafx.scene.shape.Circle;
|
|
||||||
import javafx.scene.shape.Polygon;
|
|
||||||
import javafx.scene.text.Text;
|
|
||||||
import javafx.util.Duration;
|
|
||||||
import seng302.model.ClientYacht;
|
|
||||||
import seng302.gameServer.messages.RoundingSide;
|
|
||||||
import seng302.model.ClientYacht;
|
|
||||||
import seng302.model.Colors;
|
|
||||||
import seng302.model.GeoPoint;
|
|
||||||
import seng302.model.Limit;
|
import seng302.model.Limit;
|
||||||
|
import seng302.model.ScaledPoint;
|
||||||
import seng302.model.mark.CompoundMark;
|
import seng302.model.mark.CompoundMark;
|
||||||
import seng302.model.mark.Corner;
|
import seng302.model.mark.Corner;
|
||||||
import seng302.model.mark.Mark;
|
|
||||||
import seng302.utilities.GeoUtility;
|
|
||||||
import seng302.visualiser.fxObjects.AnnotationBox;
|
|
||||||
import seng302.visualiser.fxObjects.BoatObject;
|
|
||||||
import seng302.visualiser.fxObjects.CourseBoundary;
|
|
||||||
import seng302.visualiser.fxObjects.Gate;
|
|
||||||
import seng302.visualiser.fxObjects.MarkArrowFactory;
|
|
||||||
import seng302.visualiser.fxObjects.Marker;
|
|
||||||
import seng302.visualiser.map.Boundary;
|
|
||||||
import seng302.visualiser.map.CanvasMap;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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;
|
double canvasWidth, canvasHeight;
|
||||||
private double panelWidth = 1260; // it should be 1280 but, minors 40 to cancel the bias.
|
ScaledPoint scaledPoint;
|
||||||
private double panelHeight = 960;
|
|
||||||
private double canvasWidth = 1100;
|
List<Limit> borderPoints;
|
||||||
private double canvasHeight = 920;
|
Group gameObjects = new Group();
|
||||||
private boolean horizontalInversion = false;
|
Group markers = new Group();
|
||||||
|
Group tokens = new Group();
|
||||||
private double distanceScaleFactor;
|
List<CompoundMark> course = new ArrayList<>();
|
||||||
private ScaleDirection scaleDirection;
|
List<CompoundMark> compoundMarks = new ArrayList<>();
|
||||||
private GeoPoint minLatPoint, minLonPoint, maxLatPoint, maxLonPoint;
|
List<Corner> courseOrder = new ArrayList<>();
|
||||||
private double referencePointX, referencePointY;
|
|
||||||
private double metersPerPixelX, metersPerPixelY;
|
public abstract Node getAssets();
|
||||||
|
public abstract void updateCourse(List<CompoundMark> newCourse, List<Corner> sequence);
|
||||||
final double SCALE_DELTA = 1.1;
|
public abstract void updateBorder(List<Limit> border);
|
||||||
|
|
||||||
private Text fpsDisplay = new Text();
|
|
||||||
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, Marker> markerObjects;
|
|
||||||
|
|
||||||
private Map<ClientYacht, BoatObject> boatObjects = new HashMap<>();
|
|
||||||
private Map<ClientYacht, AnnotationBox> annotations = new HashMap<>();
|
|
||||||
private ObservableList<Node> gameObjects;
|
|
||||||
private BoatObject selectedBoat = null;
|
|
||||||
private Group annotationsGroup = new Group();
|
|
||||||
private Group wakesGroup = new Group();
|
|
||||||
private Group boatObjectGroup = new Group();
|
|
||||||
private Group trails = new Group();
|
|
||||||
private Group markers = new Group();
|
|
||||||
private List<CompoundMark> course = new ArrayList<>();
|
|
||||||
|
|
||||||
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 void zoomOut() {
|
|
||||||
scaleFactor = 0.1;
|
|
||||||
if (this.getScaleX() > 0.5) {
|
|
||||||
this.setScaleX(this.getScaleX() - scaleFactor);
|
|
||||||
this.setScaleY(this.getScaleY() - scaleFactor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void zoomIn() {
|
|
||||||
scaleFactor = 0.10;
|
|
||||||
if (this.getScaleX() < 2.5) {
|
|
||||||
this.setScaleX(this.getScaleX() + scaleFactor);
|
|
||||||
this.setScaleY(this.getScaleY() + scaleFactor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private enum ScaleDirection {
|
|
||||||
HORIZONTAL,
|
|
||||||
VERTICAL
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void trackBoat() {
|
|
||||||
if (selectedBoat != null) {
|
|
||||||
double x = selectedBoat.getBoatLayoutX();
|
|
||||||
double y = selectedBoat.getBoatLayoutY();
|
|
||||||
double displacementX = this.getWidth();
|
|
||||||
double displacementY = this.getHeight();
|
|
||||||
this.setLayoutX((-x + (displacementX / 2.0)) * this.getScaleX());
|
|
||||||
this.setLayoutY((-y + (displacementY / 2.0)) * this.getScaleY());
|
|
||||||
} else {
|
|
||||||
this.setLayoutX(0);
|
|
||||||
this.setLayoutY(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public GameView () {
|
|
||||||
gameObjects = this.getChildren();
|
|
||||||
// create image view for map, bind panel size to image
|
|
||||||
gameObjects.add(mapImage);
|
|
||||||
gameObjects.add(raceBorder);
|
|
||||||
gameObjects.add(markers);
|
|
||||||
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) {
|
|
||||||
trackBoat();
|
|
||||||
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());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* First find the top right and bottom left points' geo locations, then retrieve map from google
|
|
||||||
* to display on image view. - Haoming 22/5/2017
|
|
||||||
*/
|
|
||||||
private void drawGoogleMap() {
|
|
||||||
findMetersPerPixel();
|
|
||||||
Point2D topLeftPoint = findScaledXY(maxLatPoint.getLat(), minLonPoint.getLng());
|
|
||||||
// distance from top left extreme to panel origin (top left corner)
|
|
||||||
double distanceFromTopLeftToOrigin = Math.sqrt(
|
|
||||||
Math.pow(topLeftPoint.getX() * metersPerPixelX, 2) + Math
|
|
||||||
.pow(topLeftPoint.getY() * metersPerPixelY, 2));
|
|
||||||
// angle from top left extreme to panel origin
|
|
||||||
double bearingFromTopLeftToOrigin = Math
|
|
||||||
.toDegrees(Math.atan2(-topLeftPoint.getX(), topLeftPoint.getY()));
|
|
||||||
// the top left extreme
|
|
||||||
GeoPoint topLeftPos = new GeoPoint(maxLatPoint.getLat(), minLonPoint.getLng());
|
|
||||||
GeoPoint originPos = GeoUtility
|
|
||||||
.getGeoCoordinate(topLeftPos, bearingFromTopLeftToOrigin, distanceFromTopLeftToOrigin);
|
|
||||||
|
|
||||||
// distance from origin corner to bottom right corner of the panel
|
|
||||||
double distanceFromOriginToBottomRight = Math.sqrt(
|
|
||||||
Math.pow(panelHeight * metersPerPixelY, 2) + Math
|
|
||||||
.pow(panelWidth * metersPerPixelX, 2));
|
|
||||||
double bearingFromOriginToBottomRight = Math
|
|
||||||
.toDegrees(Math.atan2(panelWidth, -panelHeight));
|
|
||||||
GeoPoint bottomRightPos = GeoUtility
|
|
||||||
.getGeoCoordinate(originPos, bearingFromOriginToBottomRight,
|
|
||||||
distanceFromOriginToBottomRight);
|
|
||||||
|
|
||||||
Boundary boundary = new Boundary(originPos.getLat(), bottomRightPos.getLng(),
|
|
||||||
bottomRightPos.getLat(), originPos.getLng());
|
|
||||||
CanvasMap canvasMap = new CanvasMap(boundary);
|
|
||||||
mapImage.setImage(canvasMap.getMapImage());
|
|
||||||
mapImage.fitWidthProperty().bind(((AnchorPane) this.getParent()).heightProperty());
|
|
||||||
mapImage.fitHeightProperty().bind(((AnchorPane) this.getParent()).heightProperty());
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: 16/08/17 Break up this function
|
|
||||||
/**
|
|
||||||
* Adds a course to the GameView. The view is scaled accordingly unless a border is set in which
|
|
||||||
* case the course is added relative ot the border.
|
|
||||||
*
|
|
||||||
* @param newCourse the mark objects that make up the course.
|
|
||||||
* @param sequence The sequence the marks travel through
|
|
||||||
*/
|
|
||||||
public void updateCourse(List<CompoundMark> newCourse, List<Corner> sequence) {
|
|
||||||
markerObjects = new HashMap<>();
|
|
||||||
|
|
||||||
for (Corner corner : sequence) { //Makes course out of all compound marks.
|
|
||||||
for (CompoundMark compoundMark : newCourse) {
|
|
||||||
if (corner.getCompoundMarkID() == compoundMark.getId()) {
|
|
||||||
course.add(compoundMark);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: 16/08/17 Updating mark roundings here. It should not happen here. Nor should it be done this way.
|
|
||||||
for (Corner corner : sequence){
|
|
||||||
CompoundMark compoundMark = course.get(corner.getSeqID() - 1);
|
|
||||||
compoundMark.setRoundingSide(
|
|
||||||
RoundingSide.getRoundingSide(corner.getRounding())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
final List<Gate> gates = new ArrayList<>();
|
|
||||||
Paint colour = Color.BLACK;
|
|
||||||
//Creates new markers
|
|
||||||
for (CompoundMark cMark : newCourse) {
|
|
||||||
//Set start and end colour
|
|
||||||
if (cMark.getId() == sequence.get(0).getCompoundMarkID()) {
|
|
||||||
colour = Color.GREEN;
|
|
||||||
} else if (cMark.getId() == sequence.get(sequence.size() - 1).getCompoundMarkID()) {
|
|
||||||
colour = Color.RED;
|
|
||||||
}
|
|
||||||
//Create mark dots
|
|
||||||
for (Mark mark : cMark.getMarks()) {
|
|
||||||
makeAndBindMarker(mark, colour);
|
|
||||||
}
|
|
||||||
//Create gate line
|
|
||||||
if (cMark.isGate()) {
|
|
||||||
for (int i = 1; i < cMark.getMarks().size(); i++) {
|
|
||||||
gates.add(
|
|
||||||
makeAndBindGate(
|
|
||||||
markerObjects.get(cMark.getSubMark(i)),
|
|
||||||
markerObjects.get(cMark.getSubMark(i + 1)),
|
|
||||||
colour
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
colour = Color.BLACK;
|
|
||||||
}
|
|
||||||
|
|
||||||
createMarkArrows(sequence);
|
|
||||||
|
|
||||||
//Scale race to markers if there is no border.
|
|
||||||
if (borderPoints == null) {
|
|
||||||
rescaleRace(new ArrayList<>(markerObjects.keySet()));
|
|
||||||
}
|
|
||||||
//Move the Markers to initial position.
|
|
||||||
markerObjects.forEach(((mark, marker) -> {
|
|
||||||
Point2D p2d = findScaledXY(mark.getLat(), mark.getLng());
|
|
||||||
marker.setLayoutX(p2d.getX());
|
|
||||||
marker.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 = 0;
|
|
||||||
for (Mark mark : course.get(i-1).getMarks()) {
|
|
||||||
numMarks += 1;
|
|
||||||
averageLat += mark.getLat();
|
|
||||||
averageLng += mark.getLng();
|
|
||||||
}
|
|
||||||
GeoPoint lastMarkAv = new GeoPoint(averageLat / numMarks, averageLng / numMarks);
|
|
||||||
numMarks = 0;
|
|
||||||
averageLat = 0;
|
|
||||||
averageLng = 0;
|
|
||||||
for (Mark mark : course.get(i+1).getMarks()) {
|
|
||||||
numMarks += 1;
|
|
||||||
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) {
|
|
||||||
Marker marker = new Marker(colour);
|
|
||||||
// marker.addArrows(MarkArrowFactory.RoundingSide.PORT, ThreadLocalRandom.current().nextDouble(91, 180), ThreadLocalRandom.current().nextDouble(1, 90));
|
|
||||||
markerObjects.put(observableMark, marker);
|
|
||||||
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(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.
|
|
||||||
*/
|
|
||||||
public void updateBorder(List<Limit> border) {
|
|
||||||
if (borderPoints == null) {
|
|
||||||
borderPoints = border;
|
|
||||||
rescaleRace(new ArrayList<>(borderPoints));
|
|
||||||
}
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: 16/08/17 initialize zooming internal to GameView only
|
|
||||||
/**
|
|
||||||
* Enables zoom. Has to be called after this is added to a scene.
|
|
||||||
*/
|
|
||||||
public void enableZoom () {
|
|
||||||
if (this.getScene() != null) {
|
|
||||||
this.getScene().addEventHandler(KeyEvent.KEY_PRESSED, (event) -> {
|
|
||||||
if (event.getCode() == KeyCode.Z) {
|
|
||||||
zoomIn();
|
|
||||||
} else if (event.getCode() == KeyCode.X) {
|
|
||||||
zoomOut();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* 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 setSelectedBoat(BoatObject bo, Boolean isSelected) {
|
|
||||||
if (this.selectedBoat == bo && !isSelected) {
|
|
||||||
this.selectedBoat = null;
|
|
||||||
boatObjects.forEach((boat, group) ->
|
|
||||||
group.setIsSelected(false)
|
|
||||||
);
|
|
||||||
} else if (isSelected) {
|
|
||||||
this.selectedBoat = bo;
|
|
||||||
for (BoatObject group : boatObjects.values()) {
|
|
||||||
if (group != bo) {
|
|
||||||
group.setIsSelected(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Draws all the boats.
|
|
||||||
* @param yachts The yachts to set in the race
|
|
||||||
*/
|
|
||||||
public void setBoats(List<ClientYacht> yachts) {
|
|
||||||
BoatObject newBoat;
|
|
||||||
final List<Group> wakes = new ArrayList<>();
|
|
||||||
for (ClientYacht clientYacht : yachts) {
|
|
||||||
Paint colour = clientYacht.getColour();
|
|
||||||
newBoat = new BoatObject();
|
|
||||||
newBoat.addSelectedBoatListener(this::setSelectedBoat);
|
|
||||||
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());
|
|
||||||
// TODO: 1/08/17 Make this less vile to look at.
|
|
||||||
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);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
annotationsGroup.getChildren().addAll(annotations.values());
|
|
||||||
Platform.runLater(() -> {
|
|
||||||
gameObjects.addAll(trails);
|
|
||||||
gameObjects.addAll(wakes);
|
|
||||||
gameObjects.addAll(annotationsGroup);
|
|
||||||
gameObjects.addAll(boatObjectGroup);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void createAndBindAnnotationBox(ClientYacht clientYacht, Paint colour) {
|
|
||||||
AnnotationBox newAnnotation = new AnnotationBox();
|
|
||||||
newAnnotation.setFill(colour);
|
|
||||||
newAnnotation.addAnnotation(
|
|
||||||
"name", "Player: " + clientYacht.getShortName()
|
|
||||||
);
|
|
||||||
// newAnnotation.addAnnotation(
|
|
||||||
// "velocity",
|
|
||||||
// yacht.getVelocityProperty(),
|
|
||||||
// (velocity) -> String.format("Speed: %.2f ms", velocity.doubleValue())
|
|
||||||
// );
|
|
||||||
// newAnnotation.addAnnotation(
|
|
||||||
// "nextMark",
|
|
||||||
// yacht.timeTillNextProperty(),
|
|
||||||
// (time) -> {
|
|
||||||
// DateFormat format = new SimpleDateFormat("mm:ss");
|
|
||||||
// return format.format(time);
|
|
||||||
// }
|
|
||||||
// );
|
|
||||||
// newAnnotation.addAnnotation(
|
|
||||||
// "lastMark",
|
|
||||||
// yacht.timeTillNextProperty(),
|
|
||||||
// (time) -> {
|
|
||||||
// DateFormat format = new SimpleDateFormat("mm:ss");
|
|
||||||
// return format.format(time);
|
|
||||||
// }
|
|
||||||
// );
|
|
||||||
annotations.put(clientYacht, newAnnotation);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void drawFps(Double fps) {
|
|
||||||
Platform.runLater(() -> fpsDisplay.setText(String.format("%d FPS", Math.round(fps))));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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;
|
|
||||||
}
|
|
||||||
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(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 += 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Find the number of meters per pixel.
|
|
||||||
*/
|
|
||||||
private void findMetersPerPixel() {
|
|
||||||
Point2D p1, p2;
|
|
||||||
GeoPoint g1, g2;
|
|
||||||
double theta, distance, dx, dy, dHorizontal, dVertical;
|
|
||||||
g1 = new GeoPoint(maxLatPoint.getLat(), minLonPoint.getLng());
|
|
||||||
g2 = new GeoPoint(minLatPoint.getLat(), maxLatPoint.getLng());
|
|
||||||
p1 = findScaledXY(new GeoPoint(maxLatPoint.getLat(), minLonPoint.getLng()));
|
|
||||||
p2 = findScaledXY(new GeoPoint(minLatPoint.getLat(), maxLatPoint.getLng()));
|
|
||||||
theta = GeoUtility.getBearingRad(g1, g2);
|
|
||||||
distance = GeoUtility.getDistance(g1, g2);
|
|
||||||
dHorizontal = Math.abs(Math.sin(theta) * distance);
|
|
||||||
dVertical = Math.abs(Math.cos(theta) * distance);
|
|
||||||
dx = Math.abs(p1.getX() - p2.getX());
|
|
||||||
dy = Math.abs(p1.getY() - p2.getY());
|
|
||||||
metersPerPixelX = dHorizontal / dx;
|
|
||||||
metersPerPixelY = dVertical / dy;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setAnnotationVisibilities(boolean teamName, boolean velocity, boolean estTime,
|
|
||||||
boolean legTime, boolean trail, boolean wake) {
|
|
||||||
for (BoatObject boatObject : boatObjects.values()) {
|
|
||||||
boatObject.setVisibility(teamName, velocity, estTime, legTime, trail, wake);
|
|
||||||
}
|
|
||||||
for (AnnotationBox ag : annotations.values()) {
|
|
||||||
ag.setAnnotationVisibility("name", teamName);
|
|
||||||
ag.setAnnotationVisibility("velocity", velocity);
|
|
||||||
ag.setAnnotationVisibility("nextMark", estTime);
|
|
||||||
ag.setAnnotationVisibility("lastMark", legTime);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFPSVisibility(boolean visibility) {
|
|
||||||
fpsDisplay.setVisible(visibility);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void selectBoat(ClientYacht selectedClientYacht) {
|
|
||||||
boatObjects.forEach((boat, group) ->
|
|
||||||
group.setIsSelected(boat == selectedClientYacht)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void pauseRace() {
|
|
||||||
timer.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setWindDir(double windDir) {
|
|
||||||
this.windDir = windDir;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void startRace() {
|
|
||||||
timer.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
public ClientYacht getPlayerYacht() {
|
|
||||||
return playerYacht;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setBoatAsPlayer (ClientYacht playerYacht) {
|
|
||||||
this.playerYacht = playerYacht;
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateMarkArrows (ClientYacht yacht, CompoundMark compoundMark, int legNumber) {
|
|
||||||
//Only show arrows for this and next leg.
|
|
||||||
if (compoundMark != null) {
|
|
||||||
for (Mark mark : compoundMark.getMarks()) {
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Given yacht geopoint by race view controller, drawCollision will calculate canvas X and Y and
|
|
||||||
* display a flashing red circle on collision point.
|
|
||||||
*
|
|
||||||
* @param collisionPoint yacht collision point
|
|
||||||
*/
|
|
||||||
public void drawCollision(GeoPoint collisionPoint) {
|
|
||||||
Point2D point = findScaledXY(collisionPoint);
|
|
||||||
double circleRadius = 0.0;
|
|
||||||
Circle circle = new Circle(point.getX(), point.getY(), circleRadius, Color.RED);
|
|
||||||
|
|
||||||
circle.setFill(Color.TRANSPARENT);
|
|
||||||
circle.setStroke(Color.RED);
|
|
||||||
circle.setStrokeWidth(3);
|
|
||||||
|
|
||||||
Timeline timeline = new Timeline();
|
|
||||||
timeline.setCycleCount(1);
|
|
||||||
|
|
||||||
KeyFrame keyframe1 = new KeyFrame(Duration.ZERO,
|
|
||||||
new KeyValue(circle.radiusProperty(), 0),
|
|
||||||
new KeyValue(circle.strokeProperty(), Color.TRANSPARENT));
|
|
||||||
KeyFrame keyFrame2 = new KeyFrame(new Duration(1000),
|
|
||||||
new KeyValue(circle.radiusProperty(), 50),
|
|
||||||
new KeyValue(circle.strokeProperty(), Color.RED));
|
|
||||||
KeyFrame keyFrame3 = new KeyFrame(new Duration(1500),
|
|
||||||
new KeyValue(circle.strokeProperty(), Color.TRANSPARENT));
|
|
||||||
|
|
||||||
timeline.getKeyFrames().addAll(keyframe1, keyFrame2, keyFrame3);
|
|
||||||
|
|
||||||
Platform.runLater(() -> gameObjects.add(circle));
|
|
||||||
timeline.setOnFinished(event -> Platform.runLater(() -> gameObjects.remove(circle)));
|
|
||||||
timeline.play();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFrameRateFXText(Text fpsDisplay) {
|
|
||||||
this.fpsDisplay = null;
|
|
||||||
this.fpsDisplay = fpsDisplay;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,564 @@
|
|||||||
|
package seng302.visualiser;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import javafx.animation.AnimationTimer;
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.beans.value.ObservableValue;
|
||||||
|
import javafx.geometry.Point2D;
|
||||||
|
import javafx.geometry.Point3D;
|
||||||
|
import javafx.scene.Camera;
|
||||||
|
import javafx.scene.Group;
|
||||||
|
import javafx.scene.Node;
|
||||||
|
import javafx.scene.PerspectiveCamera;
|
||||||
|
import javafx.scene.SceneAntialiasing;
|
||||||
|
import javafx.scene.SubScene;
|
||||||
|
import javafx.scene.image.Image;
|
||||||
|
import javafx.scene.input.KeyEvent;
|
||||||
|
import javafx.scene.paint.Color;
|
||||||
|
import javafx.scene.transform.Rotate;
|
||||||
|
import javafx.scene.transform.Scale;
|
||||||
|
import javafx.scene.transform.Translate;
|
||||||
|
import org.fxyz3d.scene.Skybox;
|
||||||
|
import seng302.gameServer.messages.RoundingSide;
|
||||||
|
import seng302.model.ClientYacht;
|
||||||
|
import seng302.model.GameKeyBind;
|
||||||
|
import seng302.model.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.utilities.Sounds;
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collection of animated3D assets that displays a race.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class GameView3D extends GameView{
|
||||||
|
|
||||||
|
private final double FOV = 60;
|
||||||
|
private final double DEFAULT_CAMERA_X = 0;
|
||||||
|
private final double DEFAULT_CAMERA_Y = 100;
|
||||||
|
|
||||||
|
private Group root3D;
|
||||||
|
private SubScene view;
|
||||||
|
private Group gameObjects;
|
||||||
|
|
||||||
|
private Group raceBorder = new Group();
|
||||||
|
// Cameras
|
||||||
|
private PerspectiveCamera isometricCam;
|
||||||
|
private PerspectiveCamera topDownCam;
|
||||||
|
private PerspectiveCamera chaseCam;
|
||||||
|
|
||||||
|
/* 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 Map<Mark, Marker3D> markerObjects;
|
||||||
|
|
||||||
|
private BoatObject playerBoat;
|
||||||
|
private Map<ClientYacht, BoatObject> boatObjects = new HashMap<>();
|
||||||
|
private Group wakesGroup = new Group();
|
||||||
|
private Group boatObjectGroup = new Group();
|
||||||
|
private List<Node> mapTokens;
|
||||||
|
private AnimationTimer playerBoatAnimationTimer;
|
||||||
|
private Group trail = new Group();
|
||||||
|
private Double windDir;
|
||||||
|
private Skybox skybox;
|
||||||
|
|
||||||
|
public GameView3D () {
|
||||||
|
isometricCam = new IsometricCamera(DEFAULT_CAMERA_X, DEFAULT_CAMERA_Y);
|
||||||
|
topDownCam = new TopDownCamera();
|
||||||
|
chaseCam = new ChaseCamera();
|
||||||
|
|
||||||
|
canvasWidth = canvasHeight = 300;
|
||||||
|
|
||||||
|
for (PerspectiveCamera pc : Arrays.asList(isometricCam, topDownCam, chaseCam)) {
|
||||||
|
pc.setFarClip(100000);
|
||||||
|
pc.setNearClip(0.1);
|
||||||
|
pc.setFieldOfView(FOV);
|
||||||
|
}
|
||||||
|
|
||||||
|
gameObjects = new Group();
|
||||||
|
root3D = new Group(isometricCam, gameObjects);
|
||||||
|
view = new SubScene(
|
||||||
|
root3D, 5000, 3000, true, SceneAntialiasing.BALANCED
|
||||||
|
);
|
||||||
|
view.setCamera(isometricCam);
|
||||||
|
|
||||||
|
skybox = new Skybox(new Image(getClass().getResourceAsStream("/images/skybox.jpg")), 100000, isometricCam);
|
||||||
|
skybox.getTransforms().addAll(new Rotate(90, Rotate.X_AXIS));
|
||||||
|
|
||||||
|
Model land = ModelFactory.importModel(ModelType.LAND_SMOOTH);
|
||||||
|
land.getAssets().getTransforms().add(new Rotate(90, Rotate.X_AXIS));
|
||||||
|
|
||||||
|
gameObjects.getChildren().addAll(
|
||||||
|
raceBorder, trail, markers, tokens, skybox, land.getAssets()
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
view.sceneProperty().addListener((obs, old, scene) -> {
|
||||||
|
if (scene != null) {
|
||||||
|
scene.addEventHandler(KeyEvent.KEY_PRESSED, this::cameraMovement);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@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) {
|
||||||
|
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<Group> gates = new ArrayList<>();
|
||||||
|
|
||||||
|
//Creates new markers
|
||||||
|
for (CompoundMark cMark : newCourse) {
|
||||||
|
for (Mark mark : cMark.getMarks()) {
|
||||||
|
if (cMark.getId() == sequence.get(0).getCompoundMarkID()) {
|
||||||
|
makeAndBindMarker(mark, ModelType.START_MARKER);
|
||||||
|
} else if (cMark.getId() == sequence.get(sequence.size() - 1).getCompoundMarkID()) {
|
||||||
|
makeAndBindMarker(mark, ModelType.FINISH_MARKER);
|
||||||
|
} else {
|
||||||
|
makeAndBindMarker(mark, ModelType.PLAIN_MARKER);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//Create gate line
|
||||||
|
if (cMark.isGate()) {
|
||||||
|
ModelType gateType;
|
||||||
|
if (cMark.getId() == sequence.get(0).getCompoundMarkID()) {
|
||||||
|
gateType = ModelType.START_LINE;
|
||||||
|
} else if (cMark.getId() == sequence.get(sequence.size() - 1).getCompoundMarkID()) {
|
||||||
|
gateType = ModelType.FINISH_LINE;
|
||||||
|
} else {
|
||||||
|
gateType = ModelType.GATE_LINE;
|
||||||
|
}
|
||||||
|
gates.add(makeGate(
|
||||||
|
cMark.getSubMark(1), cMark.getSubMark(2), gateType
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
createMarkArrows();
|
||||||
|
|
||||||
|
//Scale race to markers if there is no border.
|
||||||
|
if (borderPoints == null) {
|
||||||
|
scaledPoint = ScaledPoint.makeScaledPoint(
|
||||||
|
canvasWidth, canvasHeight, new ArrayList<>(markerObjects.keySet()), true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
//Move the Markers to initial position.
|
||||||
|
markerObjects.forEach(((mark, marker) -> {
|
||||||
|
Point2D p2d = scaledPoint.findScaledXY(mark.getLat(), mark.getLng());
|
||||||
|
marker.setLayoutX(p2d.getX());
|
||||||
|
marker.setLayoutY(p2d.getY());
|
||||||
|
}));
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
markers.getChildren().clear();
|
||||||
|
markers.getChildren().addAll(gates);
|
||||||
|
markers.getChildren().addAll(markerObjects.values());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new Marker and binds it's position to the given Mark.
|
||||||
|
*
|
||||||
|
* @param observableMark The mark to bind the marker to.
|
||||||
|
* @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) {
|
||||||
|
markerObjects.put(observableMark, new Marker3D(markerType));
|
||||||
|
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 gateType The type of model for the gate.
|
||||||
|
* @return the new gate.
|
||||||
|
*/
|
||||||
|
private Group makeGate(Mark m1, Mark m2, ModelType gateType) {
|
||||||
|
Point2D m1Location = scaledPoint.findScaledXY(m1);
|
||||||
|
Point2D m2Location = scaledPoint.findScaledXY(m2);
|
||||||
|
|
||||||
|
Group barrier = ModelFactory.importModel(gateType).getAssets();
|
||||||
|
barrier.getTransforms().addAll(
|
||||||
|
new Rotate(
|
||||||
|
Math.toDegrees(
|
||||||
|
Math.atan2(m2Location.getY() - m1Location.getY(), m2Location.getX() - m1Location.getX())
|
||||||
|
) + 90,
|
||||||
|
new Point3D(0,0,1)
|
||||||
|
),
|
||||||
|
new Scale(1, m1Location.distance(m2Location) / 10, 1)
|
||||||
|
);
|
||||||
|
|
||||||
|
Point2D midPoint = m2Location.midpoint(m1Location);
|
||||||
|
barrier.setLayoutX(midPoint.getX());
|
||||||
|
barrier.setLayoutY(midPoint.getY());
|
||||||
|
return barrier;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates all the data needed for to create mark arrows. Requires that a course has been
|
||||||
|
* added to the gameview.
|
||||||
|
*/
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
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())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void cameraMovement(KeyEvent event) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draws all the boats.
|
||||||
|
* @param yachts The yachts to set in the race
|
||||||
|
*/
|
||||||
|
public void setBoats(List<ClientYacht> yachts) {
|
||||||
|
BoatObject newBoat;
|
||||||
|
final List<Group> wakes = new ArrayList<>();
|
||||||
|
for (ClientYacht clientYacht : yachts) {
|
||||||
|
Color colour = clientYacht.getColour();
|
||||||
|
newBoat = new BoatObject(clientYacht.getBoatType());
|
||||||
|
newBoat.setFill(colour);
|
||||||
|
boatObjects.put(clientYacht, newBoat);
|
||||||
|
wakesGroup.getChildren().add(newBoat.getWake());
|
||||||
|
wakes.add(newBoat.getWake());
|
||||||
|
boatObjectGroup.getChildren().add(newBoat);
|
||||||
|
clientYacht.addLocationListener(this::updateBoatLocation);
|
||||||
|
clientYacht.addColorChangeListener(this::updateBoatColor);
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
ClientYacht playerYacht = ViewManager.getInstance().getGameClient().getAllBoatsMap()
|
||||||
|
.get(ViewManager.getInstance().getGameClient().getServerThread().getClientId());
|
||||||
|
|
||||||
|
for (ObservableValue o : Arrays
|
||||||
|
.asList(playerBoat.layoutXProperty(), playerBoat.layoutXProperty())) {
|
||||||
|
o.addListener((obs, oldVal, newVal) -> {
|
||||||
|
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public Node getAssets () {
|
||||||
|
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.
|
||||||
|
*
|
||||||
|
* @param border the race border to be drawn.
|
||||||
|
*/
|
||||||
|
public void updateBorder(List<Limit> border) {
|
||||||
|
if (borderPoints == null) {
|
||||||
|
borderPoints = border;
|
||||||
|
scaledPoint = ScaledPoint.makeScaledPoint(
|
||||||
|
canvasWidth, canvasHeight, new ArrayList<>(borderPoints), true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
List<Node> boundaryAssets = new ArrayList<>();
|
||||||
|
|
||||||
|
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 = 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());
|
||||||
|
|
||||||
|
Group barrier = ModelFactory.importModel(ModelType.BORDER_BARRIER).getAssets();
|
||||||
|
barrier.getTransforms().addAll(
|
||||||
|
new Rotate(
|
||||||
|
Math.toDegrees(
|
||||||
|
Math.atan2(location.getY() - lastLocation.getY(), location.getX() - lastLocation.getX())
|
||||||
|
),
|
||||||
|
new Point3D(0,0,1)
|
||||||
|
),
|
||||||
|
new Scale((lastLocation.distance(location) / 10)-0.2, 1, 1)
|
||||||
|
);
|
||||||
|
|
||||||
|
Point2D midPoint = location.midpoint(lastLocation);
|
||||||
|
barrier.setLayoutX(midPoint.getX());
|
||||||
|
barrier.setLayoutY(midPoint.getY());
|
||||||
|
|
||||||
|
lastLocation = location;
|
||||||
|
|
||||||
|
boundaryAssets.add(barrier);
|
||||||
|
boundaryAssets.add(pylon);
|
||||||
|
}
|
||||||
|
|
||||||
|
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(
|
||||||
|
Math.toDegrees(
|
||||||
|
Math.atan2(lastLocation.getY() - firstLocation.getY(), lastLocation.getX() - firstLocation.getX())
|
||||||
|
),
|
||||||
|
new Point3D(0,0,1)
|
||||||
|
),
|
||||||
|
new Scale((firstLocation.distance(lastLocation) / 10)-0.2, 1, 1)
|
||||||
|
);
|
||||||
|
|
||||||
|
Point2D midPoint = lastLocation.midpoint(firstLocation);
|
||||||
|
barrier.setLayoutX(midPoint.getX());
|
||||||
|
barrier.setLayoutY(midPoint.getY());
|
||||||
|
boundaryAssets.add(barrier);
|
||||||
|
|
||||||
|
Platform.runLater(() -> raceBorder.getChildren().setAll(boundaryAssets));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replaces all tokens in the course with those passed in
|
||||||
|
*
|
||||||
|
* @param newTokens the tokens to be put on the course.
|
||||||
|
*/
|
||||||
|
public void updateTokens(List<Token> newTokens) {
|
||||||
|
mapTokens = new ArrayList<>();
|
||||||
|
for (Token token : newTokens) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
tokens.getChildren().setAll(mapTokens);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBoatAsPlayer (ClientYacht playerYacht) {
|
||||||
|
playerYacht.toggleSail();
|
||||||
|
playerBoatAnimationTimer = new AnimationTimer() {
|
||||||
|
|
||||||
|
Point2D lastLocation = scaledPoint.findScaledXY(playerYacht.getLocation());
|
||||||
|
|
||||||
|
@Override
|
||||||
|
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() > 50) {
|
||||||
|
trail.getChildren().remove(0);
|
||||||
|
}
|
||||||
|
lastLocation = location;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateMarkArrows (ClientYacht yacht, int legNumber) {
|
||||||
|
CompoundMark compoundMark;
|
||||||
|
if (legNumber - 1 >= 0) {
|
||||||
|
Sounds.playMarkRoundingSound();
|
||||||
|
compoundMark = course.get(legNumber-1);
|
||||||
|
for (Mark mark : compoundMark.getMarks()) {
|
||||||
|
markerObjects.get(mark).showNextExitArrow();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CompoundMark nextMark = null;
|
||||||
|
if (legNumber < course.size() - 1) {
|
||||||
|
Sounds.playMarkRoundingSound();
|
||||||
|
nextMark = course.get(legNumber);
|
||||||
|
for (Mark mark : nextMark.getMarks()) {
|
||||||
|
markerObjects.get(mark).showNextEnterArrow();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (legNumber - 2 >= 0) {
|
||||||
|
CompoundMark lastMark = course.get(Math.max(0, legNumber - 2));
|
||||||
|
if (lastMark != nextMark) {
|
||||||
|
for (Mark mark : lastMark.getMarks()) {
|
||||||
|
markerObjects.get(mark).hideAllArrows();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
package seng302.visualiser;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.StringReader;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import javafx.scene.Node;
|
||||||
|
import javafx.util.Pair;
|
||||||
|
import javax.xml.parsers.DocumentBuilder;
|
||||||
|
import javax.xml.parsers.DocumentBuilderFactory;
|
||||||
|
import javax.xml.parsers.ParserConfigurationException;
|
||||||
|
import org.w3c.dom.Document;
|
||||||
|
import org.xml.sax.InputSource;
|
||||||
|
import org.xml.sax.SAXException;
|
||||||
|
import seng302.model.stream.xml.generator.RaceXMLTemplate;
|
||||||
|
import seng302.model.stream.xml.generator.RegattaXMLTemplate;
|
||||||
|
import seng302.model.stream.xml.parser.RaceXMLData;
|
||||||
|
import seng302.model.stream.xml.parser.RegattaXMLData;
|
||||||
|
import seng302.utilities.XMLGenerator;
|
||||||
|
import seng302.utilities.XMLParser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes maps from map definition xml files.
|
||||||
|
*/
|
||||||
|
public class MapMaker {
|
||||||
|
|
||||||
|
private List<MapPreview> mapPreviews = new ArrayList<>();
|
||||||
|
private List<RaceXMLData> races = new ArrayList<>();
|
||||||
|
private List<RegattaXMLData> regattas = new ArrayList<>();
|
||||||
|
private List<String> filePaths = new ArrayList<>();
|
||||||
|
private List<Integer> maxPlayers = new ArrayList<>();
|
||||||
|
private static MapMaker instance;
|
||||||
|
private int index = 0;
|
||||||
|
private XMLGenerator xmlGenerator = new XMLGenerator();
|
||||||
|
|
||||||
|
private List<String> maps = new ArrayList<>(Arrays.asList("default.xml", "horseshoe.xml"));
|
||||||
|
|
||||||
|
public static MapMaker getInstance() {
|
||||||
|
if (instance == null) {
|
||||||
|
instance = new MapMaker();
|
||||||
|
}
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
private MapMaker() {
|
||||||
|
for (String mapPath : maps){
|
||||||
|
String path = ("/maps/" + mapPath);
|
||||||
|
|
||||||
|
Pair<RegattaXMLTemplate, RaceXMLTemplate> regattaRace = XMLParser.parseRaceDef(
|
||||||
|
path, "", 1, null, false
|
||||||
|
);
|
||||||
|
|
||||||
|
RegattaXMLTemplate regattaTemplate = regattaRace.getKey();
|
||||||
|
|
||||||
|
filePaths.add(path);
|
||||||
|
|
||||||
|
regattas.add(new RegattaXMLData(
|
||||||
|
regattaTemplate.getRegattaId(),
|
||||||
|
regattaTemplate.getName(),
|
||||||
|
regattaTemplate.getCourseName(),
|
||||||
|
regattaTemplate.getLatitude(),
|
||||||
|
regattaTemplate.getLongitude(),
|
||||||
|
regattaTemplate.getUtcOffset()
|
||||||
|
));
|
||||||
|
|
||||||
|
RaceXMLTemplate raceTemplate = regattaRace.getValue();
|
||||||
|
xmlGenerator.setRaceTemplate(raceTemplate);
|
||||||
|
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
|
||||||
|
DocumentBuilder db;
|
||||||
|
Document doc = null;
|
||||||
|
try {
|
||||||
|
db = dbf.newDocumentBuilder();
|
||||||
|
doc = db.parse(new InputSource(new StringReader(xmlGenerator.getRaceAsXml())));
|
||||||
|
} catch (ParserConfigurationException | IOException | SAXException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
RaceXMLData race = XMLParser.parseRace(doc);
|
||||||
|
maxPlayers.add(XMLParser.getMaxPlayers(doc));
|
||||||
|
|
||||||
|
mapPreviews.add(new MapPreview(
|
||||||
|
new ArrayList<>(race.getCompoundMarks().values()),
|
||||||
|
race.getMarkSequence(), race.getCourseLimit()
|
||||||
|
));
|
||||||
|
|
||||||
|
races.add(race);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void next() {
|
||||||
|
index += 1;
|
||||||
|
if (index >= mapPreviews.size()) {
|
||||||
|
index = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void previous() {
|
||||||
|
index -= 1;
|
||||||
|
if (index < 0) {
|
||||||
|
index = mapPreviews.size() - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Node getCurrentGameView() {
|
||||||
|
return mapPreviews.get(index).getAssets();
|
||||||
|
}
|
||||||
|
|
||||||
|
public RaceXMLData getCurrentRace() {
|
||||||
|
return races.get(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
public RegattaXMLData getCurrentRegatta() {
|
||||||
|
return regattas.get(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCurrentRacePath() {
|
||||||
|
return filePaths.get(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getMaxPlayers() {
|
||||||
|
return maxPlayers.get(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,283 @@
|
|||||||
|
package seng302.visualiser;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.geometry.Point2D;
|
||||||
|
import javafx.scene.Node;
|
||||||
|
import javafx.scene.paint.Color;
|
||||||
|
import javafx.scene.paint.Paint;
|
||||||
|
import javafx.scene.shape.Polygon;
|
||||||
|
import seng302.gameServer.messages.RoundingSide;
|
||||||
|
import seng302.model.GeoPoint;
|
||||||
|
import seng302.model.Limit;
|
||||||
|
import seng302.model.ScaledPoint;
|
||||||
|
import seng302.model.mark.CompoundMark;
|
||||||
|
import seng302.model.mark.Corner;
|
||||||
|
import seng302.model.mark.Mark;
|
||||||
|
import seng302.utilities.GeoUtility;
|
||||||
|
import seng302.visualiser.fxObjects.MarkArrowFactory;
|
||||||
|
import seng302.visualiser.fxObjects.assets_2D.CourseBoundary;
|
||||||
|
import seng302.visualiser.fxObjects.assets_2D.Gate;
|
||||||
|
import seng302.visualiser.fxObjects.assets_2D.Marker2D;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by cir27 on 20/07/17.
|
||||||
|
*/
|
||||||
|
public class MapPreview extends GameView {
|
||||||
|
|
||||||
|
private Polygon raceBorder = new CourseBoundary();
|
||||||
|
private Map<Mark, Marker2D> markerObjects;
|
||||||
|
|
||||||
|
public MapPreview(List<CompoundMark> marks, List<Corner> course, List<Limit> border) {
|
||||||
|
this.compoundMarks = marks;
|
||||||
|
this.courseOrder = course;
|
||||||
|
this.borderPoints = border;
|
||||||
|
gameObjects.getChildren().addAll(raceBorder, markers, tokens);
|
||||||
|
gameObjects.parentProperty().addListener((obs, old, parent) -> {
|
||||||
|
if (parent != null) {
|
||||||
|
canvasWidth = parent.prefWidth(1);
|
||||||
|
canvasHeight = parent.prefHeight(1);
|
||||||
|
updateBorder(borderPoints);
|
||||||
|
updateCourse(compoundMarks, courseOrder);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Node getAssets() {
|
||||||
|
return gameObjects;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSize(double width, double height) {
|
||||||
|
canvasHeight = height;
|
||||||
|
canvasWidth = width;
|
||||||
|
updateBorder(borderPoints);
|
||||||
|
updateCourse(compoundMarks, courseOrder);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a course to the GameView. The view is scaled accordingly unless a border is set in which
|
||||||
|
* case the course is added relative ot the border.
|
||||||
|
*
|
||||||
|
* @param newCourse the mark objects that make up the course.
|
||||||
|
* @param sequence The sequence the marks travel through
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void updateCourse(List<CompoundMark> newCourse, List<Corner> sequence) {
|
||||||
|
|
||||||
|
if (newCourse.size() == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
compoundMarks = newCourse;
|
||||||
|
markerObjects = new HashMap<>();
|
||||||
|
courseOrder = sequence;
|
||||||
|
|
||||||
|
for (Corner corner : courseOrder) { //Makes course out of all compound marks.
|
||||||
|
for (CompoundMark compoundMark : newCourse) {
|
||||||
|
if (corner.getCompoundMarkID() == compoundMark.getId()) {
|
||||||
|
course.add(compoundMark);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: 16/08/17 Updating mark roundings here. It should not happen here. Nor should it be done this way.
|
||||||
|
for (Corner corner : sequence){
|
||||||
|
CompoundMark compoundMark = course.get(corner.getSeqID() - 1);
|
||||||
|
compoundMark.setRoundingSide(
|
||||||
|
RoundingSide.getRoundingSide(corner.getRounding())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<Gate> gates = new ArrayList<>();
|
||||||
|
Paint colour = Color.BLACK;
|
||||||
|
//Creates new markers
|
||||||
|
for (CompoundMark cMark : newCourse) {
|
||||||
|
//Set start and end colour
|
||||||
|
if (cMark.getId() == sequence.get(0).getCompoundMarkID()) {
|
||||||
|
colour = Color.GREEN;
|
||||||
|
} else if (cMark.getId() == sequence.get(sequence.size() - 1).getCompoundMarkID()) {
|
||||||
|
colour = Color.RED;
|
||||||
|
}
|
||||||
|
//Create mark dots
|
||||||
|
for (Mark mark : cMark.getMarks()) {
|
||||||
|
makeAndBindMarker(mark, colour);
|
||||||
|
}
|
||||||
|
//Create gate line
|
||||||
|
if (cMark.isGate()) {
|
||||||
|
for (int i = 1; i < cMark.getMarks().size(); i++) {
|
||||||
|
gates.add(
|
||||||
|
makeAndBindGate(
|
||||||
|
markerObjects.get(cMark.getSubMark(i)),
|
||||||
|
markerObjects.get(cMark.getSubMark(i + 1)),
|
||||||
|
colour
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
colour = Color.BLACK;
|
||||||
|
}
|
||||||
|
|
||||||
|
createMarkArrows(sequence);
|
||||||
|
|
||||||
|
//Scale race to markers if there is no border.
|
||||||
|
if (borderPoints == null) {
|
||||||
|
scaledPoint = ScaledPoint.makeScaledPoint(
|
||||||
|
canvasWidth, canvasHeight, new ArrayList<>(markerObjects.keySet()), false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
//Move the Markers to initial position.
|
||||||
|
markerObjects.forEach(((mark, marker2D) -> {
|
||||||
|
Point2D p2d = scaledPoint.findScaledXY(mark.getLat(), mark.getLng());
|
||||||
|
marker2D.setLayoutX(p2d.getX());
|
||||||
|
marker2D.setLayoutY(p2d.getY());
|
||||||
|
}));
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
markers.getChildren().clear();
|
||||||
|
markers.getChildren().addAll(gates);
|
||||||
|
markers.getChildren().addAll(markerObjects.values());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates all the data needed for to create mark arrows. Requires that a course has been
|
||||||
|
* added to the gameview.
|
||||||
|
* @param sequence The order in which marks are traversed.
|
||||||
|
*/
|
||||||
|
private void createMarkArrows (List<Corner> sequence) {
|
||||||
|
for (int i=1; i < sequence.size()-1; i++) { //General case.
|
||||||
|
double averageLat = 0;
|
||||||
|
double averageLng = 0;
|
||||||
|
int numMarks = course.get(i-1).getMarks().size();
|
||||||
|
for (Mark mark : course.get(i-1).getMarks()) {
|
||||||
|
averageLat += mark.getLat();
|
||||||
|
averageLng += mark.getLng();
|
||||||
|
}
|
||||||
|
GeoPoint lastMarkAv = new GeoPoint(averageLat / numMarks, averageLng / numMarks);
|
||||||
|
numMarks = course.get(i+1).getMarks().size();
|
||||||
|
averageLat = 0;
|
||||||
|
averageLng = 0;
|
||||||
|
for (Mark mark : course.get(i+1).getMarks()) {
|
||||||
|
averageLat += mark.getLat();
|
||||||
|
averageLng += mark.getLng();
|
||||||
|
}
|
||||||
|
GeoPoint nextMarkAv = new GeoPoint(averageLat / numMarks, averageLng / numMarks);
|
||||||
|
// TODO: 16/08/17 This comparison doesn't need to exist but the alternative is to user server enum client side.
|
||||||
|
for (Mark mark : course.get(i).getMarks()) {
|
||||||
|
markerObjects.get(mark).addArrows(
|
||||||
|
mark.getRoundingSide() == RoundingSide.STARBOARD ? MarkArrowFactory.RoundingSide.STARBOARD : MarkArrowFactory.RoundingSide.PORT,
|
||||||
|
GeoUtility.getBearing(lastMarkAv, mark),
|
||||||
|
GeoUtility.getBearing(mark, nextMarkAv)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
createStartLineArrows();
|
||||||
|
createFinishLineArrows();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createStartLineArrows () {
|
||||||
|
double averageLat = 0;
|
||||||
|
double averageLng = 0;
|
||||||
|
int numMarks = 0;
|
||||||
|
for (Mark mark : course.get(1).getMarks()) {
|
||||||
|
numMarks += 1;
|
||||||
|
averageLat += mark.getLat();
|
||||||
|
averageLng += mark.getLng();
|
||||||
|
}
|
||||||
|
GeoPoint firstMarkAv = new GeoPoint(averageLat / numMarks, averageLng / numMarks);
|
||||||
|
for (Mark mark : course.get(0).getMarks()) {
|
||||||
|
markerObjects.get(mark).addArrows(
|
||||||
|
mark.getRoundingSide() == RoundingSide.STARBOARD ? MarkArrowFactory.RoundingSide.STARBOARD : MarkArrowFactory.RoundingSide.PORT,
|
||||||
|
0d, //90
|
||||||
|
GeoUtility.getBearing(mark, firstMarkAv)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createFinishLineArrows () {
|
||||||
|
double numMarks = 0;
|
||||||
|
double averageLat = 0;
|
||||||
|
double averageLng = 0;
|
||||||
|
for (Mark mark : course.get(course.size()-2).getMarks()) {
|
||||||
|
numMarks += 1;
|
||||||
|
averageLat += mark.getLat();
|
||||||
|
averageLng += mark.getLng();
|
||||||
|
}
|
||||||
|
GeoPoint secondToLastMarkAv = new GeoPoint(averageLat / numMarks, averageLng / numMarks);
|
||||||
|
for (Mark mark : course.get(course.size()-1).getMarks()) {
|
||||||
|
markerObjects.get(mark).addArrows(
|
||||||
|
mark.getRoundingSide() == RoundingSide.STARBOARD ? MarkArrowFactory.RoundingSide.STARBOARD : MarkArrowFactory.RoundingSide.PORT,
|
||||||
|
GeoUtility.getBearing(secondToLastMarkAv, mark),
|
||||||
|
GeoUtility.getBearing(mark, mark)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new Marker and binds it's position to the given Mark.
|
||||||
|
*
|
||||||
|
* @param observableMark The mark to bind the marker to.
|
||||||
|
* @param colour The desired colour of the mark
|
||||||
|
*/
|
||||||
|
private void makeAndBindMarker(Mark observableMark, Paint colour) {
|
||||||
|
Marker2D marker2D = new Marker2D(colour);
|
||||||
|
// marker.addArrows(MarkArrowFactory.RoundingSide.PORT, ThreadLocalRandom.current().nextDouble(91, 180), ThreadLocalRandom.current().nextDouble(1, 90));
|
||||||
|
markerObjects.put(observableMark, marker2D);
|
||||||
|
observableMark.addPositionListener((mark, lat, lon) -> {
|
||||||
|
Point2D p2d = scaledPoint.findScaledXY(lat, lon);
|
||||||
|
markerObjects.get(mark).setLayoutX(p2d.getX());
|
||||||
|
markerObjects.get(mark).setLayoutY(p2d.getY());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new gate connecting the given marks.
|
||||||
|
*
|
||||||
|
* @param m1 The first Mark of the gate.
|
||||||
|
* @param m2 The second Mark of the gate.
|
||||||
|
* @param colour The desired colour of the gate.
|
||||||
|
* @return the new gate.
|
||||||
|
*/
|
||||||
|
private Gate makeAndBindGate(Marker2D m1, Marker2D m2, Paint colour) {
|
||||||
|
Gate gate = new Gate(colour);
|
||||||
|
gate.startXProperty().bind(
|
||||||
|
m1.layoutXProperty()
|
||||||
|
);
|
||||||
|
gate.startYProperty().bind(
|
||||||
|
m1.layoutYProperty()
|
||||||
|
);
|
||||||
|
gate.endXProperty().bind(
|
||||||
|
m2.layoutXProperty()
|
||||||
|
);
|
||||||
|
gate.endYProperty().bind(
|
||||||
|
m2.layoutYProperty()
|
||||||
|
);
|
||||||
|
return gate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a border to the GameView and rescales to the size of the border, does not rescale if a
|
||||||
|
* border already exists. Assumes the border is larger than the course.
|
||||||
|
*
|
||||||
|
* @param border the race border to be drawn.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void updateBorder(List<Limit> border) {
|
||||||
|
if (border.size() == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
borderPoints = border;
|
||||||
|
scaledPoint = ScaledPoint.makeScaledPoint(canvasWidth, canvasHeight, border, false);
|
||||||
|
|
||||||
|
List<Double> boundaryPoints = new ArrayList<>();
|
||||||
|
for (Limit limit : border) {
|
||||||
|
Point2D location = scaledPoint.findScaledXY(limit.getLat(), limit.getLng());
|
||||||
|
boundaryPoints.add(location.getX());
|
||||||
|
boundaryPoints.add(location.getY());
|
||||||
|
}
|
||||||
|
raceBorder.getPoints().setAll(boundaryPoints);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,142 @@
|
|||||||
|
package seng302.visualiser;
|
||||||
|
|
||||||
|
import seng302.gameServer.ServerAdvertiser;
|
||||||
|
import seng302.gameServer.ServerDescription;
|
||||||
|
|
||||||
|
import javax.jmdns.JmDNS;
|
||||||
|
import javax.jmdns.ServiceEvent;
|
||||||
|
import javax.jmdns.ServiceListener;
|
||||||
|
import javax.jmdns.impl.JmDNSImpl;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import static seng302.gameServer.ServerAdvertiser.getLocalHostIp;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listens for servers on the local network
|
||||||
|
*/
|
||||||
|
public class ServerListener{
|
||||||
|
private static Integer SERVICE_REFRESH_INTERVAL = 5 * 1000;
|
||||||
|
private static ServerListener instance;
|
||||||
|
private ServerListenerDelegate delegate;
|
||||||
|
private JmDNS jmdns = null;
|
||||||
|
GameServeMonitor listener;
|
||||||
|
|
||||||
|
private class GameServeMonitor implements ServiceListener {
|
||||||
|
private Set<ServerDescription> servers;
|
||||||
|
|
||||||
|
GameServeMonitor(){
|
||||||
|
servers = new HashSet<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Service has been detected but not resolved
|
||||||
|
* @param event The ServiceEvent
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void serviceAdded(ServiceEvent event) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Service has been removed / unregistered
|
||||||
|
* @param event The ServiceEvent
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void serviceRemoved(ServiceEvent event) {
|
||||||
|
String serverName = event.getInfo().getName();
|
||||||
|
|
||||||
|
ServerDescription toRemove = null;
|
||||||
|
|
||||||
|
for (ServerDescription server : servers){
|
||||||
|
if (server.getName().equals(serverName)){
|
||||||
|
toRemove = server;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (toRemove != null){
|
||||||
|
servers.remove(toRemove);
|
||||||
|
}
|
||||||
|
|
||||||
|
delegate.serverRemoved(new ArrayList<ServerDescription>(servers));
|
||||||
|
|
||||||
|
// Get all other servers with the same name to respond if they are up
|
||||||
|
jmdns.requestServiceInfo(ServerAdvertiser.SERVICE_TYPE, serverName);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Service has been added and resolved
|
||||||
|
* @param event The ServiceEvent
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void serviceResolved(ServiceEvent event) {
|
||||||
|
String address = event.getInfo().getServer();
|
||||||
|
Integer portNum = event.getInfo().getPort();
|
||||||
|
|
||||||
|
String serverName = event.getInfo().getName();
|
||||||
|
String mapName = event.getInfo().getPropertyString("map");
|
||||||
|
|
||||||
|
Integer capacity = Integer.parseInt(event.getInfo().getPropertyString("capacity"));
|
||||||
|
Integer numPlayers = Integer.parseInt(event.getInfo().getPropertyString("players"));
|
||||||
|
|
||||||
|
ServerDescription serverDescription = new ServerDescription(serverName, mapName, numPlayers, capacity, address, portNum);
|
||||||
|
|
||||||
|
servers.remove(serverDescription);
|
||||||
|
servers.add(serverDescription);
|
||||||
|
|
||||||
|
delegate.serverDetected(serverDescription, new ArrayList<>(servers));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ServerListener() throws IOException {
|
||||||
|
jmdns = JmDNS.create(InetAddress.getByName(getLocalHostIp()));
|
||||||
|
|
||||||
|
listener = new GameServeMonitor();
|
||||||
|
jmdns.addServiceListener(ServerAdvertiser.SERVICE_TYPE, listener);
|
||||||
|
|
||||||
|
/*new Timer().schedule(new TimerTask() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
}, 50, SERVICE_REFRESH_INTERVAL);*/
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ServerListener getInstance() throws IOException {
|
||||||
|
if (instance == null){
|
||||||
|
instance = new ServerListener();
|
||||||
|
}
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the delegate to handle events
|
||||||
|
* @param delegate .
|
||||||
|
*/
|
||||||
|
public void setDelegate(ServerListenerDelegate delegate){
|
||||||
|
this.delegate = delegate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void refresh(){
|
||||||
|
ArrayList<ServerDescription> servers = new ArrayList<>(listener.servers);
|
||||||
|
|
||||||
|
for (ServerDescription serverDescription : servers){
|
||||||
|
if (serverDescription.hasExpired()){
|
||||||
|
jmdns.requestServiceInfo(ServerAdvertiser.SERVICE_TYPE, serverDescription.getName());
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
serverDescription.hasBeenRefreshed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (ServerDescription server : servers){
|
||||||
|
if (server.serverShouldBeRemoved()){
|
||||||
|
listener.servers.remove(server);
|
||||||
|
delegate.serverRemoved(new ArrayList<ServerDescription>(listener.servers));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package seng302.visualiser;
|
||||||
|
|
||||||
|
import seng302.gameServer.ServerDescription;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface ServerListenerDelegate {
|
||||||
|
void serverRemoved(List<ServerDescription> currentServers);
|
||||||
|
void serverDetected(ServerDescription serverDescription, List<ServerDescription> 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 = -50.0;
|
||||||
|
private final Double FAR_ZOOM_LIMIT = -160.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 + NEAR_ZOOM_LIMIT) / 2.0;
|
||||||
|
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 = 30.0;
|
||||||
|
private final Double NEAR_ZOOM_LIMIT = -30.0;
|
||||||
|
private final Double FAR_ZOOM_LIMIT = -130.0;
|
||||||
|
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 + NEAR_ZOOM_LIMIT) / 2.0;
|
||||||
|
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,74 +0,0 @@
|
|||||||
package seng302.visualiser.controllers;
|
|
||||||
|
|
||||||
import javafx.fxml.FXML;
|
|
||||||
import javafx.scene.control.Button;
|
|
||||||
import javafx.scene.control.ColorPicker;
|
|
||||||
import javafx.scene.control.TextField;
|
|
||||||
import javafx.scene.paint.Color;
|
|
||||||
import javafx.stage.Stage;
|
|
||||||
import seng302.gameServer.messages.CustomizeRequestType;
|
|
||||||
import seng302.visualiser.ClientToServerThread;
|
|
||||||
|
|
||||||
public class CustomizationController {
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private TextField nameField;
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private ColorPicker boatColorPicker;
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private Button customizeSubmit;
|
|
||||||
|
|
||||||
private LobbyController lc;
|
|
||||||
private ClientToServerThread socketThread;
|
|
||||||
private Stage windowStage;
|
|
||||||
|
|
||||||
public void initialize() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setServerThread(ClientToServerThread ctsThread) {
|
|
||||||
this.socketThread = ctsThread;
|
|
||||||
}
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
public void submitCustomization() {
|
|
||||||
System.out.println("Attempting to send");
|
|
||||||
socketThread.sendCustomizationRequest(CustomizeRequestType.NAME, nameField.getText().getBytes());
|
|
||||||
// TODO: 16/08/17 ajm412: Turn colors into byte array.
|
|
||||||
Color color = boatColorPicker.getValue();
|
|
||||||
|
|
||||||
short red = (short) (color.getRed() * 255);
|
|
||||||
short green = (short) (color.getGreen() * 255);
|
|
||||||
short blue = (short) (color.getBlue() * 255);
|
|
||||||
|
|
||||||
byte[] colorArray = new byte[3];
|
|
||||||
|
|
||||||
colorArray[0] = (byte) red;
|
|
||||||
colorArray[1] = (byte) green;
|
|
||||||
colorArray[2] = (byte) blue;
|
|
||||||
|
|
||||||
socketThread.sendCustomizationRequest(CustomizeRequestType.COLOR, colorArray);
|
|
||||||
lc.setPlayersColor(color);
|
|
||||||
windowStage.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setLobbyController(LobbyController lc) {
|
|
||||||
this.lc = lc;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setStage(Stage stage) {
|
|
||||||
this.windowStage = stage;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPlayerName(String name) {
|
|
||||||
this.nameField.setText(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPlayerColor(Color playerColor) {
|
|
||||||
this.boatColorPicker.setValue(playerColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -15,10 +15,12 @@ import javafx.fxml.Initializable;
|
|||||||
import javafx.scene.control.TableColumn;
|
import javafx.scene.control.TableColumn;
|
||||||
import javafx.scene.control.TableView;
|
import javafx.scene.control.TableView;
|
||||||
import javafx.scene.control.cell.PropertyValueFactory;
|
import javafx.scene.control.cell.PropertyValueFactory;
|
||||||
|
import javafx.scene.input.MouseEvent;
|
||||||
import javafx.scene.layout.AnchorPane;
|
import javafx.scene.layout.AnchorPane;
|
||||||
import javafx.scene.layout.GridPane;
|
import javafx.scene.layout.GridPane;
|
||||||
import javafx.scene.layout.Pane;
|
import javafx.scene.layout.Pane;
|
||||||
import seng302.model.ClientYacht;
|
import seng302.model.ClientYacht;
|
||||||
|
import seng302.utilities.Sounds;
|
||||||
|
|
||||||
public class FinishScreenViewController implements Initializable {
|
public class FinishScreenViewController implements Initializable {
|
||||||
|
|
||||||
@@ -40,8 +42,8 @@ public class FinishScreenViewController implements Initializable {
|
|||||||
@Override
|
@Override
|
||||||
public void initialize(URL location, ResourceBundle resources) {
|
public void initialize(URL location, ResourceBundle resources) {
|
||||||
finishScreenGridPane.getStylesheets()
|
finishScreenGridPane.getStylesheets()
|
||||||
.add(getClass().getResource("/css/master.css").toString());
|
.add(getClass().getResource("/css/Master.css").toString());
|
||||||
finishOrderTable.getStylesheets().add(getClass().getResource("/css/master.css").toString());
|
finishOrderTable.getStylesheets().add(getClass().getResource("/css/Master.css").toString());
|
||||||
|
|
||||||
// set up data for table
|
// set up data for table
|
||||||
finishOrderTable.setItems(data);
|
finishOrderTable.setItems(data);
|
||||||
@@ -85,6 +87,11 @@ public class FinishScreenViewController implements Initializable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void switchToStartScreenView() {
|
public void switchToStartScreenView() {
|
||||||
|
Sounds.playButtonClick();
|
||||||
setContentPane("/views/StartScreenView.fxml");
|
setContentPane("/views/StartScreenView.fxml");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void playButtonHoverSound(MouseEvent mouseEvent) {
|
||||||
|
Sounds.playHoverSound();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,248 +1,258 @@
|
|||||||
package seng302.visualiser.controllers;
|
package seng302.visualiser.controllers;
|
||||||
|
|
||||||
import com.sun.media.jfxmedia.logging.Logger;
|
import com.jfoenix.controls.JFXButton;
|
||||||
import java.io.IOException;
|
import com.jfoenix.controls.JFXDialog;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.collections.ListChangeListener;
|
import javafx.collections.ListChangeListener;
|
||||||
import javafx.collections.ObservableList;
|
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.fxml.FXMLLoader;
|
import javafx.fxml.FXMLLoader;
|
||||||
import javafx.scene.Parent;
|
import javafx.fxml.Initializable;
|
||||||
import javafx.scene.Scene;
|
import javafx.scene.control.Label;
|
||||||
import javafx.scene.control.Button;
|
import javafx.scene.control.ScrollPane;
|
||||||
import javafx.scene.control.TextArea;
|
import javafx.scene.layout.AnchorPane;
|
||||||
import javafx.scene.image.Image;
|
import javafx.scene.layout.StackPane;
|
||||||
import javafx.scene.image.ImageView;
|
import javafx.scene.layout.VBox;
|
||||||
import javafx.scene.paint.Color;
|
import javafx.scene.paint.Color;
|
||||||
import javafx.scene.text.Text;
|
import seng302.discoveryServer.DiscoveryServerClient;
|
||||||
import javafx.stage.Stage;
|
|
||||||
import seng302.gameServer.GameStages;
|
import seng302.gameServer.GameStages;
|
||||||
import seng302.gameServer.GameState;
|
import seng302.gameServer.GameState;
|
||||||
|
import seng302.model.ClientYacht;
|
||||||
import seng302.model.Colors;
|
import seng302.model.Colors;
|
||||||
|
import seng302.model.Limit;
|
||||||
import seng302.model.RaceState;
|
import seng302.model.RaceState;
|
||||||
import seng302.visualiser.ClientToServerThread;
|
import seng302.model.mark.CompoundMark;
|
||||||
|
import seng302.model.mark.Corner;
|
||||||
|
import seng302.model.stream.xml.parser.RaceXMLData;
|
||||||
|
import seng302.utilities.Sounds;
|
||||||
|
import seng302.visualiser.MapPreview;
|
||||||
|
import seng302.visualiser.controllers.cells.PlayerCell;
|
||||||
|
import seng302.visualiser.controllers.dialogs.BoatCustomizeController;
|
||||||
|
|
||||||
/**
|
import java.io.IOException;
|
||||||
* A class describing the actions of the lobby screen
|
import java.net.URL;
|
||||||
* Created by wmu16 on 10/07/17.
|
import java.util.ArrayList;
|
||||||
*/
|
import java.util.List;
|
||||||
public class LobbyController {
|
import java.util.Map;
|
||||||
|
import java.util.ResourceBundle;
|
||||||
|
|
||||||
public enum CloseStatus {
|
public class LobbyController implements Initializable {
|
||||||
LEAVE,
|
|
||||||
READY
|
|
||||||
}
|
|
||||||
|
|
||||||
@FunctionalInterface
|
private final double INITIAL_MAP_HEIGHT = 770d;
|
||||||
public interface LobbyCloseListener {
|
private final double INITIAL_MAP_WIDTH = 574d;
|
||||||
void notify(CloseStatus exitCause);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
//--------FXML BEGIN--------//
|
||||||
@FXML
|
@FXML
|
||||||
private Text lobbyIpText;
|
private VBox playerListVBox;
|
||||||
@FXML
|
@FXML
|
||||||
private Button readyButton;
|
private ScrollPane playerListScrollPane;
|
||||||
@FXML
|
@FXML
|
||||||
private Button customizeButton;
|
private JFXButton customizeButton, leaveLobbyButton, beginRaceButton;
|
||||||
@FXML
|
@FXML
|
||||||
private TextArea playerOneTxt;
|
private StackPane serverListMainStackPane;
|
||||||
@FXML
|
@FXML
|
||||||
private TextArea playerTwoTxt;
|
private Label serverName;
|
||||||
@FXML
|
@FXML
|
||||||
private TextArea playerThreeTxt;
|
private Label mapName;
|
||||||
@FXML
|
@FXML
|
||||||
private TextArea playerFourTxt;
|
private AnchorPane serverMap;
|
||||||
@FXML
|
@FXML
|
||||||
private TextArea playerFiveTxt;
|
private Label roomLabel;
|
||||||
@FXML
|
//---------FXML END---------//
|
||||||
private TextArea playerSixTxt;
|
|
||||||
@FXML
|
|
||||||
private TextArea playerSevenTxt;
|
|
||||||
@FXML
|
|
||||||
private TextArea playerEightTxt;
|
|
||||||
@FXML
|
|
||||||
private ImageView firstImageView;
|
|
||||||
@FXML
|
|
||||||
private ImageView secondImageView;
|
|
||||||
@FXML
|
|
||||||
private ImageView thirdImageView;
|
|
||||||
@FXML
|
|
||||||
private ImageView fourthImageView;
|
|
||||||
@FXML
|
|
||||||
private ImageView fifthImageView;
|
|
||||||
@FXML
|
|
||||||
private ImageView sixthImageView;
|
|
||||||
@FXML
|
|
||||||
private ImageView seventhImageView;
|
|
||||||
@FXML
|
|
||||||
private ImageView eighthImageView;
|
|
||||||
@FXML
|
|
||||||
private Text timeUntilStart;
|
|
||||||
@FXML
|
|
||||||
private Text courseNameText;
|
|
||||||
|
|
||||||
private List<ImageView> imageViews = new ArrayList<>();
|
|
||||||
private List<TextArea> listViews = new ArrayList<>();
|
|
||||||
private RaceState raceState;
|
private RaceState raceState;
|
||||||
|
private JFXDialog customizationDialog;
|
||||||
|
public Color playersColor;
|
||||||
|
private Map<Integer, ClientYacht> playerBoats;
|
||||||
|
private Double mapWidth = INITIAL_MAP_WIDTH, mapHeight = INITIAL_MAP_HEIGHT;
|
||||||
|
private MapPreview mapPreview;
|
||||||
|
|
||||||
private ClientToServerThread socketThread;
|
@Override
|
||||||
|
public void initialize(URL location, ResourceBundle resources) {
|
||||||
private Stage customizeStage;
|
roomLabel.setText("");
|
||||||
|
this.playerBoats = ViewManager.getInstance().getGameClient().getAllBoatsMap();
|
||||||
private Color playersColor;
|
|
||||||
|
|
||||||
private int MAX_NUM_PLAYERS = 8;
|
|
||||||
private Integer playerID;
|
|
||||||
|
|
||||||
private List<LobbyCloseListener> lobbyListeners = new ArrayList<>();
|
|
||||||
private ObservableList<String> players;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add all FXObjects to lists and initialize images.
|
|
||||||
*/
|
|
||||||
public void initialize() {
|
|
||||||
Collections.addAll(listViews,
|
|
||||||
playerOneTxt, playerTwoTxt, playerThreeTxt, playerFourTxt, playerFiveTxt, playerSixTxt,
|
|
||||||
playerSevenTxt, playerEightTxt
|
|
||||||
);
|
|
||||||
Collections.addAll(imageViews,
|
|
||||||
firstImageView, secondImageView, thirdImageView, fourthImageView,
|
|
||||||
fifthImageView, sixthImageView, seventhImageView, eighthImageView
|
|
||||||
);
|
|
||||||
initialiseImageView();
|
|
||||||
|
|
||||||
timeUntilStart.setText("Waiting For Host...");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates player names.
|
|
||||||
*/
|
|
||||||
private void updatePlayers() {
|
|
||||||
//Update players if one added.
|
|
||||||
for (int i = 0; i < players.size(); i++) {
|
|
||||||
listViews.get(i).setText(players.get(i));
|
|
||||||
if (playerID == (i + 1)) {
|
|
||||||
listViews.get(i).setText(listViews.get(i).getText() + " (YOU)");
|
|
||||||
}
|
|
||||||
imageViews.get(i).setVisible(true);
|
|
||||||
}
|
|
||||||
//Update empty text fields if player left.
|
|
||||||
for (int i = MAX_NUM_PLAYERS-1; i >= players.size(); i--) {
|
|
||||||
listViews.get(i).setText("");
|
|
||||||
imageViews.get(i).setVisible(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets all images and hides them till players join.
|
|
||||||
*/
|
|
||||||
private void initialiseImageView() {
|
|
||||||
for (ImageView viewer : imageViews) {
|
|
||||||
viewer.setImage(
|
|
||||||
new Image(
|
|
||||||
RaceViewController.class.getResourceAsStream(
|
|
||||||
"/pics/sail.png")
|
|
||||||
)
|
|
||||||
);
|
|
||||||
viewer.setVisible(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
public void customize() {
|
|
||||||
Parent root;
|
|
||||||
try {
|
|
||||||
FXMLLoader fxmlLoader = new FXMLLoader(LobbyController.class.getResource("/views/customizeView.fxml"));
|
|
||||||
root = fxmlLoader.load();
|
|
||||||
root.getStylesheets().add("/css/master.css");
|
|
||||||
customizeStage = new Stage();
|
|
||||||
customizeStage.setTitle("Customize Boat");
|
|
||||||
customizeStage.setScene(new Scene(root, 700, 450));
|
|
||||||
CustomizationController cc = fxmlLoader.getController();
|
|
||||||
cc.setServerThread(this.socketThread);
|
|
||||||
cc.setPlayerName(this.players.get(playerID - 1));
|
|
||||||
|
|
||||||
if (this.playersColor == null) {
|
if (this.playersColor == null) {
|
||||||
this.playersColor = Colors.getColor(playerID - 1);
|
this.playersColor = Colors.getColor(ViewManager.getInstance().getGameClient().getServerThread().getClientId() - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
cc.setPlayerColor(this.playersColor);
|
leaveLobbyButton.setOnMouseReleased(event -> leaveLobby());
|
||||||
cc.setStage(customizeStage); // pass the stage through so it can be closed later.
|
beginRaceButton.setOnMouseReleased(event -> beginRace());
|
||||||
cc.setLobbyController(this);
|
leaveLobbyButton.setOnMouseReleased(event -> {
|
||||||
customizeStage.show();
|
Sounds.playButtonClick();
|
||||||
|
leaveLobby();
|
||||||
|
});
|
||||||
|
|
||||||
|
beginRaceButton.setOnMouseReleased(event -> {
|
||||||
|
Sounds.playButtonClick();
|
||||||
|
beginRace();
|
||||||
|
});
|
||||||
|
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
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();
|
||||||
|
|
||||||
|
playersColor = Colors.getColor(playerId - 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
leaveLobbyButton.setOnMouseEntered(e -> Sounds.playHoverSound());
|
||||||
|
customizeButton.setOnMouseEntered(e -> Sounds.playHoverSound());
|
||||||
|
beginRaceButton.setOnMouseEntered(e -> Sounds.playHoverSound());
|
||||||
|
|
||||||
|
initMapPreview();
|
||||||
|
}
|
||||||
|
|
||||||
|
private JFXDialog createCustomizeDialog() {
|
||||||
|
FXMLLoader dialog = new FXMLLoader(
|
||||||
|
getClass().getResource("/views/dialogs/BoatCustomizeDialog.fxml"));
|
||||||
|
|
||||||
|
JFXDialog customizationDialog = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
customizationDialog = new JFXDialog(serverListMainStackPane, dialog.load(),
|
||||||
|
JFXDialog.DialogTransition.CENTER);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Logger.logMsg(4, "Failed to load Customization View from resources.");
|
e.printStackTrace();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSocketThread(ClientToServerThread thread) {
|
BoatCustomizeController controller = dialog.getController();
|
||||||
this.socketThread = thread;
|
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
|
||||||
public void leaveLobbyButtonPressed() {
|
/**
|
||||||
// TODO: 10/07/17 wmu16 - Finish function!
|
* Initializes a top down preview of the race course map.
|
||||||
GameState.setCurrentStage(GameStages.CANCELLED);
|
*/
|
||||||
// TODO: 20/07/17 wmu16 - Implement some way of terminating the game
|
private void initMapPreview() {
|
||||||
for (LobbyCloseListener readyListener : lobbyListeners)
|
RaceXMLData raceData = ViewManager.getInstance().getGameClient().getCourseData();
|
||||||
readyListener.notify(CloseStatus.LEAVE);
|
List<Limit> border = raceData.getCourseLimit();
|
||||||
|
List<CompoundMark> marks = new ArrayList<>(raceData.getCompoundMarks().values());
|
||||||
|
List<Corner> corners = raceData.getMarkSequence();
|
||||||
|
|
||||||
|
mapPreview = new MapPreview(marks, corners, border);
|
||||||
|
serverMap.getChildren().clear();
|
||||||
|
serverMap.getChildren().add(mapPreview.getAssets());
|
||||||
|
|
||||||
|
mapPreview.setSize(mapWidth, mapHeight);
|
||||||
|
|
||||||
|
serverMap.widthProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
|
mapWidth = newValue.doubleValue();
|
||||||
|
mapPreview.setSize(mapWidth, mapHeight);
|
||||||
|
});
|
||||||
|
//
|
||||||
|
serverMap.heightProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
|
mapHeight = newValue.doubleValue();
|
||||||
|
mapPreview.setSize(mapWidth, mapHeight);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
/**
|
||||||
public void readyButtonPressed() {
|
*
|
||||||
GameState.setCurrentStage(GameStages.PRE_RACE);
|
*/
|
||||||
// Do countdown logic here
|
private void beginRace() {
|
||||||
|
beginRaceButton.setDisable(true);
|
||||||
for (LobbyCloseListener readyListener : lobbyListeners)
|
|
||||||
readyListener.notify(CloseStatus.READY);
|
|
||||||
customizeButton.setDisable(true);
|
customizeButton.setDisable(true);
|
||||||
|
GameState.setCurrentStage(GameStages.PRE_RACE);
|
||||||
|
GameState.resetStartTime();
|
||||||
|
Platform.runLater(()-> ViewManager.getInstance().getGameClient().startGame());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setTitle (String title) {
|
/**
|
||||||
lobbyIpText.setText(title);
|
* Refreshes the list of players and their boats, as a series of VBox PlayerCell objects.
|
||||||
|
*/
|
||||||
|
private void refreshPlayerList() {
|
||||||
|
playerListVBox.getChildren().clear();
|
||||||
|
if (this.playerBoats == null || this.playerBoats.size() == 0) {
|
||||||
|
this.playerBoats = ViewManager.getInstance().getGameClient().getAllBoatsMap();
|
||||||
|
}
|
||||||
|
// TODO: 12/09/2017 ajm412: Make it so that it only removes players who's details have changed.
|
||||||
|
for (Integer playerId : playerBoats.keySet()) {
|
||||||
|
VBox pane = null;
|
||||||
|
|
||||||
|
ClientYacht yacht = playerBoats.get(playerId);
|
||||||
|
|
||||||
|
FXMLLoader loader = new FXMLLoader(
|
||||||
|
getClass().getResource("/views/cells/PlayerCell.fxml"));
|
||||||
|
|
||||||
|
loader.setController(new PlayerCell(playerId, yacht));
|
||||||
|
|
||||||
|
try {
|
||||||
|
pane = loader.load();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setCourseName(String courseName){
|
playerListVBox.getChildren().add(pane);
|
||||||
courseNameText.setText(courseName);
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addCloseListener(LobbyCloseListener listener) {
|
private void leaveLobby() {
|
||||||
lobbyListeners.add(listener);
|
|
||||||
|
ViewManager.getInstance().getGameClient().stopGame();
|
||||||
|
ViewManager.getInstance().goToStartView();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPlayerListSource (ObservableList<String> players) {
|
public void disableReadyButton() {
|
||||||
this.players = players;
|
this.beginRaceButton.setDisable(true);
|
||||||
players.addListener((ListChangeListener<? super String>) (lcl) ->
|
this.beginRaceButton.setText("Waiting for host...");
|
||||||
Platform.runLater(this::updatePlayers)
|
|
||||||
);
|
|
||||||
Platform.runLater(this::updatePlayers);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPlayerID(Integer id) {
|
|
||||||
playerID = id;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param raceState
|
||||||
|
*/
|
||||||
public void updateRaceState(RaceState raceState){
|
public void updateRaceState(RaceState raceState){
|
||||||
this.raceState = raceState;
|
this.raceState = raceState;
|
||||||
/*if (this.customizeStage != null) {
|
this.beginRaceButton.setText("Starting in: " + raceState.getRaceTimeStr());
|
||||||
this.customizeStage.close();
|
|
||||||
}*/ // TODO: 17/08/17 ajm412: close the customization window if the host starts the game while customizing
|
|
||||||
if (!customizeButton.isDisabled()) {
|
|
||||||
customizeButton.setDisable(true);
|
|
||||||
}
|
|
||||||
timeUntilStart.setText("Starting in: " + raceState.getRaceTimeStr());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void disableReadyButton () {
|
public void setBoats(Map<Integer, ClientYacht> boats) {
|
||||||
readyButton.setDisable(true);
|
this.playerBoats = boats;
|
||||||
readyButton.setVisible(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPlayersColor(Color playerColor) {
|
public void closeCustomizationDialog() {
|
||||||
this.playersColor = playerColor;
|
customizationDialog.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setRoomCode(String roomCode) {
|
||||||
|
roomLabel.setText("Room: " + roomCode);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package seng302.visualiser.controllers;
|
package seng302.visualiser.controllers;
|
||||||
|
|
||||||
|
import com.jfoenix.controls.JFXButton;
|
||||||
|
import com.jfoenix.controls.JFXDialog;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -7,8 +9,10 @@ import java.util.Map;
|
|||||||
import java.util.Timer;
|
import java.util.Timer;
|
||||||
import java.util.TimerTask;
|
import java.util.TimerTask;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import javafx.animation.RotateTransition;
|
||||||
import javafx.animation.Timeline;
|
import javafx.animation.Timeline;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
|
import javafx.beans.property.ReadOnlyBooleanProperty;
|
||||||
import javafx.collections.FXCollections;
|
import javafx.collections.FXCollections;
|
||||||
import javafx.collections.ListChangeListener;
|
import javafx.collections.ListChangeListener;
|
||||||
import javafx.collections.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
@@ -16,43 +20,68 @@ import javafx.fxml.FXML;
|
|||||||
import javafx.fxml.FXMLLoader;
|
import javafx.fxml.FXMLLoader;
|
||||||
import javafx.geometry.Point2D;
|
import javafx.geometry.Point2D;
|
||||||
import javafx.scene.Scene;
|
import javafx.scene.Scene;
|
||||||
|
import javafx.scene.SubScene;
|
||||||
import javafx.scene.chart.LineChart;
|
import javafx.scene.chart.LineChart;
|
||||||
import javafx.scene.chart.NumberAxis;
|
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.chart.XYChart.Series;
|
||||||
import javafx.scene.control.Button;
|
import javafx.scene.control.Button;
|
||||||
import javafx.scene.control.CheckBox;
|
import javafx.scene.control.CheckBox;
|
||||||
import javafx.scene.control.ComboBox;
|
import javafx.scene.control.ComboBox;
|
||||||
|
import javafx.scene.control.Label;
|
||||||
import javafx.scene.control.Slider;
|
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.AnchorPane;
|
||||||
|
import javafx.scene.layout.GridPane;
|
||||||
import javafx.scene.layout.Pane;
|
import javafx.scene.layout.Pane;
|
||||||
|
import javafx.scene.layout.StackPane;
|
||||||
import javafx.scene.layout.VBox;
|
import javafx.scene.layout.VBox;
|
||||||
import javafx.scene.paint.Color;
|
import javafx.scene.paint.Color;
|
||||||
import javafx.scene.paint.Paint;
|
import javafx.scene.paint.Paint;
|
||||||
import javafx.scene.shape.Line;
|
import javafx.scene.shape.Line;
|
||||||
|
import javafx.scene.shape.Polyline;
|
||||||
import javafx.scene.text.Text;
|
import javafx.scene.text.Text;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
import javafx.stage.StageStyle;
|
import javafx.stage.StageStyle;
|
||||||
import javafx.util.StringConverter;
|
import javafx.util.Duration;
|
||||||
import seng302.gameServer.messages.BoatStatus;
|
|
||||||
import seng302.model.ClientYacht;
|
import seng302.model.ClientYacht;
|
||||||
import seng302.model.RaceState;
|
import seng302.model.RaceState;
|
||||||
import seng302.model.mark.CompoundMark;
|
import seng302.model.mark.CompoundMark;
|
||||||
import seng302.model.mark.Mark;
|
import seng302.model.mark.Mark;
|
||||||
import seng302.model.stream.xml.parser.RaceXMLData;
|
import seng302.model.stream.xml.parser.RaceXMLData;
|
||||||
import seng302.visualiser.GameView;
|
import seng302.model.token.TokenType;
|
||||||
import seng302.visualiser.controllers.annotations.Annotation;
|
import seng302.utilities.Sounds;
|
||||||
|
import seng302.visualiser.GameView3D;
|
||||||
import seng302.visualiser.controllers.annotations.ImportantAnnotationController;
|
import seng302.visualiser.controllers.annotations.ImportantAnnotationController;
|
||||||
import seng302.visualiser.controllers.annotations.ImportantAnnotationDelegate;
|
import seng302.visualiser.controllers.annotations.ImportantAnnotationDelegate;
|
||||||
import seng302.visualiser.controllers.annotations.ImportantAnnotationsState;
|
import seng302.visualiser.controllers.annotations.ImportantAnnotationsState;
|
||||||
import seng302.visualiser.fxObjects.BoatObject;
|
import seng302.visualiser.controllers.dialogs.FinishDialogController;
|
||||||
|
import seng302.visualiser.fxObjects.ChatHistory;
|
||||||
|
import seng302.visualiser.fxObjects.assets_2D.WindArrow;
|
||||||
|
import seng302.visualiser.fxObjects.assets_3D.BoatObject;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Controller class that manages the display of a race
|
* Controller class that manages the display of a race
|
||||||
*/
|
*/
|
||||||
public class RaceViewController extends Thread implements ImportantAnnotationDelegate {
|
public class RaceViewController extends Thread implements ImportantAnnotationDelegate {
|
||||||
|
|
||||||
|
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 AnchorPane loadingScreenPane;
|
||||||
|
@FXML
|
||||||
|
private ImageView loadingScreen;
|
||||||
|
@FXML
|
||||||
|
private Pane basePane;
|
||||||
|
@FXML
|
||||||
|
private JFXButton chatSend;
|
||||||
|
@FXML
|
||||||
|
private Pane chatHistoryHolder;
|
||||||
|
@FXML
|
||||||
|
private TextField chatInput;
|
||||||
@FXML
|
@FXML
|
||||||
private LineChart<String, Double> raceSparkLine;
|
private LineChart<String, Double> raceSparkLine;
|
||||||
@FXML
|
@FXML
|
||||||
@@ -62,11 +91,15 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
|
|||||||
@FXML
|
@FXML
|
||||||
private CheckBox toggleFps;
|
private CheckBox toggleFps;
|
||||||
@FXML
|
@FXML
|
||||||
private Text timerLabel;
|
private Label timerLabel;
|
||||||
@FXML
|
@FXML
|
||||||
private AnchorPane contentAnchorPane;
|
private StackPane contentStackPane;
|
||||||
|
|
||||||
|
private GridPane contentGridPane;
|
||||||
@FXML
|
@FXML
|
||||||
private Text windArrowText, windDirectionText;
|
private AnchorPane rvAnchorPane;
|
||||||
|
@FXML
|
||||||
|
private AnchorPane windArrowHolder;
|
||||||
@FXML
|
@FXML
|
||||||
private Slider annotationSlider;
|
private Slider annotationSlider;
|
||||||
@FXML
|
@FXML
|
||||||
@@ -76,36 +109,130 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
|
|||||||
@FXML
|
@FXML
|
||||||
private Text fpsDisplay;
|
private Text fpsDisplay;
|
||||||
@FXML
|
@FXML
|
||||||
private Text windSpeedText;
|
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;
|
||||||
|
|
||||||
//Race Data
|
//Race Data
|
||||||
private Map<Integer, ClientYacht> participants;
|
private Map<Integer, ClientYacht> participants;
|
||||||
private Map<Integer, CompoundMark> markers;
|
private Map<Integer, CompoundMark> markers;
|
||||||
private RaceXMLData courseData;
|
private RaceXMLData courseData;
|
||||||
private GameView gameView;
|
private GameView3D gameView;
|
||||||
private RaceState raceState;
|
private RaceState raceState;
|
||||||
|
|
||||||
|
private ChatHistory chatHistory;
|
||||||
|
|
||||||
private Timeline timerTimeline;
|
private Timeline timerTimeline;
|
||||||
private Timer timer = new Timer();
|
private Timer timer = new Timer();
|
||||||
private List<Series<String, Double>> sparkLineData = new ArrayList<>();
|
private List<Series<String, Double>> sparkLineData = new ArrayList<>();
|
||||||
private ImportantAnnotationsState importantAnnotations;
|
private ImportantAnnotationsState importantAnnotations;
|
||||||
|
private Polyline windArrow = new WindArrow(Color.LIGHTGRAY);
|
||||||
|
private ObservableList<ClientYacht> selectionComboBoxList = FXCollections.observableArrayList();
|
||||||
|
private ClientYacht player;
|
||||||
|
private JFXDialog finishScreenDialog;
|
||||||
|
private FinishDialogController finishDialogController;
|
||||||
|
|
||||||
|
//Icon stuff
|
||||||
|
private Timer blinkingTimer = new Timer();
|
||||||
|
private ImageView iconToDisplay;
|
||||||
|
|
||||||
|
private Double lastWindDirection;
|
||||||
|
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
// Load a default important annotation state
|
contentStackPane.setVisible(false);
|
||||||
importantAnnotations = new ImportantAnnotationsState();
|
Image loadingImage = new Image("PP.png");
|
||||||
|
loadingScreen.setImage(loadingImage);
|
||||||
//Formatting the y axis of the sparkline
|
//Centers the Image within the image view
|
||||||
// raceSparkLine.getYAxis().setRotate(180);
|
double w = 0;
|
||||||
// raceSparkLine.getYAxis().setTickLabelRotation(180);
|
double h = 0;
|
||||||
// raceSparkLine.getYAxis().setTranslateX(-5);
|
double ratioX = loadingScreen.getFitWidth() / loadingImage.getWidth();
|
||||||
raceSparkLine.visibleProperty().setValue(false);
|
double ratioY = loadingScreen.getFitHeight() / loadingImage.getHeight();
|
||||||
raceSparkLine.getYAxis().setAutoRanging(false);
|
double reduceRatio = 0;
|
||||||
sparklineYAxis.setTickMarkVisible(false);
|
if(ratioX >= ratioY) {
|
||||||
|
reduceRatio = ratioY;
|
||||||
positionVbox.getStylesheets().add(getClass().getResource("/css/master.css").toString());
|
} else {
|
||||||
|
reduceRatio = ratioX;
|
||||||
selectAnnotationBtn.setOnAction(event -> loadSelectAnnotationView());
|
|
||||||
}
|
}
|
||||||
|
w = loadingImage.getWidth() * reduceRatio;
|
||||||
|
h = loadingImage.getHeight() * reduceRatio;
|
||||||
|
loadingScreen.setX((loadingScreen.getFitWidth() - w) / 2);
|
||||||
|
loadingScreen.setY((loadingScreen.getFitHeight() - h) / 2);
|
||||||
|
Sounds.stopMusic();
|
||||||
|
Sounds.playRaceMusic();
|
||||||
|
|
||||||
|
chatInput.lengthProperty().addListener((obs, oldLen, newLen) -> {
|
||||||
|
if (newLen.intValue() > CHAT_LIMIT) {
|
||||||
|
chatInput.setText(chatInput.getText().substring(0, CHAT_LIMIT));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
chatHistory = new ChatHistory();
|
||||||
|
chatHistoryHolder.getChildren().addAll(chatHistory);
|
||||||
|
chatHistory.prefWidthProperty().bind(
|
||||||
|
chatHistoryHolder.widthProperty()
|
||||||
|
);
|
||||||
|
chatHistory.prefHeightProperty().bind(
|
||||||
|
chatHistoryHolder.heightProperty()
|
||||||
|
);
|
||||||
|
|
||||||
|
contentStackPane.setOnMouseClicked(event -> {
|
||||||
|
contentStackPane.requestFocus();
|
||||||
|
});
|
||||||
|
Platform.runLater(contentStackPane::requestFocus);
|
||||||
|
//Makes the chat history non transparent when clicked on
|
||||||
|
chatInput.focusedProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
|
if (newValue) {
|
||||||
|
chatHistory.increaseOpacity();
|
||||||
|
} else {
|
||||||
|
chatHistory.decreaseOpacity();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
lastWindDirection = 0d;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void showFinishDialog(ArrayList<ClientYacht> finishedBoats) {
|
||||||
|
raceState.setRaceStarted(false);
|
||||||
|
createFinishDialog(finishedBoats);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void showView(){
|
||||||
|
loadingScreenPane.setVisible(false);
|
||||||
|
contentStackPane.setVisible(true);
|
||||||
|
|
||||||
|
Platform.runLater(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
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 (
|
public void loadRace (
|
||||||
Map<Integer, ClientYacht> participants, RaceXMLData raceData, RaceState raceState,
|
Map<Integer, ClientYacht> participants, RaceXMLData raceData, RaceState raceState,
|
||||||
@@ -115,48 +242,115 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
|
|||||||
this.courseData = raceData;
|
this.courseData = raceData;
|
||||||
this.markers = raceData.getCompoundMarks();
|
this.markers = raceData.getCompoundMarks();
|
||||||
this.raceState = raceState;
|
this.raceState = raceState;
|
||||||
|
this.player = player;
|
||||||
initializeUpdateTimer();
|
|
||||||
initialiseFPSCheckBox();
|
|
||||||
initialiseAnnotationSlider();
|
|
||||||
initialiseBoatSelectionComboBox();
|
|
||||||
initialiseSparkLine();
|
|
||||||
|
|
||||||
raceState.getPlayerPositions().addListener((ListChangeListener<ClientYacht>) c -> {
|
raceState.getPlayerPositions().addListener((ListChangeListener<ClientYacht>) c -> {
|
||||||
while (c.next()) {
|
while (c.next()) {
|
||||||
if (c.wasPermutated()) {
|
if (c.wasPermutated()) {
|
||||||
updateOrder(raceState.getPlayerPositions());
|
updateOrder(raceState.getPlayerPositions());
|
||||||
updateSparkLine();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
player.addPowerUpListener(this::displayPowerUpIcon);
|
||||||
|
player.addPowerDownListener(this::removeIcon);
|
||||||
|
|
||||||
updateOrder(raceState.getPlayerPositions());
|
updateOrder(raceState.getPlayerPositions());
|
||||||
gameView = new GameView();
|
gameView = new GameView3D();
|
||||||
gameView.setFrameRateFXText(fpsDisplay);
|
Platform.runLater(() -> {
|
||||||
Platform.runLater(() -> contentAnchorPane.getChildren().add(0, gameView));
|
contentStackPane.getChildren().add(0, gameView.getAssets());
|
||||||
|
((SubScene) gameView.getAssets()).widthProperty()
|
||||||
|
.bind(ViewManager.getInstance().getStage().widthProperty());
|
||||||
|
((SubScene) gameView.getAssets()).heightProperty()
|
||||||
|
.bind(ViewManager.getInstance().getStage().heightProperty());
|
||||||
|
});
|
||||||
gameView.setBoats(new ArrayList<>(participants.values()));
|
gameView.setBoats(new ArrayList<>(participants.values()));
|
||||||
gameView.updateBorder(raceData.getCourseLimit());
|
gameView.updateBorder(raceData.getCourseLimit());
|
||||||
|
gameView.updateTokens(raceData.getTokens());
|
||||||
gameView.updateCourse(
|
gameView.updateCourse(
|
||||||
new ArrayList<>(raceData.getCompoundMarks().values()), raceData.getMarkSequence()
|
new ArrayList<>(raceData.getCompoundMarks().values()), raceData.getMarkSequence()
|
||||||
);
|
);
|
||||||
gameView.enableZoom();
|
|
||||||
gameView.setBoatAsPlayer(player);
|
gameView.setBoatAsPlayer(player);
|
||||||
gameView.startRace();
|
|
||||||
|
|
||||||
raceState.addCollisionListener(gameView::drawCollision);
|
// raceState.addCollisionListener(gameView::drawCollision);
|
||||||
|
|
||||||
raceState.windDirectionProperty().addListener((obs, oldDirection, newDirection) -> {
|
raceState.windDirectionProperty().addListener((obs, oldDirection, newDirection) -> {
|
||||||
gameView.setWindDir(newDirection.doubleValue());
|
gameView.setWindDir(newDirection.doubleValue());
|
||||||
updateWindDirection(newDirection.doubleValue());
|
Platform.runLater(() -> updateWindDirection(newDirection.doubleValue()));
|
||||||
});
|
|
||||||
raceState.windSpeedProperty().addListener((obs, oldSpeed, newSpeed) -> {
|
|
||||||
updateWindSpeed(newSpeed.doubleValue());
|
|
||||||
});
|
});
|
||||||
|
raceState.windSpeedProperty().addListener((obs, oldSpeed, newSpeed) ->
|
||||||
|
Platform.runLater(() -> updateWindSpeed(newSpeed.doubleValue()))
|
||||||
|
);
|
||||||
|
Platform.runLater(() -> {
|
||||||
updateWindDirection(raceState.windDirectionProperty().doubleValue());
|
updateWindDirection(raceState.windDirectionProperty().doubleValue());
|
||||||
updateWindSpeed(raceState.getWindSpeed());
|
updateWindSpeed(raceState.getWindSpeed());
|
||||||
|
});
|
||||||
gameView.setWindDir(raceState.windDirectionProperty().doubleValue());
|
gameView.setWindDir(raceState.windDirectionProperty().doubleValue());
|
||||||
|
Platform.runLater(this::initializeUpdateTimer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 yacht The yacht only for which we are displaying the icon
|
||||||
|
* @param tokenType The type of token, indicating what icon needs to be displayed
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
//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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeIcon(ClientYacht yacht) {
|
||||||
|
if (yacht == player) {
|
||||||
|
blinkingTimer.cancel();
|
||||||
|
iconToDisplay.setVisible(false);
|
||||||
|
iconToDisplay = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The important annotations have been changed, update this view
|
* The important annotations have been changed, update this view
|
||||||
*
|
*
|
||||||
@@ -195,144 +389,6 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initialiseFPSCheckBox() {
|
|
||||||
toggleFps.selectedProperty().addListener((obs, oldVal, newVal) ->
|
|
||||||
gameView.setFPSVisibility(toggleFps.isSelected())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initialiseAnnotationSlider() {
|
|
||||||
annotationSlider.setLabelFormatter(new StringConverter<Double>() {
|
|
||||||
@Override
|
|
||||||
public String toString(Double n) {
|
|
||||||
if (n == 0) {
|
|
||||||
return "None";
|
|
||||||
}
|
|
||||||
if (n == 1) {
|
|
||||||
return "Important";
|
|
||||||
}
|
|
||||||
if (n == 2) {
|
|
||||||
return "All";
|
|
||||||
}
|
|
||||||
return "All";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Double fromString(String s) {
|
|
||||||
switch (s) {
|
|
||||||
case "None":
|
|
||||||
return 0d;
|
|
||||||
case "Important":
|
|
||||||
return 1d;
|
|
||||||
case "All":
|
|
||||||
return 2d;
|
|
||||||
|
|
||||||
default:
|
|
||||||
return 2d;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
annotationSlider.setValue(2);
|
|
||||||
annotationSlider.valueProperty().addListener((obs, oldVal, newVal) ->
|
|
||||||
setAnnotations((int) annotationSlider.getValue())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used to add any new yachts into the race that may have started late or not have had data received yet
|
|
||||||
*/
|
|
||||||
private void updateSparkLine(){
|
|
||||||
// TODO: 2/08/17 there is about 0 chance of this working. Once we are keeping track of boat positions it can be fixed.
|
|
||||||
// Collect the racing yachts that aren't already in the chart
|
|
||||||
sparkLineData.clear();
|
|
||||||
List<ClientYacht> sparkLineCandidates = new ArrayList<>(participants.values());
|
|
||||||
// Create a new data series for new yachts
|
|
||||||
sparkLineCandidates
|
|
||||||
.stream()
|
|
||||||
.filter(yacht -> yacht.getPosition() != null)
|
|
||||||
.forEach(yacht -> {
|
|
||||||
Series<String, Double> yachtData = new Series<>();
|
|
||||||
yachtData.setName(yacht.getSourceId().toString());
|
|
||||||
yachtData.getData().add(
|
|
||||||
new Data<>(
|
|
||||||
Integer.toString(yacht.getLegNumber()),
|
|
||||||
1.0 + participants.size() - yacht.getPosition()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
sparkLineData.add(yachtData);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Lambda function to sort the series in order of leg (later legs shown more to the right)
|
|
||||||
sparkLineData.sort((o1, o2) -> {
|
|
||||||
Integer leg1 = Integer.parseInt(o1.getData().get(o1.getData().size()-1).getXValue());
|
|
||||||
Integer leg2 = Integer.parseInt(o2.getData().get(o2.getData().size()-1).getXValue());
|
|
||||||
if (leg2 < leg1){
|
|
||||||
return 1;
|
|
||||||
} else {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Adds the new data series to the sparkline (and set the colour of the series)
|
|
||||||
Platform.runLater(() -> {
|
|
||||||
sparkLineData
|
|
||||||
.stream()
|
|
||||||
.filter(spark -> !raceSparkLine.getData().contains(spark))
|
|
||||||
.forEach(spark -> {
|
|
||||||
raceSparkLine.getData().add(spark);
|
|
||||||
spark.getNode().lookup(".chart-series-line").setStyle("-fx-stroke:" + getBoatColorAsRGB(spark.getName()));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initialiseSparkLine() {
|
|
||||||
sparklineYAxis.setUpperBound(participants.size() + 1);
|
|
||||||
raceSparkLine.setCreateSymbols(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the yachts sparkline of the desired yacht and using the new leg number
|
|
||||||
* @param yacht The yacht to be updated on the sparkline
|
|
||||||
* @param legNumber the leg number that the position will be assigned to
|
|
||||||
*/
|
|
||||||
void updateYachtPositionSparkline(ClientYacht yacht, Integer legNumber){
|
|
||||||
for (XYChart.Series<String, Double> positionData : sparkLineData) {
|
|
||||||
positionData.getData().add(
|
|
||||||
new Data<>(
|
|
||||||
Integer.toString(legNumber),
|
|
||||||
1.0 + participants.size() - yacht.getPlacing()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// XYChart.Series<String, Double> positionData = sparkLineData.get(yacht.getSourceID());
|
|
||||||
// positionData.getData().add(
|
|
||||||
// new XYChart.Data<>(
|
|
||||||
// Integer.toString(legNumber),
|
|
||||||
// 1.0 + participants.size() - yacht.getPlacing()
|
|
||||||
// )
|
|
||||||
// );
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* gets the rgb string of the yachts colour to use for the chart via css
|
|
||||||
* @param yachtId id of yacht passed in to get the yachts colour
|
|
||||||
* @return the colour as an rgb string
|
|
||||||
*/
|
|
||||||
private String getBoatColorAsRGB(String yachtId){
|
|
||||||
Color color = participants.get(Integer.valueOf(yachtId)).getColour();
|
|
||||||
if (color == null){
|
|
||||||
return String.format("#%02X%02X%02X",255,255,255);
|
|
||||||
}
|
|
||||||
return String.format( "#%02X%02X%02X",
|
|
||||||
(int)( color.getRed() * 255 ),
|
|
||||||
(int)( color.getGreen() * 255 ),
|
|
||||||
(int)( color.getBlue() * 255 )
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialises a timer which updates elements of the RaceView such as wind direction, yacht
|
* 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.
|
* orderings etc.. which are dependent on the info from the stream parser constantly.
|
||||||
@@ -342,7 +398,10 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
|
|||||||
timer.scheduleAtFixedRate(new TimerTask() {
|
timer.scheduleAtFixedRate(new TimerTask() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
updateRaceTime();
|
Platform.runLater(() -> updatePosition());
|
||||||
|
Platform.runLater(() -> updateBoatSpeed());
|
||||||
|
Platform.runLater(() -> updateBoatHeading());
|
||||||
|
Platform.runLater(() -> updateRaceTime());
|
||||||
}
|
}
|
||||||
}, 0, 1000);
|
}, 0, 1000);
|
||||||
}
|
}
|
||||||
@@ -381,8 +440,14 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
|
|||||||
* @param direction the from north angle of the wind.
|
* @param direction the from north angle of the wind.
|
||||||
*/
|
*/
|
||||||
private void updateWindDirection(double direction) {
|
private void updateWindDirection(double direction) {
|
||||||
windDirectionText.setText(String.format("%.1f°", direction));
|
windDirectionLabel.setText(String.format("%.1f°", direction));
|
||||||
windArrowText.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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -390,7 +455,7 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
|
|||||||
* @param windSpeed Windspeed in knots.
|
* @param windSpeed Windspeed in knots.
|
||||||
*/
|
*/
|
||||||
private void updateWindSpeed(double windSpeed) {
|
private void updateWindSpeed(double windSpeed) {
|
||||||
windSpeedText.setText("Speed: " + String.format("%.1f", windSpeed) + " Knots");
|
windSpeedLabel.setText(String.format("%.1f", windSpeed) + " Knots");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -398,12 +463,57 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
|
|||||||
* Updates the clock for the race
|
* Updates the clock for the race
|
||||||
*/
|
*/
|
||||||
private void updateRaceTime() {
|
private void updateRaceTime() {
|
||||||
// if (!raceState.isRaceStarted()) {
|
if (raceState.getTimeTillStart() <= 0L && !raceState.isRaceStarted()) {
|
||||||
// timerLabel.setFill(Color.RED);
|
timerLabel.setText("Race Finished!");
|
||||||
// timerLabel.setText("Race Finished!");
|
} else {
|
||||||
// } else {
|
|
||||||
timerLabel.setText(raceState.getRaceTimeStr());
|
timerLabel.setText(raceState.getRaceTimeStr());
|
||||||
// }
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates player position with ordinal number up to 23rd position.
|
||||||
|
*/
|
||||||
|
private void updatePosition() {
|
||||||
|
if (player.getPosition() == null) {
|
||||||
|
positionLabel.setText("Position:\n-");
|
||||||
|
} else {
|
||||||
|
switch (player.getPosition()) {
|
||||||
|
case 1:
|
||||||
|
positionLabel.setText("Position:\n1st");
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
positionLabel.setText("Position:\n2nd");
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
positionLabel.setText("Position:\n3rd");
|
||||||
|
break;
|
||||||
|
case 21:
|
||||||
|
positionLabel.setText("Position:\n21st");
|
||||||
|
break;
|
||||||
|
case 22:
|
||||||
|
positionLabel.setText("Position:\n22nd");
|
||||||
|
break;
|
||||||
|
case 23:
|
||||||
|
positionLabel.setText("Position:\n23rd");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
positionLabel.setText("Position:\n" + player.getPosition() + "th");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates boat speed value displayed on race view.
|
||||||
|
*/
|
||||||
|
private void updateBoatSpeed() {
|
||||||
|
boatSpeedLabel.setText("Boat Speed:\n" + String.valueOf(player.getCurrentVelocity()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates boat heading value displayed on race view.
|
||||||
|
*/
|
||||||
|
private void updateBoatHeading() {
|
||||||
|
boatHeadingLabel.setText(String.format("Boat Heading:\n%.1f°", player.getHeading()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -411,28 +521,28 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
|
|||||||
* section
|
* section
|
||||||
*/
|
*/
|
||||||
private void updateOrder(ObservableList<ClientYacht> yachts) {
|
private void updateOrder(ObservableList<ClientYacht> yachts) {
|
||||||
List<Text> vboxEntries = new ArrayList<>();
|
// List<Text> vboxEntries = new ArrayList<>();
|
||||||
|
//
|
||||||
for (int i = 0; i < yachts.size(); i++) {
|
// for (int i = 0; i < yachts.size(); i++) {
|
||||||
// System.out.println("yacht == null " + String.valueOf(yacht == null));
|
//// System.out.println("yacht == null " + String.valueOf(yacht == null));
|
||||||
if (yachts.get(i).getBoatStatus() == BoatStatus.FINISHED
|
// if (yachts.get(i).getBoatStatus() == BoatStatus.FINISHED
|
||||||
.getCode()) { // 3 is finish status
|
// .getCode()) { // 3 is finish status
|
||||||
Text textToAdd = new Text(i + 1 + ". " +
|
// Text textToAdd = new Text(i + 1 + ". " +
|
||||||
yachts.get(i).getShortName() + " (Finished)");
|
// yachts.get(i).getShortName() + " (Finished)");
|
||||||
textToAdd.setFill(Paint.valueOf("#d3d3d3"));
|
// textToAdd.setFill(Paint.valueOf("#d3d3d3"));
|
||||||
vboxEntries.add(textToAdd);
|
// vboxEntries.add(textToAdd);
|
||||||
|
//
|
||||||
} else {
|
// } else {
|
||||||
Text textToAdd = new Text(i + 1 + ". " +
|
// Text textToAdd = new Text(i + 1 + ". " +
|
||||||
yachts.get(i).getShortName() + " ");
|
// yachts.get(i).getShortName() + " ");
|
||||||
textToAdd.setFill(Paint.valueOf("#d3d3d3"));
|
// textToAdd.setFill(Paint.valueOf("#d3d3d3"));
|
||||||
textToAdd.setStyle("");
|
// textToAdd.setStyle("");
|
||||||
vboxEntries.add(textToAdd);
|
// vboxEntries.add(textToAdd);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
Platform.runLater(() ->
|
// Platform.runLater(() ->
|
||||||
positionVbox.getChildren().setAll(vboxEntries)
|
// positionVbox.getChildren().setAll(vboxEntries)
|
||||||
);
|
// );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -533,15 +643,24 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
|
|||||||
* for the combobox to take action upon selection
|
* for the combobox to take action upon selection
|
||||||
*/
|
*/
|
||||||
private void initialiseBoatSelectionComboBox() {
|
private void initialiseBoatSelectionComboBox() {
|
||||||
yachtSelectionComboBox.setItems(
|
// yachtSelectionComboBox.setItems(
|
||||||
FXCollections.observableArrayList(participants.values())
|
// FXCollections.observableArrayList(participants.values())
|
||||||
);
|
// );
|
||||||
//Null check is if the listener is fired but nothing selected
|
// //Null check is if the listener is fired but nothing selected
|
||||||
yachtSelectionComboBox.valueProperty().addListener((obs, lastSelection, selectedBoat) -> {
|
// yachtSelectionComboBox.valueProperty().addListener((obs, lastSelection, selectedBoat) -> {
|
||||||
if (selectedBoat != null) {
|
// if (selectedBoat != null) {
|
||||||
gameView.selectBoat(selectedBoat);
|
// 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);
|
||||||
|
// }
|
||||||
|
// });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -551,9 +670,9 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
|
|||||||
FXMLLoader loader = new FXMLLoader(getClass().getResource("/views/FinishView.fxml"));
|
FXMLLoader loader = new FXMLLoader(getClass().getResource("/views/FinishView.fxml"));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
contentAnchorPane.getChildren().removeAll();
|
contentGridPane.getChildren().removeAll();
|
||||||
contentAnchorPane.getChildren().clear();
|
contentGridPane.getChildren().clear();
|
||||||
contentAnchorPane.getChildren().addAll((Pane) loader.load());
|
contentGridPane.getChildren().addAll((Pane) loader.load());
|
||||||
|
|
||||||
} catch (javafx.fxml.LoadException e) {
|
} catch (javafx.fxml.LoadException e) {
|
||||||
System.err.println(e.getCause().toString());
|
System.err.println(e.getCause().toString());
|
||||||
@@ -571,31 +690,31 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void setAnnotations(Integer annotationLevel) {
|
private void setAnnotations(Integer annotationLevel) {
|
||||||
switch (annotationLevel) {
|
// switch (annotationLevel) {
|
||||||
// No Annotations
|
// // No Annotations
|
||||||
case 0:
|
// case 0:
|
||||||
gameView.setAnnotationVisibilities(
|
// gameView.setAnnotationVisibilities(
|
||||||
false, false, false, false, false, false
|
// false, false, false, false, false, false
|
||||||
);
|
// );
|
||||||
break;
|
// break;
|
||||||
// Important Annotations
|
// // Important Annotations
|
||||||
case 1:
|
// case 1:
|
||||||
gameView.setAnnotationVisibilities(
|
// gameView.setAnnotationVisibilities(
|
||||||
importantAnnotations.getAnnotationState(Annotation.NAME),
|
// importantAnnotations.getAnnotationState(Annotation.NAME),
|
||||||
importantAnnotations.getAnnotationState(Annotation.SPEED),
|
// importantAnnotations.getAnnotationState(Annotation.SPEED),
|
||||||
importantAnnotations.getAnnotationState(Annotation.ESTTIMETONEXTMARK),
|
// importantAnnotations.getAnnotationState(Annotation.ESTTIMETONEXTMARK),
|
||||||
importantAnnotations.getAnnotationState(Annotation.LEGTIME),
|
// importantAnnotations.getAnnotationState(Annotation.LEGTIME),
|
||||||
importantAnnotations.getAnnotationState(Annotation.TRACK),
|
// importantAnnotations.getAnnotationState(Annotation.TRACK),
|
||||||
importantAnnotations.getAnnotationState(Annotation.WAKE)
|
// importantAnnotations.getAnnotationState(Annotation.WAKE)
|
||||||
);
|
// );
|
||||||
break;
|
// break;
|
||||||
// All Annotations
|
// // All Annotations
|
||||||
case 2:
|
// case 2:
|
||||||
gameView.setAnnotationVisibilities(
|
// gameView.setAnnotationVisibilities(
|
||||||
true, true, true, true, true, true
|
// true, true, true, true, true, true
|
||||||
);
|
// );
|
||||||
break;
|
// break;
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -618,8 +737,27 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
|
|||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateRaceData (RaceXMLData raceData) {
|
public void updateTokens(RaceXMLData raceData) {
|
||||||
this.courseData = raceData;
|
gameView.updateTokens(raceData.getTokens());
|
||||||
gameView.updateBorder(raceData.getCourseLimit());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ReadOnlyBooleanProperty getSendPressedProperty() {
|
||||||
|
return chatSend.pressedProperty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isChatInputFocused() {
|
||||||
|
return chatInput.focusedProperty().getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String readChatInput() {
|
||||||
|
String chat = chatInput.getText();
|
||||||
|
chatInput.clear();
|
||||||
|
contentStackPane.requestFocus();
|
||||||
|
return chat;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateChatHistory(Paint playerColour, String newMessage) {
|
||||||
|
Platform.runLater(() -> chatHistory.addMessage(playerColour, newMessage));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,342 @@
|
|||||||
|
package seng302.visualiser.controllers;
|
||||||
|
|
||||||
|
import com.jfoenix.controls.JFXButton;
|
||||||
|
import com.jfoenix.controls.JFXDialog;
|
||||||
|
import com.jfoenix.controls.JFXDialog.DialogTransition;
|
||||||
|
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;
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.fxml.FXMLLoader;
|
||||||
|
import javafx.fxml.Initializable;
|
||||||
|
import javafx.geometry.Pos;
|
||||||
|
import javafx.scene.control.Label;
|
||||||
|
import javafx.scene.control.ScrollPane;
|
||||||
|
import javafx.scene.input.KeyCode;
|
||||||
|
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;
|
||||||
|
|
||||||
|
public class ServerListController implements Initializable, ServerListenerDelegate {
|
||||||
|
|
||||||
|
//--------FXML BEGIN--------//
|
||||||
|
// Layout Related
|
||||||
|
@FXML
|
||||||
|
private VBox serverListVBox;
|
||||||
|
@FXML
|
||||||
|
private ScrollPane serverListScrollPane;
|
||||||
|
@FXML
|
||||||
|
private StackPane serverListMainStackPane;
|
||||||
|
// Host Button
|
||||||
|
@FXML
|
||||||
|
private JFXButton serverListHostButton;
|
||||||
|
//Direct Connect
|
||||||
|
@FXML
|
||||||
|
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
|
||||||
|
public void initialize(URL location, ResourceBundle resources) {
|
||||||
|
serverListVBox.minWidthProperty().bind(serverListScrollPane.widthProperty());
|
||||||
|
|
||||||
|
// Set Event Bindings
|
||||||
|
directConnectButton.setOnMouseEntered(event -> Sounds.playHoverSound());
|
||||||
|
serverListHostButton.setOnMouseEntered(event -> Sounds.playHoverSound());
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
roomNumber.setOnKeyPressed(event -> {
|
||||||
|
if (event.getCode().equals(KeyCode.ENTER)) {
|
||||||
|
connectToRoomCode(roomNumber.getText());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
directConnectButton.setOnMouseReleased(event -> {
|
||||||
|
directConnectDialog.show();
|
||||||
|
Sounds.playButtonClick();
|
||||||
|
});
|
||||||
|
|
||||||
|
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)) {
|
||||||
|
attemptToDirectConnect();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Validators as empty fields are invalid.
|
||||||
|
RequiredFieldValidator validator = new RequiredFieldValidator();
|
||||||
|
validator.setMessage("Field is Required");
|
||||||
|
textField.getValidators().add(validator);
|
||||||
|
}
|
||||||
|
|
||||||
|
autoSelectGame.setOnMouseReleased(e -> {
|
||||||
|
ServerListing listing;
|
||||||
|
DiscoveryServerClient client = new DiscoveryServerClient();
|
||||||
|
|
||||||
|
try {
|
||||||
|
listing = client.getRandomServer();
|
||||||
|
} catch (Exception e1) {
|
||||||
|
ViewManager.getInstance().showErrorSnackBar("Unable to connect to matchmaking server. Are you connected to the internet?");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (client.didFail()){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (listing == null || listing.equals(ServerRegistrationMessage.getEmptyRegistration())) {
|
||||||
|
ViewManager.getInstance().showErrorSnackBar("There are currently no servers available for you to connect to.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ViewManager.getInstance().getGameClient().runAsClient(listing.getAddress(), listing.getPortNumber());
|
||||||
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Validating the hostname
|
||||||
|
HostNameFieldValidator hostNameValidator = new HostNameFieldValidator();
|
||||||
|
hostNameValidator.setMessage("Host name incorrect");
|
||||||
|
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 {
|
||||||
|
ServerListener.getInstance().setDelegate(this);
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.warn("Could not start Server Listener Delegate");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create Label for no servers found.
|
||||||
|
noServersFound = new Label();
|
||||||
|
noServersFound.minWidthProperty().bind(serverListVBox.widthProperty());
|
||||||
|
noServersFound.setAlignment(Pos.CENTER);
|
||||||
|
noServersFound.setText("No Servers Found");
|
||||||
|
noServersFound.setStyle(
|
||||||
|
"-fx-font-size: 30px;"
|
||||||
|
+ "-fx-padding:50px;"
|
||||||
|
+ "-fx-text-fill: -fx-pp-dark-text-color;"
|
||||||
|
);
|
||||||
|
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 {
|
||||||
|
serverCreationDialog = new JFXDialog(serverListMainStackPane, dialogContent.load(),
|
||||||
|
DialogTransition.CENTER);
|
||||||
|
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())) {
|
||||||
|
DirectConnect();
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the hostName and portNumber are valid values to connect to.
|
||||||
|
* @param hostName host name to check.
|
||||||
|
* @param portNumber port number to check
|
||||||
|
* @return boolean value if host and port number are valid values
|
||||||
|
*/
|
||||||
|
private Boolean validateDirectConnection(String hostName, String portNumber) {
|
||||||
|
/*Boolean hostNameValid = ValidationTools.validateTextField(serverHostName);
|
||||||
|
*
|
||||||
|
Boolean portNumberValid = ValidationTools.validateTextField(serverPortNumber);
|
||||||
|
|
||||||
|
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.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connects the user to a lobby via the Direct Connect form.
|
||||||
|
*/
|
||||||
|
private void DirectConnect() {
|
||||||
|
Sounds.playButtonClick();
|
||||||
|
// ViewManager.getInstance().getGameClient().runAsClient(serverHostName.getText(), Integer.parseInt(serverPortNumber.getText()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refreshes the list of available servers.
|
||||||
|
* @param servers A list of ServerDescription objects showing available servers.
|
||||||
|
*/
|
||||||
|
private void refreshServers(List<ServerDescription> servers) {
|
||||||
|
serverListVBox.getChildren().clear();
|
||||||
|
|
||||||
|
if (servers.size() == 0) { // "No Servers Found"
|
||||||
|
serverListVBox.getChildren().add(noServersFound);
|
||||||
|
} else { // Populate the server list with a series of server cell objects.
|
||||||
|
for (ServerDescription server : servers) {
|
||||||
|
VBox pane = null;
|
||||||
|
|
||||||
|
FXMLLoader loader = new FXMLLoader(
|
||||||
|
getClass().getResource("/views/cells/ServerCell.fxml"));
|
||||||
|
|
||||||
|
loader.setController(new ServerCell(server));
|
||||||
|
|
||||||
|
try {
|
||||||
|
pane = loader.load();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
serverListVBox.getChildren().add(pane);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void serverRemoved(List<ServerDescription> servers) {
|
||||||
|
Platform.runLater(() -> refreshServers(servers));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
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,58 @@
|
|||||||
|
package seng302.visualiser.controllers;
|
||||||
|
|
||||||
|
import com.jfoenix.controls.JFXDecorator;
|
||||||
|
import com.jfoenix.controls.JFXSnackbar;
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.fxml.FXMLLoader;
|
||||||
|
import javafx.fxml.Initializable;
|
||||||
|
import javafx.scene.Parent;
|
||||||
|
import javafx.scene.Scene;
|
||||||
|
import javafx.scene.SceneAntialiasing;
|
||||||
|
import javafx.scene.image.Image;
|
||||||
|
import javafx.scene.layout.StackPane;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
import seng302.gameServer.ServerAdvertiser;
|
||||||
|
import seng302.utilities.Sounds;
|
||||||
|
import seng302.visualiser.GameClient;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.ResourceBundle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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(2000);
|
||||||
|
Platform.runLater(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
Stage stage = new Stage();
|
||||||
|
ViewManager.getInstance().initialStartView(stage);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
rootPane.getScene().getWindow().hide();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,168 +1,70 @@
|
|||||||
package seng302.visualiser.controllers;
|
package seng302.visualiser.controllers;
|
||||||
|
|
||||||
import java.net.Inet4Address;
|
import com.jfoenix.controls.JFXButton;
|
||||||
import java.net.InetAddress;
|
import java.io.IOException;
|
||||||
import java.net.NetworkInterface;
|
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.Enumeration;
|
import java.util.List;
|
||||||
import java.util.ResourceBundle;
|
import java.util.ResourceBundle;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.fxml.FXMLLoader;
|
||||||
import javafx.fxml.Initializable;
|
import javafx.fxml.Initializable;
|
||||||
import javafx.scene.control.TextField;
|
import javafx.scene.Node;
|
||||||
import javafx.scene.layout.AnchorPane;
|
import javafx.scene.control.Label;
|
||||||
import javafx.scene.layout.GridPane;
|
import org.slf4j.Logger;
|
||||||
import seng302.gameServer.GameState;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import seng302.gameServer.ServerDescription;
|
||||||
|
import seng302.utilities.Sounds;
|
||||||
import seng302.visualiser.GameClient;
|
import seng302.visualiser.GameClient;
|
||||||
|
|
||||||
/**
|
public class StartScreenController implements Initializable{
|
||||||
* A Class describing the actions of the start screen controller
|
|
||||||
* Created by wmu16 on 10/07/17.
|
|
||||||
*/
|
|
||||||
public class StartScreenController implements Initializable {
|
|
||||||
|
|
||||||
|
//--------FXML BEGIN--------//
|
||||||
@FXML
|
@FXML
|
||||||
private TextField ipTextField;
|
private Label headText;
|
||||||
@FXML
|
@FXML
|
||||||
private TextField portTextField;
|
private JFXButton startBtn;
|
||||||
@FXML
|
//---------FXML END---------//
|
||||||
private GridPane startScreen2;
|
|
||||||
@FXML
|
|
||||||
private AnchorPane holder;
|
|
||||||
|
|
||||||
GameClient gameClient;
|
private Node serverList;
|
||||||
|
private Logger logger = LoggerFactory.getLogger(StartScreenController.class);
|
||||||
|
private List<ServerDescription> servers;
|
||||||
|
private GameClient gameClient;
|
||||||
|
|
||||||
public void initialize(URL url, ResourceBundle resourceBundle) {
|
public void initialize(URL location, ResourceBundle resources) {
|
||||||
// gameClient = new GameClient(holder);
|
startBtn.setOnMousePressed(event -> {
|
||||||
}
|
startBtn.setText("LOADING...");
|
||||||
//
|
Sounds.playButtonClick();
|
||||||
// /**
|
});
|
||||||
// * Loads the fxml content into the parent pane
|
|
||||||
// * @param jfxUrl
|
|
||||||
// * @return the controller of the fxml
|
|
||||||
// */
|
|
||||||
// private Object setContentPane(String jfxUrl) {
|
|
||||||
// try {
|
|
||||||
// AnchorPane contentPane = (AnchorPane) startScreen2.getParent();
|
|
||||||
// contentPane.getChildren().removeAll();
|
|
||||||
// contentPane.getChildren().clear();
|
|
||||||
// contentPane.getStylesheets().add(getClass().getResource("/css/master.css").toString());
|
|
||||||
// FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(jfxUrl));
|
|
||||||
// contentPane.getChildren().addAll((Pane) fxmlLoader.load());
|
|
||||||
//
|
|
||||||
// return fxmlLoader.getController();
|
|
||||||
// } catch (IOException e) {
|
|
||||||
// e.printStackTrace();
|
|
||||||
// }
|
|
||||||
// return null;
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
startBtn.setOnMouseReleased(event -> goToServerBrowser());
|
||||||
|
startBtn.setOnMouseEntered(event -> Sounds.playHoverSound());
|
||||||
|
|
||||||
|
preloadServerListView();
|
||||||
|
|
||||||
/**
|
|
||||||
* ATTEMPTS TO:
|
|
||||||
* Sets up a new game state with your IP address as designated as the host.
|
|
||||||
* Starts a thread to listen for incoming connections.
|
|
||||||
* Starts a client to server thread and connects to own ip.
|
|
||||||
* Switches to the lobby screen
|
|
||||||
*/
|
|
||||||
@FXML
|
|
||||||
public void hostButtonPressed() {
|
|
||||||
// new GameState(getLocalHostIp());
|
|
||||||
gameClient = new GameClient(holder);
|
|
||||||
gameClient.runAsHost(getLocalHostIp(), 4942);
|
|
||||||
// try {
|
|
||||||
//// String ipAddress = InetAddress.getLocalHost().getHostAddress();
|
|
||||||
//// new GameState(ipAddress);
|
|
||||||
//// new MainServerThread();
|
|
||||||
//// ClientToServerThread clientToServerThread = new ClientToServerThread("localhost", 4950);
|
|
||||||
//// controller.setClientToServerThread(clientToServerThread);
|
|
||||||
// // get the lobby controller so that we can pass the game server thread to it
|
|
||||||
// new GameState(getLocalHostIp());
|
|
||||||
// MainServerThread mainServerThread = new MainServerThread();
|
|
||||||
//// ClientState.setHost(true);
|
|
||||||
// // host will connect and handshake to itself after setting up the server
|
|
||||||
// // TODO: 24/07/17 wmu16 - Make port number some static global type constant?
|
|
||||||
//// ClientToServerThread clientToServerThread = new ClientToServerThread(ClientState.getHostIp(), 4942);
|
|
||||||
//// ClientState.setConnectedToHost(true);
|
|
||||||
//// controller.setClientToServerThread(clientToServerThread);
|
|
||||||
// LobbyController lobbyController = (LobbyController) setContentPane("/views/LobbyView.fxml");
|
|
||||||
// lobbyController.setMainServerThread(mainServerThread);
|
|
||||||
// } catch (Exception e) {
|
|
||||||
// Alert alert = new Alert(AlertType.ERROR);
|
|
||||||
// alert.setHeaderText("Cannot host");
|
|
||||||
// alert.setContentText("Oops, failed to host, try to restart.");
|
|
||||||
// alert.showAndWait();
|
|
||||||
// e.printStackTrace();
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ATTEMPTS TO:
|
* Preloads the server list view to reduce load time between start screen and server list screen.
|
||||||
* Connect to an ip address and port using the ip and port specified on start screen.
|
|
||||||
* Starts a Client To Server Thread to maintain connection to host.
|
|
||||||
* Switch view to lobby view.
|
|
||||||
*/
|
*/
|
||||||
@FXML
|
private void preloadServerListView(){
|
||||||
public void connectButtonPressed() {
|
|
||||||
// TODO: 10/07/17 wmu16 - Finish function
|
|
||||||
gameClient = new GameClient(holder);
|
|
||||||
gameClient.runAsClient(ipTextField.getText().trim().toLowerCase(), 4942);
|
|
||||||
|
|
||||||
// try {
|
|
||||||
// String ipAddress = ipTextField.getText().trim().toLowerCase();
|
|
||||||
// Integer port = Integer.valueOf(portTextField.getText().trim());
|
|
||||||
//
|
|
||||||
//// ClientToServerThread clientToServerThread = new ClientToServerThread(ipAddress, port);
|
|
||||||
//// ClientState.setHost(false);
|
|
||||||
//// ClientState.setConnectedToHost(true);
|
|
||||||
//
|
|
||||||
//// controller.setClientToServerThread(clientToServerThread);
|
|
||||||
//// setContentPane("/views/LobbyView.fxml");
|
|
||||||
// } catch (Exception e) {
|
|
||||||
// Alert alert = new Alert(AlertType.ERROR);
|
|
||||||
// alert.setHeaderText("Cannot reach the host");
|
|
||||||
// alert.setContentText("Please check your host IP address.");
|
|
||||||
// alert.showAndWait();
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
// public void setController(Controller controller) {
|
|
||||||
// this.controller = controller;
|
|
||||||
// }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the local host ip address and sets this ip to ClientState.
|
|
||||||
* Only runs by the host.
|
|
||||||
*
|
|
||||||
* @return the localhost ip address
|
|
||||||
*/
|
|
||||||
private String getLocalHostIp() {
|
|
||||||
String ipAddress = null;
|
|
||||||
try {
|
try {
|
||||||
Enumeration<NetworkInterface> e = NetworkInterface.getNetworkInterfaces();
|
serverList = FXMLLoader
|
||||||
while (e.hasMoreElements()) {
|
.load(StartScreenController.class.getResource("/views/ServerListView.fxml"));
|
||||||
NetworkInterface ni = e.nextElement();
|
} catch (IOException e) {
|
||||||
if (ni.isLoopback())
|
e.printStackTrace();
|
||||||
continue;
|
logger.error("Could not preload server list view");
|
||||||
if(ni.isPointToPoint())
|
}
|
||||||
continue;
|
}
|
||||||
if(ni.isVirtual())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
Enumeration<InetAddress> addresses = ni.getInetAddresses();
|
/**
|
||||||
while(addresses.hasMoreElements()) {
|
* Changes the view to the Server Browser.
|
||||||
InetAddress address = addresses.nextElement();
|
*/
|
||||||
if(address instanceof Inet4Address) { // skip all ipv6
|
public void goToServerBrowser() {
|
||||||
ipAddress = address.getHostAddress();
|
try {
|
||||||
}
|
ViewManager.getInstance().setScene(serverList);
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
if (ipAddress == null) {
|
|
||||||
System.out.println("[HOST] Cannot obtain local host ip address.");
|
|
||||||
}
|
|
||||||
// ClientState.setHostIp(ipAddress);
|
|
||||||
return ipAddress;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,414 @@
|
|||||||
|
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 javafx.application.Platform;
|
||||||
|
import javafx.collections.ObservableList;
|
||||||
|
import javafx.fxml.FXMLLoader;
|
||||||
|
import javafx.scene.*;
|
||||||
|
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.Sounds;
|
||||||
|
import seng302.visualiser.GameClient;
|
||||||
|
import seng302.visualiser.controllers.dialogs.KeyBindingDialogController;
|
||||||
|
import seng302.visualiser.controllers.dialogs.PopupDialogController;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
public class ViewManager {
|
||||||
|
|
||||||
|
private static ViewManager instance;
|
||||||
|
private GameClient gameClient;
|
||||||
|
private JFXDecorator decorator;
|
||||||
|
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);
|
||||||
|
private Stage stage;
|
||||||
|
private JFXSnackbar jfxSnackbar;
|
||||||
|
private JFXDialog keyBindingDialog;
|
||||||
|
|
||||||
|
private ViewManager() {
|
||||||
|
properties = new HashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private FXMLLoader loadFxml(String fxmlLocation) {
|
||||||
|
return new FXMLLoader(
|
||||||
|
getClass().getResource(fxmlLocation)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ViewManager getInstance() {
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
public void initialStartView(Stage stage) throws Exception {
|
||||||
|
this.stage = stage;
|
||||||
|
Parent root = FXMLLoader.load(getClass().getResource("/views/StartScreenView.fxml"));
|
||||||
|
stage.setTitle("Party Parrots At Sea");
|
||||||
|
JFXDecorator decorator = new JFXDecorator(stage, root, false, true, true);
|
||||||
|
decorator.setCustomMaximize(true);
|
||||||
|
decorator.applyCss();
|
||||||
|
decorator.getStylesheets()
|
||||||
|
.add(getClass().getResource("/css/Master.css").toExternalForm());
|
||||||
|
gameClient = new GameClient(decorator);
|
||||||
|
setDecorator(decorator);
|
||||||
|
|
||||||
|
stage.getIcons().add(new Image(getClass().getResourceAsStream("/PP.png")));
|
||||||
|
Scene scene = new Scene(decorator, 1200, 800, false, SceneAntialiasing.BALANCED);
|
||||||
|
stage.setMinHeight(800);
|
||||||
|
stage.setMinWidth(1200);
|
||||||
|
stage.setScene(scene);
|
||||||
|
stage.show();
|
||||||
|
|
||||||
|
stage.setOnCloseRequest(e -> closeAll());
|
||||||
|
|
||||||
|
decorator.setOnCloseButtonAction(this::closeAll);
|
||||||
|
|
||||||
|
// TODO Platform.runLater(this::checkCompatibility);
|
||||||
|
|
||||||
|
Sounds.stopMusic();
|
||||||
|
Sounds.playMenuMusic();
|
||||||
|
|
||||||
|
decorator.setOnCloseButtonAction(() -> {
|
||||||
|
try {
|
||||||
|
ServerAdvertiser.getInstance().unregister();
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.warn("Couldn't unregister server");
|
||||||
|
}
|
||||||
|
|
||||||
|
gameClient.stopGame();
|
||||||
|
System.exit(0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the decorator when a new one is created (and ideally the old one destroyed)
|
||||||
|
* Also allows injection of buttons into the decorator for custom functions.
|
||||||
|
*
|
||||||
|
* @param newDecorator The new JFXDecorator to handle the game window.
|
||||||
|
*/
|
||||||
|
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",
|
||||||
|
"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",
|
||||||
|
"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(16, 16);
|
||||||
|
spacer.setSize(40, 16);
|
||||||
|
keyBindingGlyph.setSize(24, 16);
|
||||||
|
|
||||||
|
// Determine which graphic should go on the button
|
||||||
|
if (Sounds.isMusicMuted() && Sounds.isSoundEffectsMuted()) {
|
||||||
|
btnMute.setGraphic(volumeOff);
|
||||||
|
} else {
|
||||||
|
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)) {
|
||||||
|
btnMute.setGraphic(volumeOn);
|
||||||
|
} else {
|
||||||
|
btnMute.setGraphic(volumeOff);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
jfxSnackbar = new JFXSnackbar(decorator);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 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) {
|
||||||
|
logger.warn("Could not un-register game");
|
||||||
|
}
|
||||||
|
|
||||||
|
System.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public JFXDecorator getDecorator() {
|
||||||
|
return decorator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setScene(Node scene) {
|
||||||
|
Platform.runLater(() -> decorator.setContent(scene));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new stage and re-initialize the start view in the new stage.
|
||||||
|
*/
|
||||||
|
public void goToStartView() {
|
||||||
|
try {
|
||||||
|
this.stage.close();
|
||||||
|
Stage stage = new Stage();
|
||||||
|
initialStartView(stage);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public GameClient getGameClient() {
|
||||||
|
return gameClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getProperty(String key) {
|
||||||
|
return properties.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProperty(String key, String val) {
|
||||||
|
properties.put(key, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPlayerList(ObservableList<String> playerList) {
|
||||||
|
this.playerList = playerList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObservableList<String> getPlayerList() {
|
||||||
|
return playerList;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
public LobbyController goToLobby(Boolean disableReadyButton) {
|
||||||
|
FXMLLoader loader = loadFxml("/views/LobbyView.fxml");
|
||||||
|
|
||||||
|
try {
|
||||||
|
setScene(loader.load());
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.error("Could not load lobby view");
|
||||||
|
}
|
||||||
|
|
||||||
|
LobbyController lobbyController = loader.getController();
|
||||||
|
|
||||||
|
if (disableReadyButton) {
|
||||||
|
lobbyController.disableReadyButton();
|
||||||
|
}
|
||||||
|
|
||||||
|
return loader.getController();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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();
|
||||||
|
decorator.getStylesheets()
|
||||||
|
.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);
|
||||||
|
|
||||||
|
stage.setMinHeight(500);
|
||||||
|
stage.setMinWidth(800);
|
||||||
|
stage.setTitle("Party Parrots At Sea");
|
||||||
|
stage.getIcons().add(new Image(getClass().getResourceAsStream("/PP.png")));
|
||||||
|
stage.setOnCloseRequest(e -> closeAll());
|
||||||
|
stage.setScene(scene);
|
||||||
|
stage.show();
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
while (loader.getController() == null) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(50);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return loader.getController();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void showErrorSnackBar(String msg){
|
||||||
|
decorator.getStylesheets()
|
||||||
|
.add(getClass().getResource("/css/dialogs/Snackbar.css").toExternalForm());
|
||||||
|
|
||||||
|
JFXSnackbar bar = new JFXSnackbar(decorator);
|
||||||
|
bar.enqueue(new JFXSnackbar.SnackbarEvent(msg));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Stage getStage() {
|
||||||
|
return stage;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
package seng302.visualiser.controllers.cells;
|
||||||
|
|
||||||
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.scene.Group;
|
||||||
|
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;
|
||||||
|
|
||||||
|
public class PlayerCell {
|
||||||
|
|
||||||
|
//--------FXML BEGIN--------//
|
||||||
|
@FXML
|
||||||
|
private Label playerName;
|
||||||
|
@FXML
|
||||||
|
private GridPane playerListCell;
|
||||||
|
@FXML
|
||||||
|
private Pane boatPane;
|
||||||
|
//---------FXML END---------//
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
private Color boatColor;
|
||||||
|
private Integer playerId;
|
||||||
|
private BoatMeshType boatType;
|
||||||
|
|
||||||
|
public PlayerCell(Integer playerId, ClientYacht yacht) {
|
||||||
|
this.playerId = playerId;
|
||||||
|
this.name = yacht.getBoatName();
|
||||||
|
this.boatColor = yacht.getColour();
|
||||||
|
this.boatType = yacht.getBoatType();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void initialize() {
|
||||||
|
// Set Player Name
|
||||||
|
playerName.setText(name);
|
||||||
|
// Add Rotating Boat to Player Cell with players color on it.
|
||||||
|
Group group = new Group();
|
||||||
|
boatPane.getChildren().add(group);
|
||||||
|
BoatModel bo = ModelFactory.boatIconView(boatType, boatColor);
|
||||||
|
group.getChildren().add(bo.getAssets());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getPlayerId() {
|
||||||
|
return playerId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPlayerName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Color getBoatColor() {
|
||||||
|
return boatColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
package seng302.visualiser.controllers.cells;
|
||||||
|
|
||||||
|
import com.jfoenix.controls.JFXButton;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.ResourceBundle;
|
||||||
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.fxml.Initializable;
|
||||||
|
import javafx.scene.control.Label;
|
||||||
|
import javafx.scene.layout.GridPane;
|
||||||
|
import javafx.scene.layout.VBox;
|
||||||
|
import seng302.gameServer.ServerDescription;
|
||||||
|
import seng302.utilities.Sounds;
|
||||||
|
import seng302.visualiser.controllers.ViewManager;
|
||||||
|
|
||||||
|
public class ServerCell implements Initializable {
|
||||||
|
|
||||||
|
//--------FXML BEGIN--------//
|
||||||
|
//Layout
|
||||||
|
@FXML
|
||||||
|
private VBox serverCellVBox;
|
||||||
|
@FXML
|
||||||
|
private GridPane serverListCell;
|
||||||
|
//Server Information
|
||||||
|
@FXML
|
||||||
|
private Label serverName;
|
||||||
|
@FXML
|
||||||
|
private Label mapName;
|
||||||
|
@FXML
|
||||||
|
private Label serverPlayerCount;
|
||||||
|
//Server Connection
|
||||||
|
@FXML
|
||||||
|
private JFXButton serverConnButton;
|
||||||
|
//---------FXML END---------//
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
private String mapNameString;
|
||||||
|
|
||||||
|
private String currPlayerCount;
|
||||||
|
|
||||||
|
private String hostName;
|
||||||
|
private Integer portNumber;
|
||||||
|
|
||||||
|
public ServerCell(ServerDescription server) {
|
||||||
|
this.name = server.getName();
|
||||||
|
|
||||||
|
this.currPlayerCount = server.getNumPlayers().toString() + "/" + server.getCapacity().toString();
|
||||||
|
this.mapNameString = server.getMapName();
|
||||||
|
|
||||||
|
// Can cause issues on windows PCs without the bonjour service installed.
|
||||||
|
this.hostName = server.getAddress();
|
||||||
|
this.portNumber = server.portNumber();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void initialize(URL location, ResourceBundle resources) {
|
||||||
|
serverName.setText(name);
|
||||||
|
serverPlayerCount.setText(currPlayerCount);
|
||||||
|
mapName.setText(mapNameString);
|
||||||
|
|
||||||
|
serverCellVBox.setOnMouseEntered(event -> Sounds.playHoverSound());
|
||||||
|
|
||||||
|
serverConnButton.setOnMouseReleased(event -> {
|
||||||
|
Sounds.playButtonClick();
|
||||||
|
joinServer();
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts to connect to the chosen server using the button on the serverCell.
|
||||||
|
*/
|
||||||
|
private void joinServer() {
|
||||||
|
ViewManager.getInstance().getGameClient().runAsClient(hostName, portNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,180 @@
|
|||||||
|
package seng302.visualiser.controllers.dialogs;
|
||||||
|
|
||||||
|
import com.jfoenix.controls.JFXButton;
|
||||||
|
import com.jfoenix.controls.JFXColorPicker;
|
||||||
|
import com.jfoenix.controls.JFXTextField;
|
||||||
|
import com.jfoenix.validation.RequiredFieldValidator;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.ResourceBundle;
|
||||||
|
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;
|
||||||
|
|
||||||
|
public class BoatCustomizeController implements Initializable{
|
||||||
|
|
||||||
|
//--------FXML BEGIN--------//
|
||||||
|
@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
|
||||||
|
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) {
|
||||||
|
|
||||||
|
socketThread = ViewManager.getInstance().getGameClient().getServerThread();
|
||||||
|
findMaxStats();
|
||||||
|
RequiredFieldValidator playerNameReqValidator = new RequiredFieldValidator();
|
||||||
|
playerNameReqValidator.setMessage("Player name required.");
|
||||||
|
|
||||||
|
FieldLengthValidator playerNameLengthValidator = new FieldLengthValidator(20);
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts to submit a valid customization packet for boat name and boat color.
|
||||||
|
*/
|
||||||
|
private void submitCustomization() {
|
||||||
|
|
||||||
|
if (ValidationTools.validateTextField(boatName)) {
|
||||||
|
socketThread
|
||||||
|
.sendCustomizationRequest(CustomizeRequestType.NAME, boatName.getText().getBytes());
|
||||||
|
|
||||||
|
Color color = colorPicker.getValue();
|
||||||
|
short red = (short) (color.getRed() * 255);
|
||||||
|
short green = (short) (color.getGreen() * 255);
|
||||||
|
short blue = (short) (color.getBlue() * 255);
|
||||||
|
|
||||||
|
byte[] colorArray = new byte[3];
|
||||||
|
|
||||||
|
colorArray[0] = (byte) red;
|
||||||
|
colorArray[1] = (byte) green;
|
||||||
|
colorArray[2] = (byte) blue;
|
||||||
|
|
||||||
|
socketThread.sendCustomizationRequest(CustomizeRequestType.COLOR, colorArray);
|
||||||
|
socketThread.sendCustomizationRequest(CustomizeRequestType.SHAPE, currentBoat.toString().getBytes());
|
||||||
|
lobbyController.closeCustomizationDialog();
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPlayerName(String name) {
|
||||||
|
this.boatName.setText(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPlayerColor(Color playerColor) {
|
||||||
|
this.colorPicker.setValue(playerColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
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,195 @@
|
|||||||
|
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 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());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,179 @@
|
|||||||
|
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.fxml.FXML;
|
||||||
|
import javafx.fxml.Initializable;
|
||||||
|
import javafx.scene.control.Label;
|
||||||
|
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;
|
||||||
|
|
||||||
|
public class ServerCreationController implements Initializable {
|
||||||
|
|
||||||
|
//--------FXML BEGIN--------//
|
||||||
|
@FXML
|
||||||
|
private JFXTextField serverName;
|
||||||
|
@FXML
|
||||||
|
private JFXSlider maxPlayersSlider;
|
||||||
|
@FXML
|
||||||
|
private Label maxPlayersLabel;
|
||||||
|
@FXML
|
||||||
|
private JFXButton submitBtn;
|
||||||
|
@FXML
|
||||||
|
private Label closeLabel;
|
||||||
|
@FXML
|
||||||
|
private JFXButton nextMapButton;
|
||||||
|
@FXML
|
||||||
|
private JFXButton lastMapButton;
|
||||||
|
@FXML
|
||||||
|
private Label mapNameLabel;
|
||||||
|
@FXML
|
||||||
|
private JFXSlider legsSlider;
|
||||||
|
@FXML
|
||||||
|
private Label legsSliderLabel;
|
||||||
|
@FXML
|
||||||
|
private JFXCheckBox pickupsCheckBox;
|
||||||
|
@FXML
|
||||||
|
private AnchorPane mapHolder;
|
||||||
|
|
||||||
|
private MapMaker mapMaker = MapMaker.getInstance();
|
||||||
|
|
||||||
|
//---------FXML END---------//
|
||||||
|
|
||||||
|
private List<ServerCreationDialogListener> serverCreationDialogListeners;
|
||||||
|
|
||||||
|
public void initialize(URL location, ResourceBundle resources) {
|
||||||
|
|
||||||
|
maxPlayersSlider.valueProperty().addListener(
|
||||||
|
(observable, oldValue, newValue) -> updateMaxPlayerLabel()
|
||||||
|
);
|
||||||
|
maxPlayersSlider.setMax(mapMaker.getMaxPlayers());
|
||||||
|
maxPlayersSlider.setValue(mapMaker.getMaxPlayers());
|
||||||
|
|
||||||
|
legsSlider.valueProperty().addListener(
|
||||||
|
(obs, oldVal, newVal) -> updateLegSliderLabel()
|
||||||
|
);
|
||||||
|
legsSlider.setMax(10);
|
||||||
|
|
||||||
|
updateMaxPlayerLabel();
|
||||||
|
updateLegSliderLabel();
|
||||||
|
|
||||||
|
FieldLengthValidator fieldLengthValidator = new FieldLengthValidator(40);
|
||||||
|
fieldLengthValidator.setMessage("Server name too long.");
|
||||||
|
|
||||||
|
RequiredFieldValidator fieldRequiredValidator = new RequiredFieldValidator();
|
||||||
|
fieldRequiredValidator.setMessage("Server name is required.");
|
||||||
|
|
||||||
|
serverName.setValidators(fieldLengthValidator, fieldRequiredValidator);
|
||||||
|
|
||||||
|
submitBtn.setOnMouseReleased(event -> {
|
||||||
|
Sounds.playButtonClick();
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates that a server has a valid name and creates the server.
|
||||||
|
*/
|
||||||
|
private void validateServerSettings() {
|
||||||
|
submitBtn.setText("CREATING...");
|
||||||
|
if (ValidationTools.validateTextField(serverName)) {
|
||||||
|
createServer();
|
||||||
|
} else {
|
||||||
|
submitBtn.setText("SUBMIT");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a server with a given set of details.
|
||||||
|
*/
|
||||||
|
private void createServer() {
|
||||||
|
ServerDescription serverDescription = ViewManager.getInstance().getGameClient()
|
||||||
|
.runAsHost("localhost", 4941, 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());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates a label as the user slides along the max players slider.
|
||||||
|
*/
|
||||||
|
private void updateMaxPlayerLabel() {
|
||||||
|
maxPlayersSlider.setValue(Math.floor(maxPlayersSlider.getValue()));
|
||||||
|
maxPlayersLabel.setText(String.format("Max players: %.0f", maxPlayersSlider.getValue()));
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,400 +0,0 @@
|
|||||||
package seng302.visualiser.fxObjects;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import javafx.application.Platform;
|
|
||||||
import javafx.geometry.Point2D;
|
|
||||||
import javafx.scene.Group;
|
|
||||||
import javafx.scene.Node;
|
|
||||||
import javafx.scene.paint.Color;
|
|
||||||
import javafx.scene.paint.Paint;
|
|
||||||
import javafx.scene.shape.Line;
|
|
||||||
import javafx.scene.shape.Polygon;
|
|
||||||
import javafx.scene.shape.Polyline;
|
|
||||||
import javafx.scene.shape.StrokeLineCap;
|
|
||||||
import javafx.scene.transform.Rotate;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 {
|
|
||||||
|
|
||||||
@FunctionalInterface
|
|
||||||
public interface SelectedBoatListener {
|
|
||||||
|
|
||||||
void notifySelected(BoatObject boatObject, Boolean isSelected);
|
|
||||||
}
|
|
||||||
|
|
||||||
//Constants for drawing
|
|
||||||
private static final double BOAT_HEIGHT = 15d;
|
|
||||||
private static final double BOAT_WIDTH = 10d;
|
|
||||||
|
|
||||||
private double xVelocity;
|
|
||||||
private double yVelocity;
|
|
||||||
private double lastHeading;
|
|
||||||
private double sailState;
|
|
||||||
//Graphical objects
|
|
||||||
private Polyline trail = new Polyline();
|
|
||||||
private Polygon boatPoly;
|
|
||||||
private Polygon sail;
|
|
||||||
private Wake wake;
|
|
||||||
private Line leftLayLine;
|
|
||||||
private Line rightLayline;
|
|
||||||
private double distanceTravelled, lastRotation;
|
|
||||||
private Point2D lastPoint;
|
|
||||||
private Paint colour = Color.BLACK;
|
|
||||||
private Boolean isSelected = false, destinationSet; //All boats are initialised as selected
|
|
||||||
private boolean isPlayer = false;
|
|
||||||
|
|
||||||
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) {
|
|
||||||
boatPoly = new Polygon(points);
|
|
||||||
boatPoly.setFill(colour);
|
|
||||||
boatPoly.setFill(this.colour);
|
|
||||||
boatPoly.setOnMouseEntered(event -> {
|
|
||||||
boatPoly.setFill(Color.FLORALWHITE);
|
|
||||||
boatPoly.setStroke(Color.RED);
|
|
||||||
});
|
|
||||||
boatPoly.setOnMouseExited(event -> {
|
|
||||||
boatPoly.setFill(colour);
|
|
||||||
boatPoly.setFill(this.colour);
|
|
||||||
boatPoly.setStroke(Color.BLACK);
|
|
||||||
});
|
|
||||||
boatPoly.setOnMouseClicked(event -> setIsSelected(!isSelected));
|
|
||||||
boatPoly.setCache(true);
|
|
||||||
// boatPoly.setCacheHint(CacheHint.SPEED);
|
|
||||||
|
|
||||||
// annotationBox = new AnnotationBox();
|
|
||||||
// annotationBox.setFill(colour);
|
|
||||||
|
|
||||||
leftLayLine = new Line();
|
|
||||||
rightLayline = new Line();
|
|
||||||
trail.getStrokeDashArray().setAll(5d, 10d);
|
|
||||||
trail.setCache(true);
|
|
||||||
wake = new Wake(0, -BOAT_HEIGHT);
|
|
||||||
wake.setVisible(true);
|
|
||||||
|
|
||||||
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();
|
|
||||||
super.getChildren().addAll(boatPoly, sail);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFill (Paint value) {
|
|
||||||
this.colour = value;
|
|
||||||
boatPoly.setFill(colour);
|
|
||||||
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.
|
|
||||||
*/
|
|
||||||
public void moveTo(double x, double y, double rotation, double velocity, Boolean sailIn, double windDir) {
|
|
||||||
Double dx = Math.abs(boatPoly.getLayoutX() - x);
|
|
||||||
Double dy = Math.abs(boatPoly.getLayoutY() - y);
|
|
||||||
Platform.runLater(() -> {
|
|
||||||
rotateTo(rotation, sailIn, windDir);
|
|
||||||
boatPoly.setLayoutX(x);
|
|
||||||
boatPoly.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);
|
|
||||||
// boatPoly.setLayoutX(x);
|
|
||||||
// boatPoly.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) {
|
|
||||||
boatPoly.getTransforms().setAll(new Rotate(heading));
|
|
||||||
if (sailsIn) {
|
|
||||||
Double sailWindOffset = 30.0;
|
|
||||||
Double upwindAngleLimit = 15.0;
|
|
||||||
Double downwindAngleLimit = 10.0; //Upwind from normalised horizontal
|
|
||||||
Double normalizedHeading = normalizeHeading(heading, windDir);
|
|
||||||
if (normalizedHeading < 180) {
|
|
||||||
sail.getTransforms().setAll(new Rotate(windDir + 90 + sailWindOffset));
|
|
||||||
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);
|
|
||||||
if (normalizedHeading > 90 + sailWindOffset){
|
|
||||||
sail.getTransforms().setAll(new Rotate(heading + downwindAngleLimit));
|
|
||||||
}
|
|
||||||
if (normalizedHeading < sailWindOffset + upwindAngleLimit){
|
|
||||||
sail.getTransforms().setAll(new Rotate(heading + 90 - upwindAngleLimit));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
sail.getTransforms().setAll(new Rotate(windDir + 90 - sailWindOffset));
|
|
||||||
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);
|
|
||||||
if (normalizedHeading < 270 - sailWindOffset){
|
|
||||||
sail.getTransforms().setAll(new Rotate(heading + 180 - downwindAngleLimit));
|
|
||||||
}
|
|
||||||
if (normalizedHeading > 360 - (sailWindOffset + upwindAngleLimit)){
|
|
||||||
sail.getTransforms().setAll(new Rotate(heading + 90 + upwindAngleLimit));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
sail.getTransforms().setAll(new Rotate(windDir));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
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] = (BOAT_HEIGHT * i) / BOAT_HEIGHT / 2;
|
|
||||||
points[199 - (i * 2)] = (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() {
|
|
||||||
// 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(),
|
|
||||||
// boatPoly.getLayoutX(),
|
|
||||||
// boatPoly.getLayoutY()
|
|
||||||
// );
|
|
||||||
// l.getStrokeDashArray().setAll(3d, 7d);
|
|
||||||
// l.setStroke(colour);
|
|
||||||
// l.setCache(true);
|
|
||||||
// l.setCacheHint(CacheHint.SPEED);
|
|
||||||
// lineGroup.getChildren().add(l);
|
|
||||||
// }
|
|
||||||
// lastPoint = new Point2D(boatPoly.getLayoutX(), boatPoly.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(boatPoly.getLayoutX(), boatPoly.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 boatPoly.getLayoutX();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public Double getBoatLayoutY() {
|
|
||||||
return boatPoly.getLayoutY();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets this boat to appear highlighted
|
|
||||||
*/
|
|
||||||
public void setAsPlayer() {
|
|
||||||
boatPoly.getPoints().setAll(
|
|
||||||
-BOAT_WIDTH / 1.75, BOAT_HEIGHT / 1.75,
|
|
||||||
0.0, -BOAT_HEIGHT / 1.75,
|
|
||||||
BOAT_WIDTH / 1.75, BOAT_HEIGHT / 1.75
|
|
||||||
);
|
|
||||||
boatPoly.setStroke(Color.BLACK);
|
|
||||||
boatPoly.setStrokeWidth(2);
|
|
||||||
boatPoly.setStrokeLineCap(StrokeLineCap.ROUND);
|
|
||||||
isPlayer = true;
|
|
||||||
animateSail();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTrajectory(double heading, double velocity, double windDir) {
|
|
||||||
wake.setRotation(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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user