mirror of
https://github.com/michaelrausch/Party-Parrots-At-Sea.git
synced 2026-05-09 14:28:43 +00:00
Compare commits
535 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c0bd498f1b | |||
| b18d9e8573 | |||
| e9881bb24a | |||
| ab5ad58237 | |||
| df7264cc1f | |||
| 5248921576 | |||
| 2dcdd1c248 | |||
| 9b00f76907 | |||
| f11c457d28 | |||
| 9e4fa30787 | |||
| 99ce4fa11d | |||
| 69d1fa9488 | |||
| 66e6a8a2a4 | |||
| dd43097677 | |||
| 1a53579317 | |||
| 132a729758 | |||
| 2e7487fdfc | |||
| 1bd4db73cd | |||
| e990c68d40 | |||
| 83871a0336 | |||
| 6cba024d64 | |||
| 51747e2d13 | |||
| b3981b19e0 | |||
| 3d7a64068f | |||
| c12f7408ad | |||
| 7027de80c4 | |||
| 35b50d1436 | |||
| 5c50e77efa | |||
| 0e93be7b36 | |||
| 191b818e38 | |||
| 00b09997b0 | |||
| d250c635d8 | |||
| 376c4d25a8 | |||
| e66abb4340 | |||
| a19e191684 | |||
| 19db6668da | |||
| 44275aec04 | |||
| 64245833cd | |||
| aa0149b9a7 | |||
| f6b41f0513 | |||
| 9b00ba654a | |||
| 8dfdb228e9 | |||
| 1042817e4e | |||
| 066557584f | |||
| 4011295b8b | |||
| 0a885dd8fd | |||
| e9b50038a9 | |||
| 364264377a | |||
| 9112183ac3 | |||
| 957821f1f2 | |||
| 094eb4c1cf | |||
| 607acff7c6 | |||
| 22fdf1e4ac | |||
| da8c91f5c1 | |||
| 52dc7a956d | |||
| 9f64b2380d | |||
| b05580f018 | |||
| c20c6fb264 | |||
| faeece27ff | |||
| 6ca75b2cac | |||
| 6ff309a40c | |||
| 40a7f9bc5b | |||
| c4a6113f6c | |||
| 307e79ecfc | |||
| 7d8a6afa5f | |||
| ea0be5e952 | |||
| 7197bc2bee | |||
| fba522d0c3 | |||
| 0e829874c2 | |||
| c5d56065b6 | |||
| 410d765745 | |||
| fe76e85c71 | |||
| 9d61a43bd7 | |||
| c39582de5c | |||
| d4d7ddf8e2 | |||
| a1933c2869 | |||
| 8084a61333 | |||
| 03f5f91043 | |||
| 9ed52a1225 | |||
| 027324cc4f | |||
| 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 | |||
| a501b21d66 | |||
| 5fb8a0c2c1 | |||
| d867a4b7a2 | |||
| 3c91db59f3 | |||
| 72a390b484 | |||
| 978def4cf7 | |||
| 2331942bf9 | |||
| d41bdfebbc | |||
| d0565503e8 | |||
| f0c48e76e1 | |||
| 3639e0d3ce | |||
| 0276911b88 | |||
| af3a400841 | |||
| 5a2a78a7d5 | |||
| 5fc7442359 | |||
| 39cfaf6780 | |||
| 7a11b5eb77 | |||
| faf4600f51 | |||
| 3e383465a9 | |||
| ac279583df | |||
| ef2659a7b9 | |||
| 769d1956b3 | |||
| 8ca455ed24 | |||
| 7bda2bc580 | |||
| d7a290478d | |||
| d73e4f8ec5 | |||
| 6e02d3e533 | |||
| 7b4a70817b | |||
| 87acce71ea | |||
| 95b1c8a01f | |||
| dfd421c3b7 | |||
| 4596f1e0f8 | |||
| a59d06fbbf | |||
| 65286f273b | |||
| 9727e86249 | |||
| db614fe845 | |||
| 85899e3fbe | |||
| 87d6799c10 | |||
| 67f0c213c2 | |||
| c103595bba | |||
| 86a7c2565c | |||
| 21e6819f16 | |||
| 28569506f0 | |||
| dad2ebf693 | |||
| 02a7b804c1 | |||
| 9dbb31dcef | |||
| a932f41cc2 | |||
| 3570a3d2eb | |||
| 85852df176 | |||
| 6d045e9976 | |||
| 4fc00f8a3c | |||
| 9c2bac36b6 | |||
| 8501fc0b6d | |||
| e9e7f306cd | |||
| fa68a5fdff | |||
| 07a39722fb | |||
| 1d4ab0e1fa | |||
| 653651f97f | |||
| b1ba6e729a | |||
| 99685d76a9 | |||
| 711c94001b | |||
| 7f3d66d01d | |||
| 1db75a8ae4 | |||
| 7b9d28ade9 | |||
| 5843fc9212 | |||
| 3542c646f8 | |||
| 8fb5ea2223 | |||
| 958c1e216f | |||
| 100689a20b | |||
| 2da887e677 | |||
| bd213bcd9e | |||
| bf016356a6 | |||
| 76a750a764 | |||
| 000d562ffe | |||
| d02f2385dd | |||
| a5261494cd | |||
| ac47e9d88a | |||
| 7329f7dc65 | |||
| 7c5f146b11 | |||
| e3fbbd4590 | |||
| dc8baa09a3 | |||
| 720ce0ae5b | |||
| a7a667b4bc | |||
| 4e68cf31cf | |||
| d03460d69e | |||
| 50baf6f85b | |||
| 23a04facbe | |||
| 7fbecc8b3d | |||
| 9cb5956f3c | |||
| 366ebb3adb | |||
| 458bd5408f | |||
| c00d165f47 | |||
| d2bb15471a | |||
| c0cd260610 | |||
| 6a9357f598 | |||
| 6909f99773 | |||
| ce5424cc79 | |||
| c125708a4a | |||
| 2dc0ba07d9 | |||
| 2a3231d334 | |||
| d6a436d2eb | |||
| baacd8a9c0 | |||
| e1dddd317d | |||
| b9ae9c4730 | |||
| fab5f9229f | |||
| ff92262a78 | |||
| a4547e12cb | |||
| 1a39b6e4a3 | |||
| 58446ffaed | |||
| 1f09005147 | |||
| 3754b71f4d | |||
| 1d2222b599 | |||
| 89ceab0c4b | |||
| 76dabb8138 | |||
| 8b543488e3 | |||
| c52c345e53 | |||
| 9420717b44 | |||
| 9c1fe72f6b | |||
| 2e892f35ea | |||
| bedd742c93 | |||
| add6856727 | |||
| 4dc6b2af2b | |||
| e65558b8ac | |||
| e4cc5a0950 | |||
| 4b4f8a25a3 | |||
| c77adf385e | |||
| 9b7f4a93d8 | |||
| e780b47160 | |||
| 028fc44dce | |||
| a185c9dc96 | |||
| 52e10997f1 | |||
| f2f750298c | |||
| 1755b00079 | |||
| a844585faf | |||
| b7fe79a5cc | |||
| 2d5492601f | |||
| 0ee12021e2 | |||
| 6218d5506b | |||
| d79b0421c2 | |||
| 0b978593d4 | |||
| 8cb5b8caec | |||
| 2093e79b6a | |||
| 8ec6490627 | |||
| 0a7a9018f3 | |||
| 93cb0ca600 | |||
| 25c2de63a2 | |||
| 2411a3cc2c | |||
| fda233f5ad | |||
| c58cb1a476 | |||
| d2fd9ebaea | |||
| fda6625256 | |||
| 6ddaaa0dfa | |||
| 55c22fa7e2 | |||
| a7b8b0dbc3 | |||
| 7e1686a980 | |||
| 32b231e78a | |||
| 3ad37faedc | |||
| 09c4f98056 | |||
| 1c2870649a | |||
| a2ee4411be | |||
| a23352ef85 | |||
| f9d5bd10b1 | |||
| 0b8ad137b3 | |||
| a746191dba | |||
| 1772d1c05e | |||
| 1d7b527130 | |||
| 430779c943 | |||
| 9c79897e01 | |||
| 07386ed2db | |||
| abb15f6edf | |||
| 87f2f1fe63 | |||
| 249ad9e5c0 | |||
| 9d02d2fbea | |||
| b1598ccb0f | |||
| 08304f9c3e | |||
| 4cc48a355e | |||
| 2c5fddb695 | |||
| 126d8ea870 | |||
| 30a6cb98ec | |||
| 8813d06010 | |||
| 0fbca89030 | |||
| 421ef3c65a | |||
| 5937f8b640 | |||
| 5ec67d0b80 | |||
| 941febaf62 | |||
| a545e9dbc3 | |||
| b0e7dddaf3 | |||
| ed0a783374 | |||
| 97696cc95b | |||
| 4375b73257 | |||
| f97b18d594 | |||
| c7857872ce | |||
| 79105a1bdc | |||
| a4b22190c0 | |||
| a3ce5998ff | |||
| 7f0329dda6 | |||
| 8a40119a98 | |||
| a470cb66a2 | |||
| ecf2c52cfa | |||
| 43788bd153 | |||
| 81c2a8e0fd | |||
| e90a0ce435 | |||
| a727014fcb | |||
| ae28ccf228 | |||
| 281ce2d842 | |||
| f8af9cc259 | |||
| 8af80e6c3a | |||
| 874cdec654 | |||
| 99d5545ed3 | |||
| 423f1acdb6 | |||
| 454e9ac9f1 | |||
| a8e70b3631 | |||
| eb83e9dcc5 | |||
| 1ab849fd0d | |||
| db078538ff | |||
| 1c0d869894 | |||
| f9e6df46c1 | |||
| 5228c078bc | |||
| f5f73ede6b | |||
| 1160f274ee | |||
| 53f5d63f15 | |||
| 1150ec3e43 | |||
| c655cb3fab | |||
| 8e24c204fd | |||
| 7885b3fae2 | |||
| b01d39f19f | |||
| bbf494e9a1 | |||
| eae201cb4b | |||
| 87ef37a689 | |||
| b2c7b65191 | |||
| 908c0749cf | |||
| 47c5e6f155 | |||
| 9deba732b0 | |||
| b82d0d0137 | |||
| f1ad03e913 | |||
| 6cae338c1e | |||
| 7894e31926 | |||
| ecbb3f6658 | |||
| 8ac44d13df | |||
| 9c9f6e4e80 | |||
| 08e369f1ae | |||
| b0e99ab444 | |||
| acd54dec7a | |||
| 6242ab0b2e | |||
| aad93d8913 | |||
| 3ec1242a9a | |||
| 881f7f8e30 | |||
| 9ecaa7c3b3 | |||
| 037b0db01b | |||
| 360c55fdb9 | |||
| d9f5f7a137 | |||
| aee62c29fe | |||
| 3bd8added4 | |||
| ba527a1979 | |||
| 945acb6071 | |||
| 6f132f1e38 | |||
| a1e8d29b9c | |||
| e26f2af93d | |||
| 6a6ed3ed44 |
+4
-1
@@ -7,7 +7,6 @@
|
||||
.mtj.tmp/
|
||||
|
||||
# Package Files #
|
||||
*.jar
|
||||
*.war
|
||||
*.ear
|
||||
|
||||
@@ -180,3 +179,7 @@ local.properties
|
||||
.recommenders/
|
||||
|
||||
Makefile
|
||||
|
||||
infer-out/
|
||||
infer.txt
|
||||
log.log
|
||||
@@ -23,5 +23,5 @@ Haoming Yin <hyi25@uclive.ac.nz> <haoming.y@icloud.com>
|
||||
Peter Galloway <ptg19@uclive.ac.nz> Peter <ptg19@uclive.ac.nz>
|
||||
Zhi You Tan <zyt10@uclive.ac.nz> zyt10 <zyt10@uclive.ac.nz>
|
||||
Zhi You Tan <zyt10@uclive.ac.nz> Ryan Tan <ryan_zhiyou@hotmail.com>
|
||||
Alistair McIntyre <ajm412@uclive.ac.nz> alistairjmcintyre <alistairjmcintyre@gmail.com>
|
||||
Alistair McIntyre <ajm412@uclive.ac.nz> <alistairjmcintyre@gmail.com>
|
||||
Calum <cir27@uclive.ac.nz> cir27 <cir27@uclive.ac.nz>
|
||||
@@ -9,7 +9,7 @@ prints out event details, including time, involved boats and legs.
|
||||
|
||||
- Configuration file
|
||||
|
||||
We decided to store the team information including team names and boat velocity, as well as race configuration setting in external file.
|
||||
We decided to store the team information including team names and boat currentVelocity, as well as race configuration setting in external file.
|
||||
|
||||
To read external files, "Json-simple" library has been used to parse information.
|
||||
By using this library, we did not have to write our json parser and benefited from the flexibility of json files.
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@ You can specify a config file using the using the -f flag, for example 'java -ja
|
||||
|
||||
## The config file
|
||||
|
||||
The teams/boats are specified in the config file under 'teams', each team requires a team name, and a velocity (in meters per second).
|
||||
The teams/boats are specified in the config file under 'teams', each team requires a team name, and a currentVelocity (in meters per second).
|
||||
|
||||
The 'time-scale' option lets you change how long the race takes to complete. A time-scale of 1.0 is normal speed, 2.0 is 2x etc.
|
||||
|
||||
|
||||
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>
|
||||
<maven.compiler.source>1.8</maven.compiler.source>
|
||||
<maven.compiler.target>1.8</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
@@ -37,12 +38,77 @@
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- https://mvnrepository.com/artifact/org.freemarker/freemarker -->
|
||||
<dependency>
|
||||
<groupId>info.cukes</groupId>
|
||||
<artifactId>cucumber-junit</artifactId>
|
||||
<version>1.2.5</version>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/info.cukes/cucumber-java -->
|
||||
<dependency>
|
||||
<groupId>info.cukes</groupId>
|
||||
<artifactId>cucumber-java</artifactId>
|
||||
<version>1.2.5</version>
|
||||
</dependency>
|
||||
|
||||
<!-- https://mvnrepository.com/artifact/org.freemarker/freemarker -->
|
||||
<dependency>
|
||||
<groupId>org.freemarker</groupId>
|
||||
<artifactId>freemarker</artifactId>
|
||||
<version>2.3.26-incubating</version>
|
||||
</dependency>
|
||||
|
||||
<!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-slf4j-impl -->
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
<version>1.1.7</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>commons-cli</groupId>
|
||||
<artifactId>commons-cli</artifactId>
|
||||
<version>1.4</version>
|
||||
</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>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
@@ -125,4 +191,18 @@
|
||||
</plugin>
|
||||
</plugins>
|
||||
</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>
|
||||
@@ -1,39 +1,79 @@
|
||||
package seng302;
|
||||
|
||||
import ch.qos.logback.classic.Level;
|
||||
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 seng302.client.ClientPacketParser;
|
||||
import seng302.client.ClientState;
|
||||
import seng302.models.PolarTable;
|
||||
import org.apache.commons.cli.CommandLine;
|
||||
import org.apache.commons.cli.CommandLineParser;
|
||||
import org.apache.commons.cli.DefaultParser;
|
||||
import org.apache.commons.cli.Options;
|
||||
import org.apache.commons.cli.ParseException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import seng302.visualiser.controllers.ViewManager;
|
||||
|
||||
public class App extends Application {
|
||||
|
||||
@Override
|
||||
public void start(Stage primaryStage) throws Exception {
|
||||
PolarTable.parsePolarFile(getClass().getResourceAsStream("/config/acc_polars.csv"));
|
||||
private static Logger logger = LoggerFactory.getLogger(App.class);
|
||||
|
||||
Parent root = FXMLLoader.load(getClass().getResource("/views/MainView.fxml"));
|
||||
primaryStage.setTitle("RaceVision");
|
||||
primaryStage.setScene(new Scene(root, 1530, 960));
|
||||
primaryStage.setMaxWidth(1530);
|
||||
primaryStage.setMaxHeight(960);
|
||||
primaryStage.getIcons().add(new Image(getClass().getResourceAsStream("/PP.png")));
|
||||
// primaryStage.setMaximized(true);
|
||||
public static void parseArgs(String[] args) throws ParseException {
|
||||
Options options = new Options();
|
||||
CommandLineParser parser = new DefaultParser();
|
||||
CommandLine cmd;
|
||||
|
||||
primaryStage.show();
|
||||
primaryStage.setOnCloseRequest(e -> {
|
||||
ClientPacketParser.appClose();
|
||||
System.exit(0);
|
||||
});
|
||||
ch.qos.logback.classic.Logger rootLogger = (ch.qos.logback.classic.Logger) LoggerFactory
|
||||
.getLogger(Logger.ROOT_LOGGER_NAME);
|
||||
|
||||
ClientState.primaryStage = primaryStage;
|
||||
options.addOption("debugLevel", true, "Set the application debug level");
|
||||
|
||||
cmd = parser.parse(options, args);
|
||||
|
||||
if (cmd.hasOption("debugLevel")) {
|
||||
|
||||
switch (cmd.getOptionValue("debugLevel")) {
|
||||
case "DEBUG":
|
||||
rootLogger.setLevel(Level.DEBUG);
|
||||
break;
|
||||
|
||||
case "ALL":
|
||||
rootLogger.setLevel(Level.ALL);
|
||||
break;
|
||||
|
||||
case "WARNING":
|
||||
rootLogger.setLevel(Level.WARN);
|
||||
break;
|
||||
|
||||
case "ERROR":
|
||||
rootLogger.setLevel(Level.ERROR);
|
||||
break;
|
||||
|
||||
case "INFO":
|
||||
rootLogger.setLevel(Level.INFO);
|
||||
|
||||
case "TRACE":
|
||||
rootLogger.setLevel(Level.TRACE);
|
||||
|
||||
default:
|
||||
rootLogger.setLevel(Level.ALL);
|
||||
}
|
||||
} else {
|
||||
rootLogger.setLevel(Level.WARN);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(Stage primaryStage) throws Exception {
|
||||
ViewManager.getInstance().initialStartView(primaryStage);
|
||||
}
|
||||
|
||||
|
||||
public static void main(String[] args) {
|
||||
try {
|
||||
parseArgs(args);
|
||||
} catch (ParseException e) {
|
||||
logger.error("Could not parse command line arguments");
|
||||
}
|
||||
|
||||
launch(args);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,638 +0,0 @@
|
||||
package seng302.client;
|
||||
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
import java.util.TimeZone;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentSkipListMap;
|
||||
import java.util.concurrent.PriorityBlockingQueue;
|
||||
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.models.Yacht;
|
||||
import seng302.models.mark.Mark;
|
||||
import seng302.models.stream.XMLParser;
|
||||
import seng302.models.stream.packets.BoatPositionPacket;
|
||||
import seng302.models.stream.packets.StreamPacket;
|
||||
|
||||
/**
|
||||
* The purpose of this class is to take in the stream of divided packets so they can be read
|
||||
* and parsed in by turning the byte arrays into useful data. There are two public static hashmaps
|
||||
* that are threadsafe so the visualiser can always access the latest speed and position available
|
||||
* Created by kre39 on 23/04/17.
|
||||
*/
|
||||
public class ClientPacketParser {
|
||||
|
||||
public static ConcurrentHashMap<Long, PriorityBlockingQueue<BoatPositionPacket>> markLocations = new ConcurrentHashMap<>();
|
||||
public static ConcurrentHashMap<Long, PriorityBlockingQueue<BoatPositionPacket>> boatLocations = new ConcurrentHashMap<>();
|
||||
private static boolean newRaceXmlReceived = false;
|
||||
private static boolean raceStarted = false;
|
||||
private static XMLParser xmlObject = new XMLParser();
|
||||
private static boolean raceFinished = false;
|
||||
private static boolean streamStatus = false;
|
||||
private static long timeSinceStart = -1;
|
||||
private static Map<Integer, Yacht> boats = new ConcurrentHashMap<>();
|
||||
private static Map<Integer, Yacht> boatsPos = new ConcurrentSkipListMap<>();
|
||||
private static double windDirection = 0;
|
||||
private static Double windSpeed = 0d;
|
||||
private static Long currentTimeLong;
|
||||
private static String currentTimeString;
|
||||
private static boolean appRunning;
|
||||
private static Map<Integer, Yacht> clientStateBoats = new ConcurrentHashMap<>();
|
||||
|
||||
//CONVERSION CONSTANTS
|
||||
public static final Double MS_TO_KNOTS = 1.94384;
|
||||
|
||||
/**
|
||||
* Used to initialise the thread name and stream parser object so a thread can be executed
|
||||
*/
|
||||
public ClientPacketParser() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks at the type of the packet then sends it to the appropriate parser to extract the
|
||||
* specific data associated with that packet type
|
||||
*
|
||||
* @param packet the packet to be looked at and processed
|
||||
*/
|
||||
public static void parsePacket(StreamPacket packet) {
|
||||
try {
|
||||
switch (packet.getType()) {
|
||||
case HEARTBEAT:
|
||||
extractHeartBeat(packet);
|
||||
break;
|
||||
case RACE_STATUS:
|
||||
extractRaceStatus(packet);
|
||||
break;
|
||||
case DISPLAY_TEXT_MESSAGE:
|
||||
extractDisplayMessage(packet);
|
||||
break;
|
||||
case XML_MESSAGE:
|
||||
extractXmlMessage(packet);
|
||||
break;
|
||||
case RACE_START_STATUS:
|
||||
extractRaceStartStatus(packet);
|
||||
break;
|
||||
case YACHT_EVENT_CODE:
|
||||
extractYachtEventCode(packet);
|
||||
break;
|
||||
case YACHT_ACTION_CODE:
|
||||
extractYachtActionCode(packet);
|
||||
break;
|
||||
case CHATTER_TEXT:
|
||||
extractChatterText(packet);
|
||||
break;
|
||||
case BOAT_LOCATION:
|
||||
extractBoatLocation(packet);
|
||||
break;
|
||||
case MARK_ROUNDING:
|
||||
extractMarkRounding(packet);
|
||||
break;
|
||||
case COURSE_WIND:
|
||||
extractCourseWind(packet);
|
||||
break;
|
||||
case AVG_WIND:
|
||||
extractAvgWind(packet);
|
||||
break;
|
||||
}
|
||||
} catch (NullPointerException e) {
|
||||
System.out.println("Error parsing packet");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the seq num used in the heartbeat packet
|
||||
*
|
||||
* @param packet Packet parsed in to use the payload
|
||||
*/
|
||||
private static void extractHeartBeat(StreamPacket packet) {
|
||||
long heartbeat = bytesToLong(packet.getPayload());
|
||||
}
|
||||
|
||||
private static String getTimeZoneString() {
|
||||
|
||||
Integer offset = xmlObject.getRegattaXML().getUtcOffset();
|
||||
StringBuilder utcOffset = new StringBuilder();
|
||||
utcOffset.append("GMT");
|
||||
if (offset > 0) {
|
||||
utcOffset.append("+");
|
||||
utcOffset.append(offset);
|
||||
} else if (offset < 0) {
|
||||
utcOffset.append("-");
|
||||
utcOffset.append(offset);
|
||||
}
|
||||
return utcOffset.toString();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the useful race status data from race status type packets. This method will also
|
||||
* print to the console the current state of the race (if it has started/finished or is about to
|
||||
* start), along side this it'll also display the amount of time since the race has started or
|
||||
* time till it starts
|
||||
*
|
||||
* @param packet Packet parsed in to use the payload
|
||||
*/
|
||||
private static void extractRaceStatus(StreamPacket packet) {
|
||||
byte[] payload = packet.getPayload();
|
||||
int messageVersionNo = payload[0];
|
||||
long currentTime = bytesToLong(Arrays.copyOfRange(payload, 1, 7));
|
||||
long raceId = bytesToLong(Arrays.copyOfRange(payload, 7, 11));
|
||||
int raceStatus = payload[11];
|
||||
long expectedStartTime = bytesToLong(Arrays.copyOfRange(payload,12,18));
|
||||
long windDir = bytesToLong(Arrays.copyOfRange(payload,18,20));
|
||||
long rawWindSpeed = bytesToLong(Arrays.copyOfRange(payload,20,22));
|
||||
|
||||
currentTimeLong = currentTime;
|
||||
DateFormat format = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
|
||||
if (xmlObject.getRegattaXML() != null) {
|
||||
format.setTimeZone(TimeZone.getTimeZone(getTimeZoneString()));
|
||||
currentTimeString = format.format((new Date(currentTime)).getTime());
|
||||
}
|
||||
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;
|
||||
ClientState.setRaceStarted(false);
|
||||
} else if (!raceStarted) {
|
||||
raceStarted = true;
|
||||
ClientState.setRaceStarted(true);
|
||||
raceFinished = false;
|
||||
}
|
||||
timeSinceStart = timeTillStart;
|
||||
}
|
||||
|
||||
double windDirFactor = 0x4000 / 90; //0x4000 is 90 degrees, 0x8000 is 180 degrees, etc...
|
||||
windDirection = windDir / windDirFactor;
|
||||
windSpeed = rawWindSpeed / 1000 * MS_TO_KNOTS;
|
||||
|
||||
int noBoats = payload[22];
|
||||
int raceType = payload[23];
|
||||
for (int i = 0; i < noBoats; i++) {
|
||||
long boatStatusSourceID = bytesToLong(
|
||||
Arrays.copyOfRange(payload, 24 + (i * 20), 28 + (i * 20)));
|
||||
int boatStatus = (int) payload[28 + (i * 20)];
|
||||
int boatLegNumber = (int) payload[29 + (i * 20)];
|
||||
int boatPenaltyAwarded = (int) payload[30 + (i * 20)];
|
||||
int boatPenaltyServed = (int) payload[31 + (i * 20)];
|
||||
long estTimeAtNextMark = bytesToLong(
|
||||
Arrays.copyOfRange(payload, 32 + (i * 20), 38 + (i * 20)));
|
||||
long estTimeAtFinish = bytesToLong(
|
||||
Arrays.copyOfRange(payload, 38 + (i * 20), 44 + (i * 20)));
|
||||
|
||||
Yacht boat = boats.get((int) boatStatusSourceID);
|
||||
boat.setBoatStatus((boatStatus));
|
||||
setBoatLegPosition(boat, boatLegNumber);
|
||||
boat.setPenaltiesAwarded(boatPenaltyAwarded);
|
||||
boat.setPenaltiesServed(boatPenaltyServed);
|
||||
boat.setEstimateTimeAtNextMark(estTimeAtNextMark);
|
||||
boat.setEstimateTimeAtFinish(estTimeAtFinish);
|
||||
|
||||
// Update Client State boats when receive race status packet.
|
||||
// Potentially could replace boats in ClientPacketParser.
|
||||
Yacht clientBoat = ClientState.getBoats().get((int) boatStatusSourceID);
|
||||
clientBoat.setBoatStatus((boatStatus));
|
||||
setBoatLegPosition(clientBoat, boatLegNumber);
|
||||
clientBoat.setPenaltiesAwarded(boatPenaltyAwarded);
|
||||
clientBoat.setPenaltiesServed(boatPenaltyServed);
|
||||
clientBoat.setEstimateTimeAtNextMark(estTimeAtNextMark);
|
||||
clientBoat.setEstimateTimeAtFinish(estTimeAtFinish);
|
||||
}
|
||||
|
||||
// 3 is race started.
|
||||
// ClientState race started flag will be set to true if race started, else set false.
|
||||
if (raceStatus == 3) {
|
||||
ClientState.setRaceStarted(true);
|
||||
} else {
|
||||
ClientState.setRaceStarted(false);
|
||||
}
|
||||
}
|
||||
|
||||
private static void setBoatLegPosition(Yacht updatingBoat, Integer leg){
|
||||
Integer placing = 1;
|
||||
|
||||
if (/* TODO implement when we are getting this data /TODO leg != updatingBoat.getLegNumber() && */(raceStarted || raceFinished)) {
|
||||
for (Yacht boat : boats.values()) {
|
||||
placing = boat.getSourceId();
|
||||
/* See above to-do
|
||||
if (boat.getLegNumber() != null && leg <= boat.getLegNumber()){
|
||||
placing += 1;
|
||||
}*/
|
||||
}
|
||||
updatingBoat.setPosition(placing.toString());
|
||||
updatingBoat.setLegNumber(leg);
|
||||
boatsPos.putIfAbsent(placing, updatingBoat);
|
||||
boatsPos.replace(placing, updatingBoat);
|
||||
} else if(updatingBoat.getLegNumber() == null){
|
||||
updatingBoat.setPosition("-");
|
||||
updatingBoat.setLegNumber(leg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to extract the messages passed through with the display message packet
|
||||
*
|
||||
* @param packet Packet parsed in to use the payload
|
||||
*/
|
||||
private static void extractDisplayMessage(StreamPacket packet) {
|
||||
byte[] payload = packet.getPayload();
|
||||
int messageVersionNo = payload[0];
|
||||
int numOfLines = payload[3];
|
||||
int totalLen = 0;
|
||||
for (int i = 0; i < numOfLines; i++) {
|
||||
int lineNum = payload[4 + totalLen];
|
||||
int textLength = payload[5 + totalLen];
|
||||
byte[] messageTextBytes = Arrays
|
||||
.copyOfRange(payload, 6 + totalLen, 6 + textLength + totalLen);
|
||||
String messageText = new String(messageTextBytes);
|
||||
totalLen += 2 + textLength;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to read in the xml data. Will call the specific methods to create the course and boats
|
||||
*
|
||||
* @param packet Packet parsed in to use the payload
|
||||
*/
|
||||
private static void extractXmlMessage(StreamPacket packet) {
|
||||
xmlObject = new XMLParser();
|
||||
byte[] payload = packet.getPayload();
|
||||
int messageType = payload[9];
|
||||
long messageLength = bytesToLong(Arrays.copyOfRange(payload, 12, 14));
|
||||
String xmlMessage = new String(
|
||||
(Arrays.copyOfRange(payload, 14, (int) (14 + messageLength)))).trim();
|
||||
|
||||
//Create XML document Object
|
||||
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
|
||||
DocumentBuilder db = null;
|
||||
Document doc = null;
|
||||
try {
|
||||
db = dbf.newDocumentBuilder();
|
||||
doc = db.parse(new InputSource(new StringReader(xmlMessage)));
|
||||
} catch (ParserConfigurationException | IOException | SAXException e) {
|
||||
System.out.println("[ClientPacketParser] ParserConfigurationException | IOException | SAXException");
|
||||
}
|
||||
|
||||
xmlObject.constructXML(doc, messageType);
|
||||
|
||||
if (messageType == 7) { //7 is the boat XML
|
||||
boats = xmlObject.getBoatXML().getCompetingBoats();
|
||||
// Set/Update the ClientState boats after receiving new boat xml.
|
||||
// Flag boatsUpdated in ClientState to true.
|
||||
ClientState.setBoats(xmlObject.getBoatXML().getCompetingBoats());
|
||||
ClientState.setBoatsUpdated(true);
|
||||
}
|
||||
if (messageType == 6) { //6 is race info xml
|
||||
newRaceXmlReceived = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the race start status from the packet, currently is unused within the app but
|
||||
* is here for potential future use
|
||||
*
|
||||
* @param packet Packet parsed in to use the payload
|
||||
*/
|
||||
private static void extractRaceStartStatus(StreamPacket packet) {
|
||||
byte[] payload = packet.getPayload();
|
||||
int messageVersionNo = payload[0];
|
||||
long timeStamp = bytesToLong(Arrays.copyOfRange(payload, 1, 7));
|
||||
long raceStartTime = bytesToLong(Arrays.copyOfRange(payload, 9, 15));
|
||||
long raceId = bytesToLong(Arrays.copyOfRange(payload, 15, 19));
|
||||
int notificationType = payload[19];
|
||||
}
|
||||
|
||||
/**
|
||||
* When a yacht event occurs this will parse the byte array to retrieve the necessary info,
|
||||
* currently unused
|
||||
*
|
||||
* @param packet Packet parsed in to use the payload
|
||||
*/
|
||||
private static void extractYachtEventCode(StreamPacket packet) {
|
||||
byte[] payload = packet.getPayload();
|
||||
int messageVersionNo = payload[0];
|
||||
long timeStamp = bytesToLong(Arrays.copyOfRange(payload, 1, 7));
|
||||
long raceId = bytesToLong(Arrays.copyOfRange(payload, 9, 13));
|
||||
long subjectId = bytesToLong(Arrays.copyOfRange(payload, 13, 17));
|
||||
long incidentId = bytesToLong(Arrays.copyOfRange(payload, 17, 21));
|
||||
int eventId = payload[21];
|
||||
}
|
||||
|
||||
/**
|
||||
* When a yacht action occurs this will parse the parse the byte array to retrieve the necessary
|
||||
* info, currently unused
|
||||
*
|
||||
* @param packet Packet parsed in to use the payload
|
||||
*/
|
||||
private static void extractYachtActionCode(StreamPacket packet) {
|
||||
byte[] payload = packet.getPayload();
|
||||
int messageVersionNo = payload[0];
|
||||
long timeStamp = bytesToLong(Arrays.copyOfRange(payload, 1, 7));
|
||||
long subjectId = bytesToLong(Arrays.copyOfRange(payload, 9, 13));
|
||||
long incidentId = bytesToLong(Arrays.copyOfRange(payload, 13, 17));
|
||||
int eventId = payload[17];
|
||||
}
|
||||
|
||||
/**
|
||||
* Strips the message from the chatter text type packets, currently the message is unused
|
||||
*
|
||||
* @param packet Packet parsed in to use the payload
|
||||
*/
|
||||
private static void extractChatterText(StreamPacket packet) {
|
||||
byte[] payload = packet.getPayload();
|
||||
int messageVersionNo = payload[0];
|
||||
int messageType = payload[1];
|
||||
int length = payload[2];
|
||||
String message = new String(Arrays.copyOfRange(payload, 3, 3 + length));
|
||||
System.out.println(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to breakdown the boatlocation packets so the boat coordinates, id and groundspeed are
|
||||
* all used All the other extra data is still being read and translated however is unused.
|
||||
*
|
||||
* @param packet Packet parsed in to use the payload
|
||||
*/
|
||||
private static void extractBoatLocation(StreamPacket packet) {
|
||||
byte[] payload = packet.getPayload();
|
||||
|
||||
int deviceType = (int) payload[15];
|
||||
long timeValid = bytesToLong(Arrays.copyOfRange(payload, 1, 7));
|
||||
long seq = bytesToLong(Arrays.copyOfRange(payload, 11, 15));
|
||||
long boatId = bytesToLong(Arrays.copyOfRange(payload, 7, 11));
|
||||
long rawLat = bytesToLong(Arrays.copyOfRange(payload, 16, 20));
|
||||
long rawLon = bytesToLong(Arrays.copyOfRange(payload, 20, 24));
|
||||
//Converts the double to a usable lat/lon
|
||||
double lat = ((180d * (double) rawLat) / Math.pow(2, 31));
|
||||
double lon = ((180d * (double) rawLon) / Math.pow(2, 31));
|
||||
// System.out.println("[CLIENT] Lat: " + lat + " Lon: " + lon);
|
||||
long heading = bytesToLong(Arrays.copyOfRange(payload, 28, 30));
|
||||
double groundSpeed = bytesToLong(Arrays.copyOfRange(payload, 38, 40)) / 1000.0;
|
||||
//type 1 is a racing yacht and type 3 is a mark, needed for updating positions of the mark and boat
|
||||
if (deviceType == 1){
|
||||
Yacht boat = boats.get((int) boatId);
|
||||
boat.setVelocity(groundSpeed);
|
||||
BoatPositionPacket boatPacket = new BoatPositionPacket(boatId, timeValid, lat, lon, heading, groundSpeed);
|
||||
|
||||
//add a new priority que to the boatLocations HashMap
|
||||
if (!boatLocations.containsKey(boatId)) {
|
||||
boatLocations.put(boatId,
|
||||
new PriorityBlockingQueue<>(256, new Comparator<BoatPositionPacket>() {
|
||||
@Override
|
||||
public int compare(BoatPositionPacket p1, BoatPositionPacket p2) {
|
||||
return (int) (p1.getTimeValid() - p2.getTimeValid());
|
||||
}
|
||||
}));
|
||||
}
|
||||
boatLocations.get(boatId).put(boatPacket);
|
||||
} else if (deviceType == 3) {
|
||||
BoatPositionPacket markPacket = new BoatPositionPacket(boatId, timeValid, lat, lon,
|
||||
heading, groundSpeed);
|
||||
|
||||
//add a new priority que to the boatLocations HashMap
|
||||
if (!markLocations.containsKey(boatId)) {
|
||||
markLocations.put(boatId,
|
||||
new PriorityBlockingQueue<>(256, new Comparator<BoatPositionPacket>() {
|
||||
@Override
|
||||
public int compare(BoatPositionPacket p1, BoatPositionPacket p2) {
|
||||
return (int) (p1.getTimeValid() - p2.getTimeValid());
|
||||
}
|
||||
}));
|
||||
}
|
||||
markLocations.get(boatId).put(markPacket);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This packet type is received when a mark or gate is rounded by a boat
|
||||
*
|
||||
* @param packet The packet containing the payload
|
||||
*/
|
||||
private static void extractMarkRounding(StreamPacket packet) {
|
||||
byte[] payload = packet.getPayload();
|
||||
int messageVersionNo = payload[0];
|
||||
long timeStamp = bytesToLong(Arrays.copyOfRange(payload, 1, 7));
|
||||
long raceId = bytesToLong(Arrays.copyOfRange(payload, 9, 13));
|
||||
long subjectId = bytesToLong(Arrays.copyOfRange(payload, 13, 17));
|
||||
int boatStatus = payload[17];
|
||||
int roundingSide = payload[18];
|
||||
int markType = payload[19];
|
||||
int markId = payload[20];
|
||||
|
||||
// assign mark rounding time to boat
|
||||
boats.get((int)subjectId).setMarkRoundingTime(timeStamp);
|
||||
|
||||
for (Mark mark : xmlObject.getRaceXML().getAllCompoundMarks()) {
|
||||
if (mark.getCompoundMarkID() == markId) {
|
||||
boats.get((int)subjectId).setLastMarkRounded(mark);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This packet type contains periodic data on the state of the wind
|
||||
*
|
||||
* @param packet The packet containing the payload
|
||||
*/
|
||||
private static void extractCourseWind(StreamPacket packet) {
|
||||
byte[] payload = packet.getPayload();
|
||||
int messageVersionNo = payload[0];
|
||||
int selectedWindId = payload[1];
|
||||
int loopCount = payload[2];
|
||||
ArrayList<String> windInfo = new ArrayList<>();
|
||||
for (int i = 0; i < loopCount; i++) {
|
||||
String wind = "WindId: " + payload[3 + (20 * i)];
|
||||
wind +=
|
||||
"\nTime: " + bytesToLong(Arrays.copyOfRange(payload, 4 + (20 * i), 10 + (20 * i)));
|
||||
wind += "\nRaceId: " + bytesToLong(
|
||||
Arrays.copyOfRange(payload, 10 + (20 * i), 14 + (20 * i)));
|
||||
wind += "\nWindDirection: " + bytesToLong(
|
||||
Arrays.copyOfRange(payload, 14 + (20 * i), 16 + (20 * i)));
|
||||
wind += "\nWindSpeed: " + bytesToLong(
|
||||
Arrays.copyOfRange(payload, 16 + (20 * i), 18 + (20 * i)));
|
||||
wind += "\nBestUpWindAngle: " + bytesToLong(
|
||||
Arrays.copyOfRange(payload, 18 + (20 * i), 20 + (20 * i)));
|
||||
wind += "\nBestDownWindAngle: " + bytesToLong(
|
||||
Arrays.copyOfRange(payload, 20 + (20 * i), 22 + (20 * i)));
|
||||
wind += "\nFlags: " + String
|
||||
.format("%8s", Integer.toBinaryString(payload[22 + (20 * i)] & 0xFF))
|
||||
.replace(' ', '0');
|
||||
windInfo.add(wind);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This packet conatins the average wind to ground speed
|
||||
*
|
||||
* @param packet The packet containing the paylaod
|
||||
*/
|
||||
private static void extractAvgWind(StreamPacket packet) {
|
||||
byte[] payload = packet.getPayload();
|
||||
int messageVersionNo = payload[0];
|
||||
long timeStamp = bytesToLong(Arrays.copyOfRange(payload, 1, 7));
|
||||
long rawPeriod = bytesToLong(Arrays.copyOfRange(payload, 7, 9));
|
||||
long rawSamplePeriod = bytesToLong(Arrays.copyOfRange(payload, 9, 11));
|
||||
long period2 = bytesToLong(Arrays.copyOfRange(payload, 11, 13));
|
||||
long speed2 = bytesToLong(Arrays.copyOfRange(payload, 13, 15));
|
||||
long period3 = bytesToLong(Arrays.copyOfRange(payload, 15, 17));
|
||||
long speed3 = bytesToLong(Arrays.copyOfRange(payload, 17, 19));
|
||||
long period4 = bytesToLong(Arrays.copyOfRange(payload, 19, 21));
|
||||
long speed4 = bytesToLong(Arrays.copyOfRange(payload, 21, 23));
|
||||
}
|
||||
|
||||
/**
|
||||
* takes an array of up to 7 bytes and returns a positive
|
||||
* long constructed from the input bytes
|
||||
*
|
||||
* @return a positive long if there is less than 7 bytes -1 otherwise
|
||||
*/
|
||||
private static long bytesToLong(byte[] bytes) {
|
||||
long partialLong = 0;
|
||||
int index = 0;
|
||||
for (byte b : bytes) {
|
||||
if (index > 6) {
|
||||
return -1;
|
||||
}
|
||||
partialLong = partialLong | (b & 0xFFL) << (index * 8);
|
||||
index++;
|
||||
}
|
||||
return partialLong;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns false if race not started, true otherwise
|
||||
*
|
||||
* @return race started status
|
||||
*/
|
||||
public static boolean isRaceStarted() {
|
||||
return raceStarted;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns false if stream not connected, true otherwise
|
||||
*
|
||||
* @return stream started status
|
||||
*/
|
||||
public static boolean isStreamStatus() {
|
||||
return streamStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns race timer
|
||||
*
|
||||
* @return race timer in long
|
||||
*/
|
||||
public static long getTimeSinceStart() {
|
||||
return timeSinceStart;
|
||||
}
|
||||
|
||||
/**
|
||||
* return false if race not finished, true otherwise
|
||||
*
|
||||
* @return race finished status
|
||||
*/
|
||||
public static boolean isRaceFinished() {
|
||||
return raceFinished;
|
||||
}
|
||||
|
||||
/**
|
||||
* return a map of boats with sourceID and the boat
|
||||
*
|
||||
* @return map of boats
|
||||
*/
|
||||
public static Map<Integer, Yacht> getBoats() {
|
||||
return boats;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* returns the latest updated object from xml parser
|
||||
*
|
||||
* @return the latest xml object
|
||||
*/
|
||||
public static XMLParser getXmlObject() {
|
||||
return xmlObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the wind direction in degrees
|
||||
*
|
||||
* @return a double wind direction value
|
||||
*/
|
||||
public static double getWindDirection() {
|
||||
return windDirection;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the wind speed in knots
|
||||
* @return A double indicating the wind speed in knots
|
||||
*/
|
||||
public static Double getWindSpeed() {
|
||||
return windSpeed;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns stream time in formatted string format
|
||||
*
|
||||
* @return String of stream time
|
||||
*/
|
||||
public static String getCurrentTimeString() {
|
||||
return currentTimeString;
|
||||
}
|
||||
|
||||
/**
|
||||
* used in boat position since tree map can sort position efficiently.
|
||||
*
|
||||
* @return a map of time to finish and boat.
|
||||
*/
|
||||
public static Map<Integer, Yacht> getBoatsPos() {
|
||||
|
||||
return boatsPos;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns current time in stream in long
|
||||
*
|
||||
* @return a long value of current time
|
||||
*/
|
||||
public static Long getCurrentTimeLong() {
|
||||
return currentTimeLong;
|
||||
}
|
||||
|
||||
public static void appClose() {
|
||||
appRunning = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to check if a new un-processed xml has been found, if so will return true before
|
||||
* toggling off so that the next check will return false.
|
||||
*
|
||||
* @return the status of if new xml has been received
|
||||
*/
|
||||
public static boolean isNewRaceXmlReceived() {
|
||||
if (newRaceXmlReceived) {
|
||||
newRaceXmlReceived = false;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
package seng302.client;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import javafx.stage.Stage;
|
||||
import seng302.models.Yacht;
|
||||
|
||||
/**
|
||||
* Used by the client to store static variables, which other threads and classes
|
||||
* observer so that they can update their status accordingly.
|
||||
*/
|
||||
public class ClientState {
|
||||
|
||||
private static String hostIp = "";
|
||||
private static Boolean isHost = false;
|
||||
private static Boolean raceStarted = false;
|
||||
private static Boolean connectedToHost = false;
|
||||
private static Map<Integer, Yacht> boats = new ConcurrentHashMap<>();
|
||||
private static Boolean boatsUpdated = true;
|
||||
private static String clientSourceId = "";
|
||||
public static Stage primaryStage;
|
||||
|
||||
public static String getHostIp() {
|
||||
return hostIp;
|
||||
}
|
||||
|
||||
public static void setHostIp(String hostIp) {
|
||||
ClientState.hostIp = hostIp;
|
||||
}
|
||||
|
||||
public static Boolean isHost() {
|
||||
return isHost;
|
||||
}
|
||||
|
||||
public static void setHost(Boolean isHost) {
|
||||
ClientState.isHost = isHost;
|
||||
}
|
||||
|
||||
public static Boolean isRaceStarted() {
|
||||
return raceStarted;
|
||||
}
|
||||
|
||||
public static void setRaceStarted(Boolean raceStarted) {
|
||||
ClientState.raceStarted = raceStarted;
|
||||
}
|
||||
|
||||
public static Boolean isConnectedToHost() {
|
||||
return connectedToHost;
|
||||
}
|
||||
|
||||
public static void setConnectedToHost(Boolean connectedToHost) {
|
||||
ClientState.connectedToHost = connectedToHost;
|
||||
}
|
||||
|
||||
public static Map<Integer, Yacht> getBoats() {
|
||||
return boats;
|
||||
}
|
||||
|
||||
public static Boolean isBoatsUpdated() {
|
||||
return boatsUpdated;
|
||||
}
|
||||
|
||||
public static void setBoatsUpdated(Boolean boatsUpdated) {
|
||||
ClientState.boatsUpdated = boatsUpdated;
|
||||
}
|
||||
|
||||
public static String getClientSourceId() {
|
||||
return clientSourceId;
|
||||
}
|
||||
|
||||
public static void setClientSourceId(String clientSourceId) {
|
||||
ClientState.clientSourceId = clientSourceId;
|
||||
}
|
||||
|
||||
public static void setBoats(Map<Integer, Yacht> boats) {
|
||||
ClientState.boats = boats;
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
package seng302.client;
|
||||
|
||||
import java.util.Observable;
|
||||
|
||||
/**
|
||||
* Used by LobbyController to run a separate thread-loop
|
||||
* updates the controller when change is detected.
|
||||
*/
|
||||
public class ClientStateQueryingRunnable extends Observable implements Runnable {
|
||||
|
||||
private Boolean terminate = false;
|
||||
|
||||
public ClientStateQueryingRunnable() {}
|
||||
|
||||
/**
|
||||
* Notifies observers(the lobby controller) that "game started" if ClientState
|
||||
* raceStarted flag is true and terminates itself. Also, it notifies observers
|
||||
* to add/remove players if ClientState boatsUpdated flag is true, then resets
|
||||
* the flag to false;
|
||||
*/
|
||||
@Override
|
||||
public void run() {
|
||||
while(!terminate) {
|
||||
// Sleeping the thread so it will respond to the if statement below
|
||||
// if you know a better fix, pls tell me :) -ryan
|
||||
try {
|
||||
Thread.sleep(0);
|
||||
} catch (InterruptedException e) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ClientState.isRaceStarted() && ClientState.isConnectedToHost()) {
|
||||
setChanged();
|
||||
notifyObservers("game started");
|
||||
terminate();
|
||||
}
|
||||
|
||||
if (ClientState.isBoatsUpdated()) {
|
||||
setChanged();
|
||||
notifyObservers("update players");
|
||||
ClientState.setBoatsUpdated(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to terminate the thread.
|
||||
*
|
||||
* Currently called by the main while loop when game started is detected.
|
||||
*/
|
||||
public void terminate() {
|
||||
terminate = true;
|
||||
}
|
||||
}
|
||||
@@ -1,216 +0,0 @@
|
||||
package seng302.client;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
import java.net.UnknownHostException;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.zip.CRC32;
|
||||
import java.util.zip.Checksum;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.Alert.AlertType;
|
||||
import seng302.models.stream.packets.StreamPacket;
|
||||
import seng302.server.messages.BoatActionMessage;
|
||||
import seng302.server.messages.Message;
|
||||
|
||||
/**
|
||||
* A class describing a single connection to a Server for the purposes of sending and receiving on
|
||||
* its own thread.
|
||||
*/
|
||||
public class ClientToServerThread implements Runnable {
|
||||
|
||||
private static final int LOG_LEVEL = 1;
|
||||
|
||||
private Thread thread;
|
||||
|
||||
private Integer ourID;
|
||||
|
||||
private Socket socket;
|
||||
private InputStream is;
|
||||
private OutputStream os;
|
||||
|
||||
private Boolean updateClient = true;
|
||||
private ByteArrayOutputStream crcBuffer;
|
||||
|
||||
/**
|
||||
* Constructor for ClientToServerThread which takes in ipAddress and portNumber and attempts to
|
||||
* connect to the specified ipAddress and port.
|
||||
*
|
||||
* Upon successful socket connection, threeWayHandshake will be preformed and the instance will
|
||||
* be put on a thread and run immediately.
|
||||
*
|
||||
* @param ipAddress a string of ip address to be connected to
|
||||
* @param portNumber an integer port number
|
||||
* @throws Exception SocketConnection if fail to connect to ip address and port number
|
||||
* combination
|
||||
*/
|
||||
public ClientToServerThread(String ipAddress, Integer portNumber) throws Exception {
|
||||
socket = new Socket(ipAddress, portNumber);
|
||||
is = socket.getInputStream();
|
||||
os = socket.getOutputStream();
|
||||
|
||||
Integer allocatedID = threeWayHandshake();
|
||||
if (allocatedID != null) {
|
||||
ourID = allocatedID;
|
||||
clientLog("Successful handshake. Allocated ID: " + ourID, 0);
|
||||
ClientState.setClientSourceId(String.valueOf(ourID));
|
||||
} else {
|
||||
clientLog("Unsuccessful handshake", 1);
|
||||
closeSocket();
|
||||
return;
|
||||
}
|
||||
|
||||
thread = new Thread(this);
|
||||
thread.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints out log messages and the time happened.
|
||||
* Only perform task if log level is below LOG_LEVEL variable.
|
||||
*
|
||||
* @param message a string of message to be printed out
|
||||
* @param logLevel an int for log level
|
||||
*/
|
||||
static void clientLog(String message, int logLevel) {
|
||||
if (logLevel <= LOG_LEVEL) {
|
||||
System.out.println(
|
||||
"[CLIENT " + LocalDateTime.now().toLocalTime().toString() + "] " + message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the thread loop. It exits the loop if ClientState connected to host
|
||||
* variable is false.
|
||||
*/
|
||||
public void run() {
|
||||
int sync1;
|
||||
int sync2;
|
||||
// TODO: 14/07/17 wmu16 - Work out how to fix this while loop
|
||||
while (ClientState.isConnectedToHost()) {
|
||||
try {
|
||||
crcBuffer = new ByteArrayOutputStream();
|
||||
sync1 = readByte();
|
||||
sync2 = readByte();
|
||||
//checking if it is the start of the packet
|
||||
if (sync1 == 0x47 && sync2 == 0x83) {
|
||||
int type = readByte();
|
||||
//No. of milliseconds since Jan 1st 1970
|
||||
long timeStamp = Message.bytesToLong(getBytes(6));
|
||||
skipBytes(4);
|
||||
long payloadLength = Message.bytesToLong(getBytes(2));
|
||||
byte[] payload = getBytes((int) payloadLength);
|
||||
Checksum checksum = new CRC32();
|
||||
checksum.update(crcBuffer.toByteArray(), 0, crcBuffer.size());
|
||||
long computedCrc = checksum.getValue();
|
||||
long packetCrc = Message.bytesToLong(getBytes(4));
|
||||
if (computedCrc == packetCrc) {
|
||||
ClientPacketParser
|
||||
.parsePacket(new StreamPacket(type, payloadLength, timeStamp, payload));
|
||||
} else {
|
||||
clientLog("Packet has been dropped", 1);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
closeSocket();
|
||||
Platform.runLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Alert alert = new Alert(AlertType.ERROR);
|
||||
alert.setHeaderText("Host has disconnected");
|
||||
alert.setContentText("Cannot find Server");
|
||||
alert.showAndWait();
|
||||
}
|
||||
});
|
||||
clientLog("Disconnected from server", 1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
closeSocket();
|
||||
clientLog("Disconnected from server", 0);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Listens for an allocated sourceID and returns it to the server
|
||||
*
|
||||
* @return the sourceID allocated to us by the server
|
||||
*/
|
||||
private Integer threeWayHandshake() {
|
||||
Integer ourSourceID = null;
|
||||
while (true) {
|
||||
try {
|
||||
ourSourceID = is.read();
|
||||
} catch (IOException e) {
|
||||
clientLog("Three way handshake failed", 1);
|
||||
|
||||
}
|
||||
if (ourSourceID != null) {
|
||||
try {
|
||||
os.write(ourSourceID);
|
||||
return ourSourceID;
|
||||
} catch (IOException e) {
|
||||
clientLog("Three way handshake failed", 1);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Send the post-start race course information
|
||||
*/
|
||||
public void sendBoatActionMessage(BoatActionMessage boatActionMessage) {
|
||||
try {
|
||||
os.write(boatActionMessage.getBuffer());
|
||||
} catch (IOException e) {
|
||||
clientLog("Could not write to server", 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void closeSocket() {
|
||||
try {
|
||||
socket.close();
|
||||
} catch (IOException e) {
|
||||
clientLog("Failed to close the socket", 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private int readByte() throws Exception {
|
||||
int currentByte = -1;
|
||||
try {
|
||||
currentByte = is.read();
|
||||
crcBuffer.write(currentByte);
|
||||
} catch (IOException e) {
|
||||
clientLog("Read byte failed", 1);
|
||||
}
|
||||
if (currentByte == -1) {
|
||||
throw new Exception();
|
||||
}
|
||||
return currentByte;
|
||||
}
|
||||
|
||||
private byte[] getBytes(int n) throws Exception {
|
||||
byte[] bytes = new byte[n];
|
||||
for (int i = 0; i < n; i++) {
|
||||
bytes[i] = (byte) readByte();
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
private void skipBytes(long n) throws Exception {
|
||||
for (int i = 0; i < n; i++) {
|
||||
readByte();
|
||||
}
|
||||
}
|
||||
|
||||
public Thread getThread() {
|
||||
return thread;
|
||||
}
|
||||
}
|
||||
@@ -1,597 +0,0 @@
|
||||
package seng302.controllers;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.PriorityBlockingQueue;
|
||||
import javafx.animation.AnimationTimer;
|
||||
import javafx.beans.property.SimpleDoubleProperty;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.geometry.Point2D;
|
||||
import javafx.scene.Group;
|
||||
import javafx.scene.canvas.Canvas;
|
||||
import javafx.scene.canvas.GraphicsContext;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.AnchorPane;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.shape.Polygon;
|
||||
import javafx.scene.text.Text;
|
||||
import seng302.client.ClientPacketParser;
|
||||
import seng302.client.ClientState;
|
||||
import seng302.fxObjects.BoatGroup;
|
||||
import seng302.models.Colors;
|
||||
import seng302.models.Yacht;
|
||||
import seng302.models.mark.GateMark;
|
||||
import seng302.models.mark.Mark;
|
||||
import seng302.fxObjects.MarkGroup;
|
||||
import seng302.models.mark.MarkType;
|
||||
import seng302.models.mark.SingleMark;
|
||||
import seng302.models.map.Boundary;
|
||||
import seng302.models.map.CanvasMap;
|
||||
import seng302.models.stream.XMLParser;
|
||||
import seng302.models.stream.XMLParser.RaceXMLObject.Limit;
|
||||
import seng302.models.stream.XMLParser.RaceXMLObject.Participant;
|
||||
import seng302.models.stream.packets.BoatPositionPacket;
|
||||
import seng302.utilities.GeoPoint;
|
||||
import seng302.utilities.GeoUtility;
|
||||
|
||||
/**
|
||||
* Created by ptg19 on 15/03/17.
|
||||
* Modified by Haoming Yin (hyi25) on 20/3/2017.
|
||||
*/
|
||||
public class CanvasController {
|
||||
|
||||
@FXML
|
||||
private AnchorPane canvasPane;
|
||||
|
||||
private RaceViewController raceViewController;
|
||||
private ResizableCanvas canvas;
|
||||
private Group group;
|
||||
private GraphicsContext gc;
|
||||
private ImageView mapImage;
|
||||
|
||||
private final int BUFFER_SIZE = 50;
|
||||
private final int PANEL_WIDTH = 1260; // it should be 1280 but, minors 40 to cancel the bias.
|
||||
private final int PANEL_HEIGHT = 960;
|
||||
private final int CANVAS_WIDTH = 1100;
|
||||
private final int CANVAS_HEIGHT = 920;
|
||||
private boolean horizontalInversion = false;
|
||||
|
||||
private double distanceScaleFactor;
|
||||
private ScaleDirection scaleDirection;
|
||||
private Mark minLatPoint;
|
||||
private Mark minLonPoint;
|
||||
private Mark maxLatPoint;
|
||||
private Mark maxLonPoint;
|
||||
private double referencePointX;
|
||||
private double referencePointY;
|
||||
private double metersPerPixelX;
|
||||
private double metersPerPixelY;
|
||||
|
||||
private List<MarkGroup> markGroups = new ArrayList<>();
|
||||
private List<BoatGroup> boatGroups = new ArrayList<>();
|
||||
private Text FPSDisplay = new Text();
|
||||
private Polygon raceBorder = new Polygon();
|
||||
|
||||
//FRAME RATE
|
||||
private Double frameRate = 60.0;
|
||||
private final long[] frameTimes = new long[30];
|
||||
private int frameTimeIndex = 0;
|
||||
private boolean arrayFilled = false;
|
||||
|
||||
public AnimationTimer timer;
|
||||
|
||||
private enum ScaleDirection {
|
||||
HORIZONTAL,
|
||||
VERTICAL
|
||||
}
|
||||
|
||||
public void setup(RaceViewController raceViewController) {
|
||||
this.raceViewController = raceViewController;
|
||||
}
|
||||
|
||||
public void initialize() {
|
||||
raceViewController = new RaceViewController();
|
||||
canvas = new ResizableCanvas();
|
||||
group = new Group();
|
||||
|
||||
// create image view for map, bind panel size to image
|
||||
mapImage = new ImageView();
|
||||
canvasPane.getChildren().add(mapImage);
|
||||
mapImage.fitWidthProperty().bind(canvasPane.widthProperty());
|
||||
mapImage.fitHeightProperty().bind(canvasPane.heightProperty());
|
||||
|
||||
canvasPane.getChildren().add(canvas);
|
||||
canvasPane.getChildren().add(group);
|
||||
// Bind canvas size to stack pane size.
|
||||
canvas.widthProperty().bind(new SimpleDoubleProperty(CANVAS_WIDTH));
|
||||
canvas.heightProperty().bind(new SimpleDoubleProperty(CANVAS_HEIGHT));
|
||||
|
||||
}
|
||||
|
||||
public void initializeCanvas() {
|
||||
|
||||
gc = canvas.getGraphicsContext2D();
|
||||
gc.setGlobalAlpha(0.5);
|
||||
fitMarksToCanvas();
|
||||
drawGoogleMap();
|
||||
FPSDisplay.setLayoutX(5);
|
||||
FPSDisplay.setLayoutY(20);
|
||||
FPSDisplay.setStrokeWidth(2);
|
||||
group.getChildren().add(FPSDisplay);
|
||||
group.getChildren().add(raceBorder);
|
||||
initializeMarks();
|
||||
initializeBoats();
|
||||
|
||||
timer = new AnimationTimer() {
|
||||
private long lastTime = 0;
|
||||
private int FPSCount = 30;
|
||||
|
||||
@Override
|
||||
public void handle(long now) {
|
||||
if (lastTime == 0) {
|
||||
lastTime = now;
|
||||
} else {
|
||||
if (now - lastTime >= (1e8 / 60)) { //Fix for framerate going above 60 when minimized
|
||||
long oldFrameTime = frameTimes[frameTimeIndex];
|
||||
frameTimes[frameTimeIndex] = now;
|
||||
frameTimeIndex = (frameTimeIndex + 1) % frameTimes.length;
|
||||
if (frameTimeIndex == 0) {
|
||||
arrayFilled = true;
|
||||
}
|
||||
long elapsedNanos;
|
||||
if (arrayFilled) {
|
||||
elapsedNanos = now - oldFrameTime;
|
||||
long elapsedNanosPerFrame = elapsedNanos / frameTimes.length;
|
||||
frameRate = 1_000_000_000.0 / elapsedNanosPerFrame;
|
||||
if (FPSCount-- == 0) {
|
||||
FPSCount = 30;
|
||||
drawFps(frameRate.intValue());
|
||||
}
|
||||
raceViewController.updateSparkLine();
|
||||
}
|
||||
updateGroups();
|
||||
if (ClientPacketParser.isRaceFinished()) {
|
||||
this.stop();
|
||||
}
|
||||
lastTime = now;
|
||||
}
|
||||
}
|
||||
if (ClientPacketParser.isRaceFinished()) {
|
||||
this.stop();
|
||||
switchToFinishScreen();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void switchToFinishScreen() {
|
||||
try {
|
||||
// canvas view -> anchor pane -> grid pane -> main view
|
||||
GridPane gridPane = (GridPane) canvasPane.getParent().getParent();
|
||||
AnchorPane contentPane = (AnchorPane) gridPane.getParent();
|
||||
contentPane.getChildren().removeAll();
|
||||
contentPane.getChildren().clear();
|
||||
contentPane.getStylesheets().add(getClass().getResource("/css/master.css").toString());
|
||||
contentPane.getChildren().addAll(
|
||||
(Pane) FXMLLoader.load(getClass().getResource("/views/FinishScreenView.fxml")));
|
||||
} catch (javafx.fxml.LoadException e) {
|
||||
System.out.println("[Controller] FXML load exception");
|
||||
} catch (IOException e) {
|
||||
System.out.println("[Controller] IO exception");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.getLatitude(), minLonPoint.getLongitude());
|
||||
// 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.getLatitude(), minLonPoint.getLongitude());
|
||||
GeoPoint originPos = GeoUtility
|
||||
.getGeoCoordinate(topLeftPos, bearingFromTopLeftToOrigin, distanceFromTopLeftToOrigin);
|
||||
|
||||
// distance from origin corner to bottom right corner of the panel
|
||||
double distanceFromOriginToBottomRight = Math.sqrt(
|
||||
Math.pow(PANEL_HEIGHT * metersPerPixelY, 2) + Math
|
||||
.pow(PANEL_WIDTH * metersPerPixelX, 2));
|
||||
double bearingFromOriginToBottomRight = Math
|
||||
.toDegrees(Math.atan2(PANEL_WIDTH, -PANEL_HEIGHT));
|
||||
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());
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds border marks to the canvas, taken from the XML file
|
||||
*
|
||||
* NOTE: This is quite confusing as objects are grabbed from the XMLParser such as Mark and
|
||||
* CompoundMark which are named the same as those in the model package but are, however not the
|
||||
* same, so they do not have things such as a type and must be derived from the number of marks
|
||||
* in a compound mark etc..
|
||||
*/
|
||||
private void addRaceBorder() {
|
||||
XMLParser.RaceXMLObject raceXMLObject = ClientPacketParser.getXmlObject().getRaceXML();
|
||||
ArrayList<Limit> courseLimits = raceXMLObject.getCourseLimit();
|
||||
raceBorder.setStroke(new Color(0.0f, 0.0f, 0.74509807f, 1));
|
||||
raceBorder.setStrokeWidth(3);
|
||||
raceBorder.setFill(new Color(0,0,0,0));
|
||||
List<Double> boundaryPoints = new ArrayList<>();
|
||||
for (Limit limit : courseLimits) {
|
||||
Point2D location = findScaledXY(limit.getLat(), limit.getLng());
|
||||
boundaryPoints.add(location.getX());
|
||||
boundaryPoints.add(location.getY());
|
||||
}
|
||||
raceBorder.getPoints().setAll(boundaryPoints);
|
||||
}
|
||||
|
||||
private void updateGroups() {
|
||||
for (BoatGroup boatGroup : boatGroups) {
|
||||
// some raceObjects will have multiple ID's (for instance gate marks)
|
||||
//checking if the current "ID" has any updates associated with it
|
||||
if (ClientPacketParser.boatLocations.containsKey(boatGroup.getRaceId())) {
|
||||
updateBoatGroup(boatGroup);
|
||||
}
|
||||
}
|
||||
for (MarkGroup markGroup : markGroups) {
|
||||
for (Long id : markGroup.getRaceIds()) {
|
||||
if (ClientPacketParser.markLocations.containsKey(id)) {
|
||||
updateMarkGroup(id, markGroup);
|
||||
}
|
||||
}
|
||||
}
|
||||
checkForCourseChanges();
|
||||
}
|
||||
|
||||
private void checkForCourseChanges() {
|
||||
if (ClientPacketParser.isNewRaceXmlReceived()) {
|
||||
addRaceBorder();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateBoatGroup(BoatGroup boatGroup) {
|
||||
PriorityBlockingQueue<BoatPositionPacket> movementQueue = ClientPacketParser.boatLocations
|
||||
.get(boatGroup.getRaceId());
|
||||
// giving the movementQueue a 5 packet buffer to account for slightly out of order packets
|
||||
if (movementQueue.size() > 0) {
|
||||
try {
|
||||
BoatPositionPacket positionPacket = movementQueue.take();
|
||||
Point2D p2d = findScaledXY(positionPacket.getLat(), positionPacket.getLon());
|
||||
double heading = 360.0 / 0xffff * positionPacket.getHeading();
|
||||
boatGroup.setDestination(
|
||||
p2d.getX(), p2d.getY(), heading, positionPacket.getGroundSpeed(),
|
||||
positionPacket.getTimeValid(), frameRate);
|
||||
} catch (InterruptedException e){
|
||||
System.out.println("[CanvasController] Interrupted Exception");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void updateMarkGroup (long raceId, MarkGroup markGroup) {
|
||||
PriorityBlockingQueue<BoatPositionPacket> movementQueue = ClientPacketParser.markLocations
|
||||
.get(raceId);
|
||||
if (movementQueue.size() > 0){
|
||||
try {
|
||||
BoatPositionPacket positionPacket = movementQueue.take();
|
||||
Point2D p2d = findScaledXY(positionPacket.getLat(), positionPacket.getLon());
|
||||
markGroup.moveMarkTo(p2d.getX(), p2d.getY(), raceId);
|
||||
} catch (InterruptedException e) {
|
||||
System.out.println("[CanvasController] Interrupted exception");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws all the boats.
|
||||
*/
|
||||
private void initializeBoats() {
|
||||
Map<Integer, Yacht> boats = ClientPacketParser.getBoats();
|
||||
Group wakes = new Group();
|
||||
Group trails = new Group();
|
||||
Group annotations = new Group();
|
||||
|
||||
ArrayList<Participant> participants = ClientPacketParser.getXmlObject().getRaceXML()
|
||||
.getParticipants();
|
||||
ArrayList<Integer> participantIDs = new ArrayList<>();
|
||||
for (Participant p : participants) {
|
||||
participantIDs.add(p.getsourceID());
|
||||
}
|
||||
|
||||
for (Yacht boat : boats.values()) {
|
||||
if (participantIDs.contains(boat.getSourceId())) {
|
||||
boat.setColour(Colors.getColor());
|
||||
BoatGroup boatGroup = new BoatGroup(boat, boat.getColour());
|
||||
if (boat.getSourceId().equals(Integer.parseInt(ClientState.getClientSourceId()))) {
|
||||
boatGroup.setAsPlayer();
|
||||
boatGroups.add(boatGroup);
|
||||
annotations.getChildren().add(boatGroup.getAnnotations());
|
||||
} else {
|
||||
//Move annotations and boat to bottom of group keeping player ontop.
|
||||
if (boatGroups.size() > 0) {
|
||||
boatGroups.add(0, boatGroup);
|
||||
} else {
|
||||
boatGroups.add(boatGroup);
|
||||
}
|
||||
if (annotations.getChildren().size() > 0) {
|
||||
annotations.getChildren().add(0, boatGroup.getAnnotations());
|
||||
} else {
|
||||
annotations.getChildren().add(boatGroup.getAnnotations());
|
||||
}
|
||||
}
|
||||
trails.getChildren().add(boatGroup.getTrail());
|
||||
wakes.getChildren().add(boatGroup.getWake());
|
||||
}
|
||||
}
|
||||
group.getChildren().addAll(trails);
|
||||
group.getChildren().addAll(wakes);
|
||||
group.getChildren().addAll(annotations);
|
||||
group.getChildren().addAll(boatGroups);
|
||||
}
|
||||
|
||||
private void initializeMarks() {
|
||||
List<Mark> allMarks = ClientPacketParser.getXmlObject().getRaceXML()
|
||||
.getNonDupCompoundMarks();
|
||||
for (Mark mark : allMarks) {
|
||||
if (mark.getMarkType() == MarkType.SINGLE_MARK) {
|
||||
SingleMark sMark = (SingleMark) mark;
|
||||
|
||||
MarkGroup markGroup = new MarkGroup(sMark, findScaledXY(sMark));
|
||||
markGroups.add(markGroup);
|
||||
} else {
|
||||
GateMark gMark = (GateMark) mark;
|
||||
|
||||
MarkGroup markGroup = new MarkGroup(gMark, findScaledXY(gMark.getSingleMark1()),
|
||||
findScaledXY(gMark.getSingleMark2())); //should be 2 objects in the list.
|
||||
markGroups.add(markGroup);
|
||||
}
|
||||
}
|
||||
group.getChildren().addAll(markGroups);
|
||||
}
|
||||
|
||||
class ResizableCanvas extends Canvas {
|
||||
|
||||
ResizableCanvas() {
|
||||
// Redraw canvas when size changes.
|
||||
widthProperty().addListener(evt -> draw());
|
||||
heightProperty().addListener(evt -> draw());
|
||||
}
|
||||
|
||||
private void draw() {
|
||||
double width = getWidth();
|
||||
double height = getHeight();
|
||||
|
||||
GraphicsContext gc = getGraphicsContext2D();
|
||||
gc.clearRect(0, 0, width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isResizable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double prefWidth(double height) {
|
||||
return getWidth();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double prefHeight(double width) {
|
||||
return getHeight();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void drawFps(int fps){
|
||||
if (raceViewController.isDisplayFps()){
|
||||
FPSDisplay.setVisible(true);
|
||||
FPSDisplay.setText(String.format("%d FPS", fps));
|
||||
} else {
|
||||
FPSDisplay.setVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates x and y location for every marker that fits it to the canvas the race will be
|
||||
* drawn on.
|
||||
*/
|
||||
private void fitMarksToCanvas() {
|
||||
//Check is called once to avoid unnecessarily change the course limits once the race is running
|
||||
ClientPacketParser.isNewRaceXmlReceived();
|
||||
findMinMaxPoint();
|
||||
double minLonToMaxLon = scaleRaceExtremities();
|
||||
calculateReferencePointLocation(minLonToMaxLon);
|
||||
//givePointsXY();
|
||||
addRaceBorder();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the class variables minLatPoint, maxLatPoint, minLonPoint, maxLonPoint to the marker
|
||||
* with the leftmost marker, rightmost marker, southern most marker and northern most marker
|
||||
* respectively.
|
||||
*/
|
||||
private void findMinMaxPoint() {
|
||||
List<Limit> sortedPoints = new ArrayList<>();
|
||||
|
||||
for (Limit limit : ClientPacketParser.getXmlObject().getRaceXML().getCourseLimit()) {
|
||||
sortedPoints.add(limit);
|
||||
}
|
||||
sortedPoints.sort(Comparator.comparingDouble(Limit::getLat));
|
||||
Limit minLatMark = sortedPoints.get(0);
|
||||
Limit maxLatMark = sortedPoints.get(sortedPoints.size()-1);
|
||||
minLatPoint = new SingleMark(minLatMark.toString(), minLatMark.getLat(), minLatMark.getLng(), minLatMark.getSeqID(), minLatMark.getSeqID());
|
||||
maxLatPoint = new SingleMark(maxLatMark.toString(), maxLatMark.getLat(), maxLatMark.getLng(), maxLatMark.getSeqID(), minLatMark.getSeqID());
|
||||
|
||||
sortedPoints.sort(Comparator.comparingDouble(Limit::getLng));
|
||||
//If the course is on a point on the earth where longitudes wrap around.
|
||||
Limit minLonMark = sortedPoints.get(0);
|
||||
Limit maxLonMark = sortedPoints.get(sortedPoints.size()-1);
|
||||
minLonPoint = new SingleMark(minLonMark.toString(), minLonMark.getLat(), minLonMark.getLng(), minLonMark.getSeqID(), minLonMark.getSeqID());
|
||||
maxLonPoint = new SingleMark(maxLonMark.toString(), maxLonMark.getLat(), maxLonMark.getLng(), maxLonMark.getSeqID(), minLonMark.getSeqID());
|
||||
if (maxLonPoint.getLongitude() - minLonPoint.getLongitude() > 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) {
|
||||
Mark referencePoint = minLatPoint;
|
||||
double referenceAngle;
|
||||
|
||||
if (scaleDirection == ScaleDirection.HORIZONTAL) {
|
||||
referenceAngle = Math.abs(Mark.calculateHeadingRad(referencePoint, minLonPoint));
|
||||
referencePointX = BUFFER_SIZE + distanceScaleFactor * Math.sin(referenceAngle) * Mark.calculateDistance(referencePoint, minLonPoint);
|
||||
|
||||
referenceAngle = Math.abs(Mark.calculateHeadingRad(referencePoint, maxLatPoint));
|
||||
referencePointY = CANVAS_HEIGHT - (BUFFER_SIZE + BUFFER_SIZE);
|
||||
referencePointY -= distanceScaleFactor * Math.cos(referenceAngle) * Mark.calculateDistance(referencePoint, maxLatPoint);
|
||||
referencePointY = referencePointY / 2;
|
||||
referencePointY += BUFFER_SIZE;
|
||||
referencePointY += distanceScaleFactor * Math.cos(referenceAngle) * Mark.calculateDistance(referencePoint, maxLatPoint);
|
||||
} else {
|
||||
referencePointY = CANVAS_HEIGHT - BUFFER_SIZE;
|
||||
|
||||
referenceAngle = Math.abs(Mark.calculateHeadingRad(referencePoint, minLonPoint));
|
||||
referencePointX = BUFFER_SIZE;
|
||||
referencePointX += distanceScaleFactor * Math.sin(referenceAngle) * Mark.calculateDistance(referencePoint, minLonPoint);
|
||||
referencePointX += ((CANVAS_WIDTH - (BUFFER_SIZE + BUFFER_SIZE)) - (minLonToMaxLon * distanceScaleFactor)) / 2;
|
||||
}
|
||||
if(horizontalInversion) {
|
||||
referencePointX = CANVAS_WIDTH - BUFFER_SIZE - (referencePointX - BUFFER_SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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(Mark.calculateHeadingRad(minLatPoint, maxLatPoint));
|
||||
double vertDistance =
|
||||
Math.cos(vertAngle) * Mark.calculateDistance(minLatPoint, maxLatPoint);
|
||||
double horiAngle = Mark.calculateHeadingRad(minLonPoint, maxLonPoint);
|
||||
|
||||
if (horiAngle <= (Math.PI / 2)) {
|
||||
horiAngle = (Math.PI / 2) - horiAngle;
|
||||
} else {
|
||||
horiAngle = horiAngle - (Math.PI / 2);
|
||||
}
|
||||
double horiDistance =
|
||||
Math.cos(horiAngle) * Mark.calculateDistance(minLonPoint, maxLonPoint);
|
||||
|
||||
double vertScale = (CANVAS_HEIGHT - (BUFFER_SIZE + BUFFER_SIZE)) / vertDistance;
|
||||
|
||||
if ((horiDistance * vertScale) > (CANVAS_WIDTH - (BUFFER_SIZE + BUFFER_SIZE))) {
|
||||
distanceScaleFactor = (CANVAS_WIDTH - (BUFFER_SIZE + BUFFER_SIZE)) / horiDistance;
|
||||
scaleDirection = ScaleDirection.HORIZONTAL;
|
||||
} else {
|
||||
distanceScaleFactor = vertScale;
|
||||
scaleDirection = ScaleDirection.VERTICAL;
|
||||
}
|
||||
return horiDistance;
|
||||
}
|
||||
|
||||
private Point2D findScaledXY(Mark unscaled) {
|
||||
return findScaledXY(unscaled.getLatitude(), unscaled.getLongitude());
|
||||
}
|
||||
|
||||
public Point2D findScaledXY (double unscaledLat, double unscaledLon) {
|
||||
double distanceFromReference;
|
||||
double angleFromReference;
|
||||
int xAxisLocation = (int) referencePointX;
|
||||
int yAxisLocation = (int) referencePointY;
|
||||
|
||||
angleFromReference = Mark
|
||||
.calculateHeadingRad(minLatPoint.getLatitude(), minLatPoint.getLongitude(), unscaledLat,
|
||||
unscaledLon);
|
||||
distanceFromReference = Mark
|
||||
.calculateDistance(minLatPoint.getLatitude(), minLatPoint.getLongitude(), unscaledLat,
|
||||
unscaledLon);
|
||||
if (angleFromReference >= 0 && angleFromReference <= Math.PI / 2) {
|
||||
xAxisLocation += (int) Math
|
||||
.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference);
|
||||
yAxisLocation -= (int) Math
|
||||
.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference);
|
||||
} else if (angleFromReference >= 0) {
|
||||
angleFromReference = angleFromReference - Math.PI / 2;
|
||||
xAxisLocation += (int) Math
|
||||
.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference);
|
||||
yAxisLocation += (int) Math
|
||||
.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference);
|
||||
} else if (angleFromReference < 0 && angleFromReference >= -Math.PI / 2) {
|
||||
angleFromReference = Math.abs(angleFromReference);
|
||||
xAxisLocation -= (int) Math
|
||||
.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference);
|
||||
yAxisLocation -= (int) Math
|
||||
.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference);
|
||||
} else {
|
||||
angleFromReference = Math.abs(angleFromReference) - Math.PI / 2;
|
||||
xAxisLocation -= (int) Math
|
||||
.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference);
|
||||
yAxisLocation += (int) Math
|
||||
.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference);
|
||||
}
|
||||
if(horizontalInversion) {
|
||||
xAxisLocation = CANVAS_WIDTH - BUFFER_SIZE - (xAxisLocation - BUFFER_SIZE);
|
||||
}
|
||||
return new Point2D(xAxisLocation, yAxisLocation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the number of meters per pixel.
|
||||
*/
|
||||
private void findMetersPerPixel() {
|
||||
Point2D p1, p2;
|
||||
Mark m1, m2;
|
||||
double theta, distance, dx, dy, dHorizontal, dVertical;
|
||||
m1 = new SingleMark("m1", maxLatPoint.getLatitude(), minLonPoint.getLongitude(), 1, 0);
|
||||
m2 = new SingleMark("m2", minLatPoint.getLatitude(), maxLonPoint.getLongitude(), 2, 0);
|
||||
p1 = findScaledXY(m1);
|
||||
p2 = findScaledXY(m2);
|
||||
theta = Mark.calculateHeadingRad(m1, m2);
|
||||
distance = Mark.calculateDistance(m1, m2);
|
||||
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;
|
||||
}
|
||||
|
||||
List<BoatGroup> getBoatGroups() {
|
||||
return boatGroups;
|
||||
}
|
||||
|
||||
List<MarkGroup> getMarkGroups() {
|
||||
return markGroups;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
package seng302.controllers;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.ResourceBundle;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.fxml.Initializable;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.layout.AnchorPane;
|
||||
import seng302.client.ClientPacketParser;
|
||||
import seng302.client.ClientState;
|
||||
import seng302.client.ClientToServerThread;
|
||||
import seng302.server.messages.BoatActionMessage;
|
||||
import seng302.server.messages.BoatActionType;
|
||||
|
||||
public class Controller implements Initializable {
|
||||
|
||||
@FXML
|
||||
private AnchorPane contentPane;
|
||||
private ClientToServerThread clientToServerThread;
|
||||
private long lastSendingTime;
|
||||
private int KEY_STROKE_SENDING_FREQUENCY = 50;
|
||||
|
||||
public Object setContentPane(String jfxUrl) {
|
||||
try {
|
||||
contentPane.getChildren().removeAll();
|
||||
contentPane.getChildren().clear();
|
||||
contentPane.getStylesheets().add(getClass().getResource("/css/master.css").toString());
|
||||
FXMLLoader fxmlLoader = new FXMLLoader((getClass().getResource(jfxUrl)));
|
||||
Parent view = fxmlLoader.load();
|
||||
contentPane.getChildren().addAll(view);
|
||||
return fxmlLoader.getController();
|
||||
} catch (javafx.fxml.LoadException e) {
|
||||
System.err.println(e.getCause());
|
||||
} catch (IOException e) {
|
||||
System.err.println(e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize(URL location, ResourceBundle resources) {
|
||||
setUpStartScreen();
|
||||
lastSendingTime = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
void setUpStartScreen() {
|
||||
contentPane.getChildren().removeAll();
|
||||
contentPane.getChildren().clear();
|
||||
contentPane.getStylesheets().add(getClass().getResource("/css/master.css").toString());
|
||||
StartScreenController startScreenController = (StartScreenController) setContentPane("/views/StartScreenView.fxml");
|
||||
startScreenController.setController(this);
|
||||
ClientPacketParser.boatLocations.clear();
|
||||
}
|
||||
|
||||
|
||||
/** Handle the key-pressed event from the text field. */
|
||||
public void keyPressed(KeyEvent e) {
|
||||
BoatActionMessage boatActionMessage;
|
||||
long currentTime = System.currentTimeMillis();
|
||||
if (currentTime - lastSendingTime > KEY_STROKE_SENDING_FREQUENCY && ClientState.isRaceStarted()) {
|
||||
lastSendingTime = currentTime;
|
||||
switch (e.getCode()) {
|
||||
case SPACE: // align with vmg
|
||||
boatActionMessage = new BoatActionMessage(BoatActionType.VMG);
|
||||
clientToServerThread.sendBoatActionMessage(boatActionMessage);
|
||||
break;
|
||||
case PAGE_UP: // upwind
|
||||
boatActionMessage = new BoatActionMessage(BoatActionType.UPWIND);
|
||||
clientToServerThread.sendBoatActionMessage(boatActionMessage);
|
||||
break;
|
||||
case PAGE_DOWN: // downwind
|
||||
boatActionMessage = new BoatActionMessage(BoatActionType.DOWNWIND);
|
||||
clientToServerThread.sendBoatActionMessage(boatActionMessage);
|
||||
break;
|
||||
case ENTER: // tack/gybe
|
||||
boatActionMessage = new BoatActionMessage(BoatActionType.TACK_GYBE);
|
||||
clientToServerThread.sendBoatActionMessage(boatActionMessage);
|
||||
break;
|
||||
//TODO Allow a zoom in and zoom out methods
|
||||
case Z: // zoom in
|
||||
System.out.println("Zoom in");
|
||||
break;
|
||||
case X: // zoom out
|
||||
System.out.println("Zoom out");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void keyReleased(KeyEvent e) {
|
||||
switch (e.getCode()) {
|
||||
//TODO 12/07/17 Determine the sail state and send the appropriate packet (eg. if sails are in, send a sail out packet)
|
||||
case SHIFT: // sails in/sails out
|
||||
BoatActionMessage boatActionMessage = new BoatActionMessage(BoatActionType.SAILS_IN);
|
||||
clientToServerThread.sendBoatActionMessage(boatActionMessage);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void setClientToServerThread(ClientToServerThread ctt) {
|
||||
clientToServerThread = ctt;
|
||||
}
|
||||
}
|
||||
@@ -1,235 +0,0 @@
|
||||
package seng302.controllers;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.*;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.fxml.Initializable;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ListView;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.AnchorPane;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.scene.text.Text;
|
||||
import seng302.client.ClientState;
|
||||
import seng302.client.ClientStateQueryingRunnable;
|
||||
import seng302.gameServer.GameStages;
|
||||
import seng302.gameServer.GameState;
|
||||
import seng302.gameServer.MainServerThread;
|
||||
|
||||
/**
|
||||
* A class describing the actions of the lobby screen
|
||||
* Created by wmu16 on 10/07/17.
|
||||
*/
|
||||
public class LobbyController implements Initializable, Observer{
|
||||
@FXML
|
||||
private GridPane lobbyScreen;
|
||||
@FXML
|
||||
private Text lobbyIpText;
|
||||
@FXML
|
||||
private Button readyButton;
|
||||
@FXML
|
||||
private ListView firstListView;
|
||||
@FXML
|
||||
private ListView secondListView;
|
||||
@FXML
|
||||
private ListView thirdListView;
|
||||
@FXML
|
||||
private ListView fourthListView;
|
||||
@FXML
|
||||
private ListView fifthListView;
|
||||
@FXML
|
||||
private ListView sixthListView;
|
||||
@FXML
|
||||
private ListView seventhListView;
|
||||
@FXML
|
||||
private ListView eighthListView;
|
||||
@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;
|
||||
|
||||
private static List<ObservableList<String>> competitors = new ArrayList<>();
|
||||
private static ObservableList<String> firstCompetitor = FXCollections.observableArrayList();
|
||||
private static ObservableList<String> secondCompetitor = FXCollections.observableArrayList();
|
||||
private static ObservableList<String> thirdCompetitor = FXCollections.observableArrayList();
|
||||
private static ObservableList<String> fourthCompetitor = FXCollections.observableArrayList();
|
||||
private static ObservableList<String> fifthCompetitor = FXCollections.observableArrayList();
|
||||
private static ObservableList<String> sixthCompetitor = FXCollections.observableArrayList();
|
||||
private static ObservableList<String> seventhCompetitor = FXCollections.observableArrayList();
|
||||
private static ObservableList<String> eighthCompetitor = FXCollections.observableArrayList();
|
||||
private ClientStateQueryingRunnable clientStateQueryingRunnable;
|
||||
private static List<ImageView> imageViews;
|
||||
private static List<ListView> listViews;
|
||||
|
||||
private int MAX_NUM_PLAYERS = 8;
|
||||
|
||||
private Boolean switchedPane = false;
|
||||
private MainServerThread mainServerThread;
|
||||
private Controller controller;
|
||||
|
||||
private void setContentPane(String jfxUrl) {
|
||||
try {
|
||||
AnchorPane contentPane = (AnchorPane) lobbyScreen.getParent();
|
||||
contentPane.getChildren().removeAll();
|
||||
contentPane.getChildren().clear();
|
||||
contentPane.getStylesheets().add(getClass().getResource("/css/master.css").toString());
|
||||
contentPane.getChildren()
|
||||
.addAll((Pane) FXMLLoader.load(getClass().getResource(jfxUrl)));
|
||||
} catch (javafx.fxml.LoadException e) {
|
||||
System.out.println("[Controller] FXML load exception");
|
||||
} catch (IOException e) {
|
||||
System.out.println("[Controller] IO exception");
|
||||
} catch (NullPointerException e) {
|
||||
// System.out.println("[Controller] Null Pointer Exception");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize(URL location, ResourceBundle resources) {
|
||||
if (ClientState.isHost()) {
|
||||
lobbyIpText.setText("Lobby Host IP: " + ClientState.getHostIp());
|
||||
readyButton.setDisable(false);
|
||||
}
|
||||
else {
|
||||
lobbyIpText.setText("Connected to IP: " + ClientState.getHostIp());
|
||||
readyButton.setDisable(true);
|
||||
}
|
||||
|
||||
// put all javafx objects in lists, so we can iterate though conveniently
|
||||
imageViews = new ArrayList<>();
|
||||
Collections.addAll(imageViews, firstImageView, secondImageView, thirdImageView, fourthImageView,
|
||||
fifthImageView, sixthImageView, seventhImageView, eighthImageView);
|
||||
listViews = new ArrayList<>();
|
||||
Collections.addAll(listViews, firstListView, secondListView, thirdListView, fourthListView, fifthListView,
|
||||
sixthListView, seventhListView, eighthListView);
|
||||
competitors = new ArrayList<>();
|
||||
Collections.addAll(competitors, firstCompetitor, secondCompetitor, thirdCompetitor,
|
||||
fourthCompetitor, fifthCompetitor, sixthCompetitor, seventhCompetitor, eighthCompetitor);
|
||||
|
||||
initialiseListView();
|
||||
initialiseImageView(); // parrot gif init
|
||||
|
||||
// set up client state query thread, so that when it receives the race-started packet
|
||||
// it can switch to the race view
|
||||
ClientStateQueryingRunnable clientStateQueryingRunnable = new ClientStateQueryingRunnable();
|
||||
clientStateQueryingRunnable.addObserver(this);
|
||||
Thread clientStateQueryingThread = new Thread(clientStateQueryingRunnable, "Client State querying thread");
|
||||
clientStateQueryingThread.setDaemon(true);
|
||||
clientStateQueryingThread.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Observers "ClientStateQueryingRunnable".
|
||||
* When the clients state has been marked to "race start", the querying thread
|
||||
* will notify this lobby to change the view
|
||||
* @param o
|
||||
* @param arg
|
||||
*/
|
||||
@Override
|
||||
public void update(Observable o, Object arg) {
|
||||
Platform.runLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (arg.equals("game started") && !switchedPane) {
|
||||
switchToRaceView();
|
||||
}
|
||||
if (arg.equals(("update players"))) {
|
||||
initialiseListView();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset all ListViews and ImageViews according to the current competitors
|
||||
*/
|
||||
private void initialiseListView() {
|
||||
listViews.forEach(listView -> listView.getItems().clear());
|
||||
imageViews.forEach(gif -> gif.setVisible(false));
|
||||
competitors.forEach(ol -> ol.removeAll());
|
||||
|
||||
List<Integer> ids = new ArrayList<>(ClientState.getBoats().keySet());
|
||||
for (int i = 0; i < ids.size(); i++) {
|
||||
competitors.get(i).add(ClientState.getBoats().get(ids.get(i)).getBoatName());
|
||||
listViews.get(i).setItems(competitors.get(i));
|
||||
imageViews.get(i).setVisible(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads preset images into imageViews
|
||||
*/
|
||||
private void initialiseImageView() {
|
||||
for (int i = 0; i < MAX_NUM_PLAYERS; i++) {
|
||||
imageViews.get(i).setImage(new Image(getClass().getResourceAsStream("/pics/sail.png")));
|
||||
}
|
||||
// Image image1 = new Image(getClass().getResourceAsStream("/pics/sail.png"));
|
||||
// firstImageView.setImage(image1);
|
||||
// Image image2 = new Image(getClass().getResourceAsStream("/pics/sail.png"));
|
||||
// secondImageView.setImage(image2);
|
||||
// Image image3 = new Image(getClass().getResourceAsStream("/pics/sail.png"));
|
||||
// thirdImageView.setImage(image3);
|
||||
// Image image4 = new Image(getClass().getResourceAsStream("/pics/sail.png"));
|
||||
// fourthImageView.setImage(image4);
|
||||
// Image image5 = new Image(getClass().getResourceAsStream("/pics/sail.png"));
|
||||
// fifthImageView.setImage(image5);
|
||||
// Image image6 = new Image(getClass().getResourceAsStream("/pics/sail.png"));
|
||||
// sixthImageView.setImage(image6);
|
||||
// Image image7 = new Image(getClass().getResourceAsStream("/pics/sail.png"));
|
||||
// seventhImageView.setImage(image7);
|
||||
// Image image8 = new Image(getClass().getResourceAsStream("/pics/sail.png"));
|
||||
// eighthImageView.setImage(image8);
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void leaveLobbyButtonPressed() {
|
||||
if (ClientState.isHost()) {
|
||||
GameState.setCurrentStage(GameStages.CANCELLED);
|
||||
mainServerThread.terminate();
|
||||
}
|
||||
ClientState.setConnectedToHost(false);
|
||||
controller.setUpStartScreen();
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void readyButtonPressed() {
|
||||
GameState.setCurrentStage(GameStages.RACING);
|
||||
mainServerThread.startGame();
|
||||
}
|
||||
|
||||
|
||||
private void switchToRaceView() {
|
||||
if (!switchedPane) {
|
||||
switchedPane = true;
|
||||
setContentPane("/views/RaceView.fxml");
|
||||
}
|
||||
}
|
||||
|
||||
public void setMainServerThread(MainServerThread mainServerThread) {
|
||||
this.mainServerThread = mainServerThread;
|
||||
}
|
||||
|
||||
public void setController(Controller controller) {
|
||||
this.controller = controller;
|
||||
}
|
||||
}
|
||||
@@ -1,668 +0,0 @@
|
||||
package seng302.controllers;
|
||||
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.geometry.Point2D;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.chart.LineChart;
|
||||
import javafx.scene.chart.NumberAxis;
|
||||
import javafx.scene.chart.XYChart;
|
||||
import javafx.scene.chart.XYChart.Series;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.CheckBox;
|
||||
import javafx.scene.control.ComboBox;
|
||||
import javafx.scene.control.Slider;
|
||||
import javafx.scene.layout.AnchorPane;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.paint.Paint;
|
||||
import javafx.scene.shape.Line;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.StageStyle;
|
||||
import javafx.util.Duration;
|
||||
import javafx.util.StringConverter;
|
||||
import seng302.client.ClientPacketParser;
|
||||
import seng302.utilities.GeoUtility;
|
||||
import seng302.controllers.annotations.Annotation;
|
||||
import seng302.controllers.annotations.ImportantAnnotationController;
|
||||
import seng302.controllers.annotations.ImportantAnnotationDelegate;
|
||||
import seng302.controllers.annotations.ImportantAnnotationsState;
|
||||
import seng302.fxObjects.BoatGroup;
|
||||
import seng302.fxObjects.MarkGroup;
|
||||
import seng302.models.*;
|
||||
import seng302.models.mark.GateMark;
|
||||
import seng302.models.mark.Mark;
|
||||
import seng302.models.mark.SingleMark;
|
||||
import seng302.models.stream.XMLParser;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import seng302.models.stream.XMLParser.RaceXMLObject.Participant;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Created by ptg19 on 29/03/17.
|
||||
*/
|
||||
public class RaceViewController extends Thread implements ImportantAnnotationDelegate {
|
||||
|
||||
@FXML
|
||||
private Text windSpeedText;
|
||||
@FXML
|
||||
private LineChart raceSparkLine;
|
||||
@FXML
|
||||
private NumberAxis sparklineYAxis;
|
||||
@FXML
|
||||
private VBox positionVbox;
|
||||
@FXML
|
||||
private CheckBox toggleFps;
|
||||
@FXML
|
||||
private Text timerLabel;
|
||||
@FXML
|
||||
private AnchorPane contentAnchorPane;
|
||||
@FXML
|
||||
private Text windArrowText, windDirectionText;
|
||||
@FXML
|
||||
private Slider annotationSlider;
|
||||
@FXML
|
||||
private Button selectAnnotationBtn;
|
||||
@FXML
|
||||
private ComboBox boatSelectionComboBox;
|
||||
@FXML
|
||||
private CanvasController includedCanvasController;
|
||||
|
||||
private static ArrayList<Yacht> startingBoats = new ArrayList<>();
|
||||
private boolean displayFps;
|
||||
private Timeline timerTimeline;
|
||||
private Stage stage;
|
||||
private static HashMap<Integer, Series<String, Double>> sparkLineData = new HashMap<>();
|
||||
private static ArrayList<Yacht> racingBoats = new ArrayList<>();
|
||||
private ImportantAnnotationsState importantAnnotations;
|
||||
private Yacht selectedBoat;
|
||||
|
||||
public void initialize() {
|
||||
// Load a default important annotation state
|
||||
importantAnnotations = new ImportantAnnotationsState();
|
||||
|
||||
//Formatting the y axis of the sparkline
|
||||
raceSparkLine.getYAxis().setRotate(180);
|
||||
raceSparkLine.getYAxis().setTickLabelRotation(180);
|
||||
raceSparkLine.getYAxis().setTranslateX(-5);
|
||||
raceSparkLine.getYAxis().setAutoRanging(false);
|
||||
sparklineYAxis.setTickMarkVisible(false);
|
||||
startingBoats = new ArrayList<>(ClientPacketParser.getBoats().values());
|
||||
|
||||
includedCanvasController.setup(this);
|
||||
includedCanvasController.initializeCanvas();
|
||||
initializeUpdateTimer();
|
||||
initialiseFPSCheckBox();
|
||||
initialiseAnnotationSlider();
|
||||
initialiseBoatSelectionComboBox();
|
||||
includedCanvasController.timer.start();
|
||||
selectAnnotationBtn.setOnAction(event -> loadSelectAnnotationView());
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The important annotations have been changed, update this view
|
||||
*
|
||||
* @param importantAnnotationsState The current state of the selected annotations
|
||||
*/
|
||||
public void importantAnnotationsChanged(ImportantAnnotationsState importantAnnotationsState) {
|
||||
this.importantAnnotations = importantAnnotationsState;
|
||||
setAnnotations((int) annotationSlider.getValue()); // Refresh the displayed annotations
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Loads the "select annotations" view in a new window
|
||||
*/
|
||||
private void loadSelectAnnotationView() {
|
||||
try {
|
||||
FXMLLoader fxmlLoader = new FXMLLoader();
|
||||
Stage stage = new Stage();
|
||||
|
||||
// Set controller
|
||||
ImportantAnnotationController controller = new ImportantAnnotationController(this,
|
||||
stage);
|
||||
fxmlLoader.setController(controller);
|
||||
|
||||
// Load FXML and set CSS
|
||||
fxmlLoader
|
||||
.setLocation(getClass().getResource("/views/importantAnnotationSelectView.fxml"));
|
||||
Scene scene = new Scene(fxmlLoader.load(), 469, 298);
|
||||
scene.getStylesheets().add(getClass().getResource("/css/master.css").toString());
|
||||
stage.initStyle(StageStyle.UNDECORATED);
|
||||
stage.setScene(scene);
|
||||
stage.show();
|
||||
|
||||
controller.loadState(importantAnnotations);
|
||||
|
||||
} catch (IOException e) {
|
||||
System.out.println("[RaceViewController] IO exception");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void initialiseFPSCheckBox() {
|
||||
displayFps = true;
|
||||
toggleFps.selectedProperty().addListener(
|
||||
(observable, oldValue, newValue) -> displayFps = !displayFps);
|
||||
}
|
||||
|
||||
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.valueProperty().addListener((obs, oldval, newVal) ->
|
||||
setAnnotations((int) annotationSlider.getValue()));
|
||||
|
||||
annotationSlider.setValue(2);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Used to add any new boats into the race that may have started late or not have had data received yet
|
||||
*/
|
||||
void updateSparkLine(){
|
||||
// Collect the racing boats that aren't already in the chart
|
||||
ArrayList<Yacht> sparkLineCandidates = startingBoats.stream().filter(yacht -> !sparkLineData.containsKey(yacht.getSourceId())
|
||||
&& yacht.getPosition() != null & yacht.getPosition() != "-").collect(Collectors.toCollection(ArrayList::new));
|
||||
|
||||
// Obtain the qualifying boats to set the max on the Y axis
|
||||
racingBoats = startingBoats.stream().filter(yacht ->
|
||||
yacht.getPosition() != null & yacht.getPosition() != "-").collect(Collectors.toCollection(ArrayList::new));
|
||||
sparklineYAxis.setUpperBound(racingBoats.size() + 1);
|
||||
|
||||
// Create a new data series for new boats
|
||||
sparkLineCandidates.stream().filter(yacht -> yacht.getPosition() != null).forEach(yacht -> {
|
||||
Series<String, Double> yachtData = new Series<>();
|
||||
yachtData.setName(yacht.getBoatName());
|
||||
yachtData.getData().add(new XYChart.Data<>(Integer.toString(yacht.getLegNumber()), 1 + racingBoats.size() - Double.parseDouble(yacht.getPosition())));
|
||||
sparkLineData.put(yacht.getSourceId(), yachtData);
|
||||
});
|
||||
|
||||
// Lambda function to sort the series in order of leg (later legs shown more to the right)
|
||||
List<XYChart.Series<String, Double>> positions = new ArrayList<>(sparkLineData.values());
|
||||
Collections.sort(positions, (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)
|
||||
raceSparkLine.setCreateSymbols(false);
|
||||
positions.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()));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Updates the yachts sparkline of the desired boat 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
|
||||
*/
|
||||
public static void updateYachtPositionSparkline(Yacht yacht, Integer legNumber){
|
||||
XYChart.Series<String, Double> positionData = sparkLineData.get(yacht.getSourceId());
|
||||
positionData.getData().add(new XYChart.Data<>(Integer.toString(legNumber), 1 + racingBoats.size() - Double.parseDouble(yacht.getPosition())));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* gets the rgb string of the boats colour to use for the chart via css
|
||||
* @param boatName boat passed in to get the boats colour
|
||||
* @return the colour as an rgb string
|
||||
*/
|
||||
private String getBoatColorAsRGB(String boatName){
|
||||
Color color = Color.WHITE;
|
||||
for (Yacht yacht: startingBoats){
|
||||
if (Objects.equals(yacht.getBoatName(), boatName)){
|
||||
color = yacht.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 ) );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Initalises a timer which updates elements of the RaceView such as wind direction, boat
|
||||
* orderings etc.. which are dependent on the info from the stream parser constantly.
|
||||
* Updates of each of these attributes are called ONCE EACH SECOND
|
||||
*/
|
||||
private void initializeUpdateTimer() {
|
||||
timerTimeline = new Timeline();
|
||||
timerTimeline.setCycleCount(Timeline.INDEFINITE);
|
||||
// Run timer update every second
|
||||
timerTimeline.getKeyFrames().add(
|
||||
new KeyFrame(Duration.seconds(1),
|
||||
event -> {
|
||||
updateRaceTime();
|
||||
updateWindDirection();
|
||||
// updateOrder();
|
||||
updateBoatSelectionComboBox();
|
||||
updateOrder();
|
||||
})
|
||||
);
|
||||
|
||||
// Start the timer
|
||||
timerTimeline.playFromStart();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Iterates over all corners until ones SeqID matches with the boats current leg number.
|
||||
* Then it gets the compoundMarkID of that corner and uses it to fetch the appropriate mark
|
||||
* Returns null if no next mark found.
|
||||
* @param bg The BoatGroup to find the next mark of
|
||||
* @return The next Mark or null if none found
|
||||
*/
|
||||
private Mark getNextMark(BoatGroup bg) {
|
||||
Integer legNumber = bg.getBoat().getLegNumber();
|
||||
|
||||
List<XMLParser.RaceXMLObject.Corner> markSequence = ClientPacketParser.getXmlObject()
|
||||
.getRaceXML().getCompoundMarkSequence();
|
||||
|
||||
if (legNumber == 0) {
|
||||
return null;
|
||||
} else if (legNumber == markSequence.size() - 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (XMLParser.RaceXMLObject.Corner corner : markSequence) {
|
||||
if (legNumber + 2 == corner.getSeqID()) {
|
||||
Integer thisCompoundMarkID = corner.getCompoundMarkID();
|
||||
|
||||
for (Mark mark : ClientPacketParser.getXmlObject().getRaceXML()
|
||||
.getAllCompoundMarks()) {
|
||||
if (mark.getCompoundMarkID() == thisCompoundMarkID) {
|
||||
return mark;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Updates the wind direction arrow and text as from info from the ClientPacketParser
|
||||
*/
|
||||
private void updateWindDirection() {
|
||||
windDirectionText.setText(String.format("%.1f°", ClientPacketParser.getWindDirection()));
|
||||
windArrowText.setRotate(ClientPacketParser.getWindDirection());
|
||||
windSpeedText.setText(String.format("%.1f Knots", ClientPacketParser.getWindSpeed()));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Updates the clock for the race
|
||||
*/
|
||||
private void updateRaceTime() {
|
||||
if (ClientPacketParser.isRaceFinished()) {
|
||||
timerLabel.setFill(Color.RED);
|
||||
timerLabel.setText("Race Finished!");
|
||||
} else {
|
||||
timerLabel.setText(getTimeSinceStartOfRace());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Grabs the boats currently in the race as from the ClientPacketParser and sets them to be selectable
|
||||
* in the boat selection combo box
|
||||
*/
|
||||
private void updateBoatSelectionComboBox() {
|
||||
ObservableList<Yacht> observableBoats = FXCollections
|
||||
.observableArrayList(ClientPacketParser.getBoatsPos().values());
|
||||
boatSelectionComboBox.setItems(observableBoats);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Updates the order of the boats as from the ClientPacketParser and sets them in the boat order
|
||||
* section
|
||||
*/
|
||||
private void updateOrder() {
|
||||
positionVbox.getChildren().clear();
|
||||
positionVbox.getChildren().removeAll();
|
||||
positionVbox.getStylesheets().add(getClass().getResource("/css/master.css").toString());
|
||||
|
||||
// list of racing boat id
|
||||
ArrayList<Participant> participants = ClientPacketParser.getXmlObject().getRaceXML()
|
||||
.getParticipants();
|
||||
ArrayList<Integer> participantIDs = new ArrayList<>();
|
||||
for (Participant p : participants) {
|
||||
participantIDs.add(p.getsourceID());
|
||||
}
|
||||
|
||||
if (ClientPacketParser.isRaceStarted()) {
|
||||
/*
|
||||
for (Yacht boat : ClientPacketParser.getBoatsPos().values()) {
|
||||
System.out.println("Hi tjere" + boat.getBoatName());
|
||||
if (participantIDs.contains(boat.getSourceId()) || true
|
||||
) { // check if the boat is racing
|
||||
if (boat.getBoatStatus() == 69) { // 3 is finish status
|
||||
Text textToAdd = new Text(boat.getPosition() + ". " +
|
||||
boat.getShortName() + " (Finished)");
|
||||
textToAdd.setFill(Paint.valueOf("#d3d3d3"));
|
||||
positionVbox.getChildren().add(textToAdd);
|
||||
|
||||
} else {
|
||||
Text textToAdd = new Text(boat.getPosition() + ". " +
|
||||
boat.getShortName() + " ");
|
||||
textToAdd.setFill(Paint.valueOf("#d3d3d3"));
|
||||
textToAdd.setStyle("");
|
||||
positionVbox.getChildren().add(textToAdd);
|
||||
System.out.println("Adding " + textToAdd.getText());
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
for (Yacht boat : ClientPacketParser.getBoats().values()){
|
||||
Text textToAdd = new Text(boat.getSourceId() + ". " + boat.getShortName() + " ");
|
||||
textToAdd.setFill(Paint.valueOf("#d3d3d3"));
|
||||
textToAdd.setStyle("");
|
||||
positionVbox.getChildren().add(textToAdd);
|
||||
}
|
||||
} else {
|
||||
for (Yacht boat : ClientPacketParser.getBoats().values()) {
|
||||
if (participantIDs.contains(boat.getSourceId())) { // check if the boat is racing
|
||||
Text textToAdd = new Text(boat.getPosition() + ". " +
|
||||
boat.getShortName() + " ");
|
||||
textToAdd.setFill(Paint.valueOf("#d3d3d3"));
|
||||
textToAdd.setStyle("");
|
||||
positionVbox.getChildren().add(textToAdd);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void updateLaylines(BoatGroup bg) {
|
||||
|
||||
Mark nextMark = getNextMark(bg);
|
||||
Boolean isUpwind = null;
|
||||
// Can only calc leg direction if there is a next mark and it is a gate mark
|
||||
if (nextMark != null) {
|
||||
if (nextMark instanceof GateMark) {
|
||||
if (bg.isUpwindLeg(includedCanvasController, nextMark)) {
|
||||
isUpwind = true;
|
||||
} else {
|
||||
isUpwind = false;
|
||||
}
|
||||
|
||||
for(MarkGroup mg : includedCanvasController.getMarkGroups()) {
|
||||
|
||||
mg.removeLaylines();
|
||||
|
||||
if (mg.getMainMark().getId() == nextMark.getId()) {
|
||||
|
||||
SingleMark singleMark1 = ((GateMark) nextMark).getSingleMark1();
|
||||
SingleMark singleMark2 = ((GateMark) nextMark).getSingleMark2();
|
||||
Point2D markPoint1 = includedCanvasController.findScaledXY(singleMark1.getLatitude(), singleMark1.getLongitude());
|
||||
Point2D markPoint2 = includedCanvasController.findScaledXY(singleMark2.getLatitude(), singleMark2.getLongitude());
|
||||
HashMap<Double, Double> angleAndSpeed;
|
||||
if (isUpwind) {
|
||||
angleAndSpeed = PolarTable
|
||||
.getOptimalUpwindVMG(ClientPacketParser.getWindSpeed());
|
||||
} else {
|
||||
angleAndSpeed = PolarTable
|
||||
.getOptimalDownwindVMG(ClientPacketParser.getWindSpeed());
|
||||
}
|
||||
|
||||
Double resultingAngle = angleAndSpeed.keySet().iterator().next();
|
||||
|
||||
|
||||
Point2D boatCurrentPos = new Point2D(bg.getBoatLayoutX(), bg.getBoatLayoutY());
|
||||
Point2D gateMidPoint = markPoint1.midpoint(markPoint2);
|
||||
Integer lineFuncResult = GeoUtility.lineFunction(boatCurrentPos, gateMidPoint, markPoint2);
|
||||
Line rightLayline = new Line();
|
||||
Line leftLayline = new Line();
|
||||
if (lineFuncResult == 1) {
|
||||
rightLayline = makeRightLayline(markPoint2, 180 - resultingAngle,
|
||||
ClientPacketParser
|
||||
.getWindDirection());
|
||||
leftLayline = makeLeftLayline(markPoint1, 180 - resultingAngle,
|
||||
ClientPacketParser
|
||||
.getWindDirection());
|
||||
} else if (lineFuncResult == -1) {
|
||||
rightLayline = makeRightLayline(markPoint1, 180 - resultingAngle,
|
||||
ClientPacketParser
|
||||
.getWindDirection());
|
||||
leftLayline = makeLeftLayline(markPoint2, 180 - resultingAngle,
|
||||
ClientPacketParser
|
||||
.getWindDirection());
|
||||
}
|
||||
|
||||
leftLayline.setStrokeWidth(0.5);
|
||||
leftLayline.setStroke(bg.getBoat().getColour());
|
||||
|
||||
rightLayline.setStrokeWidth(0.5);
|
||||
rightLayline.setStroke(bg.getBoat().getColour());
|
||||
|
||||
bg.setLaylines(leftLayline, rightLayline);
|
||||
mg.addLaylines(leftLayline, rightLayline);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private Point2D getPointRotation(Point2D ref, Double distance, Double angle){
|
||||
Double newX = ref.getX() + (ref.getX() + distance -ref.getX())*Math.cos(angle) - (ref.getY() + distance -ref.getY())*Math.sin(angle);
|
||||
Double newY = ref.getY() + (ref.getX() + distance -ref.getX())*Math.sin(angle) + (ref.getY() + distance -ref.getY())*Math.cos(angle);
|
||||
|
||||
return new Point2D(newX, newY);
|
||||
}
|
||||
|
||||
|
||||
public Line makeLeftLayline(Point2D startPoint, Double layLineAngle, Double baseAngle) {
|
||||
|
||||
Point2D ep = getPointRotation(startPoint, 50.0, baseAngle + layLineAngle);
|
||||
Line line = new Line(startPoint.getX(), startPoint.getY(), ep.getX(), ep.getY());
|
||||
return line;
|
||||
|
||||
}
|
||||
|
||||
|
||||
public Line makeRightLayline(Point2D startPoint, Double layLineAngle, Double baseAngle) {
|
||||
|
||||
Point2D ep = getPointRotation(startPoint, 50.0, baseAngle - layLineAngle);
|
||||
Line line = new Line(startPoint.getX(), startPoint.getY(), ep.getX(), ep.getY());
|
||||
return line;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Initialised the combo box with any boats currently in the race and adds the required listener
|
||||
* for the combobox to take action upon selection
|
||||
*/
|
||||
private void initialiseBoatSelectionComboBox() {
|
||||
updateBoatSelectionComboBox();
|
||||
boatSelectionComboBox.valueProperty().addListener((observable, oldValue, newValue) -> {
|
||||
//This listener is fired whenever the combo box changes. This means when the values are updated
|
||||
//We dont want to set the selected value if the values are updated but nothing clicked (null)
|
||||
if (newValue != null && newValue != selectedBoat) {
|
||||
Yacht thisYacht = (Yacht) newValue;
|
||||
setSelectedBoat(thisYacht);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Display the list of boats in the order they finished the race
|
||||
*/
|
||||
private void loadRaceResultView() {
|
||||
FXMLLoader loader = new FXMLLoader(getClass().getResource("/views/FinishView.fxml"));
|
||||
|
||||
try {
|
||||
contentAnchorPane.getChildren().removeAll();
|
||||
contentAnchorPane.getChildren().clear();
|
||||
contentAnchorPane.getChildren().addAll((Pane) loader.load());
|
||||
|
||||
} catch (javafx.fxml.LoadException e) {
|
||||
System.err.println(e.getCause());
|
||||
} catch (IOException e) {
|
||||
System.err.println(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Convert seconds to a string of the format mm:ss
|
||||
*
|
||||
* @param time the time in seconds
|
||||
* @return a formatted string
|
||||
*/
|
||||
public String convertTimeToMinutesSeconds(int time) {
|
||||
if (time < 0) {
|
||||
return String.format("-%02d:%02d", (time * -1) / 60, (time * -1) % 60);
|
||||
}
|
||||
return String.format("%02d:%02d", time / 60, time % 60);
|
||||
}
|
||||
|
||||
private String getTimeSinceStartOfRace() {
|
||||
String timerString = "0:00";
|
||||
if (ClientPacketParser.getTimeSinceStart() > 0) {
|
||||
String timerMinute = Long.toString(ClientPacketParser.getTimeSinceStart() / 60);
|
||||
String timerSecond = Long.toString(ClientPacketParser.getTimeSinceStart() % 60);
|
||||
if (timerSecond.length() == 1) {
|
||||
timerSecond = "0" + timerSecond;
|
||||
}
|
||||
timerString = "-" + timerMinute + ":" + timerSecond;
|
||||
} else {
|
||||
String timerMinute = Long.toString(-1 * ClientPacketParser.getTimeSinceStart() / 60);
|
||||
String timerSecond = Long.toString(-1 * ClientPacketParser.getTimeSinceStart() % 60);
|
||||
if (timerSecond.length() == 1) {
|
||||
timerSecond = "0" + timerSecond;
|
||||
}
|
||||
timerString = timerMinute + ":" + timerSecond;
|
||||
}
|
||||
return timerString;
|
||||
}
|
||||
|
||||
|
||||
boolean isDisplayFps() {
|
||||
return displayFps;
|
||||
}
|
||||
|
||||
private void setAnnotations(Integer annotationLevel) {
|
||||
switch (annotationLevel) {
|
||||
// No Annotations
|
||||
case 0:
|
||||
for (BoatGroup bg : includedCanvasController.getBoatGroups()) {
|
||||
bg.setVisibility(false, false, false, false, false, false);
|
||||
}
|
||||
break;
|
||||
// Important Annotations
|
||||
case 1:
|
||||
for (BoatGroup bg : includedCanvasController.getBoatGroups()) {
|
||||
bg.setVisibility(
|
||||
importantAnnotations.getAnnotationState(Annotation.NAME),
|
||||
importantAnnotations.getAnnotationState(Annotation.SPEED),
|
||||
importantAnnotations.getAnnotationState(Annotation.ESTTIMETONEXTMARK),
|
||||
importantAnnotations.getAnnotationState(Annotation.LEGTIME),
|
||||
importantAnnotations.getAnnotationState(Annotation.TRACK),
|
||||
importantAnnotations.getAnnotationState(Annotation.WAKE)
|
||||
);
|
||||
}
|
||||
break;
|
||||
// All Annotations
|
||||
case 2:
|
||||
for (BoatGroup bg : includedCanvasController.getBoatGroups()) {
|
||||
bg.setVisibility(true, true, true, true, true, true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets all the annotations of the selected boat to be visible and all others to be hidden
|
||||
*
|
||||
* @param yacht The yacht for which we want to view all annotations
|
||||
*/
|
||||
private void setSelectedBoat(Yacht yacht) {
|
||||
for (BoatGroup bg : includedCanvasController.getBoatGroups()) {
|
||||
//We need to iterate over all race groups to get the matching boat group belonging to this boat if we
|
||||
//are to toggle its annotations, there is no other backwards knowledge of a yacht to its boatgroup.
|
||||
if (bg.getBoat().getHullID().equals(yacht.getHullID())) {
|
||||
updateLaylines(bg);
|
||||
bg.setIsSelected(true);
|
||||
selectedBoat = yacht;
|
||||
} else {
|
||||
bg.setIsSelected(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void setStage(Stage stage) {
|
||||
this.stage = stage;
|
||||
}
|
||||
|
||||
Stage getStage() {
|
||||
return stage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for when the boat attempts to add data to the sparkline (first checks if the sparkline contains info on it)
|
||||
* @param yachtId
|
||||
* @return
|
||||
*/
|
||||
public static boolean sparkLineStatus(Integer yachtId) {
|
||||
return sparkLineData.containsKey(yachtId);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,164 +0,0 @@
|
||||
package seng302.controllers;
|
||||
|
||||
import java.net.Inet4Address;
|
||||
import java.net.NetworkInterface;
|
||||
import java.util.Enumeration;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.Alert.AlertType;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.layout.AnchorPane;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.Pane;
|
||||
import seng302.client.ClientState;
|
||||
import seng302.client.ClientToServerThread;
|
||||
import seng302.gameServer.GameState;
|
||||
import seng302.gameServer.MainServerThread;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
/**
|
||||
* A Class describing the actions of the start screen controller
|
||||
* Created by wmu16 on 10/07/17.
|
||||
*/
|
||||
public class StartScreenController {
|
||||
|
||||
@FXML
|
||||
private TextField ipTextField;
|
||||
@FXML
|
||||
private TextField portTextField;
|
||||
@FXML
|
||||
private GridPane startScreen2;
|
||||
|
||||
private Controller controller;
|
||||
|
||||
/**
|
||||
* 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 (javafx.fxml.LoadException e) {
|
||||
System.out.println("[Controller] FXML load exception");
|
||||
} catch (IOException e) {
|
||||
System.out.println("[Controller] IO exception");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* ATTEMPTS TO:
|
||||
* Sets up a new game state with your IP address as designated as the host.
|
||||
* Starts a thread to listen for incoming connections.
|
||||
* Starts a client to server thread and connects to own ip.
|
||||
* Switches to the lobby screen
|
||||
*/
|
||||
@FXML
|
||||
public void hostButtonPressed() {
|
||||
try {
|
||||
String ipAddress = InetAddress.getLocalHost().getHostAddress();
|
||||
// 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);
|
||||
lobbyController.setController(controller);
|
||||
} catch (Exception e) {
|
||||
Alert alert = new Alert(AlertType.ERROR);
|
||||
alert.setHeaderText("Cannot host");
|
||||
alert.setContentText("Oops, failed to host, try to restart.");
|
||||
alert.showAndWait();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* ATTEMPTS TO:
|
||||
* Connect to an ip address and port using the ip and port specified on start screen.
|
||||
* Starts a Client To Server Thread to maintain connection to host.
|
||||
* Switch view to lobby view.
|
||||
*/
|
||||
@FXML
|
||||
public void connectButtonPressed() {
|
||||
// TODO: 10/07/17 wmu16 - Finish function
|
||||
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);
|
||||
|
||||
ClientState.setHostIp(ipAddress);
|
||||
controller.setClientToServerThread(clientToServerThread);
|
||||
LobbyController lobbyController = (LobbyController) setContentPane("/views/LobbyView.fxml");
|
||||
lobbyController.setController(controller);
|
||||
} 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 {
|
||||
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) {
|
||||
System.out.println("[StartScreenController] Exception");
|
||||
}
|
||||
if (ipAddress == null) {
|
||||
System.out.println("[HOST] Cannot obtain local host ip address.");
|
||||
}
|
||||
ClientState.setHostIp(ipAddress);
|
||||
return ipAddress;
|
||||
}
|
||||
}
|
||||
@@ -1,154 +0,0 @@
|
||||
package seng302.fxObjects;
|
||||
|
||||
import javafx.scene.CacheHint;
|
||||
import javafx.scene.Group;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.shape.Rectangle;
|
||||
import javafx.scene.text.Text;
|
||||
import seng302.client.ClientPacketParser;
|
||||
import seng302.models.Yacht;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
|
||||
/**
|
||||
* Collection of annotations for boats.
|
||||
*/
|
||||
public class BoatAnnotations extends Group{
|
||||
|
||||
//Text offset constants
|
||||
private static final double X_OFFSET_TEXT = 18d;
|
||||
private static final double Y_OFFSET_TEXT_INIT = -29d;
|
||||
private static final double Y_OFFSET_PER_TEXT = 12d;
|
||||
//Background constants
|
||||
private static final double TEXT_BUFFER = 3;
|
||||
private static final double BACKGROUND_X = X_OFFSET_TEXT - TEXT_BUFFER;
|
||||
private static final double BACKGROUND_Y = Y_OFFSET_TEXT_INIT - TEXT_BUFFER;
|
||||
private static final double BACKGROUND_H_PER_TEXT = 9.5d;
|
||||
private static final double BACKGROUND_W = 125d;
|
||||
private static final double BACKGROUND_ARC_SIZE = 10;
|
||||
|
||||
private Rectangle background = new Rectangle();
|
||||
private Text teamNameObject;
|
||||
private Text velocityObject;
|
||||
private Text estTimeToNextMarkObject;
|
||||
private Text legTimeObject;
|
||||
private boolean isPlayer = false;
|
||||
private Yacht boat;
|
||||
|
||||
BoatAnnotations (Yacht boat, Color theme) {
|
||||
super.setCache(true);
|
||||
this.boat = boat;
|
||||
background.setX(BACKGROUND_X);
|
||||
background.setY(BACKGROUND_Y);
|
||||
background.setWidth(BACKGROUND_W);
|
||||
background.setHeight(Math.abs(BACKGROUND_X) + TEXT_BUFFER + BACKGROUND_H_PER_TEXT * 4);
|
||||
background.setArcHeight(BACKGROUND_ARC_SIZE);
|
||||
background.setArcWidth(BACKGROUND_ARC_SIZE);
|
||||
background.setFill(new Color(1, 1, 1, 0.75));
|
||||
background.setStroke(theme);
|
||||
background.setStrokeWidth(2);
|
||||
background.setCache(true);
|
||||
background.setCacheHint(CacheHint.SPEED);
|
||||
|
||||
teamNameObject = getTextObject(boat.getShortName(), theme);
|
||||
teamNameObject.relocate(X_OFFSET_TEXT, Y_OFFSET_TEXT_INIT + Y_OFFSET_PER_TEXT);
|
||||
|
||||
velocityObject = getTextObject("0 m/s", theme);
|
||||
velocityObject.relocate(X_OFFSET_TEXT, Y_OFFSET_TEXT_INIT + Y_OFFSET_PER_TEXT * 2);
|
||||
velocityObject.setVisible(false);
|
||||
|
||||
estTimeToNextMarkObject = getTextObject("Next mark: ", theme);
|
||||
estTimeToNextMarkObject.relocate(X_OFFSET_TEXT, Y_OFFSET_TEXT_INIT + Y_OFFSET_PER_TEXT * 3);
|
||||
estTimeToNextMarkObject.setVisible(false);
|
||||
|
||||
legTimeObject = getTextObject("Last mark: -", theme);
|
||||
legTimeObject.relocate(X_OFFSET_TEXT, Y_OFFSET_TEXT_INIT + Y_OFFSET_PER_TEXT * 4);
|
||||
legTimeObject.setVisible(false);
|
||||
|
||||
this.setVisible(true, false, false, false);
|
||||
|
||||
super.getChildren().addAll(background, teamNameObject, velocityObject, estTimeToNextMarkObject, legTimeObject);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a text object with caching and a color applied
|
||||
*
|
||||
* @param defaultText The default text to display
|
||||
* @param fill The text fill color
|
||||
* @return The text object
|
||||
*/
|
||||
private Text getTextObject(String defaultText, Color fill) {
|
||||
Text text = new Text(defaultText);
|
||||
text.setFill(fill);
|
||||
text.setStrokeWidth(2);
|
||||
text.setCacheHint(CacheHint.SPEED);
|
||||
text.setCache(true);
|
||||
return text;
|
||||
}
|
||||
|
||||
void update () {
|
||||
teamNameObject.setText("Player: " + boat.getShortName());
|
||||
|
||||
velocityObject.setText(String.format(String.format("%.2f m/s", boat.getVelocityMMS())));
|
||||
|
||||
if (boat.getTimeTillNext() != null) {
|
||||
DateFormat format = new SimpleDateFormat("mm:ss");
|
||||
String timeToNextMark = format
|
||||
.format(boat.getTimeTillNext() - ClientPacketParser.getCurrentTimeLong());
|
||||
estTimeToNextMarkObject.setText("Next mark: " + timeToNextMark);
|
||||
} else {
|
||||
estTimeToNextMarkObject.setText("Next mark: -");
|
||||
}
|
||||
|
||||
if (boat.getMarkRoundTime() != null) {
|
||||
DateFormat format = new SimpleDateFormat("mm:ss");
|
||||
String elapsedTime = format
|
||||
.format(ClientPacketParser.getCurrentTimeLong() - boat.getMarkRoundTime());
|
||||
legTimeObject.setText("Last mark: " + elapsedTime);
|
||||
}else {
|
||||
legTimeObject.setText("Last mark: - ");
|
||||
}
|
||||
}
|
||||
|
||||
void setVisible(boolean nameVisibility, boolean speedVisibility,
|
||||
boolean estTimeVisibility, boolean lastMarkVisibility) {
|
||||
int totalVisible = 0;
|
||||
|
||||
/*
|
||||
This is a temporary fix until the new annotation group is added along with the visualiser
|
||||
overhaul.
|
||||
*/
|
||||
totalVisible = updateVisibility(nameVisibility, teamNameObject, totalVisible);
|
||||
if (isPlayer)
|
||||
totalVisible = updateVisibility(speedVisibility, velocityObject, totalVisible);
|
||||
// totalVisible = updateVisibility(estTimeVisibility, estTimeToNextMarkObject, totalVisible);
|
||||
// totalVisible = updateVisibility(lastMarkVisibility, legTimeObject, totalVisible);
|
||||
if (totalVisible != 0) {
|
||||
background.setVisible(true);
|
||||
background.setHeight(Math.abs(BACKGROUND_X) + TEXT_BUFFER + BACKGROUND_H_PER_TEXT * totalVisible);
|
||||
} else {
|
||||
background.setVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
private int updateVisibility (boolean visibility, Text text, int totalVisible) {
|
||||
if (visibility){
|
||||
totalVisible ++;
|
||||
text.setVisible(true);
|
||||
text.setLayoutX(X_OFFSET_TEXT);
|
||||
text.setLayoutY(Y_OFFSET_TEXT_INIT + Y_OFFSET_PER_TEXT * totalVisible);
|
||||
} else {
|
||||
text.setVisible(false);
|
||||
}
|
||||
return totalVisible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets these annotations to show more detailed info.
|
||||
*/
|
||||
public void setAsPlayer () {
|
||||
isPlayer = true;
|
||||
velocityObject.setVisible(true);
|
||||
}
|
||||
}
|
||||
@@ -1,349 +0,0 @@
|
||||
package seng302.fxObjects;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import javafx.geometry.Point2D;
|
||||
import javafx.scene.CacheHint;
|
||||
import javafx.scene.Group;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.shape.Circle;
|
||||
import javafx.scene.shape.Line;
|
||||
import javafx.scene.shape.Polygon;
|
||||
import javafx.scene.transform.Rotate;
|
||||
import seng302.client.ClientPacketParser;
|
||||
import seng302.models.Yacht;
|
||||
import seng302.utilities.GeoUtility;
|
||||
import seng302.controllers.CanvasController;
|
||||
import seng302.models.mark.GateMark;
|
||||
import seng302.models.mark.Mark;
|
||||
import seng302.models.mark.SingleMark;
|
||||
|
||||
/**
|
||||
* 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 BoatGroup extends Group {
|
||||
|
||||
//Constants for drawing
|
||||
private static final double BOAT_HEIGHT = 15d;
|
||||
private static final double BOAT_WIDTH = 10d;
|
||||
//Variables for boat logic.
|
||||
private boolean isStopped = true;
|
||||
private double xIncrement;
|
||||
private double yIncrement;
|
||||
private long lastTimeValid = 0;
|
||||
private Double lastRotation = 0.0;
|
||||
private long framesToMove;
|
||||
//Graphical objects
|
||||
private Yacht boat;
|
||||
private Group lineGroup = new Group();
|
||||
private Polygon boatPoly;
|
||||
private Wake wake;
|
||||
private Line leftLayLine;
|
||||
private Line rightLayline;
|
||||
private Double distanceTravelled = 0.0;
|
||||
private Point2D lastPoint;
|
||||
private boolean destinationSet;
|
||||
private BoatAnnotations boatAnnotations;
|
||||
private Color color;
|
||||
private Boolean isSelected = true; //All boats are initialised as selected\
|
||||
private boolean isPlayer = false;
|
||||
|
||||
/**
|
||||
* Creates a BoatGroup with the default triangular boat polygon.
|
||||
*
|
||||
* @param boat The boat that the BoatGroup will represent. Must contain an ID which will be used
|
||||
* to tell which BoatGroup to update.
|
||||
* @param color The colour of the boat polygon and the trailing line.
|
||||
*/
|
||||
public BoatGroup(Yacht boat, Color color) {
|
||||
destinationSet = false;
|
||||
this.boat = boat;
|
||||
initChildren(color);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a BoatGroup with the boat being the default polygon. The head of the boat should be
|
||||
* at point (0,0).
|
||||
*
|
||||
* @param boat The boat that the BoatGroup will represent. Must contain an ID which will be used
|
||||
* to tell which BoatGroup to update.
|
||||
* @param color The colour of the boat polygon and the trailing line.
|
||||
* @param points An array of co-ordinates x1,y1,x2,y2,x3,y3... that will make up the boat
|
||||
* polygon.
|
||||
*/
|
||||
public BoatGroup(Yacht boat, Color color, double... points) {
|
||||
destinationSet = false;
|
||||
this.boat = boat;
|
||||
initChildren(color, points);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the javafx objects that will be the in the group by default.
|
||||
*
|
||||
* @param color The colour of the boat polygon and the trailing line.
|
||||
* @param points An array of co-ordinates x1,y1,x2,y2,x3,y3... that will make up the boat
|
||||
* polygon.
|
||||
*/
|
||||
private void initChildren(Color color, double... points) {
|
||||
this.color = color;
|
||||
boatPoly = new Polygon(points);
|
||||
boatPoly.setFill(this.color);
|
||||
boatPoly.setOnMouseEntered(event -> {
|
||||
boatPoly.setFill(Color.FLORALWHITE);
|
||||
boatPoly.setStroke(Color.RED);
|
||||
});
|
||||
boatPoly.setOnMouseExited(event -> {
|
||||
boatPoly.setFill(this.color);
|
||||
boatPoly.setStroke(Color.BLACK);
|
||||
});
|
||||
boatPoly.setOnMouseClicked(event -> setIsSelected(!isSelected));
|
||||
boatPoly.setCache(true);
|
||||
boatPoly.setCacheHint(CacheHint.SPEED);
|
||||
boatAnnotations = new BoatAnnotations(boat, this.color);
|
||||
|
||||
leftLayLine = new Line();
|
||||
rightLayline = new Line();
|
||||
|
||||
wake = new Wake(0, -BOAT_HEIGHT);
|
||||
super.getChildren().addAll(boatPoly, boatAnnotations);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the javafx objects that will be the in the group by default.
|
||||
*
|
||||
* @param color The colour of the boat polygon and the trailing line.
|
||||
*/
|
||||
private void initChildren(Color color) {
|
||||
initChildren(color,
|
||||
-BOAT_WIDTH / 2, BOAT_HEIGHT / 2,
|
||||
0.0, -BOAT_HEIGHT / 2,
|
||||
BOAT_WIDTH / 2, BOAT_HEIGHT / 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the boat and its children annotations from its current coordinates by specified
|
||||
* amounts.
|
||||
*
|
||||
* @param dx The amount to move the X coordinate by
|
||||
* @param dy The amount to move the Y coordinate by
|
||||
*/
|
||||
private void moveGroupBy(double dx, double dy) {
|
||||
boatPoly.setLayoutX(boatPoly.getLayoutX() + dx);
|
||||
boatPoly.setLayoutY(boatPoly.getLayoutY() + dy);
|
||||
boatAnnotations.setLayoutX(boatAnnotations.getLayoutX() + dx);
|
||||
boatAnnotations.setLayoutY(boatAnnotations.getLayoutY() + dy);
|
||||
wake.setLayoutX(wake.getLayoutX() + dx);
|
||||
wake.setLayoutY(wake.getLayoutY() + dy);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
private void moveTo(double x, double y, double rotation) {
|
||||
rotateTo(rotation);
|
||||
boatPoly.setLayoutX(x);
|
||||
boatPoly.setLayoutY(y);
|
||||
boatAnnotations.setLayoutX(x);
|
||||
boatAnnotations.setLayoutY(y);
|
||||
wake.setLayoutX(x);
|
||||
wake.setLayoutY(y);
|
||||
wake.rotate(rotation);
|
||||
}
|
||||
|
||||
private void rotateTo(double rotation) {
|
||||
boatPoly.getTransforms().setAll(new Rotate(rotation));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the destination of the boat and the headng it should have once it reaches
|
||||
*
|
||||
* @param newXValue The X co-ordinate the boat needs to move to.
|
||||
* @param newYValue The Y co-ordinate the boat needs to move to.
|
||||
* @param rotation Rotation to move graphics to.
|
||||
* @param timeValid the time the position values are valid for
|
||||
*/
|
||||
public void setDestination(double newXValue, double newYValue, double rotation,
|
||||
double groundSpeed, long timeValid, double frameRate) {
|
||||
|
||||
destinationSet = true;
|
||||
Double dx = Math.abs(boatPoly.getLayoutX() - newXValue);
|
||||
Double dy = Math.abs(boatPoly.getLayoutY() - newYValue);
|
||||
moveTo(newXValue, newYValue, rotation);
|
||||
|
||||
|
||||
rotateTo(rotation);
|
||||
wake.setRotation(rotation, groundSpeed);
|
||||
boat.setVelocity(groundSpeed);
|
||||
isStopped = false;
|
||||
lastRotation = rotation;
|
||||
boatAnnotations.update();
|
||||
|
||||
distanceTravelled += Math.sqrt((dx * dx) + (dy * dy));
|
||||
|
||||
if (distanceTravelled > 10 && isPlayer) {
|
||||
distanceTravelled = 0d;
|
||||
|
||||
if (lastPoint != null) {
|
||||
Line l = new Line(
|
||||
lastPoint.getX(),
|
||||
lastPoint.getY(),
|
||||
boatPoly.getLayoutX(),
|
||||
boatPoly.getLayoutY()
|
||||
);
|
||||
l.getStrokeDashArray().setAll(3d, 7d);
|
||||
l.setStroke(boat.getColour());
|
||||
l.setCache(true);
|
||||
l.setCacheHint(CacheHint.SPEED);
|
||||
lineGroup.getChildren().add(l);
|
||||
}
|
||||
if (destinationSet) {
|
||||
lastPoint = new Point2D(boatPoly.getLayoutX(), boatPoly.getLayoutY());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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(CanvasController canvasController, Mark nextMark) {
|
||||
|
||||
Double windAngle = ClientPacketParser.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.
|
||||
*/
|
||||
if (boatLineFuncResult == windLineFuncResult) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
public void setIsSelected(Boolean isSelected) {
|
||||
this.isSelected = isSelected;
|
||||
setLineGroupVisible(isSelected);
|
||||
setWakeVisible(isSelected);
|
||||
boatAnnotations.setVisible(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.lineGroup.setVisible(trail);
|
||||
}
|
||||
|
||||
public void setLineGroupVisible(Boolean visible) {
|
||||
lineGroup.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 Yacht getBoat() {
|
||||
return boat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all raceIds associated with this group. For BoatGroups the ID's are for the boat.
|
||||
*
|
||||
* @return An array containing all ID's associated with this RaceObject.
|
||||
*/
|
||||
public long getRaceId() {
|
||||
return boat.getSourceId();
|
||||
}
|
||||
|
||||
public Group getWake () {
|
||||
return wake;
|
||||
}
|
||||
|
||||
public Group getTrail() {
|
||||
return lineGroup;
|
||||
}
|
||||
|
||||
public Group getAnnotations() {
|
||||
return boatAnnotations;
|
||||
}
|
||||
|
||||
public Double getBoatLayoutX() {
|
||||
return boatPoly.getLayoutX();
|
||||
}
|
||||
|
||||
|
||||
public Double getBoatLayoutY() {
|
||||
return boatPoly.getLayoutY();
|
||||
}
|
||||
|
||||
public boolean isStopped() {
|
||||
return isStopped;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return boat.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(3);
|
||||
boatAnnotations.setAsPlayer();
|
||||
isPlayer = true;
|
||||
}
|
||||
}
|
||||
@@ -1,167 +0,0 @@
|
||||
package seng302.fxObjects;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import javafx.geometry.Point2D;
|
||||
import javafx.scene.Group;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.shape.Circle;
|
||||
import javafx.scene.shape.Line;
|
||||
import seng302.models.mark.GateMark;
|
||||
import seng302.models.mark.Mark;
|
||||
import seng302.models.mark.MarkType;
|
||||
import seng302.models.mark.SingleMark;
|
||||
|
||||
/**
|
||||
* Grouping of javaFX objects needed to represent a Mark on screen.
|
||||
*/
|
||||
public class MarkGroup extends Group {
|
||||
|
||||
private static int MARK_RADIUS = 5;
|
||||
private static int LINE_THICKNESS = 2;
|
||||
private static double DASHED_GAP_LEN = 2d;
|
||||
private static double DASHED_LINE_LEN = 5d;
|
||||
|
||||
private List<Mark> marks = new ArrayList<>();
|
||||
private Mark mainMark;
|
||||
|
||||
/**
|
||||
* Constructor for singleMark groups
|
||||
* @param mark
|
||||
* @param points
|
||||
*/
|
||||
public MarkGroup (SingleMark mark, Point2D points) {
|
||||
marks.add(mark);
|
||||
mainMark = mark;
|
||||
Color color = Color.BLACK;
|
||||
if (mark.getName().equals("Start")){
|
||||
color = Color.GREEN;
|
||||
} else if (mark.getName().equals("Finish")){
|
||||
color = Color.RED;
|
||||
}
|
||||
Circle markCircle;
|
||||
markCircle = new Circle(
|
||||
points.getX(),
|
||||
points.getY(),
|
||||
MARK_RADIUS,
|
||||
color
|
||||
);
|
||||
super.getChildren().add(markCircle);
|
||||
}
|
||||
|
||||
public void addLaylines(Line line1, Line line2) {
|
||||
|
||||
super.getChildren().addAll(line1, line2);
|
||||
}
|
||||
|
||||
|
||||
public void removeLaylines() {
|
||||
ArrayList<Node> toRemove = new ArrayList<>();
|
||||
for(Node node : super.getChildren()) {
|
||||
if (node instanceof Line) {
|
||||
Line layLine = (Line) node;
|
||||
|
||||
/***
|
||||
* OOHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHhhh
|
||||
*/
|
||||
if (layLine.getStrokeWidth() == 0.5){
|
||||
toRemove.add(layLine);
|
||||
}
|
||||
}
|
||||
}
|
||||
super.getChildren().removeAll(toRemove);
|
||||
}
|
||||
|
||||
public MarkGroup(GateMark mark, Point2D points1, Point2D points2) {
|
||||
marks.add(mark.getSingleMark1());
|
||||
marks.add(mark.getSingleMark2());
|
||||
mainMark = mark;
|
||||
Color color = Color.BLACK;
|
||||
if (mark.getName().equals("Start")){
|
||||
color = Color.GREEN;
|
||||
} else if (mark.getName().equals("Finish")){
|
||||
color = Color.RED;
|
||||
}
|
||||
Circle markCircle;
|
||||
markCircle = new Circle(
|
||||
points1.getX(),
|
||||
points1.getY(),
|
||||
MARK_RADIUS,
|
||||
color
|
||||
);
|
||||
super.getChildren().add(markCircle);
|
||||
|
||||
markCircle = new Circle(
|
||||
points2.getX(),
|
||||
points2.getY(),
|
||||
MARK_RADIUS,
|
||||
color
|
||||
);
|
||||
super.getChildren().add(markCircle);
|
||||
Line line = new Line(
|
||||
points1.getX(),
|
||||
points1.getY(),
|
||||
points2.getX(),
|
||||
points2.getY()
|
||||
);
|
||||
line.setStrokeWidth(LINE_THICKNESS);
|
||||
line.setStroke(color);
|
||||
if (mark.getMarkType() == MarkType.OPEN_GATE) {
|
||||
line.getStrokeDashArray().addAll(DASHED_GAP_LEN, DASHED_LINE_LEN);
|
||||
}
|
||||
super.getChildren().add(line);
|
||||
|
||||
}
|
||||
|
||||
public void moveMarkTo (double x, double y, long raceId)
|
||||
{
|
||||
if (mainMark.getMarkType() == MarkType.SINGLE_MARK) {
|
||||
Circle markCircle = (Circle) super.getChildren().get(0);
|
||||
//One of the test streams produced frequent, jittery movements. Added this as a fix.
|
||||
if (Math.abs(markCircle.getCenterX() - x) > 5 || Math.abs(markCircle.getCenterY() - y) > 5) {
|
||||
markCircle.setCenterX(x);
|
||||
markCircle.setCenterY(y);
|
||||
}
|
||||
} else {
|
||||
Circle markCircle1 = (Circle) super.getChildren().get(0);
|
||||
Circle markCircle2 = (Circle) super.getChildren().get(1);
|
||||
Line connectingLine = (Line) super.getChildren().get(2);
|
||||
if (marks.get(0).getId() == raceId) {
|
||||
if (Math.abs(markCircle1.getCenterX() - x) > 5 || Math.abs(markCircle1.getCenterY() - y) > 5) {
|
||||
markCircle1.setCenterX(x);
|
||||
markCircle1.setCenterY(y);
|
||||
connectingLine.setStartX(markCircle1.getCenterX());
|
||||
connectingLine.setStartY(markCircle1.getCenterY());
|
||||
}
|
||||
} else if (marks.get(1).getId() == raceId) {
|
||||
if (Math.abs(markCircle2.getCenterX() - x) > 5 || Math.abs(markCircle2.getCenterY() - y) > 5) {
|
||||
markCircle2.setCenterX(x);
|
||||
markCircle2.setCenterY(y);
|
||||
connectingLine.setEndX(markCircle2.getCenterX());
|
||||
connectingLine.setEndY(markCircle2.getCenterY());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasRaceId (int... raceIds) {
|
||||
for (int id : raceIds)
|
||||
for (Mark mark : marks)
|
||||
if (id == mark.getId())
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public long[] getRaceIds () {
|
||||
long[] idArray = new long[marks.size()];
|
||||
int i = 0;
|
||||
for (Mark mark : marks)
|
||||
idArray[i++] = mark.getId();
|
||||
return idArray;
|
||||
}
|
||||
|
||||
public Mark getMainMark() {
|
||||
return mainMark;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package seng302.gameServer;
|
||||
|
||||
import seng302.models.Player;
|
||||
import seng302.model.Player;
|
||||
|
||||
public interface ClientConnectionDelegate {
|
||||
/**
|
||||
|
||||
@@ -1,68 +1,163 @@
|
||||
package seng302.gameServer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import seng302.client.ClientPacketParser;
|
||||
import seng302.models.Player;
|
||||
import seng302.models.Yacht;
|
||||
import seng302.server.messages.BoatActionType;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
import javafx.scene.paint.Color;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import seng302.gameServer.messages.BoatAction;
|
||||
import seng302.gameServer.messages.BoatStatus;
|
||||
import seng302.gameServer.messages.ChatterMessage;
|
||||
import seng302.gameServer.messages.CustomizeRequestType;
|
||||
import seng302.gameServer.messages.MarkRoundingMessage;
|
||||
import seng302.gameServer.messages.MarkType;
|
||||
import seng302.gameServer.messages.Message;
|
||||
import seng302.gameServer.messages.RoundingBoatStatus;
|
||||
import seng302.gameServer.messages.YachtEventCodeMessage;
|
||||
import seng302.gameServer.messages.YachtEventType;
|
||||
import seng302.model.GeoPoint;
|
||||
import seng302.model.Limit;
|
||||
import seng302.model.Player;
|
||||
import seng302.model.PolarTable;
|
||||
import seng302.model.ServerYacht;
|
||||
import seng302.model.mark.CompoundMark;
|
||||
import seng302.model.mark.Mark;
|
||||
import seng302.model.mark.MarkOrder;
|
||||
import seng302.model.stream.xml.parser.RaceXMLData;
|
||||
import seng302.model.token.Token;
|
||||
import seng302.model.token.TokenType;
|
||||
import seng302.utilities.GeoUtility;
|
||||
import seng302.visualiser.fxObjects.assets_3D.BoatMeshType;
|
||||
|
||||
/**
|
||||
* A Static class to hold information about the current state of the game (model)
|
||||
* Also contains logic for updating itself on regular time intervals on its own thread
|
||||
* Created by wmu16 on 10/07/17.
|
||||
*/
|
||||
public class GameState implements Runnable {
|
||||
|
||||
private static Integer STATE_UPDATES_PER_SECOND = 60;
|
||||
@FunctionalInterface
|
||||
interface NewMessageListener {
|
||||
void notify(Message message);
|
||||
}
|
||||
|
||||
private static Logger logger = LoggerFactory.getLogger(GameState.class);
|
||||
|
||||
|
||||
static final int WARNING_TIME = 10 * -1000;
|
||||
static final int PREPATORY_TIME = 5 * -1000;
|
||||
private static final int TIME_TILL_START = 10 * 1000;
|
||||
|
||||
private static final Long POWERUP_TIMEOUT_MS = 10_000L;
|
||||
|
||||
private static final Integer STATE_UPDATES_PER_SECOND = 60;
|
||||
private static Double ROUNDING_DISTANCE = 50d; // TODO: 14/08/17 wmu16 - Look into this value further
|
||||
private static final Double MARK_COLLISION_DISTANCE = 15d;
|
||||
public static final Double YACHT_COLLISION_DISTANCE = 25.0;
|
||||
private static final Double BOUNCE_DISTANCE_MARK = 20.0;
|
||||
public static final Double BOUNCE_DISTANCE_YACHT = 30.0;
|
||||
private static final Double COLLISION_VELOCITY_PENALTY = 0.3;
|
||||
|
||||
private static Long previousUpdateTime;
|
||||
public static Double windDirection;
|
||||
private static Double windSpeed;
|
||||
private static Double speedMultiplier = 1d;
|
||||
|
||||
private static Boolean customizationFlag; // dirty flag to tell if a player has customized their boat.
|
||||
private static Boolean playerHasLeftFlag;
|
||||
|
||||
private static String hostIpAddress;
|
||||
private static List<Player> players;
|
||||
private static Map<Integer, Yacht> yachts;
|
||||
private static Map<Integer, ServerYacht> yachts;
|
||||
private static Boolean isRaceStarted;
|
||||
private static GameStages currentStage;
|
||||
|
||||
private static long startTime = System.currentTimeMillis();
|
||||
private static MarkOrder markOrder;
|
||||
private static long startTime;
|
||||
private static Set<Mark> marks;
|
||||
private static List<Limit> courseLimit;
|
||||
private static Integer maxPlayers = 8;
|
||||
|
||||
|
||||
public GameState(String hostIpAddress) {
|
||||
private static List<Token> allTokens;
|
||||
private static List<Token> tokensInPlay;
|
||||
|
||||
private static List<NewMessageListener> newMessageListeners;
|
||||
|
||||
private static Map<Player, String> playerStringMap = new HashMap<>();
|
||||
|
||||
public GameState() {
|
||||
windDirection = 180d;
|
||||
windSpeed = 10000d;
|
||||
this.hostIpAddress = hostIpAddress;
|
||||
yachts = new HashMap<>();
|
||||
tokensInPlay = new ArrayList<>();
|
||||
marks = new HashSet<>();
|
||||
players = new ArrayList<>();
|
||||
customizationFlag = false;
|
||||
playerHasLeftFlag = false;
|
||||
speedMultiplier = 1.0;
|
||||
currentStage = GameStages.LOBBYING;
|
||||
isRaceStarted = false;
|
||||
yachts = new HashMap<>();
|
||||
//set this when game stage changes to prerace
|
||||
previousUpdateTime = System.currentTimeMillis();
|
||||
yachts = new HashMap<>();
|
||||
|
||||
new Thread(this).start();
|
||||
|
||||
newMessageListeners = new ArrayList<>();
|
||||
allTokens = makeTokens();
|
||||
resetStartTime();
|
||||
new Thread(this, "GameState").start(); //Run the auto updates on the game state
|
||||
}
|
||||
|
||||
public static String getHostIpAddress() {
|
||||
return hostIpAddress;
|
||||
public static void setRace(RaceXMLData raceXMLData) {
|
||||
markOrder = new MarkOrder(raceXMLData);
|
||||
for (CompoundMark compoundMark : raceXMLData.getCompoundMarks().values()){
|
||||
marks.addAll(compoundMark.getMarks());
|
||||
}
|
||||
courseLimit = raceXMLData.getCourseLimit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a pre defined set of tokensInPlay. //TODO wmu16 - Should read from some file for each
|
||||
* race ideally
|
||||
*
|
||||
* @return A list of possible tokensInPlay for this race
|
||||
*/
|
||||
private ArrayList<Token> makeTokens() {
|
||||
Token token1 = new Token(TokenType.BOOST, 57.66946, 11.83154);
|
||||
Token token2 = new Token(TokenType.BOOST, 57.66877, 11.83382);
|
||||
Token token3 = new Token(TokenType.BOOST, 57.66914, 11.83965);
|
||||
Token token4 = new Token(TokenType.BOOST, 57.66684, 11.83214);
|
||||
return new ArrayList<>(Arrays.asList(token1, token2, token3, token4));
|
||||
}
|
||||
|
||||
public static Set<Mark> getMarks() {
|
||||
return Collections.unmodifiableSet(marks);
|
||||
}
|
||||
|
||||
public static List<Player> getPlayers() {
|
||||
return players;
|
||||
}
|
||||
|
||||
public static List<Token> getTokensInPlay() {
|
||||
return tokensInPlay;
|
||||
}
|
||||
|
||||
public static void addPlayer(Player player) {
|
||||
players.add(player);
|
||||
String playerText = player.getYacht().getSourceId() + " " + player.getYacht().getBoatName()
|
||||
+ " " + player.getYacht().getCountry();
|
||||
playerStringMap.put(player, playerText);
|
||||
}
|
||||
|
||||
public static void removePlayer(Player player) {
|
||||
players.remove(player);
|
||||
playerStringMap.remove(player);
|
||||
}
|
||||
|
||||
public static void addYacht(Integer sourceId, Yacht yacht) {
|
||||
public static void addYacht(Integer sourceId, ServerYacht yacht) {
|
||||
yachts.put(sourceId, yacht);
|
||||
}
|
||||
|
||||
@@ -79,77 +174,49 @@ public class GameState implements Runnable {
|
||||
}
|
||||
|
||||
public static void setCurrentStage(GameStages currentStage) {
|
||||
if (currentStage == GameStages.RACING){
|
||||
startTime = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
GameState.currentStage = currentStage;
|
||||
}
|
||||
|
||||
public static long getStartTime(){
|
||||
public static MarkOrder getMarkOrder() {
|
||||
return markOrder;
|
||||
}
|
||||
|
||||
public static long getStartTime() {
|
||||
return startTime;
|
||||
}
|
||||
|
||||
public static void resetStartTime(){
|
||||
startTime = System.currentTimeMillis() + TIME_TILL_START;
|
||||
}
|
||||
|
||||
public static Double getWindDirection() {
|
||||
return windDirection;
|
||||
}
|
||||
|
||||
public static void setWindDirection(Double newWindDirection) {
|
||||
windDirection = newWindDirection;
|
||||
}
|
||||
|
||||
public static void setWindSpeed(Double newWindSpeed) {
|
||||
windSpeed = newWindSpeed;
|
||||
}
|
||||
|
||||
public static Double getWindSpeedMMS() {
|
||||
return windSpeed;
|
||||
}
|
||||
|
||||
public static Double getWindSpeedKnots() {
|
||||
return windSpeed / 1000 * ClientPacketParser.MS_TO_KNOTS;
|
||||
return GeoUtility.mmsToKnots(windSpeed); // TODO: 26/07/17 cir27 - remove magic numbers
|
||||
}
|
||||
|
||||
public static Map<Integer, Yacht> getYachts() {
|
||||
public static Map<Integer, ServerYacht> getYachts() {
|
||||
return yachts;
|
||||
}
|
||||
|
||||
public static void updateBoat(Integer sourceId, BoatActionType actionType) {
|
||||
Yacht playerYacht = yachts.get(sourceId);
|
||||
// System.out.println("-----------------------");
|
||||
switch (actionType) {
|
||||
case VMG:
|
||||
playerYacht.turnToVMG();
|
||||
// System.out.println("Snapping to VMG");
|
||||
break;
|
||||
case SAILS_IN:
|
||||
playerYacht.toggleSailIn();
|
||||
// System.out.println("Toggling Sails");
|
||||
break;
|
||||
case SAILS_OUT:
|
||||
playerYacht.toggleSailIn();
|
||||
// System.out.println("Toggling Sails");
|
||||
break;
|
||||
case TACK_GYBE:
|
||||
playerYacht.tackGybe(windDirection);
|
||||
// System.out.println("Tack/Gybe");
|
||||
break;
|
||||
case UPWIND:
|
||||
playerYacht.turnUpwind();
|
||||
// System.out.println("Moving upwind");
|
||||
break;
|
||||
case DOWNWIND:
|
||||
playerYacht.turnDownwind();
|
||||
// System.out.println("Moving downwind");
|
||||
break;
|
||||
}
|
||||
|
||||
// printBoatStatus(playerYacht);
|
||||
}
|
||||
|
||||
public static void update() {
|
||||
Long timeInterval = System.currentTimeMillis() - previousUpdateTime;
|
||||
previousUpdateTime = System.currentTimeMillis();
|
||||
for (Yacht yacht : yachts.values()) {
|
||||
yacht.update(timeInterval);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generates a new ID based off the size of current players + 1
|
||||
*
|
||||
* @return a playerID to be allocated to a new connetion
|
||||
*/
|
||||
public static Integer getUniquePlayerID() {
|
||||
@@ -164,30 +231,643 @@ public class GameState implements Runnable {
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
while(true) {
|
||||
while (currentStage != GameStages.FINISHED) {
|
||||
try {
|
||||
Thread.sleep(1000 / STATE_UPDATES_PER_SECOND);
|
||||
} catch (InterruptedException e) {
|
||||
System.out.println("[GameState] interrupted exception");
|
||||
}
|
||||
if (currentStage == GameStages.PRE_RACE) {
|
||||
update();
|
||||
}
|
||||
|
||||
//RACING
|
||||
if (currentStage == GameStages.RACING) {
|
||||
if (currentStage == GameStages.PRE_RACE || currentStage == GameStages.RACING) {
|
||||
update();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void printBoatStatus(Yacht playerYacht) {
|
||||
System.out.println("-----------------------");
|
||||
System.out.println("Sails are in: " + playerYacht.getSailIn());
|
||||
System.out.println("Heading: " + playerYacht.getHeading());
|
||||
System.out.println("Velocity: " + playerYacht.getVelocityMMS() / 1000);
|
||||
System.out.println("Lat: " + playerYacht.getLocation().getLat());
|
||||
System.out.println("Lng: " + playerYacht.getLocation().getLng());
|
||||
System.out.println("-----------------------\n");
|
||||
public static void updateBoat(Integer sourceId, BoatAction actionType) {
|
||||
ServerYacht playerYacht = yachts.get(sourceId);
|
||||
switch (actionType) {
|
||||
case VMG:
|
||||
playerYacht.turnToVMG();
|
||||
break;
|
||||
case SAILS_IN:
|
||||
playerYacht.toggleSailIn();
|
||||
break;
|
||||
case SAILS_OUT:
|
||||
playerYacht.toggleSailIn();
|
||||
break;
|
||||
case TACK_GYBE:
|
||||
playerYacht.tackGybe(windDirection);
|
||||
break;
|
||||
case UPWIND:
|
||||
playerYacht.turnUpwind();
|
||||
break;
|
||||
case DOWNWIND:
|
||||
playerYacht.turnDownwind();
|
||||
break;
|
||||
case CONTINUOUSLY_TURNING:
|
||||
playerYacht.setContinuouslyTurning(true);
|
||||
break;
|
||||
case DEFAULT_TURNING:
|
||||
playerYacht.setContinuouslyTurning(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Randomly select a subset of tokensInPlay from a pre defined superset
|
||||
* Broadasts a new race status message to show this update
|
||||
*/
|
||||
public static void spawnNewToken() {
|
||||
Random random = new Random();
|
||||
tokensInPlay.clear();
|
||||
tokensInPlay.add(allTokens.get(random.nextInt(allTokens.size())));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
Boolean raceFinished = true;
|
||||
|
||||
Double timeInterval = (System.currentTimeMillis() - previousUpdateTime) / 1000000.0;
|
||||
previousUpdateTime = System.currentTimeMillis();
|
||||
if (System.currentTimeMillis() > startTime) {
|
||||
GameState.setCurrentStage(GameStages.RACING);
|
||||
}
|
||||
for (ServerYacht yacht : yachts.values()) {
|
||||
updateVelocity(yacht);
|
||||
checkPowerUpTimeout(yacht);
|
||||
yacht.runAutoPilot();
|
||||
yacht.updateLocation(timeInterval);
|
||||
checkCollision(yacht);
|
||||
if (yacht.getBoatStatus() != BoatStatus.FINISHED) {
|
||||
checkForLegProgression(yacht);
|
||||
raceFinished = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (raceFinished) {
|
||||
currentStage = GameStages.FINISHED;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void checkPowerUpTimeout(ServerYacht yacht) {
|
||||
if (yacht.getPowerUp() != null) {
|
||||
if (System.currentTimeMillis() - yacht.getPowerUpStartTime() > POWERUP_TIMEOUT_MS) {
|
||||
yacht.powerDown();
|
||||
sendServerMessage(yacht.getSourceId(), yacht.getBoatName() + "'s power-up token expired");
|
||||
logger.debug("Yacht: " + yacht.getShortName() + " powered down!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if the yacht has crossed the course limit
|
||||
*
|
||||
* @param yacht the yacht to be tested
|
||||
* @return a boolean value of if there is a boundary collision
|
||||
*/
|
||||
private static Boolean checkBoundaryCollision(ServerYacht yacht) {
|
||||
for (int i = 0; i < courseLimit.size() - 1; i++) {
|
||||
if (GeoUtility.checkCrossedLine(courseLimit.get(i), courseLimit.get(i + 1),
|
||||
yacht.getLastLocation(), yacht.getLocation()) != 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (GeoUtility.checkCrossedLine(courseLimit.get(courseLimit.size() - 1), courseLimit.get(0),
|
||||
yacht.getLastLocation(), yacht.getLocation()) != 0) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks all tokensInPlay to see if a yacht has picked one up
|
||||
* @return Token which was collided with
|
||||
* @param serverYacht The yacht to check for collision with a token
|
||||
*/
|
||||
private static Token checkTokenPickUp(ServerYacht serverYacht) {
|
||||
for (Token token : tokensInPlay) {
|
||||
Double distance = GeoUtility.getDistance(token, serverYacht.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) {
|
||||
//Yacht Collision
|
||||
ServerYacht collidedYacht = checkYachtCollision(serverYacht);
|
||||
Mark collidedMark = checkMarkCollision(serverYacht);
|
||||
|
||||
if (collidedYacht != null) {
|
||||
GeoPoint originalLocation = serverYacht.getLocation();
|
||||
serverYacht.setLocation(
|
||||
calculateBounceBack(serverYacht, originalLocation, BOUNCE_DISTANCE_YACHT)
|
||||
);
|
||||
serverYacht.setCurrentVelocity(
|
||||
serverYacht.getCurrentVelocity() * COLLISION_VELOCITY_PENALTY
|
||||
);
|
||||
collidedYacht.setLocation(
|
||||
calculateBounceBack(collidedYacht, originalLocation, BOUNCE_DISTANCE_YACHT)
|
||||
);
|
||||
collidedYacht.setCurrentVelocity(
|
||||
collidedYacht.getCurrentVelocity() * COLLISION_VELOCITY_PENALTY
|
||||
);
|
||||
notifyMessageListeners(
|
||||
new YachtEventCodeMessage(serverYacht.getSourceId(), YachtEventType.COLLISION)
|
||||
);
|
||||
}
|
||||
|
||||
//Mark Collision
|
||||
else if (collidedMark != null) {
|
||||
serverYacht.setLocation(
|
||||
calculateBounceBack(serverYacht, collidedMark, BOUNCE_DISTANCE_MARK)
|
||||
);
|
||||
|
||||
serverYacht.setCurrentVelocity(
|
||||
serverYacht.getCurrentVelocity() * COLLISION_VELOCITY_PENALTY
|
||||
);
|
||||
notifyMessageListeners(
|
||||
new YachtEventCodeMessage(serverYacht.getSourceId(), YachtEventType.COLLISION)
|
||||
);
|
||||
}
|
||||
|
||||
//Boundary Collision
|
||||
else if (checkBoundaryCollision(serverYacht)) {
|
||||
serverYacht.setLocation(
|
||||
calculateBounceBack(serverYacht, serverYacht.getLocation(),
|
||||
BOUNCE_DISTANCE_YACHT)
|
||||
);
|
||||
|
||||
serverYacht.setCurrentVelocity(
|
||||
serverYacht.getCurrentVelocity() * COLLISION_VELOCITY_PENALTY
|
||||
);
|
||||
notifyMessageListeners(
|
||||
new YachtEventCodeMessage(serverYacht.getSourceId(), YachtEventType.COLLISION)
|
||||
);
|
||||
}
|
||||
|
||||
//Token Collision
|
||||
Token collidedToken = checkTokenPickUp(serverYacht);
|
||||
if (collidedToken != null) {
|
||||
sendServerMessage(serverYacht.getSourceId(), serverYacht.getBoatName() + " has picked speed-up token");
|
||||
tokensInPlay.remove(collidedToken);
|
||||
serverYacht.powerUp(collidedToken.getTokenType());
|
||||
logger.debug("Yacht: " + serverYacht.getShortName() + " got powerup " + collidedToken
|
||||
.getTokenType());
|
||||
System.out.println("AGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG");
|
||||
notifyMessageListeners(MessageFactory.getRaceXML());
|
||||
notifyMessageListeners(
|
||||
new YachtEventCodeMessage(serverYacht.getSourceId(), YachtEventType.TOKEN));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void updateVelocity(ServerYacht yacht) {
|
||||
Double trueWindAngle = Math.abs(windDirection - yacht.getHeading());
|
||||
Double boatSpeedInKnots = PolarTable.getBoatSpeed(getWindSpeedKnots(), trueWindAngle);
|
||||
Double maxBoatSpeed = GeoUtility.knotsToMMS(boatSpeedInKnots) * speedMultiplier * yacht.getMaxSpeedMultiplier();
|
||||
if (yacht.getPowerUp() != null) {
|
||||
if (yacht.getPowerUp().equals(TokenType.BOOST)) {
|
||||
// TODO: 11/09/17 wmu16 CHANGE THIS TO MAGIC NUMBER
|
||||
maxBoatSpeed *= 2;
|
||||
}
|
||||
}
|
||||
|
||||
Double currentVelocity = yacht.getCurrentVelocity();
|
||||
// TODO: 15/08/17 remove magic numbers from these equations.
|
||||
if (yacht.getSailIn()) {
|
||||
if (currentVelocity < maxBoatSpeed - 500) {
|
||||
yacht.changeVelocity((maxBoatSpeed / 100) * yacht.getAccelerationMultiplier());
|
||||
} else if (currentVelocity > maxBoatSpeed + 500) {
|
||||
yacht.changeVelocity((-currentVelocity / 200) * yacht.getAccelerationMultiplier());
|
||||
} else {
|
||||
yacht.setCurrentVelocity((maxBoatSpeed) * yacht.getAccelerationMultiplier());
|
||||
}
|
||||
} else {
|
||||
if (currentVelocity > 3000) {
|
||||
yacht.changeVelocity((-currentVelocity / 200) * yacht.getAccelerationMultiplier());
|
||||
} else if (currentVelocity > 100) {
|
||||
yacht.changeVelocity((-currentVelocity / 50) * yacht.getAccelerationMultiplier());
|
||||
} else if (currentVelocity <= 100) {
|
||||
yacht.setCurrentVelocity(0d);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Calculates the distance to the next mark (closest of the two if a gate mark). For purposes of
|
||||
* mark rounding
|
||||
*
|
||||
* @return A distance in metres. Returns -1 if there is no next mark
|
||||
* @throws IndexOutOfBoundsException If the next mark is null (ie the last mark in the race)
|
||||
* Check first using {@link seng302.model.mark.MarkOrder#isLastMark(Integer)}
|
||||
*/
|
||||
private Double calcDistanceToCurrentMark(ServerYacht yacht) throws IndexOutOfBoundsException {
|
||||
Integer currentMarkSeqID = yacht.getCurrentMarkSeqID();
|
||||
CompoundMark currentMark = markOrder.getCurrentMark(currentMarkSeqID);
|
||||
GeoPoint location = yacht.getLocation();
|
||||
|
||||
if (currentMark.isGate()) {
|
||||
Mark sub1 = currentMark.getSubMark(1);
|
||||
Mark sub2 = currentMark.getSubMark(2);
|
||||
Double distance1 = GeoUtility.getDistance(location, sub1);
|
||||
Double distance2 = GeoUtility.getDistance(location, sub2);
|
||||
if (distance1 < distance2) {
|
||||
yacht.setClosestCurrentMark(sub1);
|
||||
return distance1;
|
||||
} else {
|
||||
yacht.setClosestCurrentMark(sub2);
|
||||
return distance2;
|
||||
}
|
||||
} else {
|
||||
yacht.setClosestCurrentMark(currentMark.getSubMark(1));
|
||||
return GeoUtility.getDistance(location, currentMark.getSubMark(1));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** lobbyController.setPlayerListSource(clientLobbyList);
|
||||
* 4 Different cases of progression in the race 1 - Passing the start line 2 - Passing any
|
||||
* in-race Gate 3 - Passing any in-race Mark 4 - Passing the finish line
|
||||
*
|
||||
* @param yacht the current yacht to check for progression
|
||||
*/
|
||||
private void checkForLegProgression(ServerYacht yacht) {
|
||||
Integer currentMarkSeqID = yacht.getCurrentMarkSeqID();
|
||||
CompoundMark currentMark = markOrder.getCurrentMark(currentMarkSeqID);
|
||||
|
||||
Boolean hasProgressed;
|
||||
if (currentMarkSeqID == 0) {
|
||||
hasProgressed = checkStartLineCrossing(yacht);
|
||||
} else if (markOrder.isLastMark(currentMarkSeqID)) {
|
||||
hasProgressed = checkFinishLineCrossing(yacht);
|
||||
} else if (currentMark.isGate()) {
|
||||
hasProgressed = checkGateRounding(yacht);
|
||||
} else {
|
||||
hasProgressed = checkMarkRounding(yacht);
|
||||
}
|
||||
|
||||
if (hasProgressed) {
|
||||
if (currentMarkSeqID != 0 && !markOrder.isLastMark(currentMarkSeqID)) {
|
||||
sendServerMessage(yacht.getSourceId(), yacht.getBoatName() + " passed leg " + yacht.getLegNumber());
|
||||
}
|
||||
yacht.incrementLegNumber();
|
||||
sendMarkRoundingMessage(yacht);
|
||||
logMarkRounding(yacht);
|
||||
yacht.setHasPassedLine(false);
|
||||
yacht.setHasEnteredRoundingZone(false);
|
||||
yacht.setHasPassedThroughGate(false);
|
||||
if (!markOrder.isLastMark(currentMarkSeqID)) {
|
||||
yacht.incrementMarkSeqID();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* If we pass the start line gate in the correct direction, progress
|
||||
*
|
||||
* @param yacht The current yacht to check for
|
||||
*/
|
||||
private Boolean checkStartLineCrossing(ServerYacht yacht) {
|
||||
Integer currentMarkSeqID = yacht.getCurrentMarkSeqID();
|
||||
CompoundMark currentMark = markOrder.getCurrentMark(currentMarkSeqID);
|
||||
GeoPoint lastLocation = yacht.getLastLocation();
|
||||
GeoPoint location = yacht.getLocation();
|
||||
|
||||
Mark mark1 = currentMark.getSubMark(1);
|
||||
Mark mark2 = currentMark.getSubMark(2);
|
||||
CompoundMark nextMark = markOrder.getNextMark(currentMarkSeqID);
|
||||
|
||||
Integer crossedLine = GeoUtility.checkCrossedLine(mark1, mark2, lastLocation, location);
|
||||
if (crossedLine > 0) {
|
||||
Boolean isClockwiseCross = GeoUtility.isClockwise(mark1, mark2, nextMark.getMidPoint());
|
||||
if (crossedLine == 2 && isClockwiseCross || crossedLine == 1 && !isClockwiseCross) {
|
||||
yacht.setClosestCurrentMark(mark1);
|
||||
yacht.setBoatStatus(BoatStatus.RACING);
|
||||
sendServerMessage(yacht.getSourceId(), yacht.getBoatName() + " passed start line");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This algorithm checks for mark rounding. And increments the currentMarSeqID number attribute
|
||||
* of the yacht if so. A visual representation of this algorithm can be seen on the Wiki under
|
||||
* 'mark passing algorithm'
|
||||
*
|
||||
* @param yacht The current yacht to check for
|
||||
*/
|
||||
private Boolean checkMarkRounding(ServerYacht yacht) {
|
||||
Integer currentMarkSeqID = yacht.getCurrentMarkSeqID();
|
||||
CompoundMark currentMark = markOrder.getCurrentMark(currentMarkSeqID);
|
||||
GeoPoint lastLocation = yacht.getLastLocation();
|
||||
GeoPoint location = yacht.getLocation();
|
||||
GeoPoint nextPoint = markOrder.getNextMark(currentMarkSeqID).getMidPoint();
|
||||
GeoPoint prevPoint = markOrder.getPreviousMark(currentMarkSeqID).getMidPoint();
|
||||
GeoPoint midPoint = GeoUtility.getDirtyMidPoint(nextPoint, prevPoint);
|
||||
|
||||
if (calcDistanceToCurrentMark(yacht) < ROUNDING_DISTANCE) {
|
||||
yacht.setHasEnteredRoundingZone(true);
|
||||
}
|
||||
|
||||
//In case current mark is a gate, loop through all marks just in case
|
||||
for (Mark thisCurrentMark : currentMark.getMarks()) {
|
||||
if (GeoUtility.isPointInTriangle(lastLocation, location, midPoint, thisCurrentMark)) {
|
||||
yacht.setHasPassedLine(true);
|
||||
}
|
||||
}
|
||||
|
||||
return yacht.hasPassedLine() && yacht.hasEnteredRoundingZone();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks if a gate line has been crossed and in the correct direction
|
||||
*
|
||||
* @param yacht The current yacht to check for
|
||||
*/
|
||||
private Boolean checkGateRounding(ServerYacht yacht) {
|
||||
Integer currentMarkSeqID = yacht.getCurrentMarkSeqID();
|
||||
CompoundMark currentMark = markOrder.getCurrentMark(currentMarkSeqID);
|
||||
GeoPoint lastLocation = yacht.getLastLocation();
|
||||
GeoPoint location = yacht.getLocation();
|
||||
|
||||
Mark mark1 = currentMark.getSubMark(1);
|
||||
Mark mark2 = currentMark.getSubMark(2);
|
||||
CompoundMark prevMark = markOrder.getPreviousMark(currentMarkSeqID);
|
||||
CompoundMark nextMark = markOrder.getNextMark(currentMarkSeqID);
|
||||
|
||||
Integer crossedLine = GeoUtility.checkCrossedLine(mark1, mark2, lastLocation, location);
|
||||
|
||||
//We have crossed the line
|
||||
if (crossedLine > 0) {
|
||||
Boolean isClockwiseCross = GeoUtility.isClockwise(mark1, mark2, prevMark.getMidPoint());
|
||||
|
||||
//Check we cross the line in the correct direction
|
||||
if (crossedLine == 1 && isClockwiseCross || crossedLine == 2 && !isClockwiseCross) {
|
||||
yacht.setHasPassedThroughGate(true);
|
||||
}
|
||||
}
|
||||
|
||||
Boolean prevMarkSide = GeoUtility.isClockwise(mark1, mark2, prevMark.getMidPoint());
|
||||
Boolean nextMarkSide = GeoUtility.isClockwise(mark1, mark2, nextMark.getMidPoint());
|
||||
|
||||
if (yacht.hasPassedThroughGate()) {
|
||||
//Check if we need to round this gate after passing through
|
||||
if (prevMarkSide == nextMarkSide) {
|
||||
return checkMarkRounding(yacht);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* If we pass the finish gate in the correct direction
|
||||
*
|
||||
* @param yacht The current yacht to check for
|
||||
*/
|
||||
private Boolean checkFinishLineCrossing(ServerYacht yacht) {
|
||||
Integer currentMarkSeqID = yacht.getCurrentMarkSeqID();
|
||||
CompoundMark currentMark = markOrder.getCurrentMark(currentMarkSeqID);
|
||||
GeoPoint lastLocation = yacht.getLastLocation();
|
||||
GeoPoint location = yacht.getLocation();
|
||||
|
||||
Mark mark1 = currentMark.getSubMark(1);
|
||||
Mark mark2 = currentMark.getSubMark(2);
|
||||
CompoundMark prevMark = markOrder.getPreviousMark(currentMarkSeqID);
|
||||
|
||||
Integer crossedLine = GeoUtility.checkCrossedLine(mark1, mark2, lastLocation, location);
|
||||
if (crossedLine > 0) {
|
||||
Boolean isClockwiseCross = GeoUtility.isClockwise(mark1, mark2, prevMark.getMidPoint());
|
||||
if (crossedLine == 1 && isClockwiseCross || crossedLine == 2 && !isClockwiseCross) {
|
||||
yacht.setClosestCurrentMark(mark1);
|
||||
yacht.setBoatStatus(BoatStatus.FINISHED);
|
||||
sendServerMessage(yacht.getSourceId(), yacht.getBoatName() + " passed finish line");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles player customization.
|
||||
*
|
||||
* @param playerID The ID of the player being modified.
|
||||
* @param requestType the type of player customization the player wants
|
||||
* @param customizeData the data related to the customization (color, name, shape)
|
||||
*/
|
||||
public static void customizePlayer(long playerID, CustomizeRequestType requestType,
|
||||
byte[] customizeData) {
|
||||
ServerYacht playerYacht = yachts.get((int) playerID);
|
||||
|
||||
if (requestType.equals(CustomizeRequestType.NAME)) {
|
||||
String name = new String(customizeData);
|
||||
playerYacht.setBoatName(name);
|
||||
} else if (requestType.equals(CustomizeRequestType.COLOR)) {
|
||||
int red = customizeData[0] & 0xFF;
|
||||
int green = customizeData[1] & 0xFF;
|
||||
int blue = customizeData[2] & 0xFF;
|
||||
Color yachtColor = Color.rgb(red, green, blue);
|
||||
playerYacht.setBoatColor(yachtColor);
|
||||
} else if (requestType.equals(CustomizeRequestType.SHAPE)) {
|
||||
String type = new String(customizeData);
|
||||
playerYacht.setBoatType(BoatMeshType.valueOf(type));
|
||||
}
|
||||
}
|
||||
|
||||
private static Mark checkMarkCollision(ServerYacht yacht) {
|
||||
Set<Mark> marksInRace = GameState.getMarks();
|
||||
for (Mark mark : marksInRace) {
|
||||
if (GeoUtility.getDistance(yacht.getLocation(), mark)
|
||||
<= MARK_COLLISION_DISTANCE) {
|
||||
return mark;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the new position of the boat after it has had a collision
|
||||
*
|
||||
* @return The boats new position
|
||||
*/
|
||||
private static GeoPoint calculateBounceBack(ServerYacht yacht, GeoPoint collidedWith,
|
||||
Double bounceDistance) {
|
||||
Double heading = GeoUtility.getBearing(yacht.getLastLocation(), collidedWith);
|
||||
// Invert heading
|
||||
heading -= 180;
|
||||
Integer newHeading = Math.floorMod(heading.intValue(), 360);
|
||||
return GeoUtility
|
||||
.getGeoCoordinate(yacht.getLocation(), newHeading.doubleValue(), bounceDistance);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @return yacht to compare to all other yachts.
|
||||
*/
|
||||
private static ServerYacht checkYachtCollision(ServerYacht yacht) {
|
||||
|
||||
for (ServerYacht otherYacht : GameState.getYachts().values()) {
|
||||
if (otherYacht != yacht) {
|
||||
Double distance = GeoUtility
|
||||
.getDistance(otherYacht.getLocation(), yacht.getLocation());
|
||||
if (distance < YACHT_COLLISION_DISTANCE) {
|
||||
return otherYacht;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void sendMarkRoundingMessage(ServerYacht yacht) {
|
||||
Integer sourceID = yacht.getSourceId();
|
||||
Integer currentMarkSeqID = yacht.getCurrentMarkSeqID();
|
||||
CompoundMark currentMark = markOrder.getCurrentMark(currentMarkSeqID);
|
||||
MarkType markType = (currentMark.isGate()) ? MarkType.GATE : MarkType.ROUNDING_MARK;
|
||||
Mark roundingMark = yacht.getClosestCurrentMark();
|
||||
|
||||
// TODO: 13/8/17 figure out the rounding side, rounded mark source ID and boat status.
|
||||
Message markRoundingMessage = new MarkRoundingMessage(0, 0,
|
||||
sourceID, RoundingBoatStatus.RACING, roundingMark.getRoundingSide(), markType,
|
||||
currentMarkSeqID + 1);
|
||||
|
||||
notifyMessageListeners(markRoundingMessage);
|
||||
}
|
||||
|
||||
private static void notifyMessageListeners(Message message) {
|
||||
for (NewMessageListener ml : newMessageListeners) {
|
||||
ml.notify(message);
|
||||
}
|
||||
}
|
||||
|
||||
private void logMarkRounding(ServerYacht yacht) {
|
||||
Mark roundingMark = yacht.getClosestCurrentMark();
|
||||
logger.debug(
|
||||
String.format("Yacht srcID(%d) passed Mark srcID(%d)", yacht.getSourceId(),
|
||||
roundingMark.getSourceID()));
|
||||
}
|
||||
|
||||
|
||||
public static void sendServerMessage(Integer messageType, String message) {
|
||||
notifyMessageListeners(new ChatterMessage(
|
||||
messageType, "SERVER: " + message
|
||||
));
|
||||
}
|
||||
|
||||
public static void processChatter(ChatterMessage chatterMessage, boolean isHost) {
|
||||
String chatterText = chatterMessage.getMessage();
|
||||
String[] words = chatterText.split("\\s+");
|
||||
if (words.length > 2 && isHost) {
|
||||
switch (words[2].trim()) {
|
||||
case "/speed":
|
||||
try {
|
||||
setSpeedMultiplier(Double.valueOf(words[3]));
|
||||
sendServerMessage(chatterMessage.getMessage_type(),
|
||||
"Speed modifier set to x" + words[3]);
|
||||
} catch (Exception e) {
|
||||
Logger logger = LoggerFactory.getLogger(GameState.class);
|
||||
logger.error("cannot parse >speed value");
|
||||
}
|
||||
return;
|
||||
case "/finish":
|
||||
sendServerMessage(chatterMessage.getMessage_type(),
|
||||
"Game will now finish");
|
||||
endRace();
|
||||
return;
|
||||
}
|
||||
}
|
||||
notifyMessageListeners(chatterMessage);
|
||||
}
|
||||
|
||||
public static void addMessageEventListener(NewMessageListener listener) {
|
||||
newMessageListeners.add(listener);
|
||||
}
|
||||
|
||||
public static void setCustomizationFlag() {
|
||||
customizationFlag = true;
|
||||
}
|
||||
|
||||
public static Boolean getCustomizationFlag() {
|
||||
return customizationFlag;
|
||||
}
|
||||
|
||||
public static void resetCustomizationFlag() {
|
||||
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;
|
||||
}
|
||||
|
||||
public static void endRace () {
|
||||
yachts.forEach((id, yacht) -> yacht.setBoatStatus(BoatStatus.FINISHED));
|
||||
currentStage = GameStages.FINISHED;
|
||||
}
|
||||
|
||||
public static void setSpeedMultiplier (double multiplier) {
|
||||
speedMultiplier = multiplier;
|
||||
}
|
||||
|
||||
public static double getSpeedMultiplier () {
|
||||
return speedMultiplier;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,24 @@
|
||||
package seng302.gameServer;
|
||||
|
||||
import seng302.models.Player;
|
||||
import seng302.server.messages.Heartbeat;
|
||||
import seng302.server.messages.Message;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.Stack;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import seng302.model.Player;
|
||||
import seng302.gameServer.messages.Heartbeat;
|
||||
import seng302.gameServer.messages.Message;
|
||||
|
||||
/**
|
||||
* Send Heartbeat messages to connected player at a specified interval
|
||||
* Will call .clientDisconnected on the delegate when a heartbeat message
|
||||
* cannot be sent to a player
|
||||
*/
|
||||
public class HeartbeatThread extends Thread{
|
||||
public class HeartbeatThread implements Runnable {
|
||||
|
||||
private Logger logger = LoggerFactory.getLogger(HeartbeatThread.class);
|
||||
|
||||
private final int HEARTBEAT_PERIOD = 200;
|
||||
private ClientConnectionDelegate delegate;
|
||||
private Integer seqNum;
|
||||
@@ -22,6 +28,9 @@ public class HeartbeatThread extends Thread{
|
||||
this.delegate = delegate;
|
||||
seqNum = 0;
|
||||
disconnectedPlayers = new Stack<>();
|
||||
|
||||
Thread thread = new Thread(this, "HeartBeat");
|
||||
thread.start();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -40,22 +49,23 @@ public class HeartbeatThread extends Thread{
|
||||
* The delegate is notified if a player has disconnected
|
||||
*/
|
||||
private void sendHeartbeatToAllPlayers(){
|
||||
try {
|
||||
Message heartbeat = new Heartbeat(seqNum);
|
||||
|
||||
for (Player player : GameState.getPlayers()){
|
||||
for (Player player : GameState.getPlayers()) {
|
||||
if (!player.getSocket().isConnected()) {
|
||||
playerLostConnection(player);
|
||||
}
|
||||
|
||||
try {
|
||||
player.getSocket().getOutputStream().write(heartbeat.getBuffer());
|
||||
} catch (IOException e) {
|
||||
playerLostConnection(player);
|
||||
}
|
||||
}
|
||||
|
||||
updateDelegate();
|
||||
seqNum++;
|
||||
} catch (NullPointerException ne) {
|
||||
logger.debug("Socket closed between checking for connection and sending heartbeat");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -70,7 +80,6 @@ public class HeartbeatThread extends Thread{
|
||||
|
||||
public void run(){
|
||||
Timer t = new Timer();
|
||||
|
||||
t.schedule(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
@@ -1,157 +1,506 @@
|
||||
package seng302.gameServer;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Observable;
|
||||
import seng302.client.ClientPacketParser;
|
||||
import seng302.models.Player;
|
||||
import seng302.models.stream.PacketBufferDelegate;
|
||||
import seng302.models.stream.packets.StreamPacket;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
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 java.util.concurrent.PriorityBlockingQueue;
|
||||
import java.util.logging.Logger;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import seng302.gameServer.messages.Message;
|
||||
import seng302.model.GeoPoint;
|
||||
import seng302.model.Player;
|
||||
import seng302.model.PolarTable;
|
||||
import seng302.model.ServerYacht;
|
||||
import seng302.model.stream.xml.parser.RaceXMLData;
|
||||
import seng302.model.stream.xml.parser.RegattaXMLData;
|
||||
import seng302.utilities.GeoUtility;
|
||||
|
||||
/**
|
||||
* A class describing the overall server, which creates and collects server threads for each client
|
||||
* Created by wmu16 on 13/07/17.
|
||||
*/
|
||||
public class MainServerThread extends Observable 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 Integer CLIENT_UPDATES_PER_SECOND = 10;
|
||||
private static final int LOG_LEVEL = 1;
|
||||
private static final Integer CLIENT_UPDATES_PER_SECOND = 60;
|
||||
|
||||
private static final int MAX_WIND_SPEED = 12000;
|
||||
private static final int MIN_WIND_SPEED = 8000;
|
||||
|
||||
private boolean terminated;
|
||||
|
||||
private Thread thread;
|
||||
|
||||
private ServerSocket serverSocket = null;
|
||||
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)
|
||||
.registerGame(PORT, regattaXMLData.getRegattaName());
|
||||
} catch (IOException e) {
|
||||
logger.warn("Could not register server");
|
||||
}
|
||||
}
|
||||
|
||||
public MainServerThread() {
|
||||
new GameState();
|
||||
try {
|
||||
serverSocket = new ServerSocket(PORT);
|
||||
} 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);
|
||||
}
|
||||
|
||||
terminated = false;
|
||||
thread = new Thread(this);
|
||||
thread = new Thread(this, "MainServer");
|
||||
thread.start();
|
||||
}
|
||||
|
||||
private void startServer() {
|
||||
MessageFactory.updateXMLGenerator(raceXMLData, regattaXMLData);
|
||||
GameState.setRace(raceXMLData);
|
||||
MessageFactory.updateBoats(new ArrayList<>(GameState.getYachts().values()));
|
||||
startAdvertisingServer();
|
||||
PolarTable
|
||||
.parsePolarFile(getClass().getResourceAsStream("/server_config/acc_polars.csv"));
|
||||
GameState.addMessageEventListener(this::broadcastMessage);
|
||||
startUpdatingWind();
|
||||
startSpawningTokens();
|
||||
System.out.println("SAAAANNNNNNNNNDDDDDDDDDDDDDDDDDDDDDDDDDDdd");
|
||||
sendSetupMessages();
|
||||
}
|
||||
|
||||
public void run() {
|
||||
ServerListenThread serverListenThread;
|
||||
HeartbeatThread heartbeatThread;
|
||||
|
||||
serverListenThread = new ServerListenThread(serverSocket, this);
|
||||
heartbeatThread = new HeartbeatThread(this);
|
||||
|
||||
heartbeatThread.start();
|
||||
serverListenThread.start();
|
||||
new HeartbeatThread(this);
|
||||
new ServerListenThread(serverSocket, this);
|
||||
|
||||
//You should handle interrupts in some way, so that the thread won't keep on forever if you exit the app.
|
||||
while (!terminated) {
|
||||
if (GameState.getPlayerHasLeftFlag()) {
|
||||
for (ServerToClientThread stc : serverToClientThreads) {
|
||||
if (!stc.isSocketOpen()) {
|
||||
GameState.getYachts().remove(stc.getSourceId());
|
||||
System.out.println("AAAAAAAFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF");
|
||||
sendSetupMessages();
|
||||
try {
|
||||
stc.getSocket().close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
GameState.setPlayerHasLeftFlag(false);
|
||||
}
|
||||
try {
|
||||
Thread.sleep(1000 / CLIENT_UPDATES_PER_SECOND);
|
||||
} 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()) {
|
||||
MessageFactory.updateBoats(new ArrayList<>(GameState.getYachts().values()));
|
||||
System.out.println("gfdgfdgfdg");
|
||||
sendSetupMessages();
|
||||
GameState.resetCustomizationFlag();
|
||||
}
|
||||
|
||||
if (GameState.getCurrentStage() == GameStages.PRE_RACE) {
|
||||
updateClients();
|
||||
sendBoatLocations();
|
||||
}
|
||||
|
||||
//RACING
|
||||
if (GameState.getCurrentStage() == GameStages.RACING) {
|
||||
updateClients();
|
||||
sendBoatLocations();
|
||||
}
|
||||
|
||||
//FINISHED
|
||||
else if (GameState.getCurrentStage() == GameStages.FINISHED) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// TODO: 14/07/17 wmu16 - Send out disconnect packet to clients
|
||||
broadcastMessage(MessageFactory.getRaceStatusMessage());
|
||||
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 {
|
||||
for (ServerToClientThread serverToClientThread : serverToClientThreads) {
|
||||
serverToClientThread.terminate();
|
||||
}
|
||||
serverSocket.close();
|
||||
return;
|
||||
} catch (IOException e) {
|
||||
System.out.println("IO error in server thread handler upon closing socket");
|
||||
}
|
||||
}
|
||||
|
||||
public void updateClients() {
|
||||
private void sendBoatLocations() {
|
||||
for (ServerYacht serverYacht : GameState.getYachts().values()) {
|
||||
broadcastMessage(MessageFactory.getBoatLocationMessage(serverYacht));
|
||||
}
|
||||
}
|
||||
|
||||
private void sendSetupMessages() {
|
||||
broadcastMessage(MessageFactory.getRaceXML());
|
||||
broadcastMessage(MessageFactory.getRegattaXML());
|
||||
broadcastMessage(MessageFactory.getBoatXML());
|
||||
}
|
||||
|
||||
private void broadcastMessage(Message message) {
|
||||
for (ServerToClientThread serverToClientThread : serverToClientThreads) {
|
||||
serverToClientThread.updateClient();
|
||||
serverToClientThread.sendMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
private static void updateWind(){
|
||||
Integer direction = GameState.getWindDirection().intValue();
|
||||
Integer windSpeed = GameState.getWindSpeedMMS().intValue();
|
||||
|
||||
static void serverLog(String message, int logLevel){
|
||||
if(logLevel <= LOG_LEVEL){
|
||||
System.out.println("[SERVER " + LocalDateTime.now().toLocalTime().toString() + "] " + message);
|
||||
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());
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// TODO: 29/08/17 wmu16 - This sort of update should be in game state
|
||||
private static void startUpdatingWind(){
|
||||
Timer timer = new Timer();
|
||||
timer.schedule(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
updateWind();
|
||||
}
|
||||
}, 0, 500);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start spawning coins every 60s after the first minute
|
||||
*/
|
||||
private void startSpawningTokens() {
|
||||
Timer timer = new Timer();
|
||||
timer.schedule(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
GameState.spawnNewToken();
|
||||
broadcastMessage(MessageFactory.getRaceXML());
|
||||
}
|
||||
}, 10000, 60000);
|
||||
}
|
||||
|
||||
/**
|
||||
* A client has tried to connect to the server
|
||||
*
|
||||
* @param serverToClientThread The player that connected
|
||||
*/
|
||||
@Override
|
||||
public void clientConnected(ServerToClientThread serverToClientThread) {
|
||||
serverLog("Player Connected From " + serverToClientThread.getThread().getName(), 0);
|
||||
logger.debug("Player Connected From " + serverToClientThread.getThread().getName(), 0);
|
||||
if (serverToClientThreads.size() == 0) { //Sets first client as host.
|
||||
serverToClientThread.setAsHost();
|
||||
serverToClientThread.raceXMLProperty().addListener((obs, oldVal, race) -> {
|
||||
if (race != null) {
|
||||
raceXMLData = race;
|
||||
}
|
||||
if (regattaXMLData != null) {
|
||||
startServer();
|
||||
}
|
||||
});
|
||||
serverToClientThread.regattaXMLProperty().addListener((obs, oldVal, regatta) -> {
|
||||
if (regatta != null) {
|
||||
regattaXMLData = regatta;
|
||||
}
|
||||
if (raceXMLData != null) {
|
||||
startServer();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
MessageFactory.updateBoats(new ArrayList<>(GameState.getYachts().values()));
|
||||
for (ServerYacht serverYacht : GameState.getYachts().values()) {
|
||||
System.out.println("Connecterino" + serverYacht);
|
||||
}
|
||||
serverToClientThread.addConnectionListener(() -> {
|
||||
System.out.println("LUSTENER");
|
||||
sendSetupMessages();
|
||||
});
|
||||
}
|
||||
serverToClientThreads.add(serverToClientThread);
|
||||
this.addObserver(serverToClientThread);
|
||||
setChanged();
|
||||
notifyObservers();
|
||||
serverToClientThread.addDisconnectListener(this::clientDisconnected);
|
||||
try {
|
||||
ServerAdvertiser.getInstance().setNumberOfPlayers(GameState.getNumberOfPlayers());
|
||||
} catch (IOException e) {
|
||||
logger.warn("Couldn't update advertisement");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A player has left the game, remove the player from the GameState
|
||||
*
|
||||
* @param player The player that left
|
||||
*/
|
||||
@Override
|
||||
public void clientDisconnected(Player player) {
|
||||
try {
|
||||
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);
|
||||
logger.debug("Player " + player.getYacht().getSourceId() + "'s socket disconnected", 0);
|
||||
GameState.removeYacht(player.getYacht().getSourceId());
|
||||
GameState.removePlayer(player);
|
||||
ServerToClientThread closedConnection = null;
|
||||
for (ServerToClientThread serverToClientThread : serverToClientThreads) {
|
||||
if (serverToClientThread.getSocket() == player.getSocket()) {
|
||||
this.deleteObserver(serverToClientThread);
|
||||
closedConnection = serverToClientThread;
|
||||
} else if (GameState.getCurrentStage() != GameStages.RACING){
|
||||
serverToClientThread.sendSetupMessages();
|
||||
}
|
||||
}
|
||||
setChanged();
|
||||
notifyObservers();
|
||||
serverToClientThreads.remove(closedConnection);
|
||||
|
||||
try {
|
||||
ServerAdvertiser.getInstance().setNumberOfPlayers(GameState.getNumberOfPlayers());
|
||||
} catch (IOException e) {
|
||||
logger.warn("Couldn't update advertisement");
|
||||
}
|
||||
|
||||
closedConnection.terminate();
|
||||
}
|
||||
|
||||
public void startGame() {
|
||||
try {
|
||||
ServerAdvertiser.getInstance().unregister();
|
||||
} catch (IOException e) {
|
||||
logger.warn("Error unregistering server");
|
||||
}
|
||||
|
||||
initialiseBoatPositions();
|
||||
Timer t = new Timer();
|
||||
|
||||
t.schedule(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
for (ServerToClientThread serverToClientThread : serverToClientThreads) {
|
||||
serverToClientThread.sendRaceStatusMessage();
|
||||
broadcastMessage(MessageFactory.getRaceStatusMessage());
|
||||
if (GameState.getCurrentStage() == GameStages.PRE_RACE
|
||||
|| GameState.getCurrentStage() == GameStages.LOBBYING) {
|
||||
broadcastMessage(MessageFactory.getRaceStartStatusMessage());
|
||||
}
|
||||
}
|
||||
}, 0, 500);
|
||||
|
||||
|
||||
// if (GameState.getCurrentStage() == GameStages.LOBBYING) {
|
||||
// sendSetupMessages();
|
||||
// }
|
||||
}
|
||||
|
||||
public void terminate() {
|
||||
terminated = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialise boats to specific spaced out geopoints behind starting line.
|
||||
*/
|
||||
private void initialiseBoatPositions() {
|
||||
// CompoundMark cm = GameState.getMarkOrder().getMarkOrder().get(0);
|
||||
// GeoPoint startMark1 = cm.getSubMark(1);
|
||||
// GeoPoint startMark2 = cm.getSubMark(2);
|
||||
//
|
||||
// // Calculating midpoint
|
||||
// Double perpendicularAngle = GeoUtility.getBearing(startMark1, startMark2);
|
||||
// Double length = GeoUtility.getDistance(startMark1, startMark2);
|
||||
// GeoPoint midpoint = GeoUtility.getGeoCoordinate(startMark1, perpendicularAngle, length / 2);
|
||||
//
|
||||
// // 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++;
|
||||
// }
|
||||
|
||||
// final double SEPARATION = 50.0; // distance apart in meters
|
||||
//
|
||||
// //Reverse of the angle from start to first mark
|
||||
// double angleToFirstMark = 360 - GeoUtility.getBearing(
|
||||
// GameState.getMarkOrder().getMarkOrder().get(0).getMidPoint(),
|
||||
// GameState.getMarkOrder().getMarkOrder().get(1).getMidPoint()
|
||||
// );
|
||||
//
|
||||
// //Length of start line
|
||||
// double startLineLength = GeoUtility.getDistance(
|
||||
// GameState.getMarkOrder().getMarkOrder().get(0).getSubMark(1),
|
||||
// GameState.getMarkOrder().getMarkOrder().get(0).getSubMark(2)
|
||||
// );
|
||||
//
|
||||
// //Angle of start line
|
||||
// double startMarkToMarkAngle = GeoUtility.getBearing(
|
||||
// GameState.getMarkOrder().getMarkOrder().get(0).getSubMark(1),
|
||||
// GameState.getMarkOrder().getMarkOrder().get(0).getSubMark(2)
|
||||
// );
|
||||
//
|
||||
// //How many yachts can fit along the start line
|
||||
// int spacesAlongLine = (int) Math.round(startLineLength / SEPARATION);
|
||||
// //The free space left by the boats.
|
||||
// double buffer = (startLineLength % SEPARATION) / 2;
|
||||
//
|
||||
// //Randomize starting order.
|
||||
// List<ServerYacht> serverYachtList = new ArrayList<>(GameState.getYachts().values());
|
||||
// Collections.shuffle(serverYachtList);
|
||||
//
|
||||
// //set the starting point away from start line.
|
||||
// GeoPoint startingPoint = GeoUtility.getGeoCoordinate(
|
||||
// GameState.getMarkOrder().getMarkOrder().get(0).getSubMark(1),
|
||||
// angleToFirstMark, SEPARATION
|
||||
// );
|
||||
//
|
||||
// //Move it along the start line
|
||||
// startingPoint = GeoUtility.getGeoCoordinate(
|
||||
// startingPoint, startMarkToMarkAngle, buffer
|
||||
// );
|
||||
//
|
||||
// int yachtCount = 0;
|
||||
// int repeats = 0;
|
||||
//
|
||||
// GeoPoint yachtLocation;
|
||||
//
|
||||
// for (ServerYacht serverYacht : serverYachtList) {
|
||||
//
|
||||
// //Move away from start line
|
||||
// yachtLocation = GeoUtility.getGeoCoordinate(
|
||||
// startingPoint, angleToFirstMark,repeats * SEPARATION
|
||||
// );
|
||||
// //Move along start line
|
||||
// yachtLocation = GeoUtility.getGeoCoordinate(
|
||||
// yachtLocation, startMarkToMarkAngle, yachtCount * SEPARATION
|
||||
// );
|
||||
// serverYacht.setLocation(yachtLocation);
|
||||
// serverYacht.setHeading(GeoUtility.getBearing(
|
||||
// yachtLocation, GameState.getMarkOrder().getMarkOrder().get(1).getMidPoint()
|
||||
// ));
|
||||
// //Set location for next yacht
|
||||
// yachtCount++;
|
||||
// if (yachtCount > spacesAlongLine) {
|
||||
// yachtCount = 0;
|
||||
// repeats++;
|
||||
// }
|
||||
// }
|
||||
|
||||
final double DISTANCE_TO_START = 75d;
|
||||
final double YACHT_SEPARATION = 20d;
|
||||
|
||||
//Length of start line
|
||||
double startLineLength = GeoUtility.getDistance(
|
||||
GameState.getMarkOrder().getMarkOrder().get(0).getSubMark(1),
|
||||
GameState.getMarkOrder().getMarkOrder().get(0).getSubMark(2)
|
||||
);
|
||||
|
||||
//How many yachts can fit along the start line
|
||||
int spacesAlongLine = (int) Math.round(startLineLength / YACHT_SEPARATION);
|
||||
|
||||
//Angle of start line
|
||||
double startMarkToMarkAngle = GeoUtility.getBearing(
|
||||
GameState.getMarkOrder().getMarkOrder().get(0).getSubMark(1),
|
||||
GameState.getMarkOrder().getMarkOrder().get(0).getSubMark(2)
|
||||
);
|
||||
|
||||
//angle from first mark to the start
|
||||
double angleToStart = GeoUtility.getBearing(
|
||||
GameState.getMarkOrder().getMarkOrder().get(1).getMidPoint(),
|
||||
GameState.getMarkOrder().getMarkOrder().get(0).getMidPoint()
|
||||
);
|
||||
|
||||
double angleFromStart = GeoUtility.getBearing(
|
||||
GameState.getMarkOrder().getMarkOrder().get(0).getMidPoint(),
|
||||
GameState.getMarkOrder().getMarkOrder().get(1).getMidPoint()
|
||||
);
|
||||
|
||||
GeoPoint startingPoint = GeoUtility.getGeoCoordinate(
|
||||
GameState.getMarkOrder().getMarkOrder().get(0).getMidPoint(),
|
||||
angleToStart, DISTANCE_TO_START
|
||||
);
|
||||
|
||||
List<ServerYacht> randomisedYachts = new ArrayList<>(GameState.getYachts().values());
|
||||
Collections.shuffle(randomisedYachts);
|
||||
while (randomisedYachts.size() > 0) {
|
||||
|
||||
int numYachtsInLine = spacesAlongLine > randomisedYachts.size() ? randomisedYachts.size() : spacesAlongLine;
|
||||
double yachtSpace = numYachtsInLine * YACHT_SEPARATION / 2;
|
||||
|
||||
GeoPoint firstYachtPoint = GeoUtility.getGeoCoordinate(
|
||||
startingPoint, startMarkToMarkAngle + 180, yachtSpace
|
||||
);
|
||||
|
||||
for (int i=0; i<numYachtsInLine; i++){
|
||||
randomisedYachts.get(0).setHeading(angleFromStart);
|
||||
randomisedYachts.get(0).setLocation(firstYachtPoint);
|
||||
firstYachtPoint = GeoUtility.getGeoCoordinate(
|
||||
firstYachtPoint, startMarkToMarkAngle, yachtSpace
|
||||
);
|
||||
randomisedYachts.remove(0);
|
||||
}
|
||||
|
||||
startingPoint = GeoUtility.getGeoCoordinate(
|
||||
startingPoint, angleToStart, DISTANCE_TO_START
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
package seng302.gameServer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import seng302.gameServer.messages.BoatLocationMessage;
|
||||
import seng302.gameServer.messages.BoatSubMessage;
|
||||
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.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.utilities.XMLGenerator;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
package seng302.gameServer;
|
||||
|
||||
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 Hashtable<String ,String> props;
|
||||
|
||||
private ServerAdvertiser() throws IOException{
|
||||
jmdnsInstance = JmDNS.create(InetAddress.getByName(getLocalHostIp()));
|
||||
|
||||
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) {
|
||||
System.out.println("Failed");
|
||||
}
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,83 @@
|
||||
package seng302.gameServer;
|
||||
|
||||
public class ServerDescription {
|
||||
private Integer capacity;
|
||||
private String address;
|
||||
private Integer portNum;
|
||||
private String serverName;
|
||||
private String mapName;
|
||||
private Integer numPlayers;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -1,24 +1,23 @@
|
||||
package seng302.gameServer;
|
||||
|
||||
import seng302.models.Player;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.nio.channels.ServerSocketChannel;
|
||||
import java.nio.channels.SocketChannel;
|
||||
|
||||
/**
|
||||
* A class for a thread to listen to connections
|
||||
* Created by wmu16 on 11/07/17.
|
||||
*/
|
||||
public class ServerListenThread extends Thread{
|
||||
public class ServerListenThread implements Runnable {
|
||||
private ServerSocket serverSocket;
|
||||
private ClientConnectionDelegate delegate;
|
||||
|
||||
public ServerListenThread(ServerSocket serverSocket, ClientConnectionDelegate delegate){
|
||||
this.serverSocket = serverSocket;
|
||||
this.delegate = delegate;
|
||||
|
||||
Thread thread = new Thread(this, "ServerListen");
|
||||
thread.start();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -39,7 +38,7 @@ public class ServerListenThread extends Thread{
|
||||
}
|
||||
|
||||
public void run(){
|
||||
while (true){
|
||||
while (serverSocket != null && !serverSocket.isClosed()){
|
||||
acceptConnection();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,37 +1,46 @@
|
||||
package seng302.gameServer;
|
||||
|
||||
import java.util.Arrays;
|
||||
import seng302.models.stream.packets.StreamPacket;
|
||||
import seng302.server.messages.BoatActionType;
|
||||
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.model.stream.packets.StreamPacket;
|
||||
|
||||
|
||||
public class ServerPacketParser {
|
||||
|
||||
|
||||
public static BoatActionType extractBoatAction(StreamPacket packet) {
|
||||
public static BoatAction extractBoatAction(StreamPacket packet) {
|
||||
byte[] payload = packet.getPayload();
|
||||
int messageVersionNo = payload[0];
|
||||
long actionTypeValue = bytesToLong(Arrays.copyOfRange(payload, 0, 1));
|
||||
return BoatActionType.getType((int) actionTypeValue);
|
||||
long actionTypeValue = Message.bytesToLong(Arrays.copyOfRange(payload, 0, 1));
|
||||
return BoatAction.getType((int) actionTypeValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* takes an array of up to 7 bytes and returns a positive
|
||||
* long constructed from the input bytes
|
||||
*
|
||||
* @return a positive long if there is less than 7 bytes -1 otherwise
|
||||
*/
|
||||
private static long bytesToLong(byte[] bytes) {
|
||||
long partialLong = 0;
|
||||
int index = 0;
|
||||
for (byte b : bytes) {
|
||||
if (index > 6) {
|
||||
return -1;
|
||||
public static ClientType extractClientType(StreamPacket packet){
|
||||
byte[] payload = packet.getPayload();
|
||||
long value = Message.bytesToLong(Arrays.copyOfRange(payload, 0, 1));
|
||||
return ClientType.getClientType((int) value);
|
||||
}
|
||||
partialLong = partialLong | (b & 0xFFL) << (index * 8);
|
||||
index++;
|
||||
|
||||
public static CustomizeRequestType extractCustomizationType(StreamPacket packet) {
|
||||
byte[] payload = packet.getPayload();
|
||||
long type = Message.bytesToLong(Arrays.copyOfRange(payload, 4, 5));
|
||||
return CustomizeRequestType.getRequestType((int) type);
|
||||
}
|
||||
return partialLong;
|
||||
|
||||
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,54 +1,61 @@
|
||||
package seng302.gameServer;
|
||||
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Observable;
|
||||
import java.util.Observer;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.zip.CRC32;
|
||||
import java.util.zip.Checksum;
|
||||
|
||||
import seng302.models.Player;
|
||||
import seng302.models.Yacht;
|
||||
import seng302.models.stream.packets.PacketType;
|
||||
import seng302.models.stream.packets.StreamPacket;
|
||||
import seng302.models.xml.Race;
|
||||
import seng302.models.xml.Regatta;
|
||||
import seng302.models.xml.XMLGenerator;
|
||||
import seng302.server.messages.BoatActionType;
|
||||
import seng302.server.messages.BoatLocationMessage;
|
||||
import seng302.server.messages.BoatStatus;
|
||||
import seng302.server.messages.BoatSubMessage;
|
||||
import seng302.server.messages.Message;
|
||||
|
||||
import seng302.server.messages.RaceStatus;
|
||||
import seng302.server.messages.RaceStatusMessage;
|
||||
import seng302.server.messages.RaceType;
|
||||
import seng302.server.messages.XMLMessage;
|
||||
import seng302.server.messages.XMLMessageSubType;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
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.visualiser.fxObjects.assets_3D.BoatMeshType;
|
||||
import seng302.utilities.XMLParser;
|
||||
|
||||
/**
|
||||
* 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
|
||||
* trigger client updates on its threads Created by wmu16 on 13/07/17.
|
||||
*/
|
||||
public class ServerToClientThread implements Runnable, Observer {
|
||||
public class ServerToClientThread implements Runnable {
|
||||
|
||||
private static final Integer LOG_LEVEL = 1;
|
||||
private static final Integer MAX_ID_ATTEMPTS = 10;
|
||||
/**
|
||||
* Called to notify listeners when this thread receives a connection correctly.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
interface ConnectionListener {
|
||||
void notifyConnection ();
|
||||
}
|
||||
|
||||
// TODO: 17/08/17 this is only temporary disconnects should be handled consistently
|
||||
@FunctionalInterface
|
||||
interface DisconnectListener {
|
||||
void notifyDisconnect (Player player);
|
||||
}
|
||||
|
||||
private Logger logger = LoggerFactory.getLogger(ServerToClientThread.class);
|
||||
|
||||
private Thread thread;
|
||||
|
||||
@@ -58,77 +65,84 @@ public class ServerToClientThread implements Runnable, Observer {
|
||||
|
||||
private ByteArrayOutputStream crcBuffer;
|
||||
|
||||
private Boolean userIdentified = false;
|
||||
private Boolean connected = true;
|
||||
private Boolean updateClient = true;
|
||||
// private Boolean initialisedRace = true;
|
||||
|
||||
private Integer seqNo;
|
||||
private Integer sourceId;
|
||||
|
||||
private XMLGenerator xml;
|
||||
private ClientType clientType;
|
||||
private Boolean isRegistered = false;
|
||||
private Boolean isHost = false;
|
||||
|
||||
private XMLGenerator xmlGenerator;
|
||||
|
||||
private List<ConnectionListener> connectionListeners = new ArrayList<>();
|
||||
private DisconnectListener disconnectListener;
|
||||
|
||||
private ServerYacht yacht;
|
||||
private Player player;
|
||||
|
||||
private SimpleObjectProperty<RaceXMLData> raceXMLProperty = new SimpleObjectProperty<>();
|
||||
private SimpleObjectProperty<RegattaXMLData> regattaXMLProperty = new SimpleObjectProperty<>();
|
||||
|
||||
public ServerToClientThread(Socket socket) {
|
||||
this.socket = socket;
|
||||
BufferedReader fn;
|
||||
String fName = "";
|
||||
BufferedReader ln;
|
||||
String lName = "";
|
||||
try {
|
||||
seqNo = 0;
|
||||
|
||||
try{
|
||||
is = socket.getInputStream();
|
||||
os = socket.getOutputStream();
|
||||
fn = new BufferedReader(
|
||||
new InputStreamReader(
|
||||
ServerToClientThread.class.getResourceAsStream(
|
||||
"/server_config/CSV_Database_of_First_Names.csv"
|
||||
)
|
||||
)
|
||||
);
|
||||
List<String> all = fn.lines().collect(Collectors.toList());
|
||||
fName = all.get(ThreadLocalRandom.current().nextInt(0, all.size()));
|
||||
ln = new BufferedReader(
|
||||
new InputStreamReader(
|
||||
ServerToClientThread.class.getResourceAsStream(
|
||||
"/server_config/CSV_Database_of_Last_Names.csv"
|
||||
)
|
||||
)
|
||||
);
|
||||
all = ln.lines().collect(Collectors.toList());
|
||||
lName = all.get(ThreadLocalRandom.current().nextInt(0, all.size()));
|
||||
} catch (IOException e) {
|
||||
serverLog("IO error in server thread upon grabbing streams", 1);
|
||||
}
|
||||
//Attempt threeway handshake with connection
|
||||
sourceId = GameState.getUniquePlayerID();
|
||||
if (threeWayHandshake(sourceId)) {
|
||||
serverLog("Successful handshake. Client allocated id: " + sourceId, 0);
|
||||
Yacht yacht = new Yacht(
|
||||
"Yacht", sourceId, sourceId.toString(), fName, fName + " " + lName, "NZ"
|
||||
);
|
||||
// Yacht yacht = new Yacht("Kappa", "Kap", new GeoPoint(57.6708220, 11.8321340), 90.0);
|
||||
GameState.addYacht(sourceId, yacht);
|
||||
GameState.addPlayer(new Player(socket, yacht));
|
||||
} else {
|
||||
serverLog("Unsuccessful handshake. Connection rejected", 1);
|
||||
closeSocket();
|
||||
return;
|
||||
}
|
||||
|
||||
seqNo = 0;
|
||||
thread = new Thread(this);
|
||||
thread = new Thread(this, "ServerToClient");
|
||||
thread.start();
|
||||
}
|
||||
|
||||
static void serverLog(String message, int logLevel) {
|
||||
if (logLevel <= LOG_LEVEL) {
|
||||
System.out.println(
|
||||
"[SERVER " + LocalDateTime.now().toLocalTime().toString() + "] " + message);
|
||||
}
|
||||
public Integer getSourceId() {
|
||||
return sourceId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(Observable o, Object arg) {
|
||||
sendSetupMessages();
|
||||
private void setUpPlayer(){
|
||||
String fName = "Player" + GameState.getNumberOfPlayers().toString();
|
||||
String lName = "";
|
||||
ServerYacht yacht = new ServerYacht(
|
||||
BoatMeshType.DINGHY, sourceId, sourceId.toString(), fName, fName + " " + lName, "NZ"
|
||||
);
|
||||
System.out.println(yacht);
|
||||
player = new Player(socket, yacht);
|
||||
GameState.addYacht(sourceId, yacht);
|
||||
GameState.addPlayer(player);
|
||||
System.out.println(GameState.getYachts().size());
|
||||
}
|
||||
|
||||
private void completeRegistration(ClientType clientType) throws IOException {
|
||||
// Fail if not a player
|
||||
if (!clientType.equals(ClientType.PLAYER)){
|
||||
RegistrationResponseMessage responseMessage = new RegistrationResponseMessage(0, RegistrationResponseStatus.FAILURE_GENERAL);
|
||||
os.write(responseMessage.getBuffer());
|
||||
return;
|
||||
}
|
||||
|
||||
if (GameState.getPlayers().size() >= GameState.getCapacity()){
|
||||
RegistrationResponseMessage responseMessage = new RegistrationResponseMessage(0, RegistrationResponseStatus.FAILURE_FULL);
|
||||
os.write(responseMessage.getBuffer());
|
||||
return;
|
||||
}
|
||||
|
||||
Integer sourceId = GameState.getUniquePlayerID();
|
||||
RegistrationResponseMessage responseMessage = new RegistrationResponseMessage(sourceId, RegistrationResponseStatus.SUCCESS_PLAYING);
|
||||
|
||||
this.clientType = clientType;
|
||||
this.sourceId = sourceId;
|
||||
isRegistered = true;
|
||||
os.write(responseMessage.getBuffer());
|
||||
System.out.println("MAKING A PLAYER");
|
||||
setUpPlayer();
|
||||
System.out.println("DONE MAKING A PLAYER");
|
||||
|
||||
for (ConnectionListener listener : connectionListeners) {
|
||||
listener.notifyConnection();
|
||||
}
|
||||
}
|
||||
|
||||
public void run() {
|
||||
@@ -136,23 +150,8 @@ public class ServerToClientThread implements Runnable, Observer {
|
||||
int sync2;
|
||||
// TODO: 14/07/17 wmu16 - Work out how to fix this while loop
|
||||
|
||||
while (socket.isConnected()) {
|
||||
|
||||
while (socket.isConnected() && !socket.isClosed()) {
|
||||
try {
|
||||
//Perform a write if it is time to as delegated by the MainServerThread
|
||||
if (updateClient) {
|
||||
// TODO: 13/07/17 wmu16 - Write out game state - some function that would write all appropriate messages to this output stream
|
||||
// ChatterMessage chatterMessage = new ChatterMessage(4, 14, "Hello, it's me");
|
||||
// sendMessage(chatterMessage);
|
||||
// try {
|
||||
// GameState.outputState(os);
|
||||
// } catch (IOException e) {
|
||||
// System.out.println("IO error in server thread upon writing to output stream");
|
||||
// }
|
||||
// sendBoatLocationPackets();
|
||||
updateClient = false;
|
||||
}
|
||||
|
||||
crcBuffer = new ByteArrayOutputStream();
|
||||
sync1 = readByte();
|
||||
sync2 = readByte();
|
||||
@@ -169,90 +168,70 @@ public class ServerToClientThread implements Runnable, Observer {
|
||||
long computedCrc = checksum.getValue();
|
||||
long packetCrc = Message.bytesToLong(getBytes(4));
|
||||
if (computedCrc == packetCrc) {
|
||||
//System.out.println("RECEIVED A PACKET");
|
||||
switch (PacketType.assignPacketType(type)) {
|
||||
StreamPacket packet = new StreamPacket(type, payloadLength, timeStamp, payload);
|
||||
switch (PacketType.assignPacketType(type, payload)) {
|
||||
case BOAT_ACTION:
|
||||
BoatActionType actionType = ServerPacketParser
|
||||
.extractBoatAction(
|
||||
new StreamPacket(type, payloadLength, timeStamp, payload));
|
||||
BoatAction actionType = ServerPacketParser.extractBoatAction(packet);
|
||||
GameState.updateBoat(sourceId, actionType);
|
||||
break;
|
||||
|
||||
case RACE_REGISTRATION_REQUEST:
|
||||
ClientType requestedType = ServerPacketParser
|
||||
.extractClientType(packet);
|
||||
completeRegistration(requestedType);
|
||||
break;
|
||||
case CHATTER_TEXT:
|
||||
ChatterMessage chatterMessage = ServerPacketParser
|
||||
.extractChatterText(packet);
|
||||
GameState.processChatter(chatterMessage, isHost);
|
||||
break;
|
||||
case RACE_CUSTOMIZATION_REQUEST:
|
||||
Long sourceID = Message.bytesToLong(
|
||||
Arrays.copyOfRange(payload, 0, 3)
|
||||
);
|
||||
CustomizeRequestType requestType = ServerPacketParser
|
||||
.extractCustomizationType(packet);
|
||||
|
||||
GameState.customizePlayer(sourceID, requestType,
|
||||
Arrays.copyOfRange(payload, 6, payload.length)
|
||||
);
|
||||
GameState.setCustomizationFlag();
|
||||
// TODO: 17/08/2017 ajm412: Send a response packet here, not really necessary until we do shapes.
|
||||
break;
|
||||
case RACE_XML:
|
||||
Document document = StreamParser.extractXmlMessage(packet);
|
||||
raceXMLProperty.set(
|
||||
XMLParser.parseRace(document)
|
||||
);
|
||||
GameState.setMaxPlayers(XMLParser.getMaxPlayers(document));
|
||||
break;
|
||||
case REGATTA_XML:
|
||||
regattaXMLProperty.set(
|
||||
XMLParser.parseRegatta(
|
||||
StreamParser.extractXmlMessage(packet)
|
||||
)
|
||||
);
|
||||
break;
|
||||
|
||||
}
|
||||
} else {
|
||||
serverLog("Packet has been dropped", 1);
|
||||
logger.warn("Packet has been dropped", 1);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// TODO: 24/07/17 zyt10 - fix a logic here when a client disconnected
|
||||
// serverLog("ERROR OCCURRED, CLOSING SERVER CONNECTION: " + socket.getRemoteSocketAddress().toString(), 1);
|
||||
closeSocket();
|
||||
GameState.setPlayerHasLeftFlag(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
GameState.setPlayerHasLeftFlag(true);
|
||||
logger.warn("Closed serverToClientThread" + thread, 1);
|
||||
}
|
||||
|
||||
private void sendSetupMessages() {
|
||||
xml = new XMLGenerator();
|
||||
Race race = new Race();
|
||||
|
||||
for (Yacht yacht : GameState.getYachts().values()) {
|
||||
race.addBoat(yacht);
|
||||
}
|
||||
|
||||
//@TODO calculate lat/lng values
|
||||
xml.setRegatta(new Regatta("RaceVision Test Game", 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);
|
||||
}
|
||||
|
||||
public void updateClient() {
|
||||
sendBoatLocationPackets();
|
||||
updateClient = true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tries to confirm the connection just accepted.
|
||||
* Sends ID, expects that ID echoed for confirmation,
|
||||
* if so, sends a confirmation packet back to that connection
|
||||
* Creates a player instance with that ID and this thread and adds it to the GameState
|
||||
* If not, close the socket and end the threads execution
|
||||
*
|
||||
* @param id the id to try and assign to the connection
|
||||
* @return A boolean indicating if it was a successful handshake
|
||||
*/
|
||||
private Boolean threeWayHandshake(Integer id) {
|
||||
Integer confirmationID = null;
|
||||
Integer identificationAttempt = 0;
|
||||
while (!userIdentified) {
|
||||
try {
|
||||
os.write(id); //Send out new ID looking for echo
|
||||
confirmationID = is.read();
|
||||
} catch (IOException e) {
|
||||
serverLog("Three way handshake failed", 1);
|
||||
}
|
||||
|
||||
if (id.equals(confirmationID)) { //ID is echoed back. Connection is a client
|
||||
return true;
|
||||
} else if (identificationAttempt > MAX_ID_ATTEMPTS) { //No response. not a client. tidy up and go home.
|
||||
return false;
|
||||
}
|
||||
identificationAttempt++;
|
||||
}
|
||||
|
||||
return true;
|
||||
public void sendSetupMessages() {
|
||||
sendMessage(MessageFactory.getRegattaXML());
|
||||
sendMessage(MessageFactory.getBoatXML());
|
||||
sendMessage(MessageFactory.getRaceXML());
|
||||
}
|
||||
|
||||
private void closeSocket() {
|
||||
@@ -263,15 +242,20 @@ public class ServerToClientThread implements Runnable, Observer {
|
||||
}
|
||||
}
|
||||
|
||||
public Boolean isSocketOpen() {
|
||||
return !socket.isClosed();
|
||||
}
|
||||
|
||||
private int readByte() throws Exception {
|
||||
int currentByte = -1;
|
||||
try {
|
||||
// @TODO @FIX ConnectionReset Exception when a client disconnects before it is garbage collected
|
||||
currentByte = is.read();
|
||||
crcBuffer.write(currentByte);
|
||||
} catch (SocketException se) {
|
||||
disconnectListener.notifyDisconnect(this.player);
|
||||
} catch (IOException e) {
|
||||
serverLog("Socket read failed", 1);
|
||||
disconnectListener.notifyDisconnect(this.player);
|
||||
logger.warn("Socket read failed", 1);
|
||||
}
|
||||
if (currentByte == -1) {
|
||||
throw new Exception();
|
||||
@@ -297,10 +281,9 @@ public class ServerToClientThread implements Runnable, Observer {
|
||||
try {
|
||||
os.write(message.getBuffer());
|
||||
} catch (SocketException e) {
|
||||
//serverLog("Player " + sourceId + " side socket disconnected", 1);
|
||||
return;
|
||||
logger.warn("Player " + sourceId + " side socket disconnected", 1);
|
||||
} catch (IOException e) {
|
||||
serverLog("Message send failed", 1);
|
||||
logger.warn("Message send failed", 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -309,64 +292,47 @@ public class ServerToClientThread implements Runnable, Observer {
|
||||
return seqNo;
|
||||
}
|
||||
|
||||
|
||||
private void sendBoatLocationPackets() {
|
||||
ArrayList<Yacht> yachts = new ArrayList<>(GameState.getYachts().values());
|
||||
for (Yacht yacht : yachts) {
|
||||
// System.out.println("[SERVER] Lat: " + yacht.getLocation().getLat() + " Lon: " + yacht.getLocation().getLng());
|
||||
BoatLocationMessage boatLocationMessage =
|
||||
new BoatLocationMessage(
|
||||
yacht.getSourceId(),
|
||||
getSeqNo(),
|
||||
yacht.getLocation().getLat(),
|
||||
yacht.getLocation().getLng(),
|
||||
yacht.getHeading(),
|
||||
(long) yacht.getVelocityMMS());
|
||||
|
||||
sendMessage(boatLocationMessage);
|
||||
}
|
||||
}
|
||||
|
||||
public Thread getThread() {
|
||||
return thread;
|
||||
}
|
||||
|
||||
public void sendRaceStatusMessage() {
|
||||
// variables taken from GameServerThread
|
||||
|
||||
|
||||
List<BoatSubMessage> boatSubMessages = new ArrayList<>();
|
||||
BoatStatus boatStatus;
|
||||
RaceStatus raceStatus;
|
||||
|
||||
for (Player player : GameState.getPlayers()) {
|
||||
Yacht y = player.getYacht();
|
||||
|
||||
if (GameState.getCurrentStage() == GameStages.PRE_RACE) {
|
||||
boatStatus = BoatStatus.PRESTART;
|
||||
} else if (GameState.getCurrentStage() == GameStages.RACING) {
|
||||
boatStatus = BoatStatus.RACING;
|
||||
} else {
|
||||
boatStatus = BoatStatus.UNDEFINED;
|
||||
}
|
||||
|
||||
BoatSubMessage m = new BoatSubMessage(y.getSourceId(), boatStatus, 0, 0, 0, 1234l,
|
||||
1234l);
|
||||
boatSubMessages.add(m);
|
||||
}
|
||||
|
||||
if (GameState.getCurrentStage() == GameStages.RACING) {
|
||||
raceStatus = RaceStatus.STARTED;
|
||||
} else {
|
||||
raceStatus = RaceStatus.WARNING;
|
||||
}
|
||||
|
||||
sendMessage(new RaceStatusMessage(1, raceStatus, GameState.getStartTime(), GameState.getWindDirection(),
|
||||
GameState.getWindSpeedMMS().longValue(), GameState.getPlayers().size(),
|
||||
RaceType.MATCH_RACE, 1, boatSubMessages));
|
||||
}
|
||||
|
||||
public Socket getSocket() {
|
||||
return socket;
|
||||
}
|
||||
|
||||
public ServerYacht getYacht() {
|
||||
return yacht;
|
||||
}
|
||||
|
||||
public void addConnectionListener(ConnectionListener listener) {
|
||||
connectionListeners.add(listener);
|
||||
}
|
||||
|
||||
public void removeConnectionListener(ConnectionListener listener) {
|
||||
connectionListeners.remove(listener);
|
||||
}
|
||||
|
||||
public void terminate () {
|
||||
try {
|
||||
socket.close();
|
||||
} catch (IOException ioe) {
|
||||
logger.warn("IOException attempting to terminate serverToClientThread " + this.thread);
|
||||
}
|
||||
}
|
||||
|
||||
public void addDisconnectListener(DisconnectListener disconnectListener) {
|
||||
this.disconnectListener = disconnectListener;
|
||||
}
|
||||
|
||||
public void setAsHost() {
|
||||
isHost = true;
|
||||
}
|
||||
|
||||
public SimpleObjectProperty<RaceXMLData> raceXMLProperty() {
|
||||
return raceXMLProperty;
|
||||
}
|
||||
|
||||
public SimpleObjectProperty<RegattaXMLData> regattaXMLProperty() {
|
||||
return regattaXMLProperty;
|
||||
}
|
||||
}
|
||||
|
||||
+10
-7
@@ -1,4 +1,4 @@
|
||||
package seng302.server.messages;
|
||||
package seng302.gameServer.messages;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
@@ -6,29 +6,32 @@ import java.util.Map;
|
||||
/**
|
||||
* Created by kre39 on 12/07/17.
|
||||
*/
|
||||
public enum BoatActionType {
|
||||
public enum BoatAction {
|
||||
|
||||
VMG(1),
|
||||
SAILS_IN(2),
|
||||
SAILS_OUT(3),
|
||||
TACK_GYBE(4),
|
||||
UPWIND(5),
|
||||
DOWNWIND(6);
|
||||
DOWNWIND(6),
|
||||
MAINTAIN_HEADING(7),
|
||||
CONTINUOUSLY_TURNING(8),
|
||||
DEFAULT_TURNING(9);
|
||||
|
||||
private final int type;
|
||||
private static final Map<Integer, BoatActionType> intToTypeMap = new HashMap<>();
|
||||
private static final Map<Integer, BoatAction> intToTypeMap = new HashMap<>();
|
||||
|
||||
static {
|
||||
for (BoatActionType type : BoatActionType.values()) {
|
||||
for (BoatAction type : BoatAction.values()) {
|
||||
intToTypeMap.put(type.getValue(), type);
|
||||
}
|
||||
}
|
||||
|
||||
BoatActionType(int type){
|
||||
BoatAction(int type){
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public static BoatActionType getType(int value) {
|
||||
public static BoatAction getType(int value) {
|
||||
return intToTypeMap.get(value);
|
||||
}
|
||||
|
||||
+6
-10
@@ -1,27 +1,23 @@
|
||||
package seng302.server.messages;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
package seng302.gameServer.messages;
|
||||
|
||||
/**
|
||||
* Created by kre39 on 12/07/17.
|
||||
*/
|
||||
public class BoatActionMessage extends Message{
|
||||
private final MessageType MESSAGE_TYPE = MessageType.BOAT_ACTION;
|
||||
private final int MESSAGE_SIZE = 1;
|
||||
private BoatActionType actionType;
|
||||
private final int MESSAGE_SIZE = 5;
|
||||
private BoatAction actionType;
|
||||
|
||||
public BoatActionMessage(BoatActionType actionType) {
|
||||
public BoatActionMessage(BoatAction actionType, int sourceId) {
|
||||
this.actionType = actionType;
|
||||
setHeader(new Header(MessageType.BOAT_ACTION, 0, (short) 1)); // the second variable is the source id
|
||||
setHeader(new Header(MessageType.BOAT_ACTION, sourceId, (short) MESSAGE_SIZE)); // the second variable is the source id
|
||||
allocateBuffer();
|
||||
writeHeaderToBuffer();
|
||||
// Write message fields
|
||||
putInt(actionType.getValue(), 1);
|
||||
putInt(sourceId, 4);
|
||||
writeCRC();
|
||||
rewind();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
+25
-19
@@ -1,9 +1,7 @@
|
||||
package seng302.server.messages;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
package seng302.gameServer.messages;
|
||||
|
||||
public class BoatLocationMessage extends Message {
|
||||
|
||||
private final int MESSAGE_SIZE = 56;
|
||||
|
||||
private long messageVersionNumber;
|
||||
@@ -31,6 +29,7 @@ public class BoatLocationMessage extends Message {
|
||||
|
||||
/**
|
||||
* Describes the location, altitude and sensor data from the boat.
|
||||
*
|
||||
* @param sourceId ID of the boat
|
||||
* @param sequenceNum Sequence number of the message
|
||||
* @param latitude The boats latitude
|
||||
@@ -38,7 +37,8 @@ public class BoatLocationMessage extends Message {
|
||||
* @param heading The boats heading
|
||||
* @param boatSpeed The boats speed
|
||||
*/
|
||||
public BoatLocationMessage(int sourceId, int sequenceNum, double latitude, double longitude, double heading, long boatSpeed){
|
||||
public BoatLocationMessage(int sourceId, int sequenceNum, double latitude, double longitude,
|
||||
double heading, long boatSpeed) {
|
||||
messageVersionNumber = 1;
|
||||
time = System.currentTimeMillis();
|
||||
this.sourceId = sourceId;
|
||||
@@ -52,7 +52,7 @@ public class BoatLocationMessage extends Message {
|
||||
this.roll = 0;
|
||||
this.boatSpeed = boatSpeed;
|
||||
this.COG = 2;
|
||||
this.SOG = boatSpeed ;
|
||||
this.SOG = boatSpeed;
|
||||
this.apparentWindSpeed = 0;
|
||||
this.apparentWindAngle = 0;
|
||||
this.trueWindSpeed = 0;
|
||||
@@ -66,7 +66,7 @@ public class BoatLocationMessage extends Message {
|
||||
allocateBuffer();
|
||||
writeHeaderToBuffer();
|
||||
|
||||
long headingToSend = (long)((heading/360.0) * 65535.0);
|
||||
long headingToSend = (long) ((heading / 360.0) * 65535.0);
|
||||
|
||||
putByte((byte) messageVersionNumber);
|
||||
putInt(time, 6);
|
||||
@@ -97,56 +97,62 @@ public class BoatLocationMessage extends Message {
|
||||
|
||||
/**
|
||||
* Convert binary latitude or longitude to floating point number
|
||||
*
|
||||
* @param binaryPackedLatLon Binary packed lat OR lon
|
||||
* @return Floating point lat/lon
|
||||
*/
|
||||
public static double binaryPackedToLatLon(long binaryPackedLatLon){
|
||||
return (double)binaryPackedLatLon * 180.0 / 2147483648.0;
|
||||
public static double binaryPackedToLatLon(long binaryPackedLatLon) {
|
||||
return (double) binaryPackedLatLon * 180.0 / 2147483648.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert binary packed heading to floating point number
|
||||
*
|
||||
* @param binaryPackedHeading Binary packed heading
|
||||
* @return heading as a decimal
|
||||
*/
|
||||
public static double binaryPackedHeadingToDouble(long binaryPackedHeading){
|
||||
return (double)binaryPackedHeading * 360.0 / 65536.0;
|
||||
public static double binaryPackedHeadingToDouble(long binaryPackedHeading) {
|
||||
return (double) binaryPackedHeading * 360.0 / 65536.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert binary packed wind angle to floating point number
|
||||
*
|
||||
* @param binaryPackedWindAngle Binary packed wind angle
|
||||
* @return wind angle as a decimal
|
||||
*/
|
||||
public static double binaryPackedWindAngleToDouble(long binaryPackedWindAngle){
|
||||
return (double)binaryPackedWindAngle*180.0/32768.0;
|
||||
public static double binaryPackedWindAngleToDouble(long binaryPackedWindAngle) {
|
||||
return (double) binaryPackedWindAngle * 180.0 / 32768.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a latitude or longitude to a binary packed long
|
||||
*
|
||||
* @param latLon A floating point latitude/longitude
|
||||
* @return A binary packed lat/lon
|
||||
*/
|
||||
public static long latLonToBinaryPackedLong(double latLon){
|
||||
return (long)((536870912 * latLon) / 45);
|
||||
public static long latLonToBinaryPackedLong(double latLon) {
|
||||
return (long) ((536870912 * latLon) / 45);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a heading to a binary packed long
|
||||
*
|
||||
* @param heading A floating point heading
|
||||
* @return A binary packed heading
|
||||
*/
|
||||
public static long headingToBinaryPackedLong(double heading){
|
||||
return (long)((8192*heading)/45);
|
||||
public static long headingToBinaryPackedLong(double heading) {
|
||||
return (long) ((8192 * heading) / 45);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a wind angle to a binary packed long
|
||||
*
|
||||
* @param windAngle Floating point wind angle
|
||||
* @return A binary packed wind angle
|
||||
*/
|
||||
public static long windAngleToBinaryPackedLong(double windAngle){
|
||||
return (long)((8192*windAngle)/45);
|
||||
public static long windAngleToBinaryPackedLong(double windAngle) {
|
||||
return (long) ((8192 * windAngle) / 45);
|
||||
}
|
||||
|
||||
@Override
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package seng302.server.messages;
|
||||
package seng302.gameServer.messages;
|
||||
|
||||
/**
|
||||
* The current status of a boat
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package seng302.server.messages;
|
||||
package seng302.gameServer.messages;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
+12
-4
@@ -1,4 +1,4 @@
|
||||
package seng302.server.messages;
|
||||
package seng302.gameServer.messages;
|
||||
|
||||
/**
|
||||
* Created by kre39 on 20/07/17.
|
||||
@@ -11,9 +11,11 @@ public class ChatterMessage extends Message {
|
||||
private int message_size = 21;
|
||||
private String message;
|
||||
|
||||
public ChatterMessage(int message_type, int message_size, String message) {
|
||||
public ChatterMessage(int message_type, String message) {
|
||||
byte[] byteMessage = message.getBytes();
|
||||
|
||||
this.message_type = message_type;
|
||||
this.message_size = message_size;
|
||||
this.message_size = byteMessage.length;
|
||||
this.message = message;
|
||||
|
||||
setHeader(new Header(MessageType.CHATTER_TEXT, 1, (short) getSize()));
|
||||
@@ -23,7 +25,7 @@ public class ChatterMessage extends Message {
|
||||
putByte((byte) MESSAGE_VERSION_NUMBER);
|
||||
putInt(message_type, 1);
|
||||
putInt(message_size, 1);
|
||||
putBytes(message.getBytes());
|
||||
putBytes(byteMessage);
|
||||
|
||||
writeCRC();
|
||||
rewind();
|
||||
@@ -34,5 +36,11 @@ public class ChatterMessage extends Message {
|
||||
return MESSAGE_SIZE + message_size;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public int getMessage_type() {
|
||||
return message_type;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package seng302.gameServer.messages;
|
||||
|
||||
public enum ClientType {
|
||||
SPECTATOR(0x00),
|
||||
PLAYER(0x01),
|
||||
CONTROL_TUTORIAL(0x02),
|
||||
GHOST_MODE(0x03);
|
||||
|
||||
private int type;
|
||||
|
||||
ClientType(int type){
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public int getCode(){
|
||||
return type;
|
||||
}
|
||||
|
||||
public static ClientType getClientType(int typeCode){
|
||||
switch (typeCode){
|
||||
case 0x00:
|
||||
return SPECTATOR;
|
||||
case 0x01:
|
||||
return PLAYER;
|
||||
case 0x02:
|
||||
return CONTROL_TUTORIAL;
|
||||
case 0x03:
|
||||
return GHOST_MODE;
|
||||
default:
|
||||
return PLAYER;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package seng302.gameServer.messages;
|
||||
|
||||
// TODO: 14/08/17 ajm412: this may eventually need adjusting due to conforming to the agreed spec.
|
||||
public class CustomizeRequestMessage extends Message {
|
||||
|
||||
|
||||
private static int MESSAGE_LENGTH = 6;
|
||||
|
||||
//Message fields
|
||||
private CustomizeRequestType customizeType;
|
||||
private Integer payloadLength;
|
||||
|
||||
public CustomizeRequestMessage(CustomizeRequestType customizeType, double sourceID,
|
||||
byte[] payload) {
|
||||
payloadLength = payload.length;
|
||||
setHeader(new Header(MessageType.CUSTOMIZATION_REQUEST, 1, (short) getSize()));
|
||||
allocateBuffer();
|
||||
writeHeaderToBuffer();
|
||||
|
||||
|
||||
putInt((int) sourceID, 4);
|
||||
putInt((int) customizeType.getType(), 2);
|
||||
putBytes(payload);
|
||||
|
||||
writeCRC();
|
||||
rewind();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSize() {
|
||||
return MESSAGE_LENGTH + payloadLength; // placeholder
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package seng302.gameServer.messages;
|
||||
|
||||
// TODO: 14/08/17 ajm412: this may eventually need adjusting due to conforming to the agreed spec.
|
||||
public enum CustomizeRequestType {
|
||||
NAME(0x00),
|
||||
COLOR(0x01),
|
||||
SHAPE(0x02);
|
||||
|
||||
private int type;
|
||||
|
||||
CustomizeRequestType(int type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
int getType() {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
public static CustomizeRequestType getRequestType(int typeCode) {
|
||||
switch (typeCode) {
|
||||
case 0x00:
|
||||
return NAME;
|
||||
case 0x01:
|
||||
return COLOR;
|
||||
case 0x02:
|
||||
return SHAPE;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package seng302.gameServer.messages;
|
||||
|
||||
/**
|
||||
* Created by ajm412 on 14/08/17.
|
||||
*/
|
||||
public class CustomizeResponseMessage extends Message {
|
||||
|
||||
private static int MESSAGE_LENGTH = 2;
|
||||
|
||||
public CustomizeResponseMessage(CustomizeResponseType responseType) {
|
||||
setHeader(new Header(MessageType.CUSTOMIZATION_RESPONSE, 1, (short) getSize()));
|
||||
|
||||
allocateBuffer();
|
||||
writeHeaderToBuffer();
|
||||
|
||||
putInt(responseType.getType(), 2);
|
||||
|
||||
writeCRC();
|
||||
rewind();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSize() {
|
||||
return MESSAGE_LENGTH; // placeholder
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package seng302.gameServer.messages;
|
||||
|
||||
// TODO: 14/08/17 ajm412: this may eventually need adjusting due to conforming to the agreed spec.
|
||||
public enum CustomizeResponseType {
|
||||
SUCCESS(0x00),
|
||||
FAILURE(0x01),
|
||||
FAILURE_MALFORMED_DATA(0x02),
|
||||
FAILURE_INCOMPATIBLE(0x03);
|
||||
|
||||
private int type;
|
||||
|
||||
CustomizeResponseType(int type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
int getType() {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
public static CustomizeResponseType getResponseType(int typeCode) {
|
||||
switch (typeCode) {
|
||||
case 0x00:
|
||||
return SUCCESS;
|
||||
case 0x01:
|
||||
return FAILURE;
|
||||
case 0x02:
|
||||
return FAILURE_MALFORMED_DATA;
|
||||
case 0x03:
|
||||
return FAILURE_INCOMPATIBLE;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package seng302.server.messages;
|
||||
package seng302.gameServer.messages;
|
||||
|
||||
public enum DeviceType {
|
||||
UNKNOWN(0),
|
||||
+1
-4
@@ -1,9 +1,6 @@
|
||||
package seng302.server.messages;
|
||||
package seng302.gameServer.messages;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.Collections;
|
||||
|
||||
public class Header {
|
||||
// From API spec
|
||||
+1
-4
@@ -1,7 +1,4 @@
|
||||
package seng302.server.messages;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
package seng302.gameServer.messages;
|
||||
|
||||
public class Heartbeat extends Message {
|
||||
private final int MESSAGE_SIZE = 4;
|
||||
+12
-6
@@ -1,7 +1,4 @@
|
||||
package seng302.server.messages;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
package seng302.gameServer.messages;
|
||||
|
||||
public class MarkRoundingMessage extends Message{
|
||||
private final long MESSAGE_VERSION_NUMBER = 1;
|
||||
@@ -15,13 +12,21 @@ public class MarkRoundingMessage extends Message{
|
||||
private RoundingSide roundingSide;
|
||||
private long markId;
|
||||
|
||||
|
||||
/**
|
||||
* This message is sent when a boat passes a mark, start line, or finish line
|
||||
* The purpose of this is to record the time when yachts cross marks
|
||||
* @param ackNumber ackNumber
|
||||
* @param raceId raceId
|
||||
* @param sourceId boatSourceId
|
||||
* @param roundingBoatStatus roundingBoatStatus
|
||||
* @param roundingSide roundingSide
|
||||
* @param markId markId
|
||||
* @param markType .
|
||||
*/
|
||||
public MarkRoundingMessage(int ackNumber, int raceId, int sourceId, RoundingBoatStatus roundingBoatStatus,
|
||||
RoundingSide roundingSide, int markId){
|
||||
this.time = System.currentTimeMillis() / 1000L;
|
||||
RoundingSide roundingSide, MarkType markType, int markId) {
|
||||
this.time = System.currentTimeMillis();
|
||||
this.ackNumber = ackNumber;
|
||||
this.raceId = raceId;
|
||||
this.sourceId = sourceId;
|
||||
@@ -40,6 +45,7 @@ public class MarkRoundingMessage extends Message{
|
||||
putInt((int) sourceId, 4);
|
||||
putByte((byte) boatStatus.getCode());
|
||||
putByte((byte) roundingSide.getCode());
|
||||
putByte((byte) markType.getCode());
|
||||
putByte((byte) markId);
|
||||
|
||||
writeCRC();
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package seng302.server.messages;
|
||||
package seng302.gameServer.messages;
|
||||
|
||||
/**
|
||||
* Types of marks boats can round
|
||||
+3
-4
@@ -1,7 +1,5 @@
|
||||
package seng302.server.messages;
|
||||
package seng302.gameServer.messages;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.Arrays;
|
||||
@@ -174,7 +172,7 @@ public abstract class Message {
|
||||
* Convert an integer to an array of bytes
|
||||
* @param val The value to add
|
||||
* @param len The width of the integer in the buffer
|
||||
* @return
|
||||
* @return A byte array to be sent
|
||||
*/
|
||||
public static byte[] intToByteArray(long val, int len){
|
||||
int index = 0;
|
||||
@@ -193,6 +191,7 @@ public abstract class Message {
|
||||
* takes an array of up to 7 bytes in little endian format and
|
||||
* returns a positive long constructed from the input bytes
|
||||
*
|
||||
* @param bytes the bytes to be converted to long
|
||||
* @return a positive long if there is less than 8 bytes -1 otherwise
|
||||
*/
|
||||
public static long bytesToLong(byte[] bytes){
|
||||
+9
-2
@@ -1,4 +1,4 @@
|
||||
package seng302.server.messages;
|
||||
package seng302.gameServer.messages;
|
||||
|
||||
/**
|
||||
* Enum containing the types of messages
|
||||
@@ -17,7 +17,12 @@ public enum MessageType {
|
||||
MARK_ROUNDING(38),
|
||||
COURSE_WIND(44),
|
||||
AVERAGE_WIND(47),
|
||||
BOAT_ACTION(100);
|
||||
BOAT_ACTION(100),
|
||||
REGISTRATION_REQUEST(101),
|
||||
REGISTRATION_RESPONSE(102),
|
||||
CUSTOMIZATION_REQUEST(103),
|
||||
CUSTOMIZATION_RESPONSE(104);
|
||||
|
||||
|
||||
private int code;
|
||||
|
||||
@@ -32,4 +37,6 @@ public enum MessageType {
|
||||
int getCode(){
|
||||
return this.code;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package seng302.server.messages;
|
||||
package seng302.gameServer.messages;
|
||||
|
||||
/**
|
||||
* The types of race start status messages
|
||||
+1
-4
@@ -1,7 +1,4 @@
|
||||
package seng302.server.messages;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
package seng302.gameServer.messages;
|
||||
|
||||
public class RaceStartStatusMessage extends Message {
|
||||
private final int MESSAGE_SIZE = 20;
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package seng302.server.messages;
|
||||
package seng302.gameServer.messages;
|
||||
|
||||
/**
|
||||
* The current status of the race
|
||||
+2
-4
@@ -1,7 +1,5 @@
|
||||
package seng302.server.messages;
|
||||
package seng302.gameServer.messages;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.List;
|
||||
import java.util.zip.CRC32;
|
||||
|
||||
@@ -41,7 +39,7 @@ public class RaceStatusMessage extends Message{
|
||||
this.raceId = raceId;
|
||||
this.raceStatus = raceStatus;
|
||||
this.expectedStartTime = expectedStartTime;
|
||||
this.raceWindDirection = raceWindDirection * windDirFactor;
|
||||
this.raceWindDirection = raceWindDirection * windDirFactor+100.0;
|
||||
this.windSpeed = windSpeed;
|
||||
this.numBoatsInRace = numBoatsInRace;
|
||||
this.raceType = raceType;
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package seng302.server.messages;
|
||||
package seng302.gameServer.messages;
|
||||
|
||||
/**
|
||||
* Enum containing the types of races
|
||||
@@ -0,0 +1,22 @@
|
||||
package seng302.gameServer.messages;
|
||||
|
||||
|
||||
public class RegistrationRequestMessage extends Message {
|
||||
private static int MESSAGE_LENGTH = 2;
|
||||
|
||||
public RegistrationRequestMessage(ClientType type, int clientID){
|
||||
setHeader(new Header(MessageType.REGISTRATION_REQUEST, clientID, (short) getSize()));
|
||||
|
||||
allocateBuffer();
|
||||
writeHeaderToBuffer();
|
||||
|
||||
putInt(type.getCode(), 2);
|
||||
|
||||
writeCRC();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSize() {
|
||||
return MESSAGE_LENGTH;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package seng302.gameServer.messages;
|
||||
|
||||
public class RegistrationResponseMessage extends Message{
|
||||
|
||||
public RegistrationResponseMessage(int clientSourceID, RegistrationResponseStatus status){
|
||||
setHeader(new Header(MessageType.REGISTRATION_RESPONSE, 1, (short) getSize()));
|
||||
allocateBuffer();
|
||||
writeHeaderToBuffer();
|
||||
|
||||
putInt(clientSourceID, 4);
|
||||
putInt(status.getCode(), 1);
|
||||
|
||||
writeCRC();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSize() {
|
||||
return 5;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package seng302.gameServer.messages;
|
||||
|
||||
public enum RegistrationResponseStatus {
|
||||
SUCCESS_SPECTATING(0x00),
|
||||
SUCCESS_PLAYING(0x01),
|
||||
SUCCESS_TUTORIAL(0x02),
|
||||
SUCCESS_GHOSTING(0x03),
|
||||
|
||||
FAILURE_GENERAL(0x10),
|
||||
FAILURE_FULL(0x11);
|
||||
|
||||
private int code;
|
||||
|
||||
RegistrationResponseStatus(int code){
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the message code (From the API Spec)
|
||||
* @return the message code
|
||||
*/
|
||||
int getCode(){
|
||||
return this.code;
|
||||
}
|
||||
|
||||
public static RegistrationResponseStatus getResponseStatus(int typeCode){
|
||||
switch (typeCode){
|
||||
case 0x00:
|
||||
return SUCCESS_SPECTATING;
|
||||
case 0x01:
|
||||
return SUCCESS_PLAYING;
|
||||
case 0x02:
|
||||
return SUCCESS_TUTORIAL;
|
||||
case 0x03:
|
||||
return SUCCESS_GHOSTING;
|
||||
case 0x10:
|
||||
return FAILURE_GENERAL;
|
||||
case 0x11:
|
||||
return FAILURE_FULL;
|
||||
default:
|
||||
return FAILURE_GENERAL;
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package seng302.server.messages;
|
||||
package seng302.gameServer.messages;
|
||||
|
||||
/**
|
||||
* The status of a boat rounding a mark
|
||||
@@ -0,0 +1,52 @@
|
||||
package seng302.gameServer.messages;
|
||||
|
||||
/**
|
||||
* The side the boat rounded the mark
|
||||
*/
|
||||
public enum RoundingSide {
|
||||
UNKNOWN(0, "Unknown"),
|
||||
PORT(1, "Port"),
|
||||
STARBOARD(2, "Stbd"),
|
||||
SP(3, "SP"),
|
||||
PS(4, "PS");
|
||||
|
||||
|
||||
private long code;
|
||||
private String name;
|
||||
|
||||
RoundingSide(long code, String name) {
|
||||
this.code = code;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public long getCode(){
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public static RoundingSide getRoundingSide(String identifier) {
|
||||
RoundingSide roundingSide = UNKNOWN;
|
||||
switch (identifier) {
|
||||
case "Unknown":
|
||||
roundingSide = UNKNOWN;
|
||||
break;
|
||||
case "Port":
|
||||
roundingSide = PORT;
|
||||
break;
|
||||
case "Stbd":
|
||||
roundingSide = STARBOARD;
|
||||
break;
|
||||
case "SP":
|
||||
roundingSide = SP;
|
||||
break;
|
||||
case "PS":
|
||||
roundingSide = PS;
|
||||
break;
|
||||
}
|
||||
|
||||
return roundingSide;
|
||||
}
|
||||
}
|
||||
+2
-4
@@ -1,7 +1,4 @@
|
||||
package seng302.server.messages;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
package seng302.gameServer.messages;
|
||||
|
||||
public class XMLMessage extends Message{
|
||||
private final MessageType MESSAGE_TYPE = MessageType.XML_MESSAGE;
|
||||
@@ -20,6 +17,7 @@ public class XMLMessage extends Message{
|
||||
* XML Message from the AC35 Streaming data spec
|
||||
* @param content The XML content
|
||||
* @param type The XML Message Sub Type
|
||||
* @param sequenceNum sequenceNum
|
||||
*/
|
||||
public XMLMessage(String content, XMLMessageSubType type, long sequenceNum){
|
||||
this.content = content;
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package seng302.server.messages;
|
||||
package seng302.gameServer.messages;
|
||||
|
||||
/**
|
||||
* Enum containing the types of XML messages
|
||||
@@ -0,0 +1,52 @@
|
||||
package seng302.gameServer.messages;
|
||||
|
||||
/**
|
||||
* Created by zyt10 on 10/08/17.
|
||||
*/
|
||||
public class YachtEventCodeMessage extends Message {
|
||||
|
||||
private final MessageType MESSAGE_TYPE = MessageType.YACHT_EVENT_CODE;
|
||||
private final int MESSAGE_VERSION = 1; //Always set to 1
|
||||
private final int MESSAGE_SIZE = 22;
|
||||
|
||||
// Message fields
|
||||
private long timeStamp;
|
||||
private long ack = 0x00; //Unused
|
||||
private int raceId;
|
||||
private int destSourceId;
|
||||
private int incidentId;
|
||||
private int eventId;
|
||||
|
||||
|
||||
public YachtEventCodeMessage(Integer subjectId, YachtEventType yachtEventType) {
|
||||
timeStamp = System.currentTimeMillis() / 1000L;
|
||||
ack = 0;
|
||||
raceId = 1;
|
||||
destSourceId = subjectId; // collision boat source id
|
||||
incidentId = 0;
|
||||
eventId = yachtEventType.getCode();
|
||||
|
||||
setHeader(new Header(MESSAGE_TYPE, 0x01, (short) getSize()));
|
||||
allocateBuffer();
|
||||
writeHeaderToBuffer();
|
||||
|
||||
// Write message fields
|
||||
putUnsignedByte((byte) MESSAGE_VERSION);
|
||||
putInt((int) timeStamp, 6);
|
||||
putInt((int) ack, 2);
|
||||
putInt((int) raceId, 4);
|
||||
putInt((int) destSourceId, 4);
|
||||
putInt((int) incidentId, 4);
|
||||
putInt((int) eventId, 1);
|
||||
|
||||
writeCRC();
|
||||
rewind();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The length of this message
|
||||
*/
|
||||
public int getSize() {
|
||||
return MESSAGE_SIZE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package seng302.gameServer.messages;
|
||||
|
||||
/**
|
||||
* Created by wmu16 on 11/09/17.
|
||||
*/
|
||||
public enum YachtEventType {
|
||||
COLLISION(33),
|
||||
TOKEN(34);
|
||||
|
||||
private int code;
|
||||
|
||||
YachtEventType(int code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public int getCode() {
|
||||
return code;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,318 @@
|
||||
package seng302.model;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Observable;
|
||||
import java.util.Observer;
|
||||
import java.util.Timer;
|
||||
import javafx.beans.property.ReadOnlyDoubleProperty;
|
||||
import javafx.beans.property.ReadOnlyDoubleWrapper;
|
||||
import javafx.beans.property.ReadOnlyIntegerProperty;
|
||||
import javafx.beans.property.ReadOnlyIntegerWrapper;
|
||||
import javafx.beans.property.ReadOnlyLongProperty;
|
||||
import javafx.beans.property.ReadOnlyLongWrapper;
|
||||
import javafx.scene.paint.Color;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import seng302.visualiser.fxObjects.assets_3D.BoatMeshType;
|
||||
import seng302.visualiser.fxObjects.assets_3D.BoatObject;
|
||||
|
||||
/**
|
||||
* Yacht class for the racing boat. <p> Class created to store more variables (eg. boat statuses)
|
||||
* compared to the XMLParser boat class, also done outside Boat class because some old variables are
|
||||
* not used anymore.
|
||||
*/
|
||||
public class ClientYacht extends Observable {
|
||||
|
||||
@FunctionalInterface
|
||||
public interface YachtLocationListener {
|
||||
void notifyLocation(ClientYacht clientYacht, double lat, double lon, double heading,
|
||||
Boolean sailsIn, double velocity);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface MarkRoundingListener {
|
||||
void notifyRounding(ClientYacht yacht, int legNumber);
|
||||
}
|
||||
|
||||
private Logger logger = LoggerFactory.getLogger(ClientYacht.class);
|
||||
|
||||
|
||||
private BoatMeshType boatType;
|
||||
private Integer sourceId;
|
||||
private String hullID; //matches HullNum in the XML spec.
|
||||
private String shortName;
|
||||
private String boatName;
|
||||
private String country;
|
||||
private Integer position;
|
||||
|
||||
private Long estimateTimeAtFinish;
|
||||
private Boolean sailIn = true;
|
||||
private Integer currentMarkSeqID = 0;
|
||||
private Long markRoundTime;
|
||||
private Long timeTillNext;
|
||||
private Double heading;
|
||||
private Integer legNumber = 0;
|
||||
private GeoPoint location;
|
||||
private Integer boatStatus;
|
||||
private Double currentVelocity;
|
||||
|
||||
Timer t;
|
||||
|
||||
private BoatObject boatObject;
|
||||
|
||||
private List<YachtLocationListener> locationListeners = new ArrayList<>();
|
||||
private List<MarkRoundingListener> markRoundingListeners = new ArrayList<>();
|
||||
private ReadOnlyDoubleWrapper velocityProperty = new ReadOnlyDoubleWrapper();
|
||||
private ReadOnlyLongWrapper timeTillNextProperty = new ReadOnlyLongWrapper();
|
||||
private ReadOnlyLongWrapper timeSinceLastMarkProperty = new ReadOnlyLongWrapper();
|
||||
private ReadOnlyIntegerWrapper placingProperty = new ReadOnlyIntegerWrapper();
|
||||
private ReadOnlyDoubleWrapper headingProperty = new ReadOnlyDoubleWrapper();
|
||||
private Color colour;
|
||||
|
||||
public ClientYacht(BoatMeshType boatType, Integer sourceId, String hullID, String shortName,
|
||||
String boatName, String country) {
|
||||
this.boatType = boatType;
|
||||
this.sourceId = sourceId;
|
||||
this.hullID = hullID;
|
||||
this.shortName = shortName;
|
||||
this.boatName = boatName;
|
||||
this.country = country;
|
||||
this.location = new GeoPoint(57.670341, 11.826856);
|
||||
this.heading = 120.0; //In degrees
|
||||
this.headingProperty.set(this.heading);
|
||||
this.currentVelocity = 0d;
|
||||
this.boatStatus = 1;
|
||||
this.colour = Color.rgb(0, 0, 0, 1.0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add ServerToClientThread as the observer, this observer pattern mainly server for the boat
|
||||
* rounding package.
|
||||
*/
|
||||
@Override
|
||||
public void addObserver(Observer o) {
|
||||
super.addObserver(o);
|
||||
}
|
||||
|
||||
public BoatMeshType getBoatType() {
|
||||
return boatType;
|
||||
}
|
||||
|
||||
public Integer getSourceId() {
|
||||
//@TODO Remove and merge with Creating Game Loop
|
||||
if (sourceId == null) {
|
||||
return 0;
|
||||
}
|
||||
return sourceId;
|
||||
}
|
||||
|
||||
public String getHullID() {
|
||||
if (hullID == null) {
|
||||
return "";
|
||||
}
|
||||
return hullID;
|
||||
}
|
||||
|
||||
public String getShortName() {
|
||||
return shortName;
|
||||
}
|
||||
|
||||
public String getBoatName() {
|
||||
return boatName;
|
||||
}
|
||||
|
||||
public String getCountry() {
|
||||
if (country == null) {
|
||||
return "";
|
||||
}
|
||||
return country;
|
||||
}
|
||||
|
||||
public Integer getBoatStatus() {
|
||||
return boatStatus;
|
||||
}
|
||||
|
||||
public void setBoatStatus(Integer boatStatus) {
|
||||
this.boatStatus = boatStatus;
|
||||
}
|
||||
|
||||
public Integer getLegNumber() {
|
||||
return legNumber;
|
||||
}
|
||||
|
||||
public void setLegNumber(Integer legNumber) {
|
||||
this.legNumber = legNumber;
|
||||
}
|
||||
|
||||
public void setEstimateTimeTillNextMark(Long estimateTimeTillNextMark) {
|
||||
timeTillNext = estimateTimeTillNextMark;
|
||||
}
|
||||
|
||||
public String getEstimateTimeAtFinish() {
|
||||
DateFormat format = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
|
||||
return format.format(estimateTimeAtFinish);
|
||||
}
|
||||
|
||||
public void setEstimateTimeAtFinish(Long estimateTimeAtFinish) {
|
||||
this.estimateTimeAtFinish = estimateTimeAtFinish;
|
||||
}
|
||||
|
||||
public Integer getPlacing() {
|
||||
return placingProperty.get();
|
||||
}
|
||||
|
||||
public void setPlacing(Integer position) {
|
||||
placingProperty.set(position);
|
||||
}
|
||||
|
||||
public ReadOnlyIntegerProperty placingProperty() {
|
||||
return placingProperty.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
public void updateVelocityProperty(double velocity) {
|
||||
this.velocityProperty.set(velocity);
|
||||
}
|
||||
|
||||
public void setMarkRoundingTime(Long markRoundingTime) {
|
||||
this.markRoundTime = markRoundingTime;
|
||||
}
|
||||
|
||||
public ReadOnlyDoubleProperty getVelocityProperty() {
|
||||
return velocityProperty.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
public ReadOnlyLongProperty timeTillNextProperty() {
|
||||
return timeTillNextProperty.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
public Long getTimeTillNext() {
|
||||
return timeTillNext;
|
||||
}
|
||||
|
||||
public Long getMarkRoundTime() {
|
||||
return markRoundTime;
|
||||
}
|
||||
|
||||
public GeoPoint getLocation() {
|
||||
return location;
|
||||
}
|
||||
|
||||
public Integer getPosition() {
|
||||
return position;
|
||||
}
|
||||
|
||||
public void setPosition(Integer position) {
|
||||
this.position = position;
|
||||
}
|
||||
|
||||
public void toggleSail() {
|
||||
sailIn = !sailIn;
|
||||
}
|
||||
//// TODO: 15/08/17 asd
|
||||
|
||||
/**
|
||||
* Sets the current location of the boat in lat and long whilst preserving the last location
|
||||
*
|
||||
* @param lat Latitude
|
||||
* @param lng Longitude
|
||||
*/
|
||||
public void setLocation(Double lat, Double lng) {
|
||||
location.setLat(lat);
|
||||
location.setLng(lng);
|
||||
}
|
||||
|
||||
public Double getHeading() {
|
||||
return heading;
|
||||
}
|
||||
|
||||
public void setHeading(Double heading) {
|
||||
this.heading = heading;
|
||||
setHeadingProperty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return boatName;
|
||||
}
|
||||
|
||||
public void updateTimeSinceLastMarkProperty(long timeSinceLastMark) {
|
||||
this.timeSinceLastMarkProperty.set(timeSinceLastMark);
|
||||
}
|
||||
|
||||
public ReadOnlyLongProperty timeSinceLastMarkProperty() {
|
||||
return timeSinceLastMarkProperty.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
public void setTimeTillNext(Long timeTillNext) {
|
||||
this.timeTillNext = timeTillNext;
|
||||
}
|
||||
|
||||
|
||||
public Color getColour() {
|
||||
return colour;
|
||||
}
|
||||
|
||||
public void setColour(Color colour) {
|
||||
this.colour = colour;
|
||||
}
|
||||
|
||||
public void updateLocation(double lat, double lng, double heading, double velocity) {
|
||||
setLocation(lat, lng);
|
||||
this.heading = heading;
|
||||
setHeadingProperty();
|
||||
this.currentVelocity = velocity;
|
||||
updateVelocityProperty(velocity);
|
||||
for (YachtLocationListener yll : locationListeners) {
|
||||
yll.notifyLocation(this, lat, lng, heading, sailIn, velocity);
|
||||
}
|
||||
}
|
||||
|
||||
private void setHeadingProperty() {
|
||||
headingProperty.set(heading);
|
||||
}
|
||||
|
||||
public void addLocationListener(YachtLocationListener listener) {
|
||||
locationListeners.add(listener);
|
||||
}
|
||||
|
||||
public void addMarkRoundingListener(MarkRoundingListener listener) {
|
||||
markRoundingListeners.add(listener);
|
||||
}
|
||||
|
||||
public void removeMarkRoundingListener(MarkRoundingListener listener) {
|
||||
markRoundingListeners.remove(listener);
|
||||
}
|
||||
|
||||
public boolean getSailIn () {
|
||||
return sailIn;
|
||||
}
|
||||
|
||||
public void roundMark(long markRoundTime, long timeSinceLastMark) {
|
||||
this.markRoundTime = markRoundTime;
|
||||
timeSinceLastMarkProperty.set(timeSinceLastMark);
|
||||
legNumber++;
|
||||
for (MarkRoundingListener listener : markRoundingListeners) {
|
||||
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,14 @@
|
||||
package seng302.model;
|
||||
|
||||
import javafx.scene.paint.Color;
|
||||
|
||||
/**
|
||||
* Enum for generating colours.
|
||||
*/
|
||||
public enum Colors {
|
||||
RED, PERU, GOLD, GREEN, BLUE, PURPLE, DEEPPINK, GRAY;
|
||||
|
||||
public static Color getColor(Integer index) {
|
||||
return Color.valueOf(values()[index].toString());
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+8
-3
@@ -1,12 +1,12 @@
|
||||
package seng302.utilities;
|
||||
package seng302.model;
|
||||
|
||||
/**
|
||||
* A class represent Geo location (latitude, longitude).
|
||||
* A class represent Geo location (latitude, lnggitude).
|
||||
* Created by Haoming on 15/5/2017
|
||||
*/
|
||||
public class GeoPoint {
|
||||
|
||||
double lat, lng;
|
||||
private double lat, lng;
|
||||
|
||||
public GeoPoint(double lat, double lng) {
|
||||
this.lat = lat;
|
||||
@@ -28,4 +28,9 @@ public class GeoPoint {
|
||||
public void setLng(double lng) {
|
||||
this.lng = lng;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "lat: " + lat + " lng: " + lng;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package seng302.model;
|
||||
|
||||
/**
|
||||
* Stores data on the border of a race
|
||||
*/
|
||||
public class Limit extends GeoPoint {
|
||||
|
||||
private Integer seqID;
|
||||
|
||||
public Limit(Integer seqID, Double lat, Double lng) {
|
||||
super(lat, lng);
|
||||
this.seqID = seqID;
|
||||
}
|
||||
|
||||
public Integer getSeqID() {
|
||||
return seqID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(){
|
||||
return "Limit = {seqID=" + seqID + ", lat=" + getLat() + ", lng=" + getLng() + "}";
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,6 @@
|
||||
package seng302.models;
|
||||
package seng302.model;
|
||||
|
||||
import javafx.scene.paint.Color;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.Socket;
|
||||
import java.nio.channels.SocketChannel;
|
||||
|
||||
/**
|
||||
* A Class defining a player and their respective details in the game as held by the model
|
||||
@@ -13,11 +9,11 @@ import java.nio.channels.SocketChannel;
|
||||
public class Player {
|
||||
|
||||
private Socket socket;
|
||||
private Yacht yacht;
|
||||
private ServerYacht yacht;
|
||||
private Integer lastMarkPassed;
|
||||
|
||||
|
||||
public Player(Socket socket, Yacht yacht) {
|
||||
public Player(Socket socket, ServerYacht yacht) {
|
||||
this.socket = socket;
|
||||
this.yacht = yacht;
|
||||
}
|
||||
@@ -34,7 +30,7 @@ public class Player {
|
||||
this.lastMarkPassed = lastMarkPassed;
|
||||
}
|
||||
|
||||
public Yacht getYacht() {
|
||||
public ServerYacht getYacht() {
|
||||
return yacht;
|
||||
}
|
||||
|
||||
+2
-4
@@ -1,4 +1,4 @@
|
||||
package seng302.models;
|
||||
package seng302.model;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
@@ -26,6 +26,7 @@ public final class PolarTable {
|
||||
* Iterates through each row of the polar table, in pairs, to extract the row into a hashmap of angle to boat speed.
|
||||
* These angle boatspeed hashmaps are then added to an outer hashmap at the end of wind speed key to each row hashmap
|
||||
* as a value
|
||||
* @param polarFile polarFile to be parsed
|
||||
*/
|
||||
public static void parsePolarFile(InputStream polarFile) {
|
||||
polarTable = new HashMap<>();
|
||||
@@ -70,8 +71,6 @@ public final class PolarTable {
|
||||
} catch (IOException e) {
|
||||
System.out.println("[PolarTable] IO exception");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -154,7 +153,6 @@ public final class PolarTable {
|
||||
public static Double getClosestWindSpeedInPolar(Double thisWindSpeed) {
|
||||
Double smallestDif = Double.POSITIVE_INFINITY;
|
||||
Double closestWind = 0d;
|
||||
|
||||
for (Double polarWindSpeed : polarTable.keySet()) {
|
||||
Double difference = Math.abs(polarWindSpeed - thisWindSpeed);
|
||||
if (difference < smallestDif) {
|
||||
@@ -0,0 +1,137 @@
|
||||
package seng302.model;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.TimeZone;
|
||||
import javafx.beans.property.ReadOnlyDoubleProperty;
|
||||
import javafx.beans.property.ReadOnlyDoubleWrapper;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import seng302.model.stream.parser.RaceStartData;
|
||||
import seng302.model.stream.parser.RaceStatusData;
|
||||
import seng302.utilities.Sounds;
|
||||
|
||||
/**
|
||||
* Class for storing race data that does not relate to specific vessels or marks such as time or wind.
|
||||
* Calculates the state of critical race attributes when relevant data is added.
|
||||
*/
|
||||
public class RaceState {
|
||||
|
||||
@FunctionalInterface
|
||||
public interface CollisionListener {
|
||||
void notifyCollision(GeoPoint location);
|
||||
}
|
||||
|
||||
// private final DateFormat DATE_TIME_FORMAT = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
|
||||
private final DateFormat DATE_TIME_FORMAT = new SimpleDateFormat("HH:mm:ss");
|
||||
private ReadOnlyDoubleWrapper windSpeed = new ReadOnlyDoubleWrapper();
|
||||
private ReadOnlyDoubleWrapper windDirection = new ReadOnlyDoubleWrapper();
|
||||
private long serverSystemTime;
|
||||
private long expectedStartTime;
|
||||
private boolean raceRunning = false;
|
||||
private boolean gunFired = false;
|
||||
private boolean raceFinished = false;
|
||||
long timeTillStart;
|
||||
private ObservableList<ClientYacht> playerPositions;
|
||||
private List<ClientYacht> collisions = new ArrayList<>();
|
||||
private List<CollisionListener> collisionListeners = new ArrayList<>();
|
||||
|
||||
public RaceState() {
|
||||
playerPositions = FXCollections.observableArrayList();
|
||||
}
|
||||
|
||||
public void updateState (RaceStatusData data) {
|
||||
this.windSpeed.set(data.getWindSpeed());
|
||||
this.windDirection.set(data.getWindDirection());
|
||||
this.serverSystemTime = data.getCurrentTime();
|
||||
this.expectedStartTime = data.getExpectedStartTime();
|
||||
this.raceRunning = data.isRaceStarted();
|
||||
}
|
||||
|
||||
public void setTimeZone (TimeZone timeZone) {
|
||||
DATE_TIME_FORMAT.setTimeZone(timeZone);
|
||||
}
|
||||
|
||||
public void updateState (RaceStartData data) {
|
||||
this.timeTillStart = data.getRaceStartTime();
|
||||
}
|
||||
|
||||
public String getRaceTimeStr () {
|
||||
long raceTime = serverSystemTime - expectedStartTime;
|
||||
if (raceTime < 0) {
|
||||
return "-" + DATE_TIME_FORMAT.format(-1 * (raceTime - 1000));
|
||||
} else {
|
||||
if (!gunFired) {
|
||||
gunFired = true;
|
||||
Sounds.playCapGunSound();
|
||||
}
|
||||
return DATE_TIME_FORMAT.format(serverSystemTime - expectedStartTime);
|
||||
}
|
||||
}
|
||||
|
||||
public long getTimeTillStart () {
|
||||
return (expectedStartTime - serverSystemTime);
|
||||
}
|
||||
|
||||
public double getWindSpeed() {
|
||||
return windSpeed.doubleValue();
|
||||
}
|
||||
|
||||
public ReadOnlyDoubleProperty windSpeedProperty() {
|
||||
return windSpeed.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
public ReadOnlyDoubleProperty windDirectionProperty() {
|
||||
return windDirection.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
public long getRaceTime() {
|
||||
return serverSystemTime;
|
||||
}
|
||||
|
||||
public boolean isRaceStarted () {
|
||||
return raceRunning;
|
||||
}
|
||||
|
||||
public void setRaceStarted(Boolean value) {
|
||||
this.raceRunning = value;
|
||||
}
|
||||
public void setBoats(Collection<ClientYacht> clientYachts) {
|
||||
playerPositions.setAll(clientYachts);
|
||||
}
|
||||
|
||||
public void sortPlayers() {
|
||||
playerPositions.sort((yacht1, yacht2) -> Integer.compare(yacht2.getLegNumber(),
|
||||
yacht1.getLegNumber()));
|
||||
}
|
||||
|
||||
public ObservableList<ClientYacht> getPlayerPositions() {
|
||||
return playerPositions;
|
||||
}
|
||||
|
||||
public void storeCollision(ClientYacht yacht) {
|
||||
collisions.add(yacht);
|
||||
for (CollisionListener collisionListener : collisionListeners) {
|
||||
collisionListener.notifyCollision(yacht.getLocation());
|
||||
}
|
||||
}
|
||||
|
||||
public void addCollisionListener(CollisionListener collisionListener) {
|
||||
collisionListeners.add(collisionListener);
|
||||
}
|
||||
|
||||
public void removeCollisionListener(CollisionListener 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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,460 @@
|
||||
package seng302.model;
|
||||
|
||||
import java.util.HashMap;
|
||||
import javafx.scene.paint.Color;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import seng302.gameServer.GameState;
|
||||
import seng302.gameServer.messages.BoatStatus;
|
||||
import seng302.model.mark.Mark;
|
||||
import seng302.model.token.TokenType;
|
||||
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)
|
||||
* compared to the XMLParser boat class, also done outside Boat class because some old variables are
|
||||
* not used anymore.
|
||||
*/
|
||||
public class ServerYacht {
|
||||
|
||||
private Logger logger = LoggerFactory.getLogger(ServerYacht.class);
|
||||
|
||||
//Boat info
|
||||
private BoatMeshType boatType;
|
||||
private Double turnStep = 5.0;
|
||||
private Double maxSpeedMultiplier = 1.0;
|
||||
private Double turnStepMultiplier = 1.0;
|
||||
private Double accelerationMultiplier = 1.0;
|
||||
private Integer sourceId;
|
||||
private String hullID; //matches HullNum in the XML spec.
|
||||
private String shortName;
|
||||
private String boatName;
|
||||
private String country;
|
||||
private BoatStatus boatStatus;
|
||||
private Color boatColor;
|
||||
|
||||
//Location
|
||||
private Double lastHeading;
|
||||
private Boolean sailIn;
|
||||
private Double heading;
|
||||
private GeoPoint lastLocation;
|
||||
private GeoPoint location;
|
||||
private Double currentVelocity;
|
||||
private Boolean isAuto;
|
||||
private Double autoHeading;
|
||||
private Integer legNumber;
|
||||
|
||||
//Mark Rounding
|
||||
private Integer currentMarkSeqID;
|
||||
private Boolean hasEnteredRoundingZone;
|
||||
private Mark closestCurrentMark;
|
||||
private Boolean hasPassedLine;
|
||||
private Boolean hasPassedThroughGate;
|
||||
|
||||
//PowerUp
|
||||
private TokenType powerUp;
|
||||
private Long powerUpStartTime;
|
||||
|
||||
//turning mode
|
||||
private Boolean continuouslyTurning;
|
||||
|
||||
public ServerYacht(BoatMeshType boatType, Integer sourceId, String hullID, String shortName,
|
||||
String boatName, String country) {
|
||||
setBoatType(boatType);
|
||||
this.boatStatus = BoatStatus.PRESTART;
|
||||
this.sourceId = sourceId;
|
||||
this.hullID = hullID;
|
||||
this.shortName = shortName;
|
||||
this.boatName = boatName;
|
||||
this.country = country;
|
||||
this.sailIn = false;
|
||||
this.isAuto = false;
|
||||
this.location = new GeoPoint(57.67046, 11.83751);
|
||||
this.lastLocation = location;
|
||||
this.heading = 120.0; //In degrees
|
||||
this.currentVelocity = 0d; //in mms-1
|
||||
this.currentMarkSeqID = 0;
|
||||
this.legNumber = 0;
|
||||
this.boatColor = Colors.getColor(sourceId - 1);
|
||||
this.powerUp = null;
|
||||
|
||||
this.hasEnteredRoundingZone = false;
|
||||
this.hasPassedLine = false;
|
||||
this.hasPassedThroughGate = false;
|
||||
|
||||
this.continuouslyTurning = false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Changes the boats current currentVelocity by a set amount, positive or negative
|
||||
*
|
||||
* @param velocityChange The ammount to change the currentVelocity by, in mms-1
|
||||
*/
|
||||
public void changeVelocity(Double velocityChange) {
|
||||
currentVelocity += velocityChange;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the boat to a new GeoPoint whilst preserving the last location
|
||||
*
|
||||
* @param secondsElapsed The seconds elapsed since the last update of this yacht
|
||||
*/
|
||||
public void updateLocation(Double secondsElapsed) {
|
||||
lastLocation = location;
|
||||
location = GeoUtility.getGeoCoordinate(location, heading, currentVelocity * secondsElapsed);
|
||||
}
|
||||
|
||||
public void setLocation(GeoPoint geoPoint) {
|
||||
location = geoPoint;
|
||||
}
|
||||
|
||||
public void powerUp(TokenType powerUp) {
|
||||
this.powerUp = powerUp;
|
||||
powerUpStartTime = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public void powerDown() {
|
||||
this.powerUp = null;
|
||||
}
|
||||
|
||||
public Long getPowerUpStartTime() {
|
||||
return powerUpStartTime;
|
||||
}
|
||||
|
||||
public TokenType getPowerUp() {
|
||||
return powerUp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjusts the heading of the boat by a given amount, while recording the boats last heading.
|
||||
*
|
||||
* @param amount the amount by which to adjust the boat heading.
|
||||
*/
|
||||
public void adjustHeading(Double amount) {
|
||||
Double newVal = heading + (amount * turnStepMultiplier);
|
||||
lastHeading = heading;
|
||||
heading = (double) Math.floorMod(newVal.longValue(), 360L);
|
||||
}
|
||||
|
||||
/**
|
||||
* Swaps the boats direction from one side of the wind to the other.
|
||||
* @param windDirection .
|
||||
*/
|
||||
public void tackGybe(Double windDirection) {
|
||||
if (isAuto) {
|
||||
disableAutoPilot();
|
||||
} else {
|
||||
Double normalizedHeading = normalizeHeading();
|
||||
Double newVal = (-2 * normalizedHeading) + heading;
|
||||
Double newHeading = (double) Math.floorMod(newVal.longValue(), 360L);
|
||||
setAutoPilot(newHeading);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables the boats auto pilot feature, which will move the boat towards a given heading.
|
||||
*
|
||||
* @param newHeading The heading to move the boat towards.
|
||||
*/
|
||||
private void setAutoPilot(Double newHeading) {
|
||||
isAuto = true;
|
||||
autoHeading = newHeading;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables the auto pilot function.
|
||||
*/
|
||||
public void disableAutoPilot() {
|
||||
isAuto = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the boat towards the given heading when the auto pilot was set. Disables the auto pilot
|
||||
* in the event that the boat is within the range of 1 turn step of its goal.
|
||||
*/
|
||||
public void runAutoPilot() {
|
||||
if (isAuto) {
|
||||
turnTowardsHeading(autoHeading);
|
||||
if (Math.abs(heading - autoHeading)
|
||||
<= turnStep*1.5) {
|
||||
isAuto = false;
|
||||
setHeading(autoHeading);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void toggleSailIn() {
|
||||
sailIn = !sailIn;
|
||||
}
|
||||
|
||||
public void turnUpwind() {
|
||||
disableAutoPilot();
|
||||
Double normalizedHeading = normalizeHeading();
|
||||
if (continuouslyTurning) {
|
||||
adjustHeading(turnStep);
|
||||
} else {
|
||||
if (normalizedHeading == 0) {
|
||||
if (lastHeading < 180) {
|
||||
adjustHeading(-turnStep);
|
||||
} else {
|
||||
adjustHeading(turnStep);
|
||||
}
|
||||
} else if (normalizedHeading == 180) {
|
||||
if (lastHeading < 180) {
|
||||
adjustHeading(turnStep);
|
||||
} else {
|
||||
adjustHeading(-turnStep);
|
||||
}
|
||||
} else if (normalizedHeading < 180) {
|
||||
adjustHeading(-turnStep);
|
||||
} else {
|
||||
adjustHeading(turnStep);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void turnDownwind() {
|
||||
disableAutoPilot();
|
||||
Double normalizedHeading = normalizeHeading();
|
||||
if (continuouslyTurning) {
|
||||
adjustHeading(-turnStep);
|
||||
} else {
|
||||
if (normalizedHeading == 0) {
|
||||
if (lastHeading < 180) {
|
||||
adjustHeading(turnStep);
|
||||
} else {
|
||||
adjustHeading(-turnStep);
|
||||
}
|
||||
} else if (normalizedHeading == 180) {
|
||||
if (lastHeading < 180) {
|
||||
adjustHeading(-turnStep);
|
||||
} else {
|
||||
adjustHeading(turnStep);
|
||||
}
|
||||
} else if (normalizedHeading < 180) {
|
||||
adjustHeading(turnStep);
|
||||
} else {
|
||||
adjustHeading(-turnStep);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes the VMG from the polartable for upwind or downwind depending on the boats direction,
|
||||
* and uses this to calculate a heading to move the yacht towards.
|
||||
*/
|
||||
public void turnToVMG() {
|
||||
if (isAuto) {
|
||||
disableAutoPilot();
|
||||
} else {
|
||||
Double normalizedHeading = normalizeHeading();
|
||||
Double optimalHeading;
|
||||
HashMap<Double, Double> optimalPolarMap;
|
||||
|
||||
if (normalizedHeading >= 90 && normalizedHeading <= 270) { // Downwind
|
||||
optimalPolarMap = PolarTable.getOptimalDownwindVMG(GameState.getWindSpeedKnots());
|
||||
} else {
|
||||
optimalPolarMap = PolarTable.getOptimalUpwindVMG(GameState.getWindSpeedKnots());
|
||||
}
|
||||
optimalHeading = optimalPolarMap.keySet().iterator().next();
|
||||
|
||||
if (normalizedHeading > 180) {
|
||||
optimalHeading = 360 - optimalHeading;
|
||||
}
|
||||
|
||||
// Take optimal heading and turn into a boat heading rather than a wind heading.
|
||||
optimalHeading =
|
||||
(optimalHeading + GameState.getWindDirection()) % 360;
|
||||
|
||||
setAutoPilot(optimalHeading);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a given heading and rotates the boat towards that heading. This does not care about
|
||||
* being upwind or downwind, just which direction will reach a given heading faster.
|
||||
*
|
||||
* @param newHeading The heading to turn the yacht towards.
|
||||
*/
|
||||
private void turnTowardsHeading(Double newHeading) {
|
||||
Double newVal = heading - newHeading;
|
||||
if (Math.floorMod(newVal.longValue(), 360L) > 180) {
|
||||
adjustHeading(turnStep / 5);
|
||||
} else {
|
||||
adjustHeading(-turnStep / 5);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a heading normalized for the wind direction. Heading direction into the wind is 0,
|
||||
* directly away is 180.
|
||||
*
|
||||
* @return The normalized heading accounting for wind direction.
|
||||
*/
|
||||
private Double normalizeHeading() {
|
||||
Double normalizedHeading = heading - GameState.windDirection;
|
||||
normalizedHeading = (double) Math.floorMod(normalizedHeading.longValue(), 360L);
|
||||
return normalizedHeading;
|
||||
}
|
||||
|
||||
public Integer getSourceId() {
|
||||
//@TODO Remove and merge with Creating Game Loop
|
||||
if (sourceId == null) {
|
||||
return 0;
|
||||
}
|
||||
return sourceId;
|
||||
}
|
||||
|
||||
// TODO: 15/08/17 This method is implicitly called from the XML generator for boats DO NOT DELETE
|
||||
public String getHullID() {
|
||||
if (hullID == null) {
|
||||
return "";
|
||||
}
|
||||
return hullID;
|
||||
}
|
||||
|
||||
// TODO: 15/08/17 This method is implicitly called from the XML generator for boats DO NOT DELETE
|
||||
public String getShortName() {
|
||||
return shortName;
|
||||
}
|
||||
|
||||
public String getBoatName() {
|
||||
return boatName;
|
||||
}
|
||||
|
||||
public String getCountry() {
|
||||
if (country == null) {
|
||||
return "";
|
||||
}
|
||||
return country;
|
||||
}
|
||||
|
||||
public void setBoatName(String name) {
|
||||
boatName = name;
|
||||
shortName = name.split(" ")[0];
|
||||
}
|
||||
|
||||
public GeoPoint getLocation() {
|
||||
return location;
|
||||
}
|
||||
|
||||
|
||||
public Double getHeading() {
|
||||
return heading;
|
||||
}
|
||||
|
||||
public void setHeading(Double heading) {
|
||||
this.heading = heading;
|
||||
}
|
||||
|
||||
public Boolean getSailIn() {
|
||||
return sailIn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return boatName;
|
||||
}
|
||||
|
||||
public Double getCurrentVelocity() {
|
||||
return currentVelocity;
|
||||
}
|
||||
|
||||
public void setCurrentVelocity(Double currentVelocity) {
|
||||
this.currentVelocity = currentVelocity;
|
||||
}
|
||||
|
||||
public Integer getCurrentMarkSeqID() {
|
||||
return currentMarkSeqID;
|
||||
}
|
||||
|
||||
public GeoPoint getLastLocation() {
|
||||
return lastLocation;
|
||||
}
|
||||
|
||||
public Mark getClosestCurrentMark() {
|
||||
return closestCurrentMark;
|
||||
}
|
||||
|
||||
public void setClosestCurrentMark(Mark closestCurrentMark) {
|
||||
this.closestCurrentMark = closestCurrentMark;
|
||||
}
|
||||
|
||||
public void setHasEnteredRoundingZone(Boolean hasEnteredRoundingZone) {
|
||||
this.hasEnteredRoundingZone = hasEnteredRoundingZone;
|
||||
}
|
||||
|
||||
public void setHasPassedLine(Boolean hasPassedLine) {
|
||||
this.hasPassedLine = hasPassedLine;
|
||||
}
|
||||
|
||||
public void setHasPassedThroughGate(Boolean hasPassedThroughGate) {
|
||||
this.hasPassedThroughGate = hasPassedThroughGate;
|
||||
}
|
||||
|
||||
public BoatStatus getBoatStatus() {
|
||||
return boatStatus;
|
||||
}
|
||||
|
||||
public void setBoatStatus(BoatStatus boatStatus) {
|
||||
this.boatStatus = boatStatus;
|
||||
}
|
||||
|
||||
public void incrementMarkSeqID() {
|
||||
currentMarkSeqID++;
|
||||
}
|
||||
|
||||
public Boolean hasEnteredRoundingZone() {
|
||||
return hasEnteredRoundingZone;
|
||||
}
|
||||
|
||||
public Boolean hasPassedThroughGate() {
|
||||
return hasPassedThroughGate;
|
||||
}
|
||||
|
||||
public Boolean hasPassedLine() {
|
||||
return hasPassedLine;
|
||||
}
|
||||
|
||||
public void incrementLegNumber() {
|
||||
legNumber++;
|
||||
}
|
||||
|
||||
public Integer getLegNumber() {
|
||||
return legNumber;
|
||||
}
|
||||
|
||||
public void setBoatColor(Color color) {
|
||||
this.boatColor = color;
|
||||
}
|
||||
|
||||
public Color getBoatColor() {
|
||||
return boatColor;
|
||||
}
|
||||
|
||||
public void setBoatType(BoatMeshType boatType) {
|
||||
this.accelerationMultiplier = boatType.accelerationMultiplier;
|
||||
this.maxSpeedMultiplier = boatType.maxSpeedMultiplier;
|
||||
this.turnStepMultiplier = boatType.turnStep;
|
||||
this.boatType = boatType;
|
||||
}
|
||||
|
||||
public Double getMaxSpeedMultiplier() {
|
||||
return maxSpeedMultiplier;
|
||||
}
|
||||
|
||||
public Double getAccelerationMultiplier(){
|
||||
return accelerationMultiplier;
|
||||
}
|
||||
|
||||
|
||||
public BoatMeshType getBoatType() {
|
||||
return boatType;
|
||||
}
|
||||
|
||||
public void setContinuouslyTurning(Boolean continuouslyTurning) {
|
||||
this.continuouslyTurning = continuouslyTurning;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
package seng302.model.mark;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import seng302.gameServer.messages.RoundingSide;
|
||||
import seng302.model.GeoPoint;
|
||||
import seng302.utilities.GeoUtility;
|
||||
|
||||
public class CompoundMark {
|
||||
|
||||
private int compoundMarkId;
|
||||
private String name;
|
||||
private List<Mark> marks;
|
||||
private GeoPoint midPoint;
|
||||
|
||||
public CompoundMark(int markID, String name, List<Mark> marks) {
|
||||
this.compoundMarkId = markID;
|
||||
this.name = name;
|
||||
this.marks = Collections.unmodifiableList(marks);
|
||||
if (marks.size() > 1) {
|
||||
this.midPoint = GeoUtility.getDirtyMidPoint(marks.get(0), marks.get(1));
|
||||
} else {
|
||||
this.midPoint = marks.get(0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints out compoundMark's info and its marks, good for testing
|
||||
* @return a string showing its details
|
||||
*/
|
||||
@Override
|
||||
public String toString(){
|
||||
String info = String.format(
|
||||
"CompoundMark: %d (%s), [%s", compoundMarkId, name, marks.get(0).toString()
|
||||
);
|
||||
if (marks.size() > 1) {
|
||||
info += String.format(", %s", marks.get(1).toString());
|
||||
}
|
||||
return info + "]";
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return compoundMarkId;
|
||||
}
|
||||
|
||||
public void setId (int markID) {
|
||||
this.compoundMarkId = markID;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public void setRoundingSide(RoundingSide roundingSide) {;
|
||||
switch (roundingSide) {
|
||||
case SP:
|
||||
getSubMark(1).setRoundingSide(RoundingSide.STARBOARD);
|
||||
getSubMark(2).setRoundingSide(RoundingSide.PORT);
|
||||
break;
|
||||
case PS:
|
||||
getSubMark(1).setRoundingSide(RoundingSide.PORT);
|
||||
getSubMark(2).setRoundingSide(RoundingSide.STARBOARD);
|
||||
break;
|
||||
case PORT:
|
||||
getSubMark(1).setRoundingSide(RoundingSide.PORT);
|
||||
break;
|
||||
case STARBOARD:
|
||||
getSubMark(1).setRoundingSide(RoundingSide.STARBOARD);
|
||||
break;
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the mark contained in the compound mark. Marks are numbered 1 to n;
|
||||
* @param singleMarkId the id of the desired mark contained in this compound mark.
|
||||
* @return the desired mark. Returns null if the ID is not in range (1, NUM_MARKS)
|
||||
*/
|
||||
public Mark getSubMark(int singleMarkId) {
|
||||
try {
|
||||
return marks.get(singleMarkId - 1);
|
||||
} catch (IndexOutOfBoundsException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* NOTE: This is a 'dirty' mid point as it is simply calculated as an xy point would be.
|
||||
* NO CHECKING FOR LAT / LNG WRAPPING IS DONE IN CREATION OF THIS MIDPOINT
|
||||
*
|
||||
* @return GeoPoint of the midpoint of the two marks, or the one mark if there is only one
|
||||
*/
|
||||
public GeoPoint getMidPoint() {
|
||||
return midPoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not this CompoundMark is a Gate. It is generally cleaner to program to a
|
||||
* specific singleMark or the list of marks.
|
||||
*
|
||||
* @return True if the compound mark is a gate, false otherwise.
|
||||
*/
|
||||
public boolean isGate () {
|
||||
return marks.size() > 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of marks in the compoundMark
|
||||
*
|
||||
* @return All marks contained in this mark.
|
||||
*/
|
||||
public List<Mark> getMarks () {
|
||||
return marks;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hash = 0;
|
||||
for (Mark mark : marks) {
|
||||
hash += Double.hashCode(mark.getSourceID()) + Double.hashCode(mark.getLat())
|
||||
+ Double.hashCode(mark.getLng()) + mark.getName().hashCode();
|
||||
}
|
||||
return hash + getName().hashCode() + Integer.hashCode(getId());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package seng302.model.mark;
|
||||
|
||||
/**
|
||||
* Stores the data for the cornering of a mark.
|
||||
*/
|
||||
public class Corner {
|
||||
|
||||
private Integer seqID;
|
||||
private Integer compoundMarkID;
|
||||
private String rounding;
|
||||
private Integer zoneSize;
|
||||
|
||||
public Corner(Integer seqID, Integer compoundMarkID, String rounding, Integer zoneSize) {
|
||||
this.seqID = seqID;
|
||||
this.compoundMarkID = compoundMarkID;
|
||||
this.rounding = rounding;
|
||||
this.zoneSize = zoneSize;
|
||||
}
|
||||
|
||||
public Integer getSeqID() {
|
||||
return seqID;
|
||||
}
|
||||
|
||||
public Integer getCompoundMarkID() {
|
||||
return compoundMarkID;
|
||||
}
|
||||
|
||||
public String getRounding() {
|
||||
return rounding;
|
||||
}
|
||||
|
||||
public Integer getZoneSize() {
|
||||
return zoneSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Corner = {seqID=" + seqID + ", compoundMarkID=" + compoundMarkID + ", rounding="
|
||||
+ rounding +", zoneSize=" + zoneSize + "}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package seng302.model.mark;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import seng302.gameServer.messages.RoundingSide;
|
||||
import seng302.model.GeoPoint;
|
||||
|
||||
/**
|
||||
* An abstract class to represent general marks
|
||||
* Created by Haoming Yin (hyi25) on 17/3/17.
|
||||
*/
|
||||
public class Mark extends GeoPoint {
|
||||
|
||||
@FunctionalInterface
|
||||
public interface PositionListener {
|
||||
void notifyPositionChange(Mark mark, double lat, double lon);
|
||||
}
|
||||
|
||||
private int seqID;
|
||||
private String name;
|
||||
private int sourceID;
|
||||
private List<PositionListener> positionListeners = new ArrayList<>();
|
||||
private RoundingSide roundingSide;
|
||||
|
||||
public Mark(String name, int seqID, double lat, double lng, int sourceID) {
|
||||
super(lat, lng);
|
||||
this.name = name;
|
||||
this.sourceID = sourceID;
|
||||
this.seqID = seqID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints out mark's info and its geo location, good for testing
|
||||
* @return a string showing its details
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("Mark%d: %s, source: %d, lat: %f, lng: %f", seqID, name, sourceID, getLat(), getLng());
|
||||
}
|
||||
|
||||
public int getSeqID() {
|
||||
return seqID;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public int getSourceID() {
|
||||
return sourceID;
|
||||
}
|
||||
|
||||
public RoundingSide getRoundingSide() {
|
||||
return roundingSide;
|
||||
}
|
||||
|
||||
public void setRoundingSide(RoundingSide roundingSide) {
|
||||
this.roundingSide = roundingSide;
|
||||
}
|
||||
|
||||
public void setSourceID(int sourceID) {
|
||||
this.sourceID = sourceID;
|
||||
}
|
||||
|
||||
public void updatePosition (double lat, double lon) {
|
||||
this.setLat(lat);
|
||||
this.setLng(lon);
|
||||
for (PositionListener listener : positionListeners) {
|
||||
listener.notifyPositionChange(this, lat, lon);
|
||||
}
|
||||
}
|
||||
|
||||
public void addPositionListener (PositionListener listener) {
|
||||
positionListeners.add(listener);
|
||||
}
|
||||
|
||||
public void removePositionListener (PositionListener listener) {
|
||||
positionListeners.remove(listener);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
package seng302.model.mark;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import seng302.gameServer.messages.RoundingSide;
|
||||
import seng302.model.stream.xml.parser.RaceXMLData;
|
||||
|
||||
/**
|
||||
* Class to hold the order of the marks in the race.
|
||||
*/
|
||||
public class MarkOrder {
|
||||
private List<CompoundMark> raceMarkOrder;
|
||||
private Logger logger = LoggerFactory.getLogger(MarkOrder.class);
|
||||
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return An ordered list of marks in the race
|
||||
* OR null if the mark order could not be loaded
|
||||
*/
|
||||
public List<CompoundMark> getMarkOrder() {
|
||||
if (raceMarkOrder == null){
|
||||
logger.warn("Race order accessed but not instantiated");
|
||||
return null;
|
||||
}
|
||||
return Collections.unmodifiableList(raceMarkOrder);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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)
|
||||
*/
|
||||
public Boolean isLastMark(Integer seqID) {
|
||||
return seqID == raceMarkOrder.size() - 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param currentSeqID The seqID of the current mark the boat is heading to
|
||||
* @return The mark last passed
|
||||
* @throws IndexOutOfBoundsException if there is no next mark. Check seqID != 0 first
|
||||
*/
|
||||
public CompoundMark getPreviousMark(Integer currentSeqID) throws IndexOutOfBoundsException {
|
||||
return raceMarkOrder.get(currentSeqID - 1);
|
||||
}
|
||||
|
||||
public CompoundMark getCurrentMark(Integer currentSeqID) {
|
||||
return raceMarkOrder.get(currentSeqID);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param currentSeqID The seqID of the current mark the boat is heading to
|
||||
* @return The mark following the mark that the boat is heading to
|
||||
* @throws IndexOutOfBoundsException if there is no next mark. Check using {@link
|
||||
* #isLastMark(Integer)}
|
||||
*/
|
||||
public CompoundMark getNextMark(Integer currentSeqID) throws IndexOutOfBoundsException {
|
||||
return raceMarkOrder.get(currentSeqID + 1);
|
||||
}
|
||||
}
|
||||
+26
-8
@@ -1,13 +1,12 @@
|
||||
package seng302.models.stream.packets;
|
||||
package seng302.model.stream.packets;
|
||||
|
||||
/**
|
||||
* Created by Kusal on 4/24/2017.
|
||||
*/
|
||||
public enum PacketType {
|
||||
HEARTBEAT,
|
||||
RACE_STATUS,
|
||||
DISPLAY_TEXT_MESSAGE,
|
||||
XML_MESSAGE,
|
||||
RACE_XML,
|
||||
REGATTA_XML,
|
||||
BOAT_XML,
|
||||
RACE_START_STATUS,
|
||||
YACHT_EVENT_CODE,
|
||||
YACHT_ACTION_CODE,
|
||||
@@ -17,9 +16,13 @@ public enum PacketType {
|
||||
COURSE_WIND,
|
||||
AVG_WIND,
|
||||
BOAT_ACTION,
|
||||
OTHER;
|
||||
OTHER,
|
||||
RACE_REGISTRATION_REQUEST,
|
||||
RACE_REGISTRATION_RESPONSE,
|
||||
RACE_CUSTOMIZATION_REQUEST,
|
||||
RACE_CUSTOMIZATION_RESPONSE;
|
||||
|
||||
public static PacketType assignPacketType(int packetType){
|
||||
public static PacketType assignPacketType(int packetType, byte[] payload){
|
||||
switch(packetType){
|
||||
case 1:
|
||||
return HEARTBEAT;
|
||||
@@ -28,7 +31,14 @@ public enum PacketType {
|
||||
case 20:
|
||||
return DISPLAY_TEXT_MESSAGE;
|
||||
case 26:
|
||||
return XML_MESSAGE;
|
||||
switch (payload[9]) { //The type of XML message
|
||||
case 5:
|
||||
return REGATTA_XML;
|
||||
case 6:
|
||||
return RACE_XML;
|
||||
case 7:
|
||||
return BOAT_XML;
|
||||
}
|
||||
case 27:
|
||||
return RACE_START_STATUS;
|
||||
case 29:
|
||||
@@ -47,6 +57,14 @@ public enum PacketType {
|
||||
return AVG_WIND;
|
||||
case 100:
|
||||
return BOAT_ACTION;
|
||||
case 101:
|
||||
return RACE_REGISTRATION_REQUEST;
|
||||
case 102:
|
||||
return RACE_REGISTRATION_RESPONSE;
|
||||
case 103:
|
||||
return RACE_CUSTOMIZATION_REQUEST;
|
||||
case 104:
|
||||
return RACE_CUSTOMIZATION_RESPONSE;
|
||||
default:
|
||||
}
|
||||
return OTHER;
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
package seng302.models.stream.packets;
|
||||
package seng302.model.stream.packets;
|
||||
|
||||
/**
|
||||
* Created by kre39 on 23/04/17.
|
||||
@@ -13,7 +13,7 @@ public class StreamPacket {
|
||||
private byte[] payload;
|
||||
|
||||
public StreamPacket(int type, long messageLength, long timeStamp, byte[] payload) {
|
||||
this.type = PacketType.assignPacketType(type);
|
||||
this.type = PacketType.assignPacketType(type, payload);
|
||||
this.messageLength = messageLength;
|
||||
this.timeStamp = timeStamp;
|
||||
this.payload = payload;
|
||||
@@ -0,0 +1,36 @@
|
||||
package seng302.model.stream.parser;
|
||||
|
||||
|
||||
/**
|
||||
* Simple data wrapper for mark rounding data packet.
|
||||
*/
|
||||
public class MarkRoundingData {
|
||||
|
||||
private int boatId;
|
||||
private int markId;
|
||||
private int roundingSide;
|
||||
private long timeStamp;
|
||||
|
||||
public MarkRoundingData(int boatId, int markId, int roundingSide, long timeStamp) {
|
||||
this.boatId = boatId;
|
||||
this.markId = markId;
|
||||
this.roundingSide = roundingSide;
|
||||
this.timeStamp = timeStamp;
|
||||
}
|
||||
|
||||
public int getBoatId() {
|
||||
return boatId;
|
||||
}
|
||||
|
||||
public int getMarkId() {
|
||||
return markId;
|
||||
}
|
||||
|
||||
public int getRoundingSide() {
|
||||
return roundingSide;
|
||||
}
|
||||
|
||||
public long getTimeStamp() {
|
||||
return timeStamp;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package seng302.model.stream.parser;
|
||||
|
||||
public class PositionUpdateData {
|
||||
|
||||
public enum DeviceType {
|
||||
YACHT_TYPE,
|
||||
MARK_TYPE
|
||||
}
|
||||
|
||||
private int deviceId;
|
||||
private DeviceType type;
|
||||
private double lat;
|
||||
private double lon;
|
||||
private double heading;
|
||||
private double groundSpeed;
|
||||
|
||||
public PositionUpdateData(int deviceId, DeviceType type, double lat, double lon,
|
||||
double heading, double groundSpeed) {
|
||||
this.deviceId = deviceId;
|
||||
this.type = type;
|
||||
this.lat = lat;
|
||||
this.lon = lon;
|
||||
this.heading = heading;
|
||||
this.groundSpeed = groundSpeed;
|
||||
}
|
||||
|
||||
public int getDeviceId() {
|
||||
return deviceId;
|
||||
}
|
||||
|
||||
public DeviceType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public double getLat() {
|
||||
return lat;
|
||||
}
|
||||
|
||||
public double getLon() {
|
||||
return lon;
|
||||
}
|
||||
|
||||
public double getHeading() {
|
||||
return heading;
|
||||
}
|
||||
|
||||
public double getGroundSpeed() {
|
||||
return groundSpeed;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package seng302.model.stream.parser;
|
||||
|
||||
/**
|
||||
* Class for storing data parsed from race start status packet
|
||||
*/
|
||||
public class RaceStartData {
|
||||
|
||||
private long raceId;
|
||||
private long raceStartTime;
|
||||
private int notificationType;
|
||||
private long timeStamp;
|
||||
|
||||
public RaceStartData (long raceId, long raceStartTime, int notificationType, long timeStamp) {
|
||||
this.raceId = raceId;
|
||||
this.raceStartTime = raceStartTime;
|
||||
this.notificationType = notificationType;
|
||||
this.timeStamp = timeStamp;
|
||||
}
|
||||
|
||||
public long getRaceId() {
|
||||
return raceId;
|
||||
}
|
||||
|
||||
public long getRaceStartTime() {
|
||||
return raceStartTime;
|
||||
}
|
||||
|
||||
public int getNotificationType() {
|
||||
return notificationType;
|
||||
}
|
||||
|
||||
public long getTimeStamp() {
|
||||
return timeStamp;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package seng302.model.stream.parser;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Stores parsed data from race status packets
|
||||
*/
|
||||
public class RaceStatusData {
|
||||
|
||||
//CONVERSION CONSTANTS
|
||||
private static final double WIND_DIR_FACTOR = 0x4000 / 90; //0x4000 is 90 degrees
|
||||
private static final double MS_TO_KNOTS = 1.94384;
|
||||
|
||||
private double windDirection;
|
||||
private double windSpeed;
|
||||
private boolean raceStarted = false;
|
||||
private long currentTime;
|
||||
private long expectedStartTime;
|
||||
private List<long[]> boatData = new ArrayList<>();
|
||||
|
||||
public RaceStatusData(
|
||||
long windDir, long rawWindSpeed, int raceStatus, long currentTime, long expectedStartTime) {
|
||||
|
||||
windDirection = windDir / WIND_DIR_FACTOR;
|
||||
windSpeed = rawWindSpeed / 1000 * MS_TO_KNOTS;
|
||||
raceStarted = raceStatus == 3;
|
||||
this.currentTime = currentTime;
|
||||
this.expectedStartTime = expectedStartTime;
|
||||
}
|
||||
|
||||
public void addBoatData (long boatID, long estTimeToNextMark, long estTimeToFinish, int leg, int boatStatus) {
|
||||
boatData.add(new long[] {boatID, estTimeToNextMark, estTimeToFinish, leg, boatStatus});
|
||||
}
|
||||
|
||||
public double getWindDirection() {
|
||||
return windDirection;
|
||||
}
|
||||
|
||||
public double getWindSpeed() {
|
||||
return windSpeed;
|
||||
}
|
||||
|
||||
public boolean isRaceStarted() {
|
||||
return raceStarted;
|
||||
}
|
||||
|
||||
public long getCurrentTime() {
|
||||
return currentTime;
|
||||
}
|
||||
|
||||
public long getExpectedStartTime() {
|
||||
return expectedStartTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the data for boats collected form race status packets.
|
||||
*
|
||||
* @return A list of boat data. Boat data is in the form
|
||||
* [boatID, estTimeToNextMark, estTimeToFinish, legNumber, status].
|
||||
*/
|
||||
public List<long[]> getBoatData () {
|
||||
return boatData;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package seng302.model.stream.parser;
|
||||
|
||||
/**
|
||||
* Stores parsed data from yacht event code packet
|
||||
*/
|
||||
public class YachtEventData {
|
||||
private Long subjectId;
|
||||
private Long incidentId;
|
||||
private Integer eventId;
|
||||
private Long timeStamp;
|
||||
|
||||
public YachtEventData(Long subjectId, Long incidentId, Integer eventId, Long timeStamp) {
|
||||
this.subjectId = subjectId;
|
||||
this.incidentId = incidentId;
|
||||
this.eventId = eventId;
|
||||
this.timeStamp = timeStamp;
|
||||
}
|
||||
|
||||
public Long getSubjectId() {
|
||||
return subjectId;
|
||||
}
|
||||
|
||||
public Long getIncidentId() {
|
||||
return incidentId;
|
||||
}
|
||||
|
||||
public Integer getEventId() {
|
||||
return eventId;
|
||||
}
|
||||
|
||||
public Long getTimeStamp() {
|
||||
return timeStamp;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user