mirror of
https://github.com/michaelrausch/Party-Parrots-At-Sea.git
synced 2026-05-09 14:28:43 +00:00
Compare commits
870 Commits
sprint_4.1
..
master
| Author | SHA1 | Date | |
|---|---|---|---|
| a8599d788b | |||
| 5101e55413 | |||
| 6ecd1c82f4 | |||
| ffd94b34bc | |||
| ba49660868 | |||
| 5dbc23866a | |||
| 057af2799a | |||
| 45669c333a | |||
| f7fd5494ef | |||
| a1d468c689 | |||
| 6fafb02a8f | |||
| 4f80640718 | |||
| d436d2a6e4 | |||
| 27379ae96d | |||
| ce3e08abfc | |||
| 923c381797 | |||
| ece45ff967 | |||
| 02dc1dbd3d | |||
| d9b9c2f808 | |||
| 4e3de02a93 | |||
| 261f68f143 | |||
| ed9b7acc62 | |||
| 74c1219e0d | |||
| 83218ae0a0 | |||
| 0231c43a2c | |||
| a05a41d5ec | |||
| 06bc3644bc | |||
| 7620f0023e | |||
| 77ee1ebbc0 | |||
| 6d0835b0cf | |||
| 54410efa12 | |||
| 74241ee819 | |||
| a9ce8901f5 | |||
| 8810554ce9 | |||
| 7e3ed872ed | |||
| 21ce34dda2 | |||
| 2bf318a122 | |||
| e56d284792 | |||
| 80c26a9e4a | |||
| e1ebbc71c1 | |||
| c4a3df32c8 | |||
| 1e19dd5ab6 | |||
| c98297ea79 | |||
| 1aedcaddf5 | |||
| d7fc339ad5 | |||
| a5eea10c87 | |||
| 51078c82a0 | |||
| 22fbb529ef | |||
| 02aabc3162 | |||
| d2a05de25a | |||
| 597fbe935f | |||
| 7cfad28d6b | |||
| 3345734efd | |||
| 9795083d4d | |||
| f02208ba3a | |||
| d3e8a21d2f | |||
| b9a2d60115 | |||
| 076c4c9a40 | |||
| 8d74c3b756 | |||
| 3f666fa092 | |||
| 55d82298b6 | |||
| 909407fe63 | |||
| cb4e47f71a | |||
| 499acb9733 | |||
| 5688e10e6f | |||
| c72c4929ff | |||
| 265b20ad61 | |||
| 5cbd729214 | |||
| ddf5a96e0f | |||
| 81b2d285e9 | |||
| 5d7f307260 | |||
| 852575c9e7 | |||
| 4ec23a1785 | |||
| 37e4fe4ce7 | |||
| 567e351c7f | |||
| e87931a8fc | |||
| d1edbc4b8a | |||
| 275a2cbab7 | |||
| 705669ad07 | |||
| 71f6b9accb | |||
| caf04e1e99 | |||
| b9cb6fa5b4 | |||
| 72fe8c4881 | |||
| 0b8e2499a7 | |||
| 9075d2a909 | |||
| 00e2af9c14 | |||
| ba768deabc | |||
| 73861b2ef3 | |||
| 4018dd783e | |||
| 8b7407bf89 | |||
| 00ddf117b2 | |||
| a266779709 | |||
| df24fe072a | |||
| a1a34b9bd7 | |||
| 42d490d6fd | |||
| cd4a2f8da3 | |||
| 287cfd77d0 | |||
| a08a38c985 | |||
| 810dde6a21 | |||
| 7a76efa2ce | |||
| ff4c2cd5b6 | |||
| c5e6302f86 | |||
| cd199767ae | |||
| 5cc4898ab5 | |||
| 22e1e57c24 | |||
| c5d2016733 | |||
| efc71f2003 | |||
| be72062c8e | |||
| 0d212a4a1d | |||
| 0e9b818071 | |||
| 452e83c1c3 | |||
| aded794a67 | |||
| 2b3a972ed5 | |||
| 2a523a5664 | |||
| 270326ea77 | |||
| 67f3124cfb | |||
| 7db387bdec | |||
| 2ceca2fd42 | |||
| 00ff771fc3 | |||
| d963785679 | |||
| 78f64557c3 | |||
| 982fac38a0 | |||
| edbfb2f84f | |||
| c01111038f | |||
| fd53fd52a4 | |||
| 6f62efcc93 | |||
| daf3867433 | |||
| e5af7bf666 | |||
| 85ca91db96 | |||
| 2eb7e603f1 | |||
| 658a342118 | |||
| c0bd498f1b | |||
| ea52977aeb | |||
| 10fa51a105 | |||
| b18d9e8573 | |||
| e9881bb24a | |||
| c54a1e141d | |||
| 6d51ea3574 | |||
| d56468e4aa | |||
| ab5ad58237 | |||
| df7264cc1f | |||
| 5248921576 | |||
| 330ccd272d | |||
| 4b7dfe38c4 | |||
| ab07c7f298 | |||
| b5076bc976 | |||
| 2dcdd1c248 | |||
| 9b00f76907 | |||
| f11c457d28 | |||
| 9e4fa30787 | |||
| 99ce4fa11d | |||
| 69d1fa9488 | |||
| 66e6a8a2a4 | |||
| 671efcaf08 | |||
| 8ba44d7476 | |||
| dd43097677 | |||
| 98abe64f00 | |||
| 1a53579317 | |||
| 132a729758 | |||
| 870d7a6e82 | |||
| 2e7487fdfc | |||
| 1bd4db73cd | |||
| 7a4cdbe0c9 | |||
| 735699dc85 | |||
| 81e791bd1a | |||
| 06e5f4ae00 | |||
| cd2b4cb93c | |||
| dde1c82dbb | |||
| d9c832168b | |||
| 9cfb3b9e5d | |||
| e990c68d40 | |||
| 83871a0336 | |||
| 6cba024d64 | |||
| 51747e2d13 | |||
| b3981b19e0 | |||
| 3d7a64068f | |||
| c12f7408ad | |||
| 7027de80c4 | |||
| 35b50d1436 | |||
| d0844e861d | |||
| 5d32d76d9d | |||
| 9ca39d1a7c | |||
| 30dad8509e | |||
| 0211f2df38 | |||
| dba5a5680f | |||
| 5c50e77efa | |||
| 0e93be7b36 | |||
| 191b818e38 | |||
| 00b09997b0 | |||
| d250c635d8 | |||
| 376c4d25a8 | |||
| e66abb4340 | |||
| a19e191684 | |||
| 29b97a194d | |||
| 8a0ad8d6a9 | |||
| 19db6668da | |||
| 44275aec04 | |||
| ca320f7fb8 | |||
| 64245833cd | |||
| aa0149b9a7 | |||
| f6b41f0513 | |||
| 9b00ba654a | |||
| 8dfdb228e9 | |||
| 1042817e4e | |||
| 066557584f | |||
| 4011295b8b | |||
| 78259f8e33 | |||
| c47e5b1450 | |||
| 0a885dd8fd | |||
| e9b50038a9 | |||
| 364264377a | |||
| 9112183ac3 | |||
| 8c7f9a878d | |||
| ecb3d4ecbf | |||
| e61b6d50a1 | |||
| 957821f1f2 | |||
| 061e49bab9 | |||
| 094eb4c1cf | |||
| a3c555d5fe | |||
| 607acff7c6 | |||
| 22fdf1e4ac | |||
| da8c91f5c1 | |||
| 52dc7a956d | |||
| 9f64b2380d | |||
| b05580f018 | |||
| c20c6fb264 | |||
| faeece27ff | |||
| 5e3ae40d03 | |||
| 95ad7a4840 | |||
| 6ca75b2cac | |||
| 6ff309a40c | |||
| 40a7f9bc5b | |||
| c4a6113f6c | |||
| 307e79ecfc | |||
| e17e9749d8 | |||
| 7d8a6afa5f | |||
| ea0be5e952 | |||
| 3be8cd264d | |||
| 7197bc2bee | |||
| 66d9a06f9e | |||
| fba522d0c3 | |||
| 0e829874c2 | |||
| c5d56065b6 | |||
| 410d765745 | |||
| fe76e85c71 | |||
| 9d61a43bd7 | |||
| c39582de5c | |||
| 034e4c252a | |||
| 6cde016401 | |||
| d4d7ddf8e2 | |||
| a1933c2869 | |||
| 8084a61333 | |||
| 52d3cea592 | |||
| 03f5f91043 | |||
| 9ed52a1225 | |||
| 027324cc4f | |||
| 78596ea111 | |||
| da263355f4 | |||
| ebecd25ed2 | |||
| 0f5137c2b6 | |||
| 73799954e4 | |||
| edfeb2b287 | |||
| 0355784000 | |||
| 02df69b7b4 | |||
| 242132b800 | |||
| 3a671d4ed0 | |||
| 482d987839 | |||
| cc124b2d19 | |||
| b3320ad805 | |||
| 33779ad5c1 | |||
| 3a41c27d8d | |||
| e24203904b | |||
| eb188495ce | |||
| 62a7e2b8fa | |||
| acd0e790fe | |||
| 46013474c0 | |||
| bf427f24d3 | |||
| 7d0a47446d | |||
| 889098bb50 | |||
| 6223a8fc0b | |||
| 391bd33548 | |||
| 7e0c2abbfd | |||
| 42a5e86bf8 | |||
| 20d73d8f2f | |||
| 0a62c538ca | |||
| 24a04aa530 | |||
| 7ee6a09626 | |||
| 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 | |||
| 25d8c8f9c4 | |||
| 1d9dd76356 | |||
| c2c34705d5 | |||
| 96ed5e445e | |||
| 870dc07fd2 | |||
| ecbb3f6658 | |||
| 34704bd93d | |||
| 201c88a253 | |||
| 6c4da58d9d | |||
| b56fa5cba3 | |||
| 9cedbeb6f6 | |||
| 4c6d107102 | |||
| 7917a2584b | |||
| f99f8a0d7c | |||
| 7db716f51c | |||
| 592e5a088f | |||
| 2d6850950c | |||
| ef6821a0cd | |||
| 6e9535d78f | |||
| 72a45f5984 | |||
| de600fa062 | |||
| 87e3b4f246 | |||
| 7b47d72ef0 | |||
| 7392bdb80d | |||
| af9f1417f1 | |||
| 8fc00fd750 | |||
| 4e5b67abfa | |||
| 84e8ac89fc | |||
| 12d081a1af | |||
| 5e6b402bf5 | |||
| 2bfa6cb038 | |||
| 8ac44d13df | |||
| d99055901f | |||
| 9c9f6e4e80 | |||
| 08e369f1ae | |||
| b0e99ab444 | |||
| c77a48f589 | |||
| 0135426dfe | |||
| 37c745c139 | |||
| 7880039801 | |||
| a56dac1e87 | |||
| 4ae422b47b | |||
| acd54dec7a | |||
| 6242ab0b2e | |||
| c8a96dcce9 | |||
| 4f2dca7ecf | |||
| e569574c01 | |||
| f544734b4d | |||
| 80b439470b | |||
| 539197cef5 | |||
| 1a867be387 | |||
| 3785cd705f | |||
| 5d7a438080 | |||
| 2f12f3e34f | |||
| 52bfa3ad34 | |||
| d1d659b698 | |||
| cdb9337aed | |||
| 8b0af5bb62 | |||
| 83232a935e | |||
| a30a1aa7c7 | |||
| 07cebb6c5b | |||
| 45bf65a3d3 | |||
| 60f5a99b0c | |||
| 526c12127f | |||
| 1daac842f2 | |||
| 3e4a6f0f2e | |||
| c1e937049e | |||
| 8f8d5c7384 | |||
| aad93d8913 | |||
| 027c7a1480 | |||
| df2efa3329 | |||
| 9d754c8819 | |||
| e11ceed28c | |||
| 8b8b6e4afa | |||
| ed2a22b573 | |||
| 41851ee925 | |||
| ffc61942a9 | |||
| 2e4382bff6 | |||
| f542dbb61e | |||
| 2869d139a3 | |||
| 3ec930491f | |||
| a0005064ac | |||
| 33fae9d69a | |||
| 913e5fee7b | |||
| 3992073303 | |||
| 797a99f632 | |||
| e891ed8a64 | |||
| e8c2cf809b | |||
| ec761893c7 | |||
| 5df7efda03 | |||
| 2fff73c075 | |||
| d37cbd263e | |||
| 3ec1242a9a | |||
| 881f7f8e30 | |||
| 12c2f31af9 | |||
| 49c0c029c3 | |||
| da7a34fc55 | |||
| 322ff740e2 | |||
| b1575e57df | |||
| 82b219cdba | |||
| e317de7562 | |||
| 9ecaa7c3b3 | |||
| 037b0db01b | |||
| 1e80d76acd | |||
| 360c55fdb9 | |||
| 176d65e0b2 | |||
| 0c08f5a03c | |||
| e257602b78 | |||
| 8f00f3a80c | |||
| 63d24c001f | |||
| 67668fe1fc | |||
| dbbb41e12f | |||
| 45053ba507 | |||
| d9f5f7a137 | |||
| b301ce5d27 | |||
| f02bd3b3f8 | |||
| e83eaa38e1 | |||
| 102b5f3ca1 | |||
| 63958a6717 | |||
| 4b8ac32ca9 | |||
| 00b29a1890 | |||
| c7e5f93bc4 | |||
| e4d87c91a2 | |||
| f84091e54e | |||
| e03e8825b2 | |||
| 355f8543f5 | |||
| 77e7db79cc | |||
| 2809d0d832 | |||
| 5b908ec355 | |||
| c480fca72a | |||
| c19f66a6a4 | |||
| 1e6fd1af09 | |||
| 55db2c9961 | |||
| 6ec8b0c3c5 | |||
| 78557a4536 | |||
| 8090cd7985 | |||
| 5ce34bed92 | |||
| 035841f221 | |||
| ef61a687d6 | |||
| fcb1e5e593 | |||
| 752863a0d3 | |||
| 1a3e330eb4 | |||
| 5f9da6b40a | |||
| aee62c29fe | |||
| fe76ef9cdc | |||
| acbaa838ec | |||
| f4134d83b5 | |||
| 24cc10e1cd | |||
| 20b79b40f2 | |||
| 72e2776b7e | |||
| 49e4c92da6 | |||
| ba761e4951 | |||
| c1aa1d8eae | |||
| 4231c3ccd8 | |||
| 65223ceaaf | |||
| ca22615c08 | |||
| 559a9f38c0 | |||
| 3bd8added4 | |||
| ba527a1979 | |||
| b73e4c89db | |||
| 945acb6071 | |||
| f163dfdd11 | |||
| a1eda8d91d | |||
| 5ed02a1fe1 | |||
| 66d4a4b958 | |||
| 0cd2867ac0 | |||
| 4d29354797 | |||
| 3085125f3e | |||
| 5e26ad7c36 | |||
| 765ea06c3b | |||
| 14a7305a2d | |||
| e7060d4b6f | |||
| 641039720e | |||
| 6f132f1e38 | |||
| a1e8d29b9c | |||
| c449da2916 | |||
| d22d758757 | |||
| acbde5aad8 | |||
| e26f2af93d | |||
| 6a6ed3ed44 |
+4
-1
@@ -7,7 +7,6 @@
|
|||||||
.mtj.tmp/
|
.mtj.tmp/
|
||||||
|
|
||||||
# Package Files #
|
# Package Files #
|
||||||
*.jar
|
|
||||||
*.war
|
*.war
|
||||||
*.ear
|
*.ear
|
||||||
|
|
||||||
@@ -180,3 +179,7 @@ local.properties
|
|||||||
.recommenders/
|
.recommenders/
|
||||||
|
|
||||||
Makefile
|
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>
|
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> zyt10 <zyt10@uclive.ac.nz>
|
||||||
Zhi You Tan <zyt10@uclive.ac.nz> Ryan Tan <ryan_zhiyou@hotmail.com>
|
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>
|
Calum <cir27@uclive.ac.nz> cir27 <cir27@uclive.ac.nz>
|
||||||
@@ -1,13 +1,29 @@
|
|||||||
# SENG302 Project Template
|
# Party Parrots At Sea
|
||||||
|
|
||||||
Basic Maven project with required Maven reporting setup and basic GitLab CI.
|
**Authors:** Michael Rausch, William Muir, Haoming Yin, Alistair Mcintyre, Kusal Ekanayake, Calum Irwin, Peter Galloway, Tan Zhi You
|
||||||
|
|
||||||
It is a requirement that your product should be completely built to a deliverable form using the Maven package goal.
|
### SENG302
|
||||||
|
> The Software Engineering group project gives students in-depth experience in developing software applications in groups. Participants work in groups to develop a complex real application. At the end of this course you will have practiced the skills required to be a Software Engineer in the real world, including gaining the required skills to be able to develop complex applications, dealing with vague (and often conflicting) customer requirements, working under pressure and being a valuable member of a software development team.
|
||||||
|
|
||||||
Remember to set up your GitLab CI server (refer to the student guide for instructions).
|
**Find out more:** www.canterbury.ac.nz/courseinfo/GetCourses.aspx?course=SENG302
|
||||||
|
|
||||||
|
### Running the discovery server
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
xvfb-run -a -e server.log java -jar app.jar -runAsDiscoveryServer
|
||||||
|
```
|
||||||
|
|
||||||
|
### Video
|
||||||
|
|
||||||
|
https://youtu.be/aHxJsfZLg54
|
||||||
|
|
||||||
|
### Screenshots
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
# Basic Project Structure
|
|
||||||
- `src/` Your application source
|
|
||||||
- `doc/` User and design documentation
|
|
||||||
- `doc/examples/` Demo example files for use with your application
|
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ prints out event details, including time, involved boats and legs.
|
|||||||
|
|
||||||
- Configuration file
|
- 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.
|
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.
|
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 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.
|
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>
|
<properties>
|
||||||
<maven.compiler.source>1.8</maven.compiler.source>
|
<maven.compiler.source>1.8</maven.compiler.source>
|
||||||
<maven.compiler.target>1.8</maven.compiler.target>
|
<maven.compiler.target>1.8</maven.compiler.target>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
@@ -36,6 +37,84 @@
|
|||||||
<version>2.7.13</version>
|
<version>2.7.13</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</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>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.fxyz3d</groupId>
|
||||||
|
<artifactId>fxyz3d</artifactId>
|
||||||
|
<version>0.1.1</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
@@ -118,4 +197,18 @@
|
|||||||
</plugin>
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
</reporting>
|
</reporting>
|
||||||
|
|
||||||
|
<repositories>
|
||||||
|
<repository>
|
||||||
|
<id>lib</id>
|
||||||
|
<name>third party libraries</name>
|
||||||
|
<url>file://${basedir}/lib</url>
|
||||||
|
</repository>
|
||||||
|
|
||||||
|
<repository>
|
||||||
|
<id>Homer-Core</id>
|
||||||
|
<name>Homer-core-repo</name>
|
||||||
|
<url>https://nexus.arcsmed.at/content/repositories/homer.core</url>
|
||||||
|
</repository>
|
||||||
|
</repositories>
|
||||||
</project>
|
</project>
|
||||||
@@ -1,82 +1,116 @@
|
|||||||
package seng302;
|
package seng302;
|
||||||
|
|
||||||
|
import ch.qos.logback.classic.Level;
|
||||||
import javafx.application.Application;
|
import javafx.application.Application;
|
||||||
import javafx.fxml.FXMLLoader;
|
|
||||||
import javafx.scene.Parent;
|
|
||||||
import javafx.scene.Scene;
|
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
import seng302.models.PolarTable;
|
import org.apache.commons.cli.CommandLine;
|
||||||
import seng302.models.stream.StreamParser;
|
import org.apache.commons.cli.CommandLineParser;
|
||||||
import seng302.models.stream.StreamReceiver;
|
import org.apache.commons.cli.DefaultParser;
|
||||||
import seng302.server.ServerThread;
|
import org.apache.commons.cli.Options;
|
||||||
|
import org.apache.commons.cli.ParseException;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import seng302.discoveryServer.DiscoveryServer;
|
||||||
|
import seng302.visualiser.controllers.ViewManager;
|
||||||
|
|
||||||
public class App extends Application {
|
public class App extends Application {
|
||||||
|
|
||||||
@Override
|
private static Logger logger = LoggerFactory.getLogger(App.class);
|
||||||
public void start(Stage primaryStage) throws Exception {
|
private static boolean isRunningAsCache = false;
|
||||||
PolarTable.parsePolarFile(getClass().getResource("/config/acc_polars.csv").getFile());
|
|
||||||
|
|
||||||
Parent root = FXMLLoader.load(getClass().getResource("/views/MainView.fxml"));
|
public static void parseArgs(String[] args) throws ParseException {
|
||||||
primaryStage.setTitle("RaceVision");
|
Options options = new Options();
|
||||||
primaryStage.setScene(new Scene(root, 1530, 960));
|
CommandLineParser parser = new DefaultParser();
|
||||||
primaryStage.setMaxWidth(1530);
|
CommandLine cmd;
|
||||||
primaryStage.setMaxHeight(960);
|
|
||||||
// primaryStage.setMaximized(true);
|
|
||||||
|
|
||||||
primaryStage.show();
|
ch.qos.logback.classic.Logger rootLogger = (ch.qos.logback.classic.Logger) LoggerFactory
|
||||||
primaryStage.setOnCloseRequest(e -> {
|
.getLogger(Logger.ROOT_LOGGER_NAME);
|
||||||
StreamParser.appClose();
|
|
||||||
StreamReceiver.noMoreBytes();
|
|
||||||
System.exit(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
options.addOption("debugLevel", true, "Set the application debug level");
|
||||||
|
options.addOption("runAsDiscoveryServer", false, "Run as a discovery server");
|
||||||
|
options.addOption("discoveryDevMode", false, "Use a local discovery server");
|
||||||
|
|
||||||
|
cmd = parser.parse(options, args);
|
||||||
|
|
||||||
}
|
if (cmd.hasOption("runAsDiscoveryServer")){
|
||||||
|
isRunningAsCache = true;
|
||||||
public static void main(String[] args) {
|
rootLogger.setLevel(Level.ALL);
|
||||||
StreamReceiver sr = null;
|
|
||||||
|
|
||||||
new ServerThread("Racevision Test Server");
|
|
||||||
|
|
||||||
try {
|
|
||||||
Thread.sleep(2000);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (args.length == 1 && args[0].equals("-standalone")) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (args.length == 3 && args[0].equals("-server")) {
|
if (cmd.hasOption("discoveryDevMode")) {
|
||||||
|
DiscoveryServer.DISCOVERY_SERVER = "localhost";
|
||||||
|
}
|
||||||
|
|
||||||
sr = new StreamReceiver(args[1], Integer.valueOf(args[2]), "RaceStream");
|
if (cmd.hasOption("debugLevel")) {
|
||||||
|
|
||||||
} else if (args.length == 2 && args[0].equals("-server")) {
|
switch (cmd.getOptionValue("debugLevel")) {
|
||||||
switch (args[1]) {
|
case "DEBUG":
|
||||||
case "internal":
|
rootLogger.setLevel(Level.DEBUG);
|
||||||
sr = new StreamReceiver("localhost", 4949, "RaceStream");
|
break;
|
||||||
break;
|
|
||||||
case "staffserver":
|
case "ALL":
|
||||||
sr = new StreamReceiver("csse-s302staff.canterbury.ac.nz", 4941, "RaceStream");
|
rootLogger.setLevel(Level.ALL);
|
||||||
break;
|
break;
|
||||||
case "official":
|
|
||||||
sr = new StreamReceiver("livedata.americascup.com", 4941, "RaceStream");
|
case "WARNING":
|
||||||
break;
|
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().initialiseSplashScreen(primaryStage);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void runDiscoveryServer() throws Exception {
|
||||||
|
while (true){
|
||||||
|
try {
|
||||||
|
new DiscoveryServer();
|
||||||
|
}
|
||||||
|
catch (Exception ignored){
|
||||||
|
;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//Change the StreamReceiver in this else block to change the default data source.
|
}
|
||||||
else{
|
|
||||||
sr = new StreamReceiver("localhost", 4949, "RaceStream");
|
public static void main(String[] args) throws Exception {
|
||||||
|
// new Timer().schedule(new TimerTask() {
|
||||||
|
// @Override
|
||||||
|
// public void run() {
|
||||||
|
// System.gc();
|
||||||
|
// }
|
||||||
|
// }, 0, 1_000);
|
||||||
|
|
||||||
|
try {
|
||||||
|
parseArgs(args);
|
||||||
|
} catch (ParseException e) {
|
||||||
|
logger.error("Could not parse command line arguments");
|
||||||
}
|
}
|
||||||
|
|
||||||
sr.start();
|
if (!isRunningAsCache){
|
||||||
StreamParser streamParser = new StreamParser("StreamParser");
|
launch(args);
|
||||||
streamParser.start();
|
}
|
||||||
|
else{
|
||||||
launch(args);
|
runDiscoveryServer();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,63 +0,0 @@
|
|||||||
package seng302;
|
|
||||||
|
|
||||||
import javafx.geometry.Point2D;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A Class for performing geometric calculations on the canvas
|
|
||||||
* Created by wmu16 on 24/05/17.
|
|
||||||
*/
|
|
||||||
public final class GeometryUtils {
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Performs the line function on two points of a line and a test point to test which side of the line that point is
|
|
||||||
* on. If the return value is
|
|
||||||
* return 1, then the point is on one side of the line,
|
|
||||||
* return -1 then the point is on the other side of the line
|
|
||||||
* return 0 then the point is exactly on the line.
|
|
||||||
* @param linePoint1 One point of the line
|
|
||||||
* @param linePoint2 Second point of the line
|
|
||||||
* @param testPoint The point to test with this line
|
|
||||||
* @return A return value indicating which side of the line the point is on
|
|
||||||
*/
|
|
||||||
public static Integer lineFunction(Point2D linePoint1, Point2D linePoint2, Point2D testPoint) {
|
|
||||||
|
|
||||||
Double x = testPoint.getX();
|
|
||||||
Double y = testPoint.getY();
|
|
||||||
Double x1 = linePoint1.getX();
|
|
||||||
Double y1 = linePoint1.getY();
|
|
||||||
Double x2 = linePoint2.getX();
|
|
||||||
Double y2 = linePoint2.getY();
|
|
||||||
|
|
||||||
Double result = (x - x1)*(y2 - y1) - (y - y1)*(x2 - x1); //Line function
|
|
||||||
|
|
||||||
if (result > 0) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
else if (result < 0) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Given a point and a vector (angle and vector length) Will create a new point, that vector away from the origin
|
|
||||||
* point
|
|
||||||
* @param originPoint The point with which to use as the base for our vector addition
|
|
||||||
* @param angleInDeg (DEGREES) The angle at which our new point is being created (in degrees!)
|
|
||||||
* @param vectorLength The length out on this angle from the origin point to create the new point
|
|
||||||
* @return a Point2D
|
|
||||||
*/
|
|
||||||
public static Point2D makeArbitraryVectorPoint(Point2D originPoint, Double angleInDeg, Double vectorLength) {
|
|
||||||
|
|
||||||
Double endPointX = originPoint.getX() + vectorLength * Math.cos(Math.toRadians(angleInDeg));
|
|
||||||
Double endPointY = originPoint.getY() + vectorLength * Math.sin(Math.toRadians(angleInDeg));
|
|
||||||
|
|
||||||
return new Point2D(endPointX, endPointY);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,523 +0,0 @@
|
|||||||
package seng302.controllers;
|
|
||||||
|
|
||||||
import javafx.animation.AnimationTimer;
|
|
||||||
import javafx.beans.property.SimpleDoubleProperty;
|
|
||||||
import javafx.fxml.FXML;
|
|
||||||
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.paint.Color;
|
|
||||||
import javafx.scene.text.Font;
|
|
||||||
import seng302.models.BoatGroup;
|
|
||||||
import seng302.models.Colors;
|
|
||||||
import seng302.models.Yacht;
|
|
||||||
import seng302.models.map.Boundary;
|
|
||||||
import seng302.models.map.CanvasMap;
|
|
||||||
import seng302.models.mark.*;
|
|
||||||
import seng302.models.stream.StreamParser;
|
|
||||||
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.server.simulator.GeoUtility;
|
|
||||||
import seng302.server.simulator.mark.Position;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.PriorityBlockingQueue;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 = 720;
|
|
||||||
private final int CANVAS_HEIGHT = 720;
|
|
||||||
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<>();
|
|
||||||
|
|
||||||
//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();
|
|
||||||
// TODO: 1/05/17 wmu16 - Change this call to now draw the marks as from the xml
|
|
||||||
initializeBoats();
|
|
||||||
initializeMarks();
|
|
||||||
timer = new AnimationTimer() {
|
|
||||||
|
|
||||||
private int UPDATE_FPM_PERIOD = 50; // update FPM label every 50 frames
|
|
||||||
private int updateFPMCounter = 100;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handle(long now) {
|
|
||||||
|
|
||||||
//fps stuff
|
|
||||||
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 (updateFPMCounter++ > UPDATE_FPM_PERIOD) {
|
|
||||||
updateFPMCounter = 0;
|
|
||||||
drawFps(frameRate.intValue());
|
|
||||||
}
|
|
||||||
raceViewController.updateSparkLine();
|
|
||||||
}
|
|
||||||
updateGroups();
|
|
||||||
if (StreamParser.isRaceFinished()) {
|
|
||||||
this.stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
|
||||||
Position topLeftPos = new Position(maxLatPoint.getLatitude(), minLonPoint.getLongitude());
|
|
||||||
Position 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));
|
|
||||||
Position 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 = StreamParser.getXmlObject().getRaceXML();
|
|
||||||
ArrayList<Limit> courseLimits = raceXMLObject.getCourseLimit();
|
|
||||||
gc.setStroke(Color.DARKBLUE);
|
|
||||||
gc.setLineWidth(3);
|
|
||||||
double[] xBoundaryPoints = new double[courseLimits.size()];
|
|
||||||
double[] yBoundaryPoints = new double[courseLimits.size()];
|
|
||||||
for (int i = 0; i < courseLimits.size() - 1; i++) {
|
|
||||||
Limit thisPoint1 = courseLimits.get(i);
|
|
||||||
SingleMark thisMark1 = new SingleMark("", thisPoint1.getLat(), thisPoint1.getLng(), thisPoint1.getSeqID(), thisPoint1.getSeqID());
|
|
||||||
Limit thisPoint2 = courseLimits.get(i+1);
|
|
||||||
SingleMark thisMark2 = new SingleMark("", thisPoint2.getLat(), thisPoint2.getLng(), thisPoint2.getSeqID(), thisPoint2.getSeqID());
|
|
||||||
Point2D borderPoint1 = findScaledXY(thisMark1);
|
|
||||||
Point2D borderPoint2 = findScaledXY(thisMark2);
|
|
||||||
gc.strokeLine(borderPoint1.getX(), borderPoint1.getY(),
|
|
||||||
borderPoint2.getX(), borderPoint2.getY());
|
|
||||||
xBoundaryPoints[i] = borderPoint1.getX();
|
|
||||||
yBoundaryPoints[i] = borderPoint1.getY();
|
|
||||||
}
|
|
||||||
Limit thisPoint1 = courseLimits.get(courseLimits.size()-1);
|
|
||||||
SingleMark thisMark1 = new SingleMark("", thisPoint1.getLat(), thisPoint1.getLng(), thisPoint1.getSeqID(), thisPoint1.getSeqID());
|
|
||||||
Limit thisPoint2 = courseLimits.get(0);
|
|
||||||
SingleMark thisMark2 = new SingleMark("", thisPoint2.getLat(), thisPoint2.getLng(), thisPoint2.getSeqID(), thisPoint2.getSeqID());
|
|
||||||
Point2D borderPoint1 = findScaledXY(thisMark1);
|
|
||||||
Point2D borderPoint2 = findScaledXY(thisMark2);
|
|
||||||
gc.strokeLine(borderPoint1.getX(), borderPoint1.getY(),
|
|
||||||
borderPoint2.getX(), borderPoint2.getY());
|
|
||||||
xBoundaryPoints[courseLimits.size()-1] = borderPoint1.getX();
|
|
||||||
yBoundaryPoints[courseLimits.size()-1] = borderPoint1.getY();
|
|
||||||
// gc.setFill(Color.LIGHTBLUE);
|
|
||||||
// gc.fillPolygon(xBoundaryPoints,yBoundaryPoints,yBoundaryPoints.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 (StreamParser.boatLocations.containsKey(boatGroup.getRaceId())) {
|
|
||||||
if (boatGroup.isStopped()) {
|
|
||||||
updateBoatGroup(boatGroup);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
boatGroup.move();
|
|
||||||
}
|
|
||||||
for (MarkGroup markGroup : markGroups) {
|
|
||||||
for (Long id : markGroup.getRaceIds()) {
|
|
||||||
if (StreamParser.markLocations.containsKey(id)) {
|
|
||||||
updateMarkGroup(id, markGroup);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
checkForCourseChanges();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkForCourseChanges() {
|
|
||||||
if (StreamParser.isNewRaceXmlReceived()){
|
|
||||||
gc.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
|
|
||||||
drawGoogleMap();
|
|
||||||
addRaceBorder();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateBoatGroup(BoatGroup boatGroup) {
|
|
||||||
PriorityBlockingQueue<BoatPositionPacket> movementQueue = StreamParser.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, boatGroup.getRaceId());
|
|
||||||
} catch (InterruptedException e){
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void updateMarkGroup (long raceId, MarkGroup markGroup) {
|
|
||||||
PriorityBlockingQueue<BoatPositionPacket> movementQueue = StreamParser.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){
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Draws all the boats.
|
|
||||||
*/
|
|
||||||
private void initializeBoats() {
|
|
||||||
Map<Integer, Yacht> boats = StreamParser.getBoats();
|
|
||||||
Group boatAnnotations = new Group();
|
|
||||||
|
|
||||||
ArrayList<Participant> participants = StreamParser.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());
|
|
||||||
boatGroups.add(boatGroup);
|
|
||||||
boatAnnotations.getChildren().add(boatGroup.getLowPriorityAnnotations());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
group.getChildren().add(boatAnnotations);
|
|
||||||
group.getChildren().addAll(boatGroups);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initializeMarks() {
|
|
||||||
List<Mark> allMarks = StreamParser.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()){
|
|
||||||
gc.clearRect(5, 5, 60, 30);
|
|
||||||
gc.setFont(new Font(16));
|
|
||||||
gc.setLineWidth(4);
|
|
||||||
gc.setGlobalAlpha(0.75);
|
|
||||||
gc.fillText(fps + " FPS", 5, 20);
|
|
||||||
gc.setGlobalAlpha(0.5);
|
|
||||||
} else {
|
|
||||||
gc.clearRect(5,5,60,30);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
|
||||||
StreamParser.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 : StreamParser.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,39 +0,0 @@
|
|||||||
package seng302.controllers;
|
|
||||||
|
|
||||||
import javafx.fxml.FXML;
|
|
||||||
import javafx.fxml.FXMLLoader;
|
|
||||||
import javafx.fxml.Initializable;
|
|
||||||
import javafx.scene.layout.AnchorPane;
|
|
||||||
import javafx.scene.layout.Pane;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.util.ResourceBundle;
|
|
||||||
import seng302.models.stream.StreamParser;
|
|
||||||
|
|
||||||
public class Controller implements Initializable {
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private AnchorPane contentPane;
|
|
||||||
|
|
||||||
private void setContentPane(String jfxUrl) {
|
|
||||||
try {
|
|
||||||
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.err.println(e.getCause());
|
|
||||||
} catch (IOException e) {
|
|
||||||
System.err.println(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void initialize(URL location, ResourceBundle resources) {
|
|
||||||
contentPane.getStylesheets().add(getClass().getResource("/css/master.css").toString());
|
|
||||||
setContentPane("/views/StartScreenView.fxml");
|
|
||||||
StreamParser.boatLocations.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,685 +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.geometry.Side;
|
|
||||||
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.GeometryUtils;
|
|
||||||
import seng302.controllers.annotations.Annotation;
|
|
||||||
import seng302.controllers.annotations.ImportantAnnotationController;
|
|
||||||
import seng302.controllers.annotations.ImportantAnnotationDelegate;
|
|
||||||
import seng302.controllers.annotations.ImportantAnnotationsState;
|
|
||||||
import seng302.models.*;
|
|
||||||
import seng302.models.mark.GateMark;
|
|
||||||
import seng302.models.mark.Mark;
|
|
||||||
import seng302.models.mark.MarkGroup;
|
|
||||||
import seng302.models.mark.SingleMark;
|
|
||||||
import seng302.models.stream.StreamParser;
|
|
||||||
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 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<>(StreamParser.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) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
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();
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
// 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 = StreamParser.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 : StreamParser.getXmlObject().getRaceXML().getAllCompoundMarks()) {
|
|
||||||
if (mark.getCompoundMarkID() == thisCompoundMarkID) {
|
|
||||||
return mark;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the wind direction arrow and text as from info from the StreamParser
|
|
||||||
*/
|
|
||||||
private void updateWindDirection() {
|
|
||||||
windDirectionText.setText(String.format("%.1f°", StreamParser.getWindDirection()));
|
|
||||||
windArrowText.setRotate(StreamParser.getWindDirection());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the clock for the race
|
|
||||||
*/
|
|
||||||
private void updateRaceTime() {
|
|
||||||
if (StreamParser.isRaceFinished()) {
|
|
||||||
timerLabel.setFill(Color.RED);
|
|
||||||
timerLabel.setText("Race Finished!");
|
|
||||||
} else {
|
|
||||||
timerLabel.setText(getTimeSinceStartOfRace());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Grabs the boats currently in the race as from the StreamParser and sets them to be selectable
|
|
||||||
* in the boat selection combo box
|
|
||||||
*/
|
|
||||||
private void updateBoatSelectionComboBox() {
|
|
||||||
ObservableList<Yacht> observableBoats = FXCollections
|
|
||||||
.observableArrayList(StreamParser.getBoatsPos().values());
|
|
||||||
boatSelectionComboBox.setItems(observableBoats);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the order of the boats as from the StreamParser 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 = StreamParser.getXmlObject().getRaceXML()
|
|
||||||
.getParticipants();
|
|
||||||
ArrayList<Integer> participantIDs = new ArrayList<>();
|
|
||||||
for (Participant p : participants) {
|
|
||||||
participantIDs.add(p.getsourceID());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (StreamParser.isRaceStarted()) {
|
|
||||||
for (Yacht boat : StreamParser.getBoatsPos().values()) {
|
|
||||||
if (participantIDs.contains(boat.getSourceID())) { // check if the boat is racing
|
|
||||||
if (boat.getBoatStatus() == 3) { // 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for (Yacht boat : StreamParser.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(StreamParser.getWindSpeed());
|
|
||||||
} else {
|
|
||||||
angleAndSpeed = PolarTable.getOptimalDownwindVMG(StreamParser.getWindSpeed());
|
|
||||||
}
|
|
||||||
|
|
||||||
Double resultingAngle = angleAndSpeed.keySet().iterator().next();
|
|
||||||
|
|
||||||
|
|
||||||
Point2D boatCurrentPos = new Point2D(bg.getBoatLayoutX(), bg.getBoatLayoutY());
|
|
||||||
Point2D gateMidPoint = markPoint1.midpoint(markPoint2);
|
|
||||||
Integer lineFuncResult = GeometryUtils.lineFunction(boatCurrentPos, gateMidPoint, markPoint2);
|
|
||||||
Line rightLayline = new Line();
|
|
||||||
Line leftLayline = new Line();
|
|
||||||
if (lineFuncResult == 1) {
|
|
||||||
rightLayline = makeRightLayline(markPoint2, 180 - resultingAngle, StreamParser.getWindDirection());
|
|
||||||
leftLayline = makeLeftLayline(markPoint1, 180 - resultingAngle, StreamParser.getWindDirection());
|
|
||||||
} else if (lineFuncResult == -1) {
|
|
||||||
rightLayline = makeRightLayline(markPoint1, 180 - resultingAngle, StreamParser.getWindDirection());
|
|
||||||
leftLayline = makeLeftLayline(markPoint2, 180 - resultingAngle, StreamParser.getWindDirection());
|
|
||||||
}
|
|
||||||
|
|
||||||
leftLayline.setStrokeWidth(0.5);
|
|
||||||
leftLayline.setStroke(bg.getBoat().getColour());
|
|
||||||
|
|
||||||
rightLayline.setStrokeWidth(0.5);
|
|
||||||
rightLayline.setStroke(bg.getBoat().getColour());
|
|
||||||
|
|
||||||
bg.setLaylines(leftLayline, rightLayline);
|
|
||||||
mg.addLaylines(leftLayline, rightLayline);
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private Point2D getPointRotation(Point2D ref, Double distance, Double angle){
|
|
||||||
Double newX = ref.getX() + (ref.getX() + distance -ref.getX())*Math.cos(angle) - (ref.getY() + distance -ref.getY())*Math.sin(angle);
|
|
||||||
Double newY = ref.getY() + (ref.getX() + distance -ref.getX())*Math.sin(angle) + (ref.getY() + distance -ref.getY())*Math.cos(angle);
|
|
||||||
|
|
||||||
return new Point2D(newX, newY);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public Line makeLeftLayline(Point2D startPoint, Double layLineAngle, Double baseAngle) {
|
|
||||||
|
|
||||||
Point2D ep = getPointRotation(startPoint, 50.0, baseAngle + layLineAngle);
|
|
||||||
Line line = new Line(startPoint.getX(), startPoint.getY(), ep.getX(), ep.getY());
|
|
||||||
return line;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public Line makeRightLayline(Point2D startPoint, Double layLineAngle, Double baseAngle) {
|
|
||||||
|
|
||||||
Point2D ep = getPointRotation(startPoint, 50.0, baseAngle - layLineAngle);
|
|
||||||
Line line = new Line(startPoint.getX(), startPoint.getY(), ep.getX(), ep.getY());
|
|
||||||
return line;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialised the combo box with any 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 (StreamParser.getTimeSinceStart() > 0) {
|
|
||||||
String timerMinute = Long.toString(StreamParser.getTimeSinceStart() / 60);
|
|
||||||
String timerSecond = Long.toString(StreamParser.getTimeSinceStart() % 60);
|
|
||||||
if (timerSecond.length() == 1) {
|
|
||||||
timerSecond = "0" + timerSecond;
|
|
||||||
}
|
|
||||||
timerString = "-" + timerMinute + ":" + timerSecond;
|
|
||||||
} else {
|
|
||||||
String timerMinute = Long.toString(-1 * StreamParser.getTimeSinceStart() / 60);
|
|
||||||
String timerSecond = Long.toString(-1 * StreamParser.getTimeSinceStart() % 60);
|
|
||||||
if (timerSecond.length() == 1) {
|
|
||||||
timerSecond = "0" + timerSecond;
|
|
||||||
}
|
|
||||||
timerString = timerMinute + ":" + timerSecond;
|
|
||||||
}
|
|
||||||
return timerString;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
boolean isDisplayFps() {
|
|
||||||
return displayFps;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Display the important annotations for a specific BoatGroup
|
|
||||||
* @param bg The boat group to set the annotations for
|
|
||||||
*/
|
|
||||||
private void setBoatGroupImportantAnnotations(BoatGroup bg) {
|
|
||||||
if (importantAnnotations.getAnnotationState(Annotation.NAME)) {
|
|
||||||
bg.setTeamNameObjectVisible(true);
|
|
||||||
} else {
|
|
||||||
bg.setTeamNameObjectVisible(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (importantAnnotations.getAnnotationState(Annotation.SPEED)) {
|
|
||||||
bg.setVelocityObjectVisible(true);
|
|
||||||
} else {
|
|
||||||
bg.setVelocityObjectVisible(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (importantAnnotations.getAnnotationState(Annotation.TRACK)) {
|
|
||||||
bg.setLineGroupVisible(true);
|
|
||||||
} else {
|
|
||||||
bg.setLineGroupVisible(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (importantAnnotations.getAnnotationState(Annotation.WAKE)) {
|
|
||||||
bg.setWakeVisible(true);
|
|
||||||
} else {
|
|
||||||
bg.setWakeVisible(false);
|
|
||||||
}
|
|
||||||
//TODO fix boat annotations with new boatgroup
|
|
||||||
if (importantAnnotations.getAnnotationState(Annotation.ESTTIMETONEXTMARK)) {
|
|
||||||
bg.setEstTimeToNextMarkObjectVisible(true);
|
|
||||||
} else {
|
|
||||||
bg.setEstTimeToNextMarkObjectVisible(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (importantAnnotations.getAnnotationState(Annotation.LEGTIME)) {
|
|
||||||
bg.setLegTimeObjectVisible(true);
|
|
||||||
} else {
|
|
||||||
bg.setLegTimeObjectVisible(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setAnnotations(Integer annotationLevel) {
|
|
||||||
switch (annotationLevel) {
|
|
||||||
// No Annotations
|
|
||||||
case 0:
|
|
||||||
for (BoatGroup bg : includedCanvasController.getBoatGroups()) {
|
|
||||||
bg.setTeamNameObjectVisible(false);
|
|
||||||
bg.setVelocityObjectVisible(false);
|
|
||||||
bg.setEstTimeToNextMarkObjectVisible(false);
|
|
||||||
bg.setLegTimeObjectVisible(false);
|
|
||||||
bg.setLineGroupVisible(false);
|
|
||||||
bg.setWakeVisible(false);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
// Important Annotations
|
|
||||||
case 1:
|
|
||||||
for (BoatGroup bg : includedCanvasController.getBoatGroups()) {
|
|
||||||
setBoatGroupImportantAnnotations(bg);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
// All Annotations
|
|
||||||
case 2:
|
|
||||||
for (BoatGroup bg : includedCanvasController.getBoatGroups()) {
|
|
||||||
bg.setTeamNameObjectVisible(true);
|
|
||||||
bg.setVelocityObjectVisible(true);
|
|
||||||
bg.setEstTimeToNextMarkObjectVisible(true);
|
|
||||||
bg.setLegTimeObjectVisible(true);
|
|
||||||
bg.setLineGroupVisible(true);
|
|
||||||
bg.setWakeVisible(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,188 +0,0 @@
|
|||||||
package seng302.controllers;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.ResourceBundle;
|
|
||||||
import java.util.Timer;
|
|
||||||
import java.util.TimerTask;
|
|
||||||
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.Label;
|
|
||||||
import javafx.scene.control.TableColumn;
|
|
||||||
import javafx.scene.control.TableView;
|
|
||||||
import javafx.scene.control.cell.PropertyValueFactory;
|
|
||||||
import javafx.scene.layout.AnchorPane;
|
|
||||||
import javafx.scene.layout.GridPane;
|
|
||||||
import javafx.scene.layout.Pane;
|
|
||||||
import javafx.scene.paint.Color;
|
|
||||||
import seng302.models.Yacht;
|
|
||||||
import seng302.models.stream.StreamParser;
|
|
||||||
import seng302.models.stream.XMLParser.RaceXMLObject.Participant;
|
|
||||||
|
|
||||||
public class StartScreenController implements Initializable {
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private GridPane gridPane;
|
|
||||||
@FXML
|
|
||||||
private Label timeTillLive;
|
|
||||||
@FXML
|
|
||||||
private Button streamButton;
|
|
||||||
@FXML
|
|
||||||
private Button switchToRaceViewButton;
|
|
||||||
@FXML
|
|
||||||
private TableView<Yacht> teamList;
|
|
||||||
@FXML
|
|
||||||
private TableColumn<Yacht, String> boatNameCol;
|
|
||||||
@FXML
|
|
||||||
private TableColumn<Yacht, String> shortNameCol;
|
|
||||||
@FXML
|
|
||||||
private TableColumn<Yacht, String> countryCol;
|
|
||||||
@FXML
|
|
||||||
private TableColumn<Yacht, String> posCol;
|
|
||||||
@FXML
|
|
||||||
private Label realTime;
|
|
||||||
|
|
||||||
private boolean switchedToRaceView = false;
|
|
||||||
|
|
||||||
private void setContentPane(String jfxUrl) {
|
|
||||||
try {
|
|
||||||
// get the main controller anchor pane (MainView.fxml)
|
|
||||||
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(jfxUrl)));
|
|
||||||
} catch (javafx.fxml.LoadException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void initialize(URL location, ResourceBundle resources) {
|
|
||||||
gridPane.getStylesheets().add(getClass().getResource("/css/master.css").toString());
|
|
||||||
teamList.getStylesheets().add(getClass().getResource("/css/master.css").toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Running a timer to update the livestream status on welcome screen. Update interval is 1
|
|
||||||
* second.
|
|
||||||
*/
|
|
||||||
public void startStream() {
|
|
||||||
if (StreamParser.isStreamStatus()) {
|
|
||||||
streamButton.setVisible(false);
|
|
||||||
realTime.setVisible(true);
|
|
||||||
timeTillLive.setVisible(true);
|
|
||||||
timeTillLive.setTextFill(Color.GREEN);
|
|
||||||
timeTillLive.setText("Connecting...");
|
|
||||||
Timer timer = new Timer();
|
|
||||||
timer.scheduleAtFixedRate(new TimerTask() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
Platform.runLater(() -> {
|
|
||||||
if (StreamParser.isRaceStarted()) {
|
|
||||||
if (!switchedToRaceView) {
|
|
||||||
switchToRaceView();
|
|
||||||
}
|
|
||||||
timer.cancel();
|
|
||||||
}
|
|
||||||
if (StreamParser.isRaceFinished()) {
|
|
||||||
realTime.setText(StreamParser.getCurrentTimeString());
|
|
||||||
timeTillLive.setTextFill(Color.RED);
|
|
||||||
timeTillLive.setText("Race finished! Waiting for new race...");
|
|
||||||
switchToRaceViewButton.setDisable(true);
|
|
||||||
} else if (StreamParser.getTimeSinceStart() > 0) {
|
|
||||||
realTime.setText(StreamParser.getCurrentTimeString());
|
|
||||||
updateTeamList();
|
|
||||||
timeTillLive.setTextFill(Color.RED);
|
|
||||||
switchToRaceViewButton.setDisable(false);
|
|
||||||
String timerMinute = Long
|
|
||||||
.toString(StreamParser.getTimeSinceStart() / 60);
|
|
||||||
String timerSecond = Long
|
|
||||||
.toString(StreamParser.getTimeSinceStart() % 60);
|
|
||||||
if (timerSecond.length() == 1) {
|
|
||||||
timerSecond = "0" + timerSecond;
|
|
||||||
}
|
|
||||||
String timerString = "-" + timerMinute + ":" + timerSecond;
|
|
||||||
timeTillLive.setText(timerString);
|
|
||||||
} else {
|
|
||||||
realTime.setText(StreamParser.getCurrentTimeString());
|
|
||||||
updateTeamList();
|
|
||||||
timeTillLive.setTextFill(Color.BLACK);
|
|
||||||
switchToRaceViewButton.setDisable(false);
|
|
||||||
String timerMinute = Long
|
|
||||||
.toString(-1 * StreamParser.getTimeSinceStart() / 60);
|
|
||||||
String timerSecond = Long
|
|
||||||
.toString(-1 * StreamParser.getTimeSinceStart() % 60);
|
|
||||||
if (timerSecond.length() == 1) {
|
|
||||||
timerSecond = "0" + timerSecond;
|
|
||||||
}
|
|
||||||
String timerString = timerMinute + ":" + timerSecond;
|
|
||||||
timeTillLive.setText(timerString);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, 0, 1000);
|
|
||||||
} else {
|
|
||||||
timeTillLive.setText("Stream not available.");
|
|
||||||
timeTillLive.setTextFill(Color.RED);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void switchToRaceView() {
|
|
||||||
StreamParser.boatLocations.clear();
|
|
||||||
switchedToRaceView = true;
|
|
||||||
setContentPane("/views/RaceView.fxml");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateTeamList() {
|
|
||||||
ObservableList<Yacht> data = FXCollections.observableArrayList();
|
|
||||||
|
|
||||||
teamList.setItems(data);
|
|
||||||
|
|
||||||
boatNameCol.setCellValueFactory(
|
|
||||||
new PropertyValueFactory<>("boatName")
|
|
||||||
);
|
|
||||||
shortNameCol.setCellValueFactory(
|
|
||||||
new PropertyValueFactory<>("shortName")
|
|
||||||
);
|
|
||||||
countryCol.setCellValueFactory(
|
|
||||||
new PropertyValueFactory<>("country")
|
|
||||||
);
|
|
||||||
posCol.setCellValueFactory(
|
|
||||||
new PropertyValueFactory<>("position")
|
|
||||||
);
|
|
||||||
|
|
||||||
// check if the boat is racing
|
|
||||||
ArrayList<Participant> participants = StreamParser.getXmlObject().getRaceXML()
|
|
||||||
.getParticipants();
|
|
||||||
ArrayList<Integer> participantIDs = new ArrayList<>();
|
|
||||||
for (Participant p : participants) {
|
|
||||||
participantIDs.add(p.getsourceID());
|
|
||||||
}
|
|
||||||
|
|
||||||
// add boats to the start screen list
|
|
||||||
if (StreamParser.isRaceStarted()) { // if race is started, use StreamParser.getBoatsPos()
|
|
||||||
for (Yacht boat : StreamParser.getBoatsPos().values()) {
|
|
||||||
if (participantIDs.contains(boat.getSourceID())) {
|
|
||||||
data.add(boat);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else { // else use StreamParser.getBoats()
|
|
||||||
for (Yacht boat : StreamParser.getBoats().values()) {
|
|
||||||
if (participantIDs.contains(boat.getSourceID())) {
|
|
||||||
data.add(boat);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
teamList.refresh();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,170 @@
|
|||||||
|
package seng302.discoveryServer;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import seng302.gameServer.messages.Message;
|
||||||
|
import seng302.gameServer.messages.RoomCodeRequest;
|
||||||
|
import seng302.gameServer.messages.ServerRegistrationMessage;
|
||||||
|
import seng302.model.stream.packets.PacketType;
|
||||||
|
import seng302.discoveryServer.util.ServerListing;
|
||||||
|
import seng302.discoveryServer.util.ServerRepoStreamParser;
|
||||||
|
import seng302.discoveryServer.util.ServerTable;
|
||||||
|
import seng302.visualiser.ServerListener;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.ServerSocket;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.Timer;
|
||||||
|
|
||||||
|
public class DiscoveryServer {
|
||||||
|
public static final String ANSI_GREEN = "\u001B[32m";
|
||||||
|
public static final String ANSI_YELLOW = "\u001B[33m";
|
||||||
|
public static final String ANSI_BLUE = "\u001B[34m";
|
||||||
|
public static final String ANSI_RESET = "\u001B[0m";
|
||||||
|
private static final int MAX_SERVER_TRIES = 10;
|
||||||
|
public static String DISCOVERY_SERVER = "party.sydney.srv.michaelrausch.nz";
|
||||||
|
|
||||||
|
private ServerTable serverTable;
|
||||||
|
public static final Integer PORT_NUMBER = 9969;
|
||||||
|
private ServerSocket serverSocket;
|
||||||
|
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(DiscoveryServer.class);
|
||||||
|
|
||||||
|
private void displayHeader(){
|
||||||
|
String selectedColor = Arrays.asList(ANSI_BLUE, ANSI_GREEN, ANSI_YELLOW).get(new Random().nextInt(2));
|
||||||
|
System.out.println(selectedColor);
|
||||||
|
System.out.println(" .ccccc. \n" +
|
||||||
|
" .cc;'coooxkl;. \n" +
|
||||||
|
" .:c:::c:,,,,,;c;;,.'. \n" +
|
||||||
|
" .clc,',:,..:xxocc;'..c; \n" +
|
||||||
|
" .c:,';:ox:..:c,,,,,,...cd, \n" +
|
||||||
|
" .c:'.,oxxxxl::l:.,loll;..;ol. \n" +
|
||||||
|
" ;Oc..:xxxxxxxxx:.,llll,....oc \n" +
|
||||||
|
" .,;,',:loxxxxxxxxx:.,llll;.,,.'ld, \n" +
|
||||||
|
" .lo;..:xxxxxxxxxxxx:.'cllc,.:l:'cO; \n" +
|
||||||
|
" .:;...'cxxxxxxxxxxxxoc;,::,..cdl;;l' \n" +
|
||||||
|
" .cl;':,'';oxxxxxxdxxxxxx:....,cooc,cO; \n" +
|
||||||
|
" .,,,::;,lxoc:,,:lxxxxxxxxxxxo:,,;lxxl;'oNc \n" +
|
||||||
|
" .cdxo;':lxxxxxxc'';cccccoxxxxxxxxxxxxo,.;lc. " + ANSI_YELLOW + "Party-Parrots-At-Sea Discovery Server v1.0.0 (Release) " + selectedColor +"\n" +
|
||||||
|
" .loc'.'lxxxxxxxxocc;''''';ccoxxxxxxxxx:..oc \n" +
|
||||||
|
"olc,..',:cccccccccccc:;;;;;;;;:ccccccccc,.'c, \n" +
|
||||||
|
"Ol;......................................;l' ");
|
||||||
|
System.out.println(ANSI_RESET);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DiscoveryServer() throws Exception {
|
||||||
|
displayHeader();
|
||||||
|
serverTable = new ServerTable();
|
||||||
|
|
||||||
|
try{
|
||||||
|
serverSocket = new ServerSocket(PORT_NUMBER);
|
||||||
|
}
|
||||||
|
catch(java.net.BindException e){
|
||||||
|
logger.error("FATAL - Could not bind socket, are you sure there isn't already an instance running?");
|
||||||
|
System.exit(1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info("Started successfully - Now accepting connections");
|
||||||
|
|
||||||
|
try{
|
||||||
|
while (true){
|
||||||
|
Socket clientSocket = serverSocket.accept();
|
||||||
|
|
||||||
|
parseRequest(clientSocket);
|
||||||
|
|
||||||
|
clientSocket.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e){
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void parseRequest(Socket clientSocket) throws Exception {
|
||||||
|
ServerRepoStreamParser parser = new ServerRepoStreamParser(clientSocket.getInputStream());
|
||||||
|
|
||||||
|
if (clientSocket.isConnected() && !clientSocket.isClosed()){
|
||||||
|
PacketType parsePacketResult = parser.parse();
|
||||||
|
|
||||||
|
switch (parsePacketResult){
|
||||||
|
case SERVER_REGISTRATION:
|
||||||
|
ServerListing listing = parser.getServerListing();
|
||||||
|
|
||||||
|
if (!serverTable.getAllServers().contains(listing)){
|
||||||
|
listing.setRoomCode(serverTable.getNextRoomCode().toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
serverTable.addServer(listing);
|
||||||
|
|
||||||
|
Message serverRegMessage = new RoomCodeRequest(listing.getRoomCode());
|
||||||
|
clientSocket.getOutputStream().write(serverRegMessage.getBuffer());
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ROOM_CODE_REQUEST:
|
||||||
|
String desiredRoomCode = parser.getRoomCode();
|
||||||
|
ServerListing serverListing;
|
||||||
|
|
||||||
|
if (desiredRoomCode.equals("0000")){
|
||||||
|
serverListing = getRandomFreeServer();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
serverListing = serverTable.getServerByRoomCode(desiredRoomCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
Message response;
|
||||||
|
|
||||||
|
if (serverListing != null){
|
||||||
|
response = new ServerRegistrationMessage(serverListing.getServerName(), serverListing.getMapName(), serverListing.getAddress(), serverListing.getPortNumber(), 0, 0, desiredRoomCode);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
response = ServerRegistrationMessage.getEmptyRegistration();
|
||||||
|
}
|
||||||
|
|
||||||
|
clientSocket.getOutputStream().write(response.getBuffer());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServerListing getRandomFreeServer() {
|
||||||
|
ServerListing serverToJoin;
|
||||||
|
|
||||||
|
List<ServerListing> servers = serverTable.getAllServers();
|
||||||
|
|
||||||
|
if (servers.size() <= 0){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (servers.size() == 1){
|
||||||
|
return servers.get(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
serverToJoin = servers.get(new Random().nextInt(servers.size()));
|
||||||
|
|
||||||
|
int tries = 0;
|
||||||
|
|
||||||
|
while (serverToJoin != null && serverToJoin.isMaxPlayersReached() && tries < MAX_SERVER_TRIES){
|
||||||
|
serverToJoin = servers.get(new Random().nextInt(servers.size()));
|
||||||
|
tries++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (serverToJoin != null && serverToJoin.isMaxPlayersReached()){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return serverToJoin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void close(){
|
||||||
|
try {
|
||||||
|
serverSocket.close();
|
||||||
|
} catch (IOException ignored) {
|
||||||
|
;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,199 @@
|
|||||||
|
package seng302.discoveryServer;
|
||||||
|
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import seng302.discoveryServer.util.ServerListing;
|
||||||
|
import seng302.discoveryServer.util.ServerRepoStreamParser;
|
||||||
|
import seng302.gameServer.messages.Message;
|
||||||
|
import seng302.gameServer.messages.RoomCodeRequest;
|
||||||
|
import seng302.gameServer.messages.ServerRegistrationMessage;
|
||||||
|
import seng302.model.stream.packets.PacketType;
|
||||||
|
import seng302.visualiser.controllers.ViewManager;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.Timer;
|
||||||
|
import java.util.TimerTask;
|
||||||
|
|
||||||
|
public class DiscoveryServerClient {
|
||||||
|
private final Integer UPDATE_INTERVAL_MS = 1000;
|
||||||
|
|
||||||
|
private static String roomCode = null;
|
||||||
|
private Timer serverListingUpdateTimer;
|
||||||
|
private Logger logger = LoggerFactory.getLogger(DiscoveryServerClient.class);
|
||||||
|
private String ip = "";
|
||||||
|
private Boolean isInInvalidState = false;
|
||||||
|
|
||||||
|
public DiscoveryServerClient() {
|
||||||
|
try {
|
||||||
|
ip = getInetIpAddr();
|
||||||
|
} catch (Exception e) {
|
||||||
|
failError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getInetIp(){
|
||||||
|
return ip;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void failError() {
|
||||||
|
isInInvalidState = true;
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
ViewManager.getInstance().showErrorSnackBar("You do not appear to be able to connect to the internet. Matchmaking will be unavailable.");
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean didFail(){
|
||||||
|
return isInInvalidState;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register the server with the discovery server
|
||||||
|
* @param serverListing The listing to register
|
||||||
|
*/
|
||||||
|
public void register(ServerListing serverListing){
|
||||||
|
if (isInInvalidState) return;
|
||||||
|
|
||||||
|
if (serverListingUpdateTimer != null){
|
||||||
|
serverListingUpdateTimer.cancel();
|
||||||
|
serverListingUpdateTimer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
serverListingUpdateTimer = new Timer();
|
||||||
|
|
||||||
|
serverListingUpdateTimer.schedule(new TimerTask() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
sendRegistrationUpdate(serverListing);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.debug("Could not update server listing");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 0, UPDATE_INTERVAL_MS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop updating the server registration updates
|
||||||
|
*/
|
||||||
|
public void unregister(){
|
||||||
|
if (serverListingUpdateTimer != null)
|
||||||
|
serverListingUpdateTimer.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the connection information for a server given a room code
|
||||||
|
*
|
||||||
|
* @param roomCode The room code to search for
|
||||||
|
* @return The ServerListing, or null if there was an error
|
||||||
|
* @throws Exception .
|
||||||
|
*/
|
||||||
|
public ServerListing getServerForRoomCode(String roomCode) throws Exception {
|
||||||
|
Socket socket = new Socket(DiscoveryServer.DISCOVERY_SERVER, DiscoveryServer.PORT_NUMBER);
|
||||||
|
ServerRepoStreamParser parser = new ServerRepoStreamParser(socket.getInputStream());
|
||||||
|
|
||||||
|
Message request = new RoomCodeRequest(roomCode); //roomCode);
|
||||||
|
socket.getOutputStream().write(request.getBuffer());
|
||||||
|
|
||||||
|
PacketType packetType = parser.parse();
|
||||||
|
|
||||||
|
if (packetType != PacketType.SERVER_REGISTRATION){
|
||||||
|
logger.debug("Wrong packet received in response to a room code request");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.close();
|
||||||
|
|
||||||
|
return parser.getServerListing();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServerListing getRandomServer() throws Exception {
|
||||||
|
Socket socket = new Socket(DiscoveryServer.DISCOVERY_SERVER, DiscoveryServer.PORT_NUMBER);
|
||||||
|
ServerRepoStreamParser parser = new ServerRepoStreamParser(socket.getInputStream());
|
||||||
|
|
||||||
|
Message request = new RoomCodeRequest("0000");
|
||||||
|
socket.getOutputStream().write(request.getBuffer());
|
||||||
|
|
||||||
|
PacketType packetType = parser.parse();
|
||||||
|
|
||||||
|
if (packetType != PacketType.SERVER_REGISTRATION){
|
||||||
|
logger.error("Incorrect packet type received");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.close();
|
||||||
|
|
||||||
|
ServerListing serverListing = parser.getServerListing();
|
||||||
|
|
||||||
|
if (serverListing == null || serverListing.equals(ServerRegistrationMessage.getEmptyRegistration())){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return serverListing;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a registration update to the discovery server.
|
||||||
|
*
|
||||||
|
* @param serverListing The server listing to send
|
||||||
|
* @throws Exception IF there was an error sending the update
|
||||||
|
*/
|
||||||
|
private void sendRegistrationUpdate(ServerListing serverListing) throws Exception {
|
||||||
|
Socket socket = new Socket(DiscoveryServer.DISCOVERY_SERVER, DiscoveryServer.PORT_NUMBER);
|
||||||
|
ServerRepoStreamParser parser = new ServerRepoStreamParser(socket.getInputStream());
|
||||||
|
|
||||||
|
Message req = new ServerRegistrationMessage(serverListing);
|
||||||
|
|
||||||
|
socket.getOutputStream().write(req.getBuffer());
|
||||||
|
|
||||||
|
PacketType packetType = parser.parse();
|
||||||
|
|
||||||
|
if (packetType != PacketType.ROOM_CODE_REQUEST){
|
||||||
|
socket.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String roomCode = parser.getRoomCode();
|
||||||
|
|
||||||
|
if (roomCode.length() != 0){
|
||||||
|
DiscoveryServerClient.roomCode = roomCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The last room code received by the client
|
||||||
|
*/
|
||||||
|
public static String getRoomCode(){
|
||||||
|
return roomCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getInetIpAddr() throws Exception {
|
||||||
|
URL myIp = new URL("http://checkip.amazonaws.com");
|
||||||
|
BufferedReader in = null;
|
||||||
|
try {
|
||||||
|
in = new BufferedReader(new InputStreamReader(
|
||||||
|
myIp.openStream()));
|
||||||
|
String ip = in.readLine();
|
||||||
|
return ip;
|
||||||
|
} finally {
|
||||||
|
if (in != null) {
|
||||||
|
try {
|
||||||
|
in.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package seng302.discoveryServer.util;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
public class ReadableByteInputStream {
|
||||||
|
private InputStream is;
|
||||||
|
|
||||||
|
public ReadableByteInputStream(InputStream is){
|
||||||
|
this.is = is;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get n bytes from the input stream
|
||||||
|
* @param n number of bytes
|
||||||
|
* @return the bytes read
|
||||||
|
* @throws Exception .
|
||||||
|
*/
|
||||||
|
public byte[] getBytes(int n) throws Exception {
|
||||||
|
byte[] bytes = new byte[n];
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
bytes[i] = (byte) readByte();
|
||||||
|
}
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Skip n bytes
|
||||||
|
* @param n number of bytes to skip
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public void skipBytes(long n) throws Exception {
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
readByte();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read the next byte from the stream
|
||||||
|
* @return The byte that was read
|
||||||
|
* @throws Exception .
|
||||||
|
*/
|
||||||
|
public int readByte() throws Exception {
|
||||||
|
int currentByte = is.read();
|
||||||
|
|
||||||
|
if (currentByte == -1) {
|
||||||
|
throw new Exception();
|
||||||
|
}
|
||||||
|
return currentByte;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
package seng302.discoveryServer.util;
|
||||||
|
|
||||||
|
public class ServerListing {
|
||||||
|
public final static int SERVER_TTL_DEFAULT = 5;
|
||||||
|
|
||||||
|
private String serverName = "";
|
||||||
|
private String mapName = "";
|
||||||
|
private String address = "";
|
||||||
|
private int portNumber = 0;
|
||||||
|
private int capacity = 0;
|
||||||
|
private int players = 0;
|
||||||
|
private String roomCode = "";
|
||||||
|
private int ttl = SERVER_TTL_DEFAULT;
|
||||||
|
|
||||||
|
|
||||||
|
public ServerListing(String serverName, String mapName, String address, int portNumber, int capacity){
|
||||||
|
this.serverName = serverName;
|
||||||
|
this.mapName = mapName;
|
||||||
|
this.address = address;
|
||||||
|
this.portNumber = portNumber;
|
||||||
|
this.capacity = capacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServerListing setNumberOfPlayers(int players){
|
||||||
|
this.players = players;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServerListing setRoomCode(String roomCode){
|
||||||
|
this.roomCode = roomCode;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void refreshTtl(){
|
||||||
|
ttl = SERVER_TTL_DEFAULT;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void decrementTtl(){
|
||||||
|
ttl--;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasTtlExpired(){
|
||||||
|
return ttl < 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (obj == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!ServerListing.class.isAssignableFrom(obj.getClass())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
final ServerListing other = (ServerListing) obj;
|
||||||
|
|
||||||
|
if (this.getPortNumber() != other.getPortNumber()){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.getMapName().equals(other.getMapName())){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.getServerName().equals(other.getServerName())){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.getCapacity() != other.getCapacity()){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.getAddress().equals(other.getAddress())){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return this.getServerName().hashCode() +
|
||||||
|
this.getAddress().hashCode() + this.getMapName().hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRoomCode() {
|
||||||
|
return roomCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPortNumber() {
|
||||||
|
return portNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMapName() {
|
||||||
|
return mapName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getServerName() {
|
||||||
|
return serverName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCapacity() {
|
||||||
|
return capacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAddress() {
|
||||||
|
return address;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTtl(Integer ttl){
|
||||||
|
this.ttl = ttl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isMaxPlayersReached() {
|
||||||
|
return players >= capacity;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
package seng302.discoveryServer.util;
|
||||||
|
|
||||||
|
|
||||||
|
import seng302.gameServer.messages.Message;
|
||||||
|
import seng302.model.stream.packets.PacketType;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
public class ServerRepoStreamParser {
|
||||||
|
private ReadableByteInputStream inputStream;
|
||||||
|
|
||||||
|
private String roomCode;
|
||||||
|
private String mapName;
|
||||||
|
private ServerListing serverListing;
|
||||||
|
|
||||||
|
public ServerRepoStreamParser(InputStream is){
|
||||||
|
inputStream = new ReadableByteInputStream(is);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PacketType parse() throws Exception {
|
||||||
|
int sync1 = inputStream.readByte();
|
||||||
|
int sync2 = inputStream.readByte();
|
||||||
|
|
||||||
|
PacketType packetType = null;
|
||||||
|
|
||||||
|
if (sync1 == 0x47 && sync2 == 0x83) {
|
||||||
|
int type = inputStream.readByte();
|
||||||
|
inputStream.skipBytes(10);
|
||||||
|
long payloadLength = Message.bytesToLong(inputStream.getBytes(2));
|
||||||
|
byte[] payload = inputStream.getBytes((int) payloadLength);
|
||||||
|
inputStream.skipBytes(4);
|
||||||
|
|
||||||
|
packetType = PacketType.assignPacketType(type, payload);
|
||||||
|
|
||||||
|
switch (packetType) {
|
||||||
|
case ROOM_CODE_REQUEST:
|
||||||
|
roomCode = parseRoomCodeRequest(payload);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LOBBY_REQUEST:
|
||||||
|
mapName = parseLobbyRequest(payload);
|
||||||
|
|
||||||
|
case SERVER_REGISTRATION:
|
||||||
|
serverListing = parseServerRegistration(payload);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return packetType;
|
||||||
|
}
|
||||||
|
private String parseLobbyRequest(byte[] payload) {
|
||||||
|
int mapNameLength = (int) Message.bytesToLong(Arrays.copyOfRange(payload, 0 ,4));
|
||||||
|
|
||||||
|
return new String(Arrays.copyOfRange(payload, 4, 4+mapNameLength));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String parseRoomCodeRequest(byte[] payload) {
|
||||||
|
int roomCodeLength = (int) Message.bytesToLong(Arrays.copyOfRange(payload, 0 ,6));
|
||||||
|
|
||||||
|
return new String(Arrays.copyOfRange(payload, 6, 6+roomCodeLength));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ServerListing parseServerRegistration(byte[] payload) {
|
||||||
|
int nameLength = (int) Message.bytesToLong(Arrays.copyOfRange(payload, 0, 6));
|
||||||
|
int mapNameLength = (int) Message.bytesToLong(Arrays.copyOfRange(payload, 6, 12));
|
||||||
|
int addressLength = (int) Message.bytesToLong(Arrays.copyOfRange(payload, 12, 18));
|
||||||
|
int roomCodeLength = (int) Message.bytesToLong(Arrays.copyOfRange(payload, 18, 24));
|
||||||
|
|
||||||
|
int portNumber = (int) Message.bytesToLong(Arrays.copyOfRange(payload, 24, 28));
|
||||||
|
int players = (int) Message.bytesToLong(Arrays.copyOfRange(payload, 28, 32));
|
||||||
|
int capacity = (int) Message.bytesToLong(Arrays.copyOfRange(payload, 32, 36));
|
||||||
|
|
||||||
|
int currentPos = 36;
|
||||||
|
int nextPos = currentPos + nameLength;
|
||||||
|
String serverName = new String(Arrays.copyOfRange(payload, currentPos, nextPos));
|
||||||
|
|
||||||
|
currentPos = nextPos;
|
||||||
|
nextPos = currentPos + mapNameLength;
|
||||||
|
String mapName = new String(Arrays.copyOfRange(payload, currentPos, nextPos));
|
||||||
|
|
||||||
|
currentPos = nextPos;
|
||||||
|
nextPos = currentPos + addressLength;
|
||||||
|
String address = new String(Arrays.copyOfRange(payload, currentPos, nextPos));
|
||||||
|
|
||||||
|
currentPos = nextPos;
|
||||||
|
nextPos = currentPos + roomCodeLength;
|
||||||
|
String roomCode = new String(Arrays.copyOfRange(payload, currentPos, nextPos));
|
||||||
|
|
||||||
|
ServerListing serverListing = new ServerListing(serverName, mapName, address, portNumber, capacity);
|
||||||
|
serverListing.setNumberOfPlayers(players);
|
||||||
|
serverListing.setRoomCode(roomCode);
|
||||||
|
|
||||||
|
return serverListing;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRoomCode() {
|
||||||
|
return roomCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMapName() {
|
||||||
|
return mapName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServerListing getServerListing() {
|
||||||
|
return serverListing;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
package seng302.discoveryServer.util;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
public class ServerTable {
|
||||||
|
private List<ServerListing> servers;
|
||||||
|
private int lastRoomCode = 4020;
|
||||||
|
private Logger logger = LoggerFactory.getLogger(ServerTable.class);
|
||||||
|
|
||||||
|
public ServerTable(){
|
||||||
|
servers = new ArrayList<>();
|
||||||
|
|
||||||
|
new Timer().schedule(new TimerTask() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
updateServers();
|
||||||
|
}
|
||||||
|
}, 0, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the servers TTL values, and then remove expired servers
|
||||||
|
*/
|
||||||
|
private void updateServers() {
|
||||||
|
List<ServerListing> serversToRemove = new ArrayList<>();
|
||||||
|
|
||||||
|
for (ServerListing server : servers){
|
||||||
|
server.decrementTtl();
|
||||||
|
|
||||||
|
if (server.hasTtlExpired()){
|
||||||
|
logger.debug("Removed expired server - " + server.getServerName());
|
||||||
|
serversToRemove.add(server);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
servers.removeAll(serversToRemove);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a server to the table
|
||||||
|
* @param server The server to add
|
||||||
|
*/
|
||||||
|
public void addServer(ServerListing server){
|
||||||
|
if (servers.contains(server)){
|
||||||
|
updateTtlForServer(server);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
logger.debug("Added new server - " + server.getServerName() + " at address: " + server.getAddress() + ":" + server.getPortNumber());
|
||||||
|
servers.add(server);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the TTL for a given server to the default TTL value
|
||||||
|
* @param server The server to update
|
||||||
|
*/
|
||||||
|
private void updateTtlForServer(ServerListing server) {
|
||||||
|
for (ServerListing serverListing : servers){
|
||||||
|
if (server.equals(serverListing)){
|
||||||
|
serverListing.refreshTtl();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return All the servers in the table
|
||||||
|
*/
|
||||||
|
public List<ServerListing> getAllServers(){
|
||||||
|
return Collections.unmodifiableList(servers);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a server from the table given its room code
|
||||||
|
* @param roomCode The room code to search for
|
||||||
|
* @return The ServerListing of the found server, or null
|
||||||
|
* the server wasn't found
|
||||||
|
*/
|
||||||
|
public ServerListing getServerByRoomCode(String roomCode){
|
||||||
|
for (ServerListing serverListing : servers){
|
||||||
|
if (serverListing.getRoomCode().equals(roomCode)){
|
||||||
|
return serverListing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The next available room code
|
||||||
|
*/
|
||||||
|
public Integer getNextRoomCode(){
|
||||||
|
lastRoomCode += 1;
|
||||||
|
return lastRoomCode;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package seng302.gameServer;
|
||||||
|
|
||||||
|
import seng302.model.Player;
|
||||||
|
|
||||||
|
public interface ClientConnectionDelegate {
|
||||||
|
/**
|
||||||
|
* A player has connected to the server
|
||||||
|
* @param serverToClientThread The player that has connected
|
||||||
|
*/
|
||||||
|
void clientConnected(ServerToClientThread serverToClientThread);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A player has disconnected from the server
|
||||||
|
* @param player The player that has disconnected
|
||||||
|
*/
|
||||||
|
void clientDisconnected(Player player);
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package seng302.gameServer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An enum describing the states of the game
|
||||||
|
* Created by wmu16 on 11/07/17.
|
||||||
|
*/
|
||||||
|
public enum GameStages {
|
||||||
|
|
||||||
|
LOBBYING(0),
|
||||||
|
PRE_RACE(1),
|
||||||
|
RACING(2),
|
||||||
|
FINISHED(3),
|
||||||
|
CANCELLED(4);
|
||||||
|
|
||||||
|
private long code;
|
||||||
|
|
||||||
|
GameStages(long code) {
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getCode(){
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,90 @@
|
|||||||
|
package seng302.gameServer;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
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 implements Runnable {
|
||||||
|
|
||||||
|
private Logger logger = LoggerFactory.getLogger(HeartbeatThread.class);
|
||||||
|
|
||||||
|
private final int HEARTBEAT_PERIOD = 200;
|
||||||
|
private ClientConnectionDelegate delegate;
|
||||||
|
private Integer seqNum;
|
||||||
|
private Stack<Player> disconnectedPlayers;
|
||||||
|
|
||||||
|
public HeartbeatThread(ClientConnectionDelegate delegate){
|
||||||
|
this.delegate = delegate;
|
||||||
|
seqNum = 0;
|
||||||
|
disconnectedPlayers = new Stack<>();
|
||||||
|
|
||||||
|
Thread thread = new Thread(this, "HeartBeat");
|
||||||
|
thread.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A player has lost connection to the server
|
||||||
|
* The player is added to a stack so that the delegate
|
||||||
|
* can be notified
|
||||||
|
*
|
||||||
|
* @param player The player that has disconnected
|
||||||
|
*/
|
||||||
|
private void playerLostConnection(Player player){
|
||||||
|
disconnectedPlayers.push(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a heartbeat message to each connected player
|
||||||
|
* The delegate is notified if a player has disconnected
|
||||||
|
*/
|
||||||
|
private void sendHeartbeatToAllPlayers(){
|
||||||
|
try {
|
||||||
|
Message heartbeat = new Heartbeat(seqNum);
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies the delegate about
|
||||||
|
* each disconnected player
|
||||||
|
*/
|
||||||
|
private void updateDelegate() {
|
||||||
|
while (!disconnectedPlayers.empty()){
|
||||||
|
delegate.clientDisconnected(disconnectedPlayers.pop());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run(){
|
||||||
|
Timer t = new Timer();
|
||||||
|
t.schedule(new TimerTask() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
sendHeartbeatToAllPlayers();
|
||||||
|
}
|
||||||
|
}, 0, HEARTBEAT_PERIOD);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,353 @@
|
|||||||
|
package seng302.gameServer;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.ServerSocket;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Timer;
|
||||||
|
import java.util.TimerTask;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import seng302.gameServer.messages.Message;
|
||||||
|
import seng302.model.GeoPoint;
|
||||||
|
import seng302.model.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 implements Runnable, ClientConnectionDelegate {
|
||||||
|
|
||||||
|
private static final int PORT = 4942;
|
||||||
|
private static int selectedPort = PORT;
|
||||||
|
private static final Integer CLIENT_UPDATES_PER_SECOND = 60;
|
||||||
|
private Logger logger = LoggerFactory.getLogger(MainServerThread.class);
|
||||||
|
private boolean terminated;
|
||||||
|
|
||||||
|
private boolean hasStarted = false;
|
||||||
|
|
||||||
|
private ServerSocket serverSocket = null;
|
||||||
|
private ArrayList<ServerToClientThread> serverToClientThreads = new ArrayList<>();
|
||||||
|
private RaceXMLData raceXMLData;
|
||||||
|
private RegattaXMLData regattaXMLData;
|
||||||
|
|
||||||
|
public MainServerThread() {
|
||||||
|
new GameState();
|
||||||
|
try {
|
||||||
|
serverSocket = new ServerSocket(0);
|
||||||
|
selectedPort = serverSocket.getLocalPort();
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.trace("IO error in server thread handler upon trying to make new server socket",
|
||||||
|
0);
|
||||||
|
}
|
||||||
|
terminated = false;
|
||||||
|
Thread thread = new Thread(this, "MainServer");
|
||||||
|
thread.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startAdvertisingServer() {
|
||||||
|
Integer capacity = GameState.getCapacity();
|
||||||
|
Integer numPlayers = GameState.getNumberOfPlayers();
|
||||||
|
Integer spacesLeft = capacity - numPlayers;
|
||||||
|
|
||||||
|
// No spaces left on server
|
||||||
|
if (spacesLeft < 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start advertising server
|
||||||
|
try {
|
||||||
|
ServerAdvertiser.getInstance()
|
||||||
|
.setMapName(regattaXMLData.getCourseName())
|
||||||
|
.setCapacity(capacity)
|
||||||
|
.setNumberOfPlayers(numPlayers - 1)
|
||||||
|
.registerGame(selectedPort, regattaXMLData.getRegattaName());
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.warn("Could not register server");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startServer() {
|
||||||
|
PolarTable.parsePolarFile(getClass().getResourceAsStream("/server_config/acc_polars.csv"));
|
||||||
|
MessageFactory.updateXMLGenerator(raceXMLData, regattaXMLData);
|
||||||
|
GameState.setRace(raceXMLData);
|
||||||
|
MessageFactory.updateBoats(new ArrayList<>(GameState.getYachts().values()));
|
||||||
|
startAdvertisingServer();
|
||||||
|
GameState.addMessageEventListener(this::broadcastMessage);
|
||||||
|
sendSetupMessages();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
|
||||||
|
new HeartbeatThread(this);
|
||||||
|
new ServerListenThread(serverSocket, this);
|
||||||
|
|
||||||
|
hasStarted = true;
|
||||||
|
|
||||||
|
//You should handle interrupts in some way, so that the thread won't keep on forever if you exit the app.
|
||||||
|
while (!terminated) {
|
||||||
|
if (GameState.getPlayerHasLeftFlag()) {
|
||||||
|
for (ServerToClientThread stc : serverToClientThreads) {
|
||||||
|
if (!stc.isSocketOpen()) {
|
||||||
|
GameState.getYachts().remove(stc.getSourceId());
|
||||||
|
sendSetupMessages();
|
||||||
|
try {
|
||||||
|
stc.getSocket().close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
GameState.setPlayerHasLeftFlag(false);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Thread.sleep(1000 / CLIENT_UPDATES_PER_SECOND);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
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()));
|
||||||
|
sendSetupMessages();
|
||||||
|
GameState.resetCustomizationFlag();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GameState.getCurrentStage() == GameStages.PRE_RACE) {
|
||||||
|
sendBoatLocations();
|
||||||
|
}
|
||||||
|
|
||||||
|
//RACING
|
||||||
|
if (GameState.getCurrentStage() == GameStages.RACING) {
|
||||||
|
sendBoatLocations();
|
||||||
|
}
|
||||||
|
|
||||||
|
//FINISHED
|
||||||
|
else if (GameState.getCurrentStage() == GameStages.FINISHED) {
|
||||||
|
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 {
|
||||||
|
synchronized (this) {
|
||||||
|
for (ServerToClientThread serverToClientThread : serverToClientThreads) {
|
||||||
|
serverToClientThread.terminate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
serverSocket.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
System.out.println("IO error in server thread handler upon closing socket");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendBoatLocations() {
|
||||||
|
for (ServerYacht serverYacht : GameState.getYachts().values()) {
|
||||||
|
broadcastMessage(MessageFactory.getBoatLocationMessage(serverYacht));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendSetupMessages() {
|
||||||
|
MessageFactory.updateBoats(new ArrayList<>(GameState.getYachts().values()));
|
||||||
|
broadcastMessage(MessageFactory.getRaceXML());
|
||||||
|
broadcastMessage(MessageFactory.getRegattaXML());
|
||||||
|
broadcastMessage(MessageFactory.getBoatXML());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void broadcastMessage(Message message) {
|
||||||
|
for (ServerToClientThread serverToClientThread : serverToClientThreads) {
|
||||||
|
serverToClientThread.sendMessage(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A client has tried to connect to the server
|
||||||
|
*
|
||||||
|
* @param serverToClientThread The player that connected
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void clientConnected(ServerToClientThread serverToClientThread) {
|
||||||
|
logger.debug("Player Connected From " + serverToClientThread.getThread().getName(), 0);
|
||||||
|
if (serverToClientThreads.size() == 0) { //Sets first client as host.
|
||||||
|
serverToClientThread.setAsHost();
|
||||||
|
serverToClientThread.raceXMLProperty().addListener((obs, oldVal, race) -> {
|
||||||
|
if (race != null) {
|
||||||
|
raceXMLData = race;
|
||||||
|
}
|
||||||
|
if (regattaXMLData != null) {
|
||||||
|
startServer();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
serverToClientThread.regattaXMLProperty().addListener((obs, oldVal, regatta) -> {
|
||||||
|
if (regatta != null) {
|
||||||
|
regattaXMLData = regatta;
|
||||||
|
}
|
||||||
|
if (raceXMLData != null) {
|
||||||
|
startServer();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
serverToClientThreads.add(serverToClientThread);
|
||||||
|
|
||||||
|
try {
|
||||||
|
ServerAdvertiser.getInstance().setNumberOfPlayers(GameState.getNumberOfPlayers());
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.warn("Couldn't update advertisement");
|
||||||
|
}
|
||||||
|
|
||||||
|
while (regattaXMLData == null && raceXMLData == null) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(50);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
serverToClientThread.addConnectionListener(this::sendSetupMessages);
|
||||||
|
serverToClientThread.addDisconnectListener(this::clientDisconnected);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A player has left the game, remove the player from the GameState
|
||||||
|
*
|
||||||
|
* @param player The player that left
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void clientDisconnected(Player player) {
|
||||||
|
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()) {
|
||||||
|
closedConnection = serverToClientThread;
|
||||||
|
} else if (GameState.getCurrentStage() != GameStages.RACING) {
|
||||||
|
serverToClientThread.sendSetupMessages();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
serverToClientThreads.remove(closedConnection);
|
||||||
|
|
||||||
|
try {
|
||||||
|
ServerAdvertiser.getInstance().setNumberOfPlayers(GameState.getNumberOfPlayers());
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.warn("Couldn't update advertisement");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (closedConnection != null) {
|
||||||
|
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() {
|
||||||
|
broadcastMessage(MessageFactory.getRaceStatusMessage());
|
||||||
|
if (GameState.getCurrentStage() == GameStages.PRE_RACE
|
||||||
|
|| GameState.getCurrentStage() == GameStages.LOBBYING) {
|
||||||
|
broadcastMessage(MessageFactory.getRaceStartStatusMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 0, 500);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void terminate() {
|
||||||
|
terminated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialise boats to specific spaced out geopoints behind starting line.
|
||||||
|
*/
|
||||||
|
private void initialiseBoatPositions() {
|
||||||
|
|
||||||
|
final double DISTANCE_TO_START = 75d;
|
||||||
|
final double YACHT_SEPARATION = 35d;
|
||||||
|
|
||||||
|
//Length of start line
|
||||||
|
double startLineLength = GeoUtility.getDistance(
|
||||||
|
GameState.getMarkOrder().getMarkOrder().get(0).getSubMark(1),
|
||||||
|
GameState.getMarkOrder().getMarkOrder().get(0).getSubMark(2)
|
||||||
|
) - YACHT_SEPARATION;
|
||||||
|
|
||||||
|
//How many yachts can fit along the start line
|
||||||
|
int spacesAlongLine = (int) Math.round(startLineLength / YACHT_SEPARATION);
|
||||||
|
|
||||||
|
//Angle of start line
|
||||||
|
double startMarkToMarkAngle = GeoUtility.getBearing(
|
||||||
|
GameState.getMarkOrder().getMarkOrder().get(0).getSubMark(1),
|
||||||
|
GameState.getMarkOrder().getMarkOrder().get(0).getSubMark(2)
|
||||||
|
);
|
||||||
|
|
||||||
|
//angle from first mark to the start
|
||||||
|
double angleToStart = GeoUtility.getBearing(
|
||||||
|
GameState.getMarkOrder().getMarkOrder().get(1).getMidPoint(),
|
||||||
|
GameState.getMarkOrder().getMarkOrder().get(0).getMidPoint()
|
||||||
|
);
|
||||||
|
|
||||||
|
double angleFromStart = GeoUtility.getBearing(
|
||||||
|
GameState.getMarkOrder().getMarkOrder().get(0).getMidPoint(),
|
||||||
|
GameState.getMarkOrder().getMarkOrder().get(1).getMidPoint()
|
||||||
|
);
|
||||||
|
|
||||||
|
GeoPoint midPoint = GeoUtility.getGeoCoordinate(
|
||||||
|
GameState.getMarkOrder().getMarkOrder().get(0).getMidPoint(),
|
||||||
|
angleToStart, DISTANCE_TO_START
|
||||||
|
);
|
||||||
|
|
||||||
|
List<ServerYacht> randomisedYachts = new ArrayList<>(GameState.getYachts().values());
|
||||||
|
Collections.shuffle(randomisedYachts);
|
||||||
|
while (randomisedYachts.size() > 0) {
|
||||||
|
|
||||||
|
int numYachts = spacesAlongLine > randomisedYachts.size() ? randomisedYachts.size() : spacesAlongLine;
|
||||||
|
double yachtSpace = (numYachts - 1) * YACHT_SEPARATION / 2;
|
||||||
|
|
||||||
|
GeoPoint firstYachtPoint = GeoUtility.getGeoCoordinate(
|
||||||
|
midPoint, startMarkToMarkAngle + 180, yachtSpace
|
||||||
|
);
|
||||||
|
|
||||||
|
for (int i = 0; i < numYachts; i++) {
|
||||||
|
randomisedYachts.get(0).setHeading(angleFromStart);
|
||||||
|
randomisedYachts.get(0).setLocation(firstYachtPoint);
|
||||||
|
firstYachtPoint = GeoUtility.getGeoCoordinate(
|
||||||
|
firstYachtPoint, startMarkToMarkAngle, YACHT_SEPARATION
|
||||||
|
);
|
||||||
|
randomisedYachts.remove(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
midPoint = GeoUtility.getGeoCoordinate(
|
||||||
|
midPoint, angleToStart, YACHT_SEPARATION * 1.5
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasStarted() {
|
||||||
|
return hasStarted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPortNumber() {
|
||||||
|
return selectedPort;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,225 @@
|
|||||||
|
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.ChatterMessage;
|
||||||
|
import seng302.gameServer.messages.RaceStartNotificationType;
|
||||||
|
import seng302.gameServer.messages.RaceStartStatusMessage;
|
||||||
|
import seng302.gameServer.messages.RaceStatus;
|
||||||
|
import seng302.gameServer.messages.RaceStatusMessage;
|
||||||
|
import seng302.gameServer.messages.RaceType;
|
||||||
|
import seng302.gameServer.messages.XMLMessage;
|
||||||
|
import seng302.gameServer.messages.XMLMessageSubType;
|
||||||
|
import seng302.gameServer.messages.YachtEventCodeMessage;
|
||||||
|
import seng302.gameServer.messages.YachtEventType;
|
||||||
|
import seng302.model.Player;
|
||||||
|
import seng302.model.ServerYacht;
|
||||||
|
import seng302.model.stream.xml.generator.RaceXMLTemplate;
|
||||||
|
import seng302.model.stream.xml.generator.RegattaXMLTemplate;
|
||||||
|
import seng302.model.stream.xml.parser.RaceXMLData;
|
||||||
|
import seng302.model.stream.xml.parser.RegattaXMLData;
|
||||||
|
import seng302.model.token.Token;
|
||||||
|
import seng302.model.token.TokenType;
|
||||||
|
import seng302.utilities.XMLGenerator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
||||||
|
xmlGenerator.getRace().setBoats(yachts);
|
||||||
|
String xmlStr = xmlGenerator.getBoatsAsXml();
|
||||||
|
MessageFactory.boats = new XMLMessage(xmlStr, XMLMessageSubType.BOAT, xmlStr.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void updateTokens(List<Token> tokens) {
|
||||||
|
xmlGenerator.getRace().setTokens(tokens);
|
||||||
|
String xmlStr = xmlGenerator.getRaceAsXml();
|
||||||
|
MessageFactory.race = new XMLMessage(xmlStr, XMLMessageSubType.RACE, xmlStr.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static RaceStartStatusMessage getRaceStartStatusMessage() {
|
||||||
|
return new RaceStartStatusMessage(
|
||||||
|
1,
|
||||||
|
GameState.getStartTime(),
|
||||||
|
1,
|
||||||
|
RaceStartNotificationType.SET_RACE_START_TIME);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RaceStatusMessage getRaceStatusMessage() {
|
||||||
|
// variables taken from GameServerThread
|
||||||
|
|
||||||
|
List<BoatSubMessage> boatSubMessages = new ArrayList<>();
|
||||||
|
RaceStatus raceStatus;
|
||||||
|
|
||||||
|
for (Player player : GameState.getPlayers()) {
|
||||||
|
ServerYacht y = player.getYacht();
|
||||||
|
BoatSubMessage m = new BoatSubMessage(y.getSourceId(), y.getBoatStatus(),
|
||||||
|
y.getLegNumber(),
|
||||||
|
0, 0, 1234L,
|
||||||
|
1234L);
|
||||||
|
boatSubMessages.add(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
long timeTillStart = System.currentTimeMillis() - GameState.getStartTime();
|
||||||
|
|
||||||
|
if (GameState.getCurrentStage() == GameStages.LOBBYING) {
|
||||||
|
raceStatus = RaceStatus.PRESTART;
|
||||||
|
} else if (GameState.getCurrentStage() == GameStages.PRE_RACE) {
|
||||||
|
raceStatus = RaceStatus.PRESTART;
|
||||||
|
|
||||||
|
if (timeTillStart > GameState.WARNING_TIME) {
|
||||||
|
raceStatus = RaceStatus.WARNING;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timeTillStart > GameState.PREPATORY_TIME) {
|
||||||
|
raceStatus = RaceStatus.PREPARATORY;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
raceStatus = RaceStatus.STARTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new RaceStatusMessage(1, raceStatus, GameState.getStartTime(),
|
||||||
|
GameState.getWindDirection(),
|
||||||
|
GameState.getWindSpeedMMS().longValue(), GameState.getPlayers().size(),
|
||||||
|
RaceType.MATCH_RACE, 1, boatSubMessages);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BoatLocationMessage getBoatLocationMessage(ServerYacht yacht) {
|
||||||
|
return new BoatLocationMessage(
|
||||||
|
yacht.getSourceId(),
|
||||||
|
0, // TODO: 29/08/17 wmu16 - Work out what to do with seqNo. Currently not used
|
||||||
|
yacht.getLocation().getLat(),
|
||||||
|
yacht.getLocation().getLng(),
|
||||||
|
yacht.getHeading(),
|
||||||
|
yacht.getCurrentVelocity().longValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static XMLMessage getRaceXML() {
|
||||||
|
return race;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static XMLMessage getRegattaXML() {
|
||||||
|
return regatta;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static XMLMessage getBoatXML() {
|
||||||
|
return boats;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static YachtEventCodeMessage makeCollisionMessage(ServerYacht serverYacht) {
|
||||||
|
return new YachtEventCodeMessage(serverYacht.getSourceId(), YachtEventType.COLLISION);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a message to be sent out whenever a yacht picks up a boost
|
||||||
|
*
|
||||||
|
* @param serverYacht The yacht that has picked up a power up
|
||||||
|
* @param token The token which they picked up
|
||||||
|
* @return The corresponding YachtEventCodeMessage
|
||||||
|
*/
|
||||||
|
public static YachtEventCodeMessage makePickupMessage(ServerYacht serverYacht, Token token) {
|
||||||
|
YachtEventType yachtEventType = null;
|
||||||
|
switch (token.getTokenType()) {
|
||||||
|
case BOOST:
|
||||||
|
yachtEventType = YachtEventType.TOKEN_VELOCITY;
|
||||||
|
break;
|
||||||
|
case HANDLING:
|
||||||
|
yachtEventType = YachtEventType.TOKEN_HANDLING;
|
||||||
|
break;
|
||||||
|
case WIND_WALKER:
|
||||||
|
yachtEventType = YachtEventType.TOKEN_WIND_WALKER;
|
||||||
|
break;
|
||||||
|
case BUMPER:
|
||||||
|
yachtEventType = YachtEventType.TOKEN_BUMPER;
|
||||||
|
break;
|
||||||
|
case RANDOM:
|
||||||
|
yachtEventType = YachtEventType.TOKEN_RANDOM;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return new YachtEventCodeMessage(serverYacht.getSourceId(), yachtEventType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a message representing a certain buff / debuff for a given yacht. For now this is
|
||||||
|
* just for the bumper debuff so the affected boat is aware that it has been crashed. This could
|
||||||
|
* however be extended to render affects for all boats given a certain debuff.
|
||||||
|
*
|
||||||
|
* @param yacht The yacht affected by some status
|
||||||
|
* @param token The token indicating what status they have
|
||||||
|
* @return A YachtEventCodeMessage
|
||||||
|
*/
|
||||||
|
public static YachtEventCodeMessage makeStatusEffectMessage(ServerYacht yacht,
|
||||||
|
TokenType token) {
|
||||||
|
YachtEventType yachtEventType = null;
|
||||||
|
switch (token) {
|
||||||
|
case BUMPER:
|
||||||
|
yachtEventType = YachtEventType.BUMPER_CRASH;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return new YachtEventCodeMessage(yacht.getSourceId(), yachtEventType);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a message to be sent out when a given yacht powers down (From a boost of any type)
|
||||||
|
*
|
||||||
|
* @param yacht The yacht that is powering down
|
||||||
|
* @return A YachtEventCodeMessage representing this action
|
||||||
|
*/
|
||||||
|
public static YachtEventCodeMessage makePowerDownMessage(ServerYacht yacht) {
|
||||||
|
return new YachtEventCodeMessage(yacht.getSourceId(), YachtEventType.POWER_DOWN);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ChatterMessage makeChatterMessage(Integer messageType, String message) {
|
||||||
|
return new ChatterMessage(messageType, "SERVER: " + message);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,187 @@
|
|||||||
|
package seng302.gameServer;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import seng302.discoveryServer.DiscoveryServerClient;
|
||||||
|
import seng302.discoveryServer.util.ServerListing;
|
||||||
|
|
||||||
|
import javax.jmdns.JmDNS;
|
||||||
|
import javax.jmdns.ServiceInfo;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.Inet4Address;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.NetworkInterface;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
import java.util.Hashtable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Advertises the game server on the local network
|
||||||
|
*/
|
||||||
|
public class ServerAdvertiser {
|
||||||
|
/*
|
||||||
|
Our service name & protocol
|
||||||
|
|
||||||
|
This must be in the format _Service._Proto.Name as per http://www.ietf.org/rfc/rfc2782.txt
|
||||||
|
Where Service is unique on the network, and protocol is usually _tcp.
|
||||||
|
|
||||||
|
The pseudo-domain 'local.' must end in a full-stop. This is used to indicate that
|
||||||
|
the lookup should be performed using an IP multicast query on the local IP network.
|
||||||
|
|
||||||
|
Read this before changing any of the following values
|
||||||
|
https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/NetServices/Articles/domainnames.html#//apple_ref/doc/uid/TP40002460-SW1
|
||||||
|
*/
|
||||||
|
private static String SERVICE = "_partyatsea";
|
||||||
|
private static String PROTOCOL = "_tcp";
|
||||||
|
public static String SERVICE_TYPE = SERVICE + "." + PROTOCOL + ".local.";
|
||||||
|
|
||||||
|
private static ServerAdvertiser instance = null;
|
||||||
|
private static JmDNS jmdnsInstance = null;
|
||||||
|
private ServiceInfo serviceInfo; // Note: Whenever this is changed, our service will be re-registered on the network.
|
||||||
|
private DiscoveryServerClient repositoryClient;
|
||||||
|
|
||||||
|
private Hashtable<String ,String> props;
|
||||||
|
private Logger logger = LoggerFactory.getLogger(ServerAdvertiser.class);
|
||||||
|
|
||||||
|
private ServerAdvertiser() throws IOException{
|
||||||
|
jmdnsInstance = JmDNS.create(InetAddress.getByName(getLocalHostIp()));
|
||||||
|
|
||||||
|
repositoryClient = new DiscoveryServerClient();
|
||||||
|
|
||||||
|
props = new Hashtable<>();
|
||||||
|
props.put("map", "");
|
||||||
|
props.put("spacesLeft", "0");
|
||||||
|
props.put("capacity", "0");
|
||||||
|
props.put("players", "0");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an instance of the ServerAdvertiser, create an instance if there isn't already one
|
||||||
|
* @return A ServerAdvertiser Instance
|
||||||
|
* @throws IOException If there was an exception creating the instance
|
||||||
|
*/
|
||||||
|
public static ServerAdvertiser getInstance() throws IOException {
|
||||||
|
if (instance == null){
|
||||||
|
instance = new ServerAdvertiser();
|
||||||
|
}
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the map name and broadcast an update on the network
|
||||||
|
* @param mapName The new map name
|
||||||
|
* @return The current ServerAdvertiser instance
|
||||||
|
*/
|
||||||
|
public ServerAdvertiser setMapName(String mapName){
|
||||||
|
props.replace("map", mapName);
|
||||||
|
|
||||||
|
if (serviceInfo != null){
|
||||||
|
serviceInfo.setText(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the number of players on the server and broadcast an update on the network
|
||||||
|
* @param numPlayers The number of players on the server
|
||||||
|
* @return The current ServerAdvertiser instance
|
||||||
|
*/
|
||||||
|
public ServerAdvertiser setNumberOfPlayers(Integer numPlayers){
|
||||||
|
props.replace("players", numPlayers.toString());
|
||||||
|
|
||||||
|
if (serviceInfo != null){
|
||||||
|
serviceInfo.setText(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the max capacity of the server and broadcast an update on the network
|
||||||
|
* @param capacity The maximum capacity of the server
|
||||||
|
* @return The current ServerAdvertiser instance
|
||||||
|
*/
|
||||||
|
public ServerAdvertiser setCapacity(Integer capacity){
|
||||||
|
props.replace("capacity", capacity.toString());
|
||||||
|
|
||||||
|
if (serviceInfo != null){
|
||||||
|
serviceInfo.setText(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register this service on the network
|
||||||
|
*
|
||||||
|
* Note: other parameters (map name/spaces left etc) are set after the
|
||||||
|
* service has been registered
|
||||||
|
* @param portNo The servers port number
|
||||||
|
* @param serverName The servers name
|
||||||
|
*/
|
||||||
|
public void registerGame(Integer portNo, String serverName) {
|
||||||
|
|
||||||
|
serviceInfo = ServiceInfo.create(SERVICE_TYPE, serverName, portNo, 0, 0, props);
|
||||||
|
|
||||||
|
new java.util.Timer().schedule(
|
||||||
|
new java.util.TimerTask() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
jmdnsInstance.registerService(serviceInfo);
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.warn("Failed to register service info");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
ServerListing serverListing = new ServerListing(serverName, props.get("map"), new DiscoveryServerClient().getInetIp(), portNo, Integer.parseInt(props.get("capacity")));
|
||||||
|
repositoryClient.register(serverListing);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unregister the service
|
||||||
|
*/
|
||||||
|
public void unregister(){
|
||||||
|
if (serviceInfo != null)
|
||||||
|
jmdnsInstance.unregisterService(serviceInfo);
|
||||||
|
|
||||||
|
repositoryClient.unregister();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the local host ip address.
|
||||||
|
*
|
||||||
|
* @return the localhost ip address
|
||||||
|
*/
|
||||||
|
public static String getLocalHostIp() {
|
||||||
|
String ipAddress = null;
|
||||||
|
try {
|
||||||
|
Enumeration<NetworkInterface> e = NetworkInterface.getNetworkInterfaces();
|
||||||
|
while (e.hasMoreElements()) {
|
||||||
|
NetworkInterface ni = e.nextElement();
|
||||||
|
if (ni.isLoopback())
|
||||||
|
continue;
|
||||||
|
if(ni.isPointToPoint())
|
||||||
|
continue;
|
||||||
|
if(ni.isVirtual())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
Enumeration<InetAddress> addresses = ni.getInetAddresses();
|
||||||
|
while(addresses.hasMoreElements()) {
|
||||||
|
InetAddress address = addresses.nextElement();
|
||||||
|
if(address instanceof Inet4Address) { // skip all ipv6
|
||||||
|
ipAddress = address.getHostAddress();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
if (ipAddress == null) {
|
||||||
|
System.out.println("[HOST] Cannot obtain local host ip address.");
|
||||||
|
}
|
||||||
|
return ipAddress;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
package seng302.gameServer;
|
||||||
|
|
||||||
|
public class ServerDescription {
|
||||||
|
private Integer capacity;
|
||||||
|
private String address;
|
||||||
|
private Integer portNum;
|
||||||
|
private String serverName;
|
||||||
|
private String mapName;
|
||||||
|
private Integer numPlayers;
|
||||||
|
private Long lastUpdated;
|
||||||
|
private Long lastRefreshed;
|
||||||
|
|
||||||
|
private static Long EXPIRY_INTERVAL = 5000L;
|
||||||
|
|
||||||
|
public ServerDescription(String serverName, String mapName, Integer numPlayers, Integer capacity, String address, Integer portNum){
|
||||||
|
this.serverName = serverName;
|
||||||
|
this.mapName = mapName;
|
||||||
|
this.numPlayers = numPlayers;
|
||||||
|
this.address = address;
|
||||||
|
this.portNum = portNum;
|
||||||
|
this.capacity = capacity;
|
||||||
|
lastUpdated = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return serverName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMapName() {
|
||||||
|
return mapName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer portNumber() {
|
||||||
|
return portNum;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAddress(){
|
||||||
|
return address;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getNumPlayers() {
|
||||||
|
return numPlayers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getCapacity(){
|
||||||
|
return capacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (obj == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!ServerDescription.class.isAssignableFrom(obj.getClass())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final ServerDescription other = (ServerDescription) obj;
|
||||||
|
|
||||||
|
if (!this.getAddress().equals(other.getAddress()) ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.portNumber().equals(other.portNumber())){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.getMapName().equals(other.getMapName())){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.getName().equals(other.getName())){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.getCapacity().equals(other.getCapacity())){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return this.getName().hashCode() + this.getAddress().hashCode() +
|
||||||
|
this.portNumber().hashCode() + this.getMapName().hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean hasExpired(){
|
||||||
|
return System.currentTimeMillis() - lastUpdated > EXPIRY_INTERVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean serverShouldBeRemoved() {
|
||||||
|
if (lastRefreshed == null) return false;
|
||||||
|
return System.currentTimeMillis() - lastRefreshed > EXPIRY_INTERVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void hasBeenRefreshed(){
|
||||||
|
lastRefreshed = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package seng302.gameServer;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.ServerSocket;
|
||||||
|
import java.net.Socket;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class for a thread to listen to connections
|
||||||
|
* Created by wmu16 on 11/07/17.
|
||||||
|
*/
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listens for a connection and upon finding one, creates a Player object and adds it to the universal GameState
|
||||||
|
*/
|
||||||
|
private void acceptConnection() {
|
||||||
|
try {
|
||||||
|
Socket thisClient = serverSocket.accept();
|
||||||
|
if (thisClient != null && GameState.getCurrentStage().equals(GameStages.LOBBYING)) {
|
||||||
|
ServerToClientThread thisConnection = new ServerToClientThread(thisClient);
|
||||||
|
delegate.clientConnected(thisConnection);
|
||||||
|
} else {
|
||||||
|
thisClient.close();
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.getMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run(){
|
||||||
|
while (serverSocket != null && !serverSocket.isClosed()){
|
||||||
|
acceptConnection();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package seng302.gameServer;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
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 BoatAction extractBoatAction(StreamPacket packet) {
|
||||||
|
byte[] payload = packet.getPayload();
|
||||||
|
int messageVersionNo = payload[0];
|
||||||
|
long actionTypeValue = Message.bytesToLong(Arrays.copyOfRange(payload, 0, 1));
|
||||||
|
return BoatAction.getType((int) actionTypeValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ClientType extractClientType(StreamPacket packet){
|
||||||
|
byte[] payload = packet.getPayload();
|
||||||
|
long value = Message.bytesToLong(Arrays.copyOfRange(payload, 0, 1));
|
||||||
|
return ClientType.getClientType((int) value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CustomizeRequestType extractCustomizationType(StreamPacket packet) {
|
||||||
|
byte[] payload = packet.getPayload();
|
||||||
|
long type = Message.bytesToLong(Arrays.copyOfRange(payload, 4, 5));
|
||||||
|
return CustomizeRequestType.getRequestType((int) type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ChatterMessage extractChatterText(byte[] payload) {
|
||||||
|
return new ChatterMessage(
|
||||||
|
payload[1], new String(Arrays.copyOfRange(payload, 3, payload.length))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ChatterMessage extractChatterText(StreamPacket packet) {
|
||||||
|
byte[] payload = packet.getPayload();
|
||||||
|
return new ChatterMessage(
|
||||||
|
payload[1], new String(Arrays.copyOfRange(payload, 3, payload.length))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,334 @@
|
|||||||
|
package seng302.gameServer;
|
||||||
|
|
||||||
|
|
||||||
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import seng302.gameServer.messages.*;
|
||||||
|
import org.w3c.dom.Document;
|
||||||
|
import seng302.gameServer.messages.BoatAction;
|
||||||
|
import seng302.gameServer.messages.ChatterMessage;
|
||||||
|
import seng302.gameServer.messages.ClientType;
|
||||||
|
import seng302.gameServer.messages.CustomizeRequestType;
|
||||||
|
import seng302.gameServer.messages.Message;
|
||||||
|
import seng302.gameServer.messages.RegistrationResponseMessage;
|
||||||
|
import seng302.gameServer.messages.RegistrationResponseStatus;
|
||||||
|
import seng302.model.Player;
|
||||||
|
import seng302.model.ServerYacht;
|
||||||
|
import seng302.model.stream.packets.PacketType;
|
||||||
|
import seng302.model.stream.packets.StreamPacket;
|
||||||
|
import seng302.model.stream.xml.parser.RaceXMLData;
|
||||||
|
import seng302.model.stream.xml.parser.RegattaXMLData;
|
||||||
|
import seng302.utilities.StreamParser;
|
||||||
|
import seng302.utilities.XMLGenerator;
|
||||||
|
import seng302.utilities.XMLParser;
|
||||||
|
import seng302.visualiser.fxObjects.assets_3D.BoatMeshType;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.net.SocketException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.zip.CRC32;
|
||||||
|
import java.util.zip.Checksum;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class describing a single connection to a Client for the purposes of sending and receiving on
|
||||||
|
* its own thread. All server threads created and owned by the server thread handler which can
|
||||||
|
* trigger client updates on its threads Created by wmu16 on 13/07/17.
|
||||||
|
*/
|
||||||
|
public class ServerToClientThread implements Runnable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
private InputStream is;
|
||||||
|
private OutputStream os;
|
||||||
|
private Socket socket;
|
||||||
|
|
||||||
|
private ByteArrayOutputStream crcBuffer;
|
||||||
|
|
||||||
|
private Integer seqNo;
|
||||||
|
private Integer sourceId;
|
||||||
|
|
||||||
|
private ClientType clientType;
|
||||||
|
private Boolean isRegistered = false;
|
||||||
|
private Boolean isHost = false;
|
||||||
|
|
||||||
|
private XMLGenerator xmlGenerator;
|
||||||
|
|
||||||
|
private List<ConnectionListener> connectionListeners = new ArrayList<>();
|
||||||
|
private DisconnectListener disconnectListener;
|
||||||
|
|
||||||
|
private Player player;
|
||||||
|
|
||||||
|
private SimpleObjectProperty<RaceXMLData> raceXMLProperty = new SimpleObjectProperty<>();
|
||||||
|
private SimpleObjectProperty<RegattaXMLData> regattaXMLProperty = new SimpleObjectProperty<>();
|
||||||
|
|
||||||
|
public ServerToClientThread(Socket socket) {
|
||||||
|
this.socket = socket;
|
||||||
|
seqNo = 0;
|
||||||
|
|
||||||
|
try{
|
||||||
|
is = socket.getInputStream();
|
||||||
|
os = socket.getOutputStream();
|
||||||
|
} catch (IOException e) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
thread = new Thread(this, "ServerToClient");
|
||||||
|
thread.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getSourceId() {
|
||||||
|
return sourceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setUpPlayer(){
|
||||||
|
String shortName = "P" + sourceId;
|
||||||
|
String longName = "Player " + sourceId;
|
||||||
|
|
||||||
|
ServerYacht yacht = new ServerYacht(
|
||||||
|
BoatMeshType.DINGHY, sourceId, sourceId.toString(), shortName, longName, "NZ");
|
||||||
|
|
||||||
|
player = new Player(socket, yacht);
|
||||||
|
GameState.addYacht(sourceId, yacht);
|
||||||
|
GameState.addPlayer(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
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());
|
||||||
|
|
||||||
|
setUpPlayer();
|
||||||
|
|
||||||
|
for (ConnectionListener listener : connectionListeners) {
|
||||||
|
listener.notifyConnection();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
int sync1;
|
||||||
|
int sync2;
|
||||||
|
// TODO: 14/07/17 wmu16 - Work out how to fix this while loop
|
||||||
|
|
||||||
|
while (socket.isConnected() && !socket.isClosed()) {
|
||||||
|
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) {
|
||||||
|
StreamPacket packet = new StreamPacket(type, payloadLength, timeStamp, payload);
|
||||||
|
switch (PacketType.assignPacketType(type, payload)) {
|
||||||
|
case BOAT_ACTION:
|
||||||
|
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));
|
||||||
|
GameState.setTokensEnabled(XMLParser.tokensEnabled(document));
|
||||||
|
break;
|
||||||
|
case REGATTA_XML:
|
||||||
|
regattaXMLProperty.set(
|
||||||
|
XMLParser.parseRegatta(
|
||||||
|
StreamParser.extractXmlMessage(packet)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.warn("Packet has been dropped", 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
closeSocket();
|
||||||
|
GameState.setPlayerHasLeftFlag(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
GameState.setPlayerHasLeftFlag(true);
|
||||||
|
logger.warn("Closed serverToClientThread" + thread, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendSetupMessages() {
|
||||||
|
sendMessage(MessageFactory.getRegattaXML());
|
||||||
|
sendMessage(MessageFactory.getBoatXML());
|
||||||
|
sendMessage(MessageFactory.getRaceXML());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void closeSocket() {
|
||||||
|
try {
|
||||||
|
socket.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
System.out.println("IO error in server thread upon trying to close socket");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean isSocketOpen() {
|
||||||
|
return !socket.isClosed();
|
||||||
|
}
|
||||||
|
|
||||||
|
private int readByte() throws Exception {
|
||||||
|
int currentByte = -1;
|
||||||
|
try {
|
||||||
|
currentByte = is.read();
|
||||||
|
crcBuffer.write(currentByte);
|
||||||
|
} catch (SocketException se) {
|
||||||
|
disconnectListener.notifyDisconnect(this.player);
|
||||||
|
} catch (IOException e) {
|
||||||
|
disconnectListener.notifyDisconnect(this.player);
|
||||||
|
logger.warn("Socket read 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 void sendMessage(Message message) {
|
||||||
|
try {
|
||||||
|
os.write(message.getBuffer());
|
||||||
|
} catch (SocketException e) {
|
||||||
|
logger.warn("Player " + sourceId + " side socket disconnected", 1);
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.warn("Message send failed", 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getSeqNo() {
|
||||||
|
seqNo++;
|
||||||
|
return seqNo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Thread getThread() {
|
||||||
|
return thread;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Socket getSocket() {
|
||||||
|
return socket;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package seng302.gameServer.messages;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by kre39 on 12/07/17.
|
||||||
|
*/
|
||||||
|
public enum BoatAction {
|
||||||
|
|
||||||
|
VMG(1),
|
||||||
|
SAILS_IN(2),
|
||||||
|
SAILS_OUT(3),
|
||||||
|
TACK_GYBE(4),
|
||||||
|
UPWIND(5),
|
||||||
|
DOWNWIND(6),
|
||||||
|
MAINTAIN_HEADING(7),
|
||||||
|
CONTINUOUSLY_TURNING(8),
|
||||||
|
DEFAULT_TURNING(9);
|
||||||
|
|
||||||
|
private final int type;
|
||||||
|
private static final Map<Integer, BoatAction> intToTypeMap = new HashMap<>();
|
||||||
|
|
||||||
|
static {
|
||||||
|
for (BoatAction type : BoatAction.values()) {
|
||||||
|
intToTypeMap.put(type.getValue(), type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BoatAction(int type){
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BoatAction getType(int value) {
|
||||||
|
return intToTypeMap.get(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getValue() {
|
||||||
|
return this.type;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
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 = 5;
|
||||||
|
private BoatAction actionType;
|
||||||
|
|
||||||
|
public BoatActionMessage(BoatAction actionType, int sourceId) {
|
||||||
|
this.actionType = actionType;
|
||||||
|
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
|
||||||
|
public int getSize() {
|
||||||
|
return MESSAGE_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
+71
-73
@@ -1,9 +1,7 @@
|
|||||||
package seng302.server.messages;
|
package seng302.gameServer.messages;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.channels.SocketChannel;
|
|
||||||
|
|
||||||
public class BoatLocationMessage extends Message {
|
public class BoatLocationMessage extends Message {
|
||||||
|
|
||||||
private final int MESSAGE_SIZE = 56;
|
private final int MESSAGE_SIZE = 56;
|
||||||
|
|
||||||
private long messageVersionNumber;
|
private long messageVersionNumber;
|
||||||
@@ -31,6 +29,7 @@ public class BoatLocationMessage extends Message {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Describes the location, altitude and sensor data from the boat.
|
* Describes the location, altitude and sensor data from the boat.
|
||||||
|
*
|
||||||
* @param sourceId ID of the boat
|
* @param sourceId ID of the boat
|
||||||
* @param sequenceNum Sequence number of the message
|
* @param sequenceNum Sequence number of the message
|
||||||
* @param latitude The boats latitude
|
* @param latitude The boats latitude
|
||||||
@@ -38,8 +37,8 @@ public class BoatLocationMessage extends Message {
|
|||||||
* @param heading The boats heading
|
* @param heading The boats heading
|
||||||
* @param boatSpeed The boats speed
|
* @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,
|
||||||
boatSpeed /= 10;
|
double heading, long boatSpeed) {
|
||||||
messageVersionNumber = 1;
|
messageVersionNumber = 1;
|
||||||
time = System.currentTimeMillis();
|
time = System.currentTimeMillis();
|
||||||
this.sourceId = sourceId;
|
this.sourceId = sourceId;
|
||||||
@@ -53,7 +52,7 @@ public class BoatLocationMessage extends Message {
|
|||||||
this.roll = 0;
|
this.roll = 0;
|
||||||
this.boatSpeed = boatSpeed;
|
this.boatSpeed = boatSpeed;
|
||||||
this.COG = 2;
|
this.COG = 2;
|
||||||
this.SOG = boatSpeed ;
|
this.SOG = boatSpeed;
|
||||||
this.apparentWindSpeed = 0;
|
this.apparentWindSpeed = 0;
|
||||||
this.apparentWindAngle = 0;
|
this.apparentWindAngle = 0;
|
||||||
this.trueWindSpeed = 0;
|
this.trueWindSpeed = 0;
|
||||||
@@ -64,74 +63,10 @@ public class BoatLocationMessage extends Message {
|
|||||||
this.rudderAngle = 0;
|
this.rudderAngle = 0;
|
||||||
|
|
||||||
setHeader(new Header(MessageType.BOAT_LOCATION, 1, (short) getSize()));
|
setHeader(new Header(MessageType.BOAT_LOCATION, 1, (short) getSize()));
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getSize() {
|
|
||||||
return MESSAGE_SIZE;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void send(SocketChannel outputStream) throws IOException{
|
|
||||||
allocateBuffer();
|
allocateBuffer();
|
||||||
writeHeaderToBuffer();
|
writeHeaderToBuffer();
|
||||||
|
|
||||||
long headingToSend = (long)((heading/360.0) * 65535.0);
|
long headingToSend = (long) ((heading / 360.0) * 65535.0);
|
||||||
|
|
||||||
putByte((byte) messageVersionNumber);
|
putByte((byte) messageVersionNumber);
|
||||||
putInt(time, 6);
|
putInt(time, 6);
|
||||||
@@ -158,7 +93,70 @@ public class BoatLocationMessage extends Message {
|
|||||||
|
|
||||||
writeCRC();
|
writeCRC();
|
||||||
rewind();
|
rewind();
|
||||||
|
}
|
||||||
|
|
||||||
outputStream.write(getBuffer());
|
/**
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getSize() {
|
||||||
|
return MESSAGE_SIZE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package seng302.server.messages;
|
package seng302.gameServer.messages;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The current status of a boat
|
* The current status of a boat
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package seng302.server.messages;
|
package seng302.gameServer.messages;
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package seng302.gameServer.messages;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by kre39 on 20/07/17.
|
||||||
|
*/
|
||||||
|
public class ChatterMessage extends Message {
|
||||||
|
|
||||||
|
private final long MESSAGE_VERSION_NUMBER = 1;
|
||||||
|
private final int MESSAGE_SIZE = 3;
|
||||||
|
private int message_type;
|
||||||
|
private int message_size = 21;
|
||||||
|
private String message;
|
||||||
|
|
||||||
|
public ChatterMessage(int message_type, String message) {
|
||||||
|
byte[] byteMessage = message.getBytes();
|
||||||
|
|
||||||
|
this.message_type = message_type;
|
||||||
|
this.message_size = byteMessage.length;
|
||||||
|
this.message = message;
|
||||||
|
|
||||||
|
setHeader(new Header(MessageType.CHATTER_TEXT, 1, (short) getSize()));
|
||||||
|
allocateBuffer();
|
||||||
|
writeHeaderToBuffer();
|
||||||
|
|
||||||
|
putByte((byte) MESSAGE_VERSION_NUMBER);
|
||||||
|
putInt(message_type, 1);
|
||||||
|
putInt(message_size, 1);
|
||||||
|
putBytes(byteMessage);
|
||||||
|
|
||||||
|
writeCRC();
|
||||||
|
rewind();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getSize() {
|
||||||
|
return MESSAGE_SIZE + message_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMessage() {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMessageType() {
|
||||||
|
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 {
|
public enum DeviceType {
|
||||||
UNKNOWN(0),
|
UNKNOWN(0),
|
||||||
+12
-4
@@ -1,9 +1,6 @@
|
|||||||
package seng302.server.messages;
|
package seng302.gameServer.messages;
|
||||||
|
|
||||||
import java.math.BigInteger;
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.ByteOrder;
|
|
||||||
import java.util.Collections;
|
|
||||||
|
|
||||||
public class Header {
|
public class Header {
|
||||||
// From API spec
|
// From API spec
|
||||||
@@ -43,10 +40,21 @@ public class Header {
|
|||||||
buff.position(buffPos);
|
buff.position(buffPos);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset the buffer
|
||||||
|
*/
|
||||||
|
public void reset(){
|
||||||
|
buffPos = 0;
|
||||||
|
buff.clear();
|
||||||
|
buff.position(buffPos);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return a ByteBuffer containing the message header
|
* @return a ByteBuffer containing the message header
|
||||||
*/
|
*/
|
||||||
public ByteBuffer getByteBuffer(){
|
public ByteBuffer getByteBuffer(){
|
||||||
|
reset();
|
||||||
|
|
||||||
putInBuffer(ByteBuffer.allocate(1).put((byte)syncByte1).array(), syncByte1);
|
putInBuffer(ByteBuffer.allocate(1).put((byte)syncByte1).array(), syncByte1);
|
||||||
|
|
||||||
putInBuffer(ByteBuffer.allocate(1).put((byte)syncByte2).array(), syncByte2);
|
putInBuffer(ByteBuffer.allocate(1).put((byte)syncByte2).array(), syncByte2);
|
||||||
+7
-22
@@ -1,32 +1,13 @@
|
|||||||
package seng302.server.messages;
|
package seng302.gameServer.messages;
|
||||||
|
|
||||||
import java.io.DataOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.nio.channels.Channels;
|
|
||||||
import java.nio.channels.SocketChannel;
|
|
||||||
import java.nio.channels.WritableByteChannel;
|
|
||||||
import java.util.zip.CRC32;
|
|
||||||
|
|
||||||
public class Heartbeat extends Message {
|
public class Heartbeat extends Message {
|
||||||
private final int MESSAGE_SIZE = 4;
|
private final int MESSAGE_SIZE = 4;
|
||||||
private int seqNo;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Heartbeat from the AC35 Streaming data spec
|
* Heartbeat from the AC35 Streaming data spec
|
||||||
* @param seqNo Increment every time a message is sent
|
* @param seqNo Increment every time a message is sent
|
||||||
*/
|
*/
|
||||||
public Heartbeat(int seqNo){
|
public Heartbeat(int seqNo){
|
||||||
this.seqNo = seqNo;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getSize() {
|
|
||||||
return MESSAGE_SIZE;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void send(SocketChannel outputStream) throws IOException {
|
|
||||||
setHeader(new Header(MessageType.HEARTBEAT, 0x01, (short) getSize()));
|
setHeader(new Header(MessageType.HEARTBEAT, 0x01, (short) getSize()));
|
||||||
|
|
||||||
allocateBuffer();
|
allocateBuffer();
|
||||||
@@ -36,7 +17,11 @@ public class Heartbeat extends Message {
|
|||||||
|
|
||||||
writeCRC();
|
writeCRC();
|
||||||
rewind();
|
rewind();
|
||||||
|
|
||||||
outputStream.write(getBuffer());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getSize() {
|
||||||
|
return MESSAGE_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
+16
-19
@@ -1,10 +1,4 @@
|
|||||||
package seng302.server.messages;
|
package seng302.gameServer.messages;
|
||||||
|
|
||||||
import java.io.DataOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.channels.Channels;
|
|
||||||
import java.nio.channels.SocketChannel;
|
|
||||||
import java.nio.channels.WritableByteChannel;
|
|
||||||
|
|
||||||
public class MarkRoundingMessage extends Message{
|
public class MarkRoundingMessage extends Message{
|
||||||
private final long MESSAGE_VERSION_NUMBER = 1;
|
private final long MESSAGE_VERSION_NUMBER = 1;
|
||||||
@@ -18,13 +12,21 @@ public class MarkRoundingMessage extends Message{
|
|||||||
private RoundingSide roundingSide;
|
private RoundingSide roundingSide;
|
||||||
private long markId;
|
private long markId;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This message is sent when a boat passes a mark, start line, or finish line
|
* 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
|
* 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,
|
public MarkRoundingMessage(int ackNumber, int raceId, int sourceId, RoundingBoatStatus roundingBoatStatus,
|
||||||
RoundingSide roundingSide, int markId){
|
RoundingSide roundingSide, MarkType markType, int markId) {
|
||||||
this.time = System.currentTimeMillis() / 1000L;
|
this.time = System.currentTimeMillis();
|
||||||
this.ackNumber = ackNumber;
|
this.ackNumber = ackNumber;
|
||||||
this.raceId = raceId;
|
this.raceId = raceId;
|
||||||
this.sourceId = sourceId;
|
this.sourceId = sourceId;
|
||||||
@@ -33,15 +35,6 @@ public class MarkRoundingMessage extends Message{
|
|||||||
this.markId = markId;
|
this.markId = markId;
|
||||||
|
|
||||||
setHeader(new Header(MessageType.MARK_ROUNDING, 1, (short) getSize()));
|
setHeader(new Header(MessageType.MARK_ROUNDING, 1, (short) getSize()));
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getSize() {
|
|
||||||
return MESSAGE_SIZE;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void send(SocketChannel outputStream) throws IOException {
|
|
||||||
allocateBuffer();
|
allocateBuffer();
|
||||||
writeHeaderToBuffer();
|
writeHeaderToBuffer();
|
||||||
|
|
||||||
@@ -52,11 +45,15 @@ public class MarkRoundingMessage extends Message{
|
|||||||
putInt((int) sourceId, 4);
|
putInt((int) sourceId, 4);
|
||||||
putByte((byte) boatStatus.getCode());
|
putByte((byte) boatStatus.getCode());
|
||||||
putByte((byte) roundingSide.getCode());
|
putByte((byte) roundingSide.getCode());
|
||||||
|
putByte((byte) markType.getCode());
|
||||||
putByte((byte) markId);
|
putByte((byte) markId);
|
||||||
|
|
||||||
writeCRC();
|
writeCRC();
|
||||||
rewind();
|
rewind();
|
||||||
|
}
|
||||||
|
|
||||||
outputStream.write(getBuffer());
|
@Override
|
||||||
|
public int getSize() {
|
||||||
|
return MESSAGE_SIZE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package seng302.server.messages;
|
package seng302.gameServer.messages;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Types of marks boats can round
|
* Types of marks boats can round
|
||||||
+32
-12
@@ -1,9 +1,7 @@
|
|||||||
package seng302.server.messages;
|
package seng302.gameServer.messages;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.ByteOrder;
|
import java.nio.ByteOrder;
|
||||||
import java.nio.channels.SocketChannel;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.zip.CRC32;
|
import java.util.zip.CRC32;
|
||||||
|
|
||||||
@@ -33,11 +31,6 @@ public abstract class Message {
|
|||||||
*/
|
*/
|
||||||
public abstract int getSize();
|
public abstract int getSize();
|
||||||
|
|
||||||
/**
|
|
||||||
* Send the message as through the outputStream
|
|
||||||
*/
|
|
||||||
public abstract void send(SocketChannel outputStream) throws IOException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allocate byte buffer to correct size
|
* Allocate byte buffer to correct size
|
||||||
*/
|
*/
|
||||||
@@ -45,6 +38,7 @@ public abstract class Message {
|
|||||||
buffer = ByteBuffer.allocate(Header.getSize() + getSize() + CRC_SIZE);
|
buffer = ByteBuffer.allocate(Header.getSize() + getSize() + CRC_SIZE);
|
||||||
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
bufferPosition = 0;
|
bufferPosition = 0;
|
||||||
|
buffer.position(bufferPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -161,10 +155,16 @@ public abstract class Message {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return The current buffer
|
* @return The current buffer as a byte array
|
||||||
*/
|
*/
|
||||||
public ByteBuffer getBuffer(){
|
public byte[] getBuffer(){
|
||||||
return buffer;
|
byte[] bytes = buffer.array();
|
||||||
|
|
||||||
|
// buffer.reset();
|
||||||
|
// buffer.clear();
|
||||||
|
// buffer = null;
|
||||||
|
|
||||||
|
return bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -178,7 +178,7 @@ public abstract class Message {
|
|||||||
* Convert an integer to an array of bytes
|
* Convert an integer to an array of bytes
|
||||||
* @param val The value to add
|
* @param val The value to add
|
||||||
* @param len The width of the integer in the buffer
|
* @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){
|
public static byte[] intToByteArray(long val, int len){
|
||||||
int index = 0;
|
int index = 0;
|
||||||
@@ -193,6 +193,26 @@ public abstract class Message {
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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){
|
||||||
|
long partialLong = 0;
|
||||||
|
int index = 0;
|
||||||
|
for (byte b: bytes){
|
||||||
|
if (index > 6){
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
partialLong = partialLong | (b & 0xFFL) << (index * 8);
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
return partialLong;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reverse an array of bytes
|
* Reverse an array of bytes
|
||||||
* @param data The byte[] to reverse
|
* @param data The byte[] to reverse
|
||||||
+13
-2
@@ -1,4 +1,4 @@
|
|||||||
package seng302.server.messages;
|
package seng302.gameServer.messages;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enum containing the types of messages
|
* Enum containing the types of messages
|
||||||
@@ -16,7 +16,16 @@ public enum MessageType {
|
|||||||
BOAT_LOCATION(37),
|
BOAT_LOCATION(37),
|
||||||
MARK_ROUNDING(38),
|
MARK_ROUNDING(38),
|
||||||
COURSE_WIND(44),
|
COURSE_WIND(44),
|
||||||
AVERAGE_WIND(47);
|
AVERAGE_WIND(47),
|
||||||
|
BOAT_ACTION(100),
|
||||||
|
REGISTRATION_REQUEST(101),
|
||||||
|
REGISTRATION_RESPONSE(102),
|
||||||
|
CUSTOMIZATION_REQUEST(103),
|
||||||
|
CUSTOMIZATION_RESPONSE(104),
|
||||||
|
REPO_REGISTRATION_REQUEST(201),
|
||||||
|
ROOM_CODE_REQUEST(202),
|
||||||
|
LOBBY_REQUEST(203);
|
||||||
|
|
||||||
|
|
||||||
private int code;
|
private int code;
|
||||||
|
|
||||||
@@ -31,4 +40,6 @@ public enum MessageType {
|
|||||||
int getCode(){
|
int getCode(){
|
||||||
return this.code;
|
return this.code;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package seng302.server.messages;
|
package seng302.gameServer.messages;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The types of race start status messages
|
* The types of race start status messages
|
||||||
+7
-18
@@ -1,10 +1,4 @@
|
|||||||
package seng302.server.messages;
|
package seng302.gameServer.messages;
|
||||||
|
|
||||||
import java.io.DataOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.channels.Channels;
|
|
||||||
import java.nio.channels.SocketChannel;
|
|
||||||
import java.nio.channels.WritableByteChannel;
|
|
||||||
|
|
||||||
public class RaceStartStatusMessage extends Message {
|
public class RaceStartStatusMessage extends Message {
|
||||||
private final int MESSAGE_SIZE = 20;
|
private final int MESSAGE_SIZE = 20;
|
||||||
@@ -32,15 +26,6 @@ public class RaceStartStatusMessage extends Message {
|
|||||||
this.raceId = raceId;
|
this.raceId = raceId;
|
||||||
|
|
||||||
setHeader(new Header(MessageType.RACE_START_STATUS, 1, (short) getSize()));
|
setHeader(new Header(MessageType.RACE_START_STATUS, 1, (short) getSize()));
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getSize() {
|
|
||||||
return MESSAGE_SIZE;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void send(SocketChannel outputStream) throws IOException {
|
|
||||||
allocateBuffer();
|
allocateBuffer();
|
||||||
writeHeaderToBuffer();
|
writeHeaderToBuffer();
|
||||||
|
|
||||||
@@ -53,7 +38,11 @@ public class RaceStartStatusMessage extends Message {
|
|||||||
|
|
||||||
writeCRC();
|
writeCRC();
|
||||||
rewind();
|
rewind();
|
||||||
|
|
||||||
outputStream.write(getBuffer());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getSize() {
|
||||||
|
return MESSAGE_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package seng302.server.messages;
|
package seng302.gameServer.messages;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The current status of the race
|
* The current status of the race
|
||||||
+16
-25
@@ -1,7 +1,5 @@
|
|||||||
package seng302.server.messages;
|
package seng302.gameServer.messages;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.channels.SocketChannel;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.zip.CRC32;
|
import java.util.zip.CRC32;
|
||||||
|
|
||||||
@@ -9,12 +7,14 @@ public class RaceStatusMessage extends Message{
|
|||||||
private final MessageType MESSAGE_TYPE = MessageType.RACE_STATUS;
|
private final MessageType MESSAGE_TYPE = MessageType.RACE_STATUS;
|
||||||
private final int MESSAGE_VERSION = 2; //Always set to 1
|
private final int MESSAGE_VERSION = 2; //Always set to 1
|
||||||
private final int MESSAGE_BASE_SIZE = 24;
|
private final int MESSAGE_BASE_SIZE = 24;
|
||||||
|
private final double windDirFactor = 0x4000 / 90;
|
||||||
|
|
||||||
|
|
||||||
private long currentTime;
|
private long currentTime;
|
||||||
private long raceId;
|
private long raceId;
|
||||||
private RaceStatus raceStatus;
|
private RaceStatus raceStatus;
|
||||||
private long expectedStartTime;
|
private long expectedStartTime;
|
||||||
private WindDirection raceWindDirection;
|
private double raceWindDirection;
|
||||||
private long windSpeed;
|
private long windSpeed;
|
||||||
private long numBoatsInRace;
|
private long numBoatsInRace;
|
||||||
private RaceType raceType;
|
private RaceType raceType;
|
||||||
@@ -33,13 +33,13 @@ public class RaceStatusMessage extends Message{
|
|||||||
* @param sourceId The source of this message
|
* @param sourceId The source of this message
|
||||||
* @param boats A list of boat status sub messages
|
* @param boats A list of boat status sub messages
|
||||||
*/
|
*/
|
||||||
public RaceStatusMessage(long raceId, RaceStatus raceStatus, long expectedStartTime, WindDirection raceWindDirection,
|
public RaceStatusMessage(long raceId, RaceStatus raceStatus, long expectedStartTime, double raceWindDirection,
|
||||||
long windSpeed, long numBoatsInRace, RaceType raceType, long sourceId, List<BoatSubMessage> boats){
|
long windSpeed, long numBoatsInRace, RaceType raceType, long sourceId, List<BoatSubMessage> boats){
|
||||||
currentTime = System.currentTimeMillis();
|
currentTime = System.currentTimeMillis();
|
||||||
this.raceId = raceId;
|
this.raceId = raceId;
|
||||||
this.raceStatus = raceStatus;
|
this.raceStatus = raceStatus;
|
||||||
this.expectedStartTime = expectedStartTime;
|
this.expectedStartTime = expectedStartTime;
|
||||||
this.raceWindDirection = raceWindDirection;
|
this.raceWindDirection = raceWindDirection * windDirFactor+100.0;
|
||||||
this.windSpeed = windSpeed;
|
this.windSpeed = windSpeed;
|
||||||
this.numBoatsInRace = numBoatsInRace;
|
this.numBoatsInRace = numBoatsInRace;
|
||||||
this.raceType = raceType;
|
this.raceType = raceType;
|
||||||
@@ -47,22 +47,6 @@ public class RaceStatusMessage extends Message{
|
|||||||
crc = new CRC32();
|
crc = new CRC32();
|
||||||
|
|
||||||
setHeader(new Header(MESSAGE_TYPE, (int) sourceId, (short) getSize()));
|
setHeader(new Header(MESSAGE_TYPE, (int) sourceId, (short) getSize()));
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the size of this message in bytes
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public int getSize() {
|
|
||||||
return MESSAGE_BASE_SIZE + (20 * ((int) numBoatsInRace));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send this message as a stream of bytes
|
|
||||||
* @param outputStream The output stream to send the message
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void send(SocketChannel outputStream) throws IOException {
|
|
||||||
allocateBuffer();
|
allocateBuffer();
|
||||||
writeHeaderToBuffer();
|
writeHeaderToBuffer();
|
||||||
|
|
||||||
@@ -71,7 +55,7 @@ public class RaceStatusMessage extends Message{
|
|||||||
putInt((int) raceId, 4);
|
putInt((int) raceId, 4);
|
||||||
putByte((byte) raceStatus.getCode());
|
putByte((byte) raceStatus.getCode());
|
||||||
putInt(expectedStartTime, 6);
|
putInt(expectedStartTime, 6);
|
||||||
putInt((int) raceWindDirection.getCode(), 2);
|
putInt((int) this.raceWindDirection, 2);
|
||||||
putInt((int) windSpeed, 2);
|
putInt((int) windSpeed, 2);
|
||||||
putByte((byte) numBoatsInRace);
|
putByte((byte) numBoatsInRace);
|
||||||
putByte((byte) raceType.getCode());
|
putByte((byte) raceType.getCode());
|
||||||
@@ -82,7 +66,14 @@ public class RaceStatusMessage extends Message{
|
|||||||
|
|
||||||
writeCRC();
|
writeCRC();
|
||||||
rewind();
|
rewind();
|
||||||
|
|
||||||
outputStream.write(getBuffer());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the size of this message in bytes
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int getSize() {
|
||||||
|
return MESSAGE_BASE_SIZE + (20 * ((int) numBoatsInRace));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package seng302.server.messages;
|
package seng302.gameServer.messages;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enum containing the types of races
|
* 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package seng302.gameServer.messages;
|
||||||
|
|
||||||
|
public class RoomCodeRequest extends Message{
|
||||||
|
private int size = 0;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getSize() {
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RoomCodeRequest(String roomCode){
|
||||||
|
size = roomCode.length() + 6;
|
||||||
|
|
||||||
|
setHeader(new Header(MessageType.ROOM_CODE_REQUEST, 0x01, (short)getSize()));
|
||||||
|
allocateBuffer();
|
||||||
|
writeHeaderToBuffer();
|
||||||
|
|
||||||
|
putInt(roomCode.length(), 6);
|
||||||
|
putBytes(roomCode.getBytes());
|
||||||
|
|
||||||
|
writeCRC();
|
||||||
|
rewind();
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package seng302.server.messages;
|
package seng302.gameServer.messages;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The status of a boat rounding a mark
|
* 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
package seng302.gameServer.messages;
|
||||||
|
|
||||||
|
import seng302.discoveryServer.util.ServerListing;
|
||||||
|
|
||||||
|
public class ServerRegistrationMessage extends Message {
|
||||||
|
private int size;
|
||||||
|
|
||||||
|
public ServerRegistrationMessage(ServerListing serverListing) {
|
||||||
|
String serverName = serverListing.getServerName();
|
||||||
|
String mapName = serverListing.getMapName();
|
||||||
|
String address = serverListing.getAddress();
|
||||||
|
int port = serverListing.getPortNumber();
|
||||||
|
int players = serverListing.getPortNumber();
|
||||||
|
int capacity = serverListing.getCapacity();
|
||||||
|
String roomCode = serverListing.getRoomCode();
|
||||||
|
|
||||||
|
createMessage(serverName, mapName, address, port, players, capacity, roomCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ServerRegistrationMessage getEmptyRegistration() {
|
||||||
|
return new ServerRegistrationMessage("","","",0,0,0,"");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getSize() {
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServerRegistrationMessage(String serverName, String mapName, String address, int port, int players, int capacity, String roomCode){
|
||||||
|
createMessage(serverName, mapName, address, port, players, capacity, roomCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createMessage(String serverName, String mapName, String address, int port, int players, int capacity, String roomCode){
|
||||||
|
size = serverName.getBytes().length + mapName.length() + address.length() + roomCode.length() + 36;
|
||||||
|
|
||||||
|
setHeader(new Header(MessageType.REPO_REGISTRATION_REQUEST, 0x01, (short) getSize()));
|
||||||
|
allocateBuffer();
|
||||||
|
writeHeaderToBuffer();
|
||||||
|
|
||||||
|
int nameLength = serverName.length();
|
||||||
|
int mapNameLength = mapName.length();
|
||||||
|
int addressLength = address.length();
|
||||||
|
int roomCodeLength = roomCode.length();
|
||||||
|
|
||||||
|
// Put fields here
|
||||||
|
putInt(nameLength, 6);
|
||||||
|
putInt(mapNameLength, 6);
|
||||||
|
putInt(addressLength, 6);
|
||||||
|
putInt(roomCodeLength, 6);
|
||||||
|
|
||||||
|
putInt(port, 4);
|
||||||
|
putInt(players, 4);
|
||||||
|
putInt(capacity, 4);
|
||||||
|
|
||||||
|
putBytes(serverName.getBytes());
|
||||||
|
putBytes(mapName.getBytes());
|
||||||
|
putBytes(address.getBytes());
|
||||||
|
putBytes(roomCode.getBytes());
|
||||||
|
|
||||||
|
writeCRC();
|
||||||
|
rewind();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+8
-24
@@ -1,12 +1,4 @@
|
|||||||
package seng302.server.messages;
|
package seng302.gameServer.messages;
|
||||||
|
|
||||||
import java.io.DataOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.nio.channels.Channels;
|
|
||||||
import java.nio.channels.SocketChannel;
|
|
||||||
import java.nio.channels.WritableByteChannel;
|
|
||||||
import java.util.zip.CRC32;
|
|
||||||
|
|
||||||
public class XMLMessage extends Message{
|
public class XMLMessage extends Message{
|
||||||
private final MessageType MESSAGE_TYPE = MessageType.XML_MESSAGE;
|
private final MessageType MESSAGE_TYPE = MessageType.XML_MESSAGE;
|
||||||
@@ -25,6 +17,7 @@ public class XMLMessage extends Message{
|
|||||||
* XML Message from the AC35 Streaming data spec
|
* XML Message from the AC35 Streaming data spec
|
||||||
* @param content The XML content
|
* @param content The XML content
|
||||||
* @param type The XML Message Sub Type
|
* @param type The XML Message Sub Type
|
||||||
|
* @param sequenceNum sequenceNum
|
||||||
*/
|
*/
|
||||||
public XMLMessage(String content, XMLMessageSubType type, long sequenceNum){
|
public XMLMessage(String content, XMLMessageSubType type, long sequenceNum){
|
||||||
this.content = content;
|
this.content = content;
|
||||||
@@ -35,20 +28,6 @@ public class XMLMessage extends Message{
|
|||||||
sequence = sequenceNum;
|
sequence = sequenceNum;
|
||||||
|
|
||||||
setHeader(new Header(MESSAGE_TYPE, 0x01, (short) getSize()));
|
setHeader(new Header(MESSAGE_TYPE, 0x01, (short) getSize()));
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return The length of this message
|
|
||||||
*/
|
|
||||||
public int getSize(){
|
|
||||||
return MESSAGE_SIZE + content.length();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send this message as a stream of bytes
|
|
||||||
* @param outputStream The output stream to send the message
|
|
||||||
*/
|
|
||||||
public void send(SocketChannel outputStream) throws IOException {
|
|
||||||
allocateBuffer();
|
allocateBuffer();
|
||||||
writeHeaderToBuffer();
|
writeHeaderToBuffer();
|
||||||
|
|
||||||
@@ -63,7 +42,12 @@ public class XMLMessage extends Message{
|
|||||||
|
|
||||||
writeCRC();
|
writeCRC();
|
||||||
rewind();
|
rewind();
|
||||||
|
}
|
||||||
|
|
||||||
outputStream.write(getBuffer());
|
/**
|
||||||
|
* @return The length of this message
|
||||||
|
*/
|
||||||
|
public int getSize(){
|
||||||
|
return MESSAGE_SIZE + content.length();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package seng302.server.messages;
|
package seng302.gameServer.messages;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enum containing the types of XML 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,26 @@
|
|||||||
|
package seng302.gameServer.messages;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enum for different event types for the yacht
|
||||||
|
*/
|
||||||
|
public enum YachtEventType {
|
||||||
|
COLLISION(33),
|
||||||
|
TOKEN_VELOCITY(34),
|
||||||
|
TOKEN_BUMPER(35),
|
||||||
|
TOKEN_HANDLING(36),
|
||||||
|
TOKEN_WIND_WALKER(37),
|
||||||
|
TOKEN_RANDOM(38),
|
||||||
|
POWER_DOWN(39),
|
||||||
|
BUMPER_CRASH(40);
|
||||||
|
|
||||||
|
|
||||||
|
private int code;
|
||||||
|
|
||||||
|
YachtEventType(int code) {
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCode() {
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,386 @@
|
|||||||
|
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.model.token.TokenType;
|
||||||
|
import seng302.visualiser.fxObjects.assets_3D.BoatMeshType;
|
||||||
|
import seng302.visualiser.fxObjects.assets_3D.BoatObject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Yacht class for the racing boat. <p> Class created to store more variables (eg. boat statuses)
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface ColorChangeListener {
|
||||||
|
|
||||||
|
void notifyColorChange(ClientYacht yacht);
|
||||||
|
}
|
||||||
|
|
||||||
|
//This notifies RaceViewController so it can display icon - wmu16
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface PowerUpListener {
|
||||||
|
void notifyPowerUp(ClientYacht yacht, TokenType tokenType);
|
||||||
|
}
|
||||||
|
|
||||||
|
//This notifies RaceViewController so it can remove token icon - wmu16
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface PowerDownListener {
|
||||||
|
void notifyPowerDown(ClientYacht yacht);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private Logger logger = LoggerFactory.getLogger(ClientYacht.class);
|
||||||
|
|
||||||
|
|
||||||
|
private BoatMeshType boatType;
|
||||||
|
private Integer sourceId;
|
||||||
|
private String hullID; //matches HullNum in the XML spec.
|
||||||
|
private String shortName;
|
||||||
|
private String boatName;
|
||||||
|
private String country;
|
||||||
|
private Integer position;
|
||||||
|
private TokenType powerUp;
|
||||||
|
|
||||||
|
private Long estimateTimeAtFinish;
|
||||||
|
private Boolean sailIn = 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 List<PowerUpListener> powerUpListeners = new ArrayList<>();
|
||||||
|
private List<PowerDownListener> powerDownListeners = new ArrayList<>();
|
||||||
|
private List<ColorChangeListener> colorChangeListeners = new ArrayList<>();
|
||||||
|
|
||||||
|
private ReadOnlyDoubleWrapper velocityProperty = new ReadOnlyDoubleWrapper();
|
||||||
|
private ReadOnlyLongWrapper timeTillNextProperty = new ReadOnlyLongWrapper();
|
||||||
|
private ReadOnlyLongWrapper timeSinceLastMarkProperty = new ReadOnlyLongWrapper();
|
||||||
|
private ReadOnlyIntegerWrapper placingProperty = new ReadOnlyIntegerWrapper();
|
||||||
|
private ReadOnlyDoubleWrapper headingProperty = new ReadOnlyDoubleWrapper();
|
||||||
|
private Color colour;
|
||||||
|
|
||||||
|
public ClientYacht(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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Powers down the boat and notifies the raceViewController to display
|
||||||
|
*/
|
||||||
|
public void powerDown() {
|
||||||
|
this.powerUp = null;
|
||||||
|
for (PowerDownListener listener : powerDownListeners) {
|
||||||
|
listener.notifyPowerDown(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* powers up the boat and notifies the raceViewController to display
|
||||||
|
*
|
||||||
|
* @param tokenType The type of token that this boat is being powered up with
|
||||||
|
*/
|
||||||
|
public void setPowerUp(TokenType tokenType) {
|
||||||
|
this.powerUp = tokenType;
|
||||||
|
for (PowerUpListener listener : powerUpListeners) {
|
||||||
|
listener.notifyPowerUp(this, tokenType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public TokenType getPowerUp() {
|
||||||
|
return powerUp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void toggleSail() {
|
||||||
|
sailIn = !sailIn;
|
||||||
|
}
|
||||||
|
//// 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;
|
||||||
|
System.out.println(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;
|
||||||
|
for (ColorChangeListener listener : colorChangeListeners) {
|
||||||
|
listener.notifyColorChange(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateLocation(double lat, double lng, double heading, double velocity) {
|
||||||
|
setLocation(lat, lng);
|
||||||
|
this.heading = heading;
|
||||||
|
setHeadingProperty();
|
||||||
|
this.currentVelocity = velocity;
|
||||||
|
updateVelocityProperty(velocity);
|
||||||
|
for (YachtLocationListener yll : locationListeners) {
|
||||||
|
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 addPowerUpListener(PowerUpListener listener) {
|
||||||
|
powerUpListeners.add(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addPowerDownListener(PowerDownListener listener) {
|
||||||
|
powerDownListeners.add(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addColorChangeListener(ColorChangeListener listener) {
|
||||||
|
colorChangeListeners.add(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeMarkRoundingListener(MarkRoundingListener listener) {
|
||||||
|
markRoundingListeners.remove(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+13
-8
@@ -1,18 +1,18 @@
|
|||||||
package seng302.server.simulator.mark;
|
package seng302.model;
|
||||||
|
|
||||||
public class Position {
|
/**
|
||||||
|
* 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 Position(double lat, double lng) {
|
public GeoPoint(double lat, double lng) {
|
||||||
this.lat = lat;
|
this.lat = lat;
|
||||||
this.lng = lng;
|
this.lng = lng;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String toString() {
|
|
||||||
return String.format("Position at lat:%f lng:%f.", lat, lng);
|
|
||||||
}
|
|
||||||
|
|
||||||
public double getLat() {
|
public double getLat() {
|
||||||
return lat;
|
return lat;
|
||||||
}
|
}
|
||||||
@@ -28,4 +28,9 @@ public class Position {
|
|||||||
public void setLng(double lng) {
|
public void setLng(double lng) {
|
||||||
this.lng = 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() + "}";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package seng302.model;
|
||||||
|
|
||||||
|
import java.net.Socket;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Class defining a player and their respective details in the game as held by the model
|
||||||
|
* Created by wmu16 on 10/07/17.
|
||||||
|
*/
|
||||||
|
public class Player {
|
||||||
|
|
||||||
|
private Socket socket;
|
||||||
|
private ServerYacht yacht;
|
||||||
|
private Integer lastMarkPassed;
|
||||||
|
|
||||||
|
|
||||||
|
public Player(Socket socket, ServerYacht yacht) {
|
||||||
|
this.socket = socket;
|
||||||
|
this.yacht = yacht;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Socket getSocket() {
|
||||||
|
return socket;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getLastMarkPassed() {
|
||||||
|
return lastMarkPassed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLastMarkPassed(Integer lastMarkPassed) {
|
||||||
|
this.lastMarkPassed = lastMarkPassed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServerYacht getYacht() {
|
||||||
|
return yacht;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
String playerAddress = null;
|
||||||
|
|
||||||
|
if (socket == null){
|
||||||
|
return "Disconnected Player";
|
||||||
|
}
|
||||||
|
|
||||||
|
playerAddress = socket.getRemoteSocketAddress().toString();
|
||||||
|
|
||||||
|
|
||||||
|
return playerAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (obj == null){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(obj instanceof Player)){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ((Player) obj).socket.equals(socket);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode(){
|
||||||
|
return socket.hashCode();
|
||||||
|
}
|
||||||
|
}
|
||||||
+82
-30
@@ -1,8 +1,12 @@
|
|||||||
package seng302.models;
|
package seng302.model;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.BufferedReader;
|
||||||
import java.util.ArrayList;
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A static class for parsing and storing the polars. Will parse the whole polar table and also store the optimised
|
* A static class for parsing and storing the polars. Will parse the whole polar table and also store the optimised
|
||||||
@@ -15,6 +19,7 @@ public final class PolarTable {
|
|||||||
private static HashMap<Double, HashMap<Double, Double>> polarTable;
|
private static HashMap<Double, HashMap<Double, Double>> polarTable;
|
||||||
private static HashMap<Double, HashMap<Double, Double>> upwindOptimal;
|
private static HashMap<Double, HashMap<Double, Double>> upwindOptimal;
|
||||||
private static HashMap<Double, HashMap<Double, Double>> downwindOptimal;
|
private static HashMap<Double, HashMap<Double, Double>> downwindOptimal;
|
||||||
|
private static Double optimalAngle;
|
||||||
|
|
||||||
private static int upTwaIndex;
|
private static int upTwaIndex;
|
||||||
private static int dnTwaIndex;
|
private static int dnTwaIndex;
|
||||||
@@ -24,18 +29,20 @@ 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.
|
* 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
|
* 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
|
* as a value
|
||||||
* @param file containing the polar csv information
|
* @param polarFile polarFile to be parsed
|
||||||
*/
|
*/
|
||||||
public static void parsePolarFile(String file) {
|
public static void parsePolarFile(InputStream polarFile) {
|
||||||
polarTable = new HashMap<>();
|
polarTable = new HashMap<>();
|
||||||
upwindOptimal = new HashMap<>();
|
upwindOptimal = new HashMap<>();
|
||||||
downwindOptimal = new HashMap<>();
|
downwindOptimal = new HashMap<>();
|
||||||
|
|
||||||
String line;
|
String line = null;
|
||||||
|
String check;
|
||||||
Boolean isHeaderLine = true;
|
Boolean isHeaderLine = true;
|
||||||
|
|
||||||
try (BufferedReader br = new BufferedReader(new FileReader(file))) {
|
try (BufferedReader br = new BufferedReader(new InputStreamReader(polarFile))) {
|
||||||
while ((line = br.readLine()) != null) {
|
while ((check = br.readLine()) != null) {
|
||||||
|
line = check;
|
||||||
String[] thisLine = line.split(",");
|
String[] thisLine = line.split(",");
|
||||||
|
|
||||||
//Initial line in file
|
//Initial line in file
|
||||||
@@ -64,14 +71,38 @@ public final class PolarTable {
|
|||||||
upwindOptimal.put(thisWindSpeed, thisUpWindPolar);
|
upwindOptimal.put(thisWindSpeed, thisUpWindPolar);
|
||||||
downwindOptimal.put(thisWindSpeed, thisDnWindPolar);
|
downwindOptimal.put(thisWindSpeed, thisDnWindPolar);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
getMaxSpeedAngle(line);
|
||||||
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
System.out.println("[PolarTable] IO exception");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Passes the final line of the polar table and iterates over the speeds for each
|
||||||
|
* angle, velocity pair to find the angle that produces the highest velocity
|
||||||
|
*
|
||||||
|
* @param line The last line of the polar file
|
||||||
|
*/
|
||||||
|
private static void getMaxSpeedAngle(String line) {
|
||||||
|
String[] theLastLine = line.split(",");
|
||||||
|
Double maxWindVal = Double.parseDouble(theLastLine[0]);
|
||||||
|
Double optimalAngle = Double.parseDouble(theLastLine[1]);
|
||||||
|
Double maxSpeed = Double.parseDouble(theLastLine[2]);
|
||||||
|
for (Map.Entry<Double, Double> entry : polarTable.get(maxWindVal).entrySet()) {
|
||||||
|
if (entry.getValue() > maxSpeed) {
|
||||||
|
maxSpeed = entry.getValue();
|
||||||
|
optimalAngle = entry.getKey();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PolarTable.optimalAngle = optimalAngle;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses the header line of a polar file
|
* Parses the header line of a polar file
|
||||||
@@ -83,14 +114,18 @@ public final class PolarTable {
|
|||||||
String thisItem = thisLine[i];
|
String thisItem = thisLine[i];
|
||||||
if (thisItem.toLowerCase().startsWith("uptwa")) {
|
if (thisItem.toLowerCase().startsWith("uptwa")) {
|
||||||
upTwaIndex = i;
|
upTwaIndex = i;
|
||||||
}
|
} else if (thisItem.toLowerCase().startsWith("dntwa")) {
|
||||||
else if (thisItem.toLowerCase().startsWith("dntwa")) {
|
|
||||||
dnTwaIndex = i;
|
dnTwaIndex = i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static Double getOptimalAngle() {
|
||||||
|
return optimalAngle;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return The entire polar table
|
* @return The entire polar table
|
||||||
*/
|
*/
|
||||||
@@ -122,7 +157,7 @@ public final class PolarTable {
|
|||||||
*/
|
*/
|
||||||
public static HashMap<Double, Double> getOptimalUpwindVMG(Double thisWindSpeed) {
|
public static HashMap<Double, Double> getOptimalUpwindVMG(Double thisWindSpeed) {
|
||||||
|
|
||||||
Double polarWindSpeed = getClosestMatch(thisWindSpeed);
|
Double polarWindSpeed = getClosestWindSpeedInPolar(thisWindSpeed);
|
||||||
return upwindOptimal.get(polarWindSpeed);
|
return upwindOptimal.get(polarWindSpeed);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,30 +169,47 @@ public final class PolarTable {
|
|||||||
*/
|
*/
|
||||||
public static HashMap<Double, Double> getOptimalDownwindVMG(Double thisWindSpeed) {
|
public static HashMap<Double, Double> getOptimalDownwindVMG(Double thisWindSpeed) {
|
||||||
|
|
||||||
Double polarWindSpeed = getClosestMatch(thisWindSpeed);
|
Double polarWindSpeed = getClosestWindSpeedInPolar(thisWindSpeed);
|
||||||
return downwindOptimal.get(polarWindSpeed);
|
return downwindOptimal.get(polarWindSpeed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static Double getClosestMatch(Double thisWindSpeed) {
|
public static Double getBoatSpeed(Double thisWindSpeed, Double thisHeading) {
|
||||||
|
|
||||||
ArrayList<Double> windValues = new ArrayList<>(polarTable.keySet());
|
Double polarWindSpeed = getClosestWindSpeedInPolar(thisWindSpeed);
|
||||||
|
Double polarAngle = getClosestAngleInPolar(polarTable.get(polarWindSpeed), thisHeading);
|
||||||
|
|
||||||
Double lowerVal = windValues.get(0);
|
return polarTable.get(polarWindSpeed).get(polarAngle);
|
||||||
Double upperVal = windValues.get(1);
|
|
||||||
|
|
||||||
for(int i = 0; i < windValues.size() - 1; i++) {
|
|
||||||
lowerVal = windValues.get(i);
|
|
||||||
upperVal = windValues.get(i+1);
|
|
||||||
if (thisWindSpeed <= upperVal) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Double lowerDiff = Math.abs(lowerVal - thisWindSpeed);
|
|
||||||
Double upperDiff = Math.abs(upperVal - thisWindSpeed);
|
|
||||||
|
|
||||||
return (lowerDiff <= upperDiff) ? lowerVal : upperVal;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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) {
|
||||||
|
smallestDif = difference;
|
||||||
|
closestWind = polarWindSpeed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return closestWind;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static Double getClosestAngleInPolar(HashMap<Double, Double> thisWindSpeedPolar, Double thisHeading) {
|
||||||
|
Double smallestDif = Double.POSITIVE_INFINITY;
|
||||||
|
Double closestAngle = 0d;
|
||||||
|
|
||||||
|
for (Double polarAngle : thisWindSpeedPolar.keySet()) {
|
||||||
|
Double difference = Math.abs(polarAngle - thisHeading);
|
||||||
|
if (difference < smallestDif) {
|
||||||
|
smallestDif = difference;
|
||||||
|
closestAngle = polarAngle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return closestAngle;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReadOnlyDoubleWrapper getWindDirection() {
|
||||||
|
return windDirection;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
package seng302.model;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
import javafx.geometry.Point2D;
|
||||||
|
import seng302.utilities.GeoUtility;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contains information on a scaled lat lon point for use with mapping geographical elements to a 2d plane.
|
||||||
|
*/
|
||||||
|
public class ScaledPoint extends GeoPoint {
|
||||||
|
|
||||||
|
public enum ScaleDirection {
|
||||||
|
HORIZONTAL,
|
||||||
|
VERTICAL
|
||||||
|
}
|
||||||
|
|
||||||
|
private double x, y, scaleFactor;
|
||||||
|
private ScaleDirection scaleDirection;
|
||||||
|
|
||||||
|
private ScaledPoint(double lat, double lng, double x, double y, double scaleFactor, ScaleDirection direction) {
|
||||||
|
super(lat, lng);
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
this.scaleFactor = scaleFactor;
|
||||||
|
this.scaleDirection = direction;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getX() {
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getY() {
|
||||||
|
return y;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getScaleFactor() {
|
||||||
|
return scaleFactor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ScaleDirection getScaleDirection() {
|
||||||
|
return scaleDirection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Point2D findScaledXY(GeoPoint unscaled) {
|
||||||
|
return findScaledXY(unscaled.getLat(), unscaled.getLng());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Point2D findScaledXY(double unscaledLat, double unscaledLon) {
|
||||||
|
double distanceFromReference;
|
||||||
|
double angleFromReference;
|
||||||
|
double xReference = this.getX();
|
||||||
|
double yReference = this.getY();
|
||||||
|
|
||||||
|
angleFromReference = GeoUtility.getBearingRad(
|
||||||
|
this, new GeoPoint(unscaledLat, unscaledLon)
|
||||||
|
);
|
||||||
|
distanceFromReference = GeoUtility.getDistance(
|
||||||
|
this, new GeoPoint(unscaledLat, unscaledLon)
|
||||||
|
);
|
||||||
|
if (angleFromReference >= 0 && angleFromReference <= Math.PI / 2) {
|
||||||
|
xReference += scaleFactor * Math.sin(angleFromReference) * distanceFromReference;
|
||||||
|
yReference -= scaleFactor * Math.cos(angleFromReference) * distanceFromReference;
|
||||||
|
} else if (angleFromReference >= 0) {
|
||||||
|
angleFromReference = angleFromReference - Math.PI / 2;
|
||||||
|
xReference += scaleFactor * Math.cos(angleFromReference) * distanceFromReference;
|
||||||
|
yReference += scaleFactor * Math.sin(angleFromReference) * distanceFromReference;
|
||||||
|
} else if (angleFromReference < 0 && angleFromReference >= -Math.PI / 2) {
|
||||||
|
angleFromReference = Math.abs(angleFromReference);
|
||||||
|
xReference -= scaleFactor * Math.sin(angleFromReference) * distanceFromReference;
|
||||||
|
yReference -= scaleFactor * Math.cos(angleFromReference) * distanceFromReference;
|
||||||
|
} else {
|
||||||
|
angleFromReference = Math.abs(angleFromReference) - Math.PI / 2;
|
||||||
|
xReference -= scaleFactor * Math.cos(angleFromReference) * distanceFromReference;
|
||||||
|
yReference += scaleFactor * Math.sin(angleFromReference) * distanceFromReference;
|
||||||
|
}
|
||||||
|
return new Point2D(xReference, yReference);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ScaledPoint makeScaledPoint(double width, double height,
|
||||||
|
List<? extends GeoPoint> points, boolean centered) {
|
||||||
|
|
||||||
|
double referencePointX, referencePointY, scaleFactor, lat, lng;
|
||||||
|
ScaleDirection scaleDirection;
|
||||||
|
points = new ArrayList<>(points);
|
||||||
|
points.sort(Comparator.comparingDouble(GeoPoint::getLat));
|
||||||
|
GeoPoint minLatPoint = points.get(0);
|
||||||
|
GeoPoint maxLatPoint = points.get(points.size() - 1);
|
||||||
|
|
||||||
|
points.sort(Comparator.comparingDouble(GeoPoint::getLng));
|
||||||
|
GeoPoint minLonPoint = points.get(0);
|
||||||
|
GeoPoint maxLonPoint = points.get(points.size() - 1);
|
||||||
|
|
||||||
|
referencePointX = centered ? 0 : width / 2;
|
||||||
|
referencePointY = centered ? 0 : height / 2;
|
||||||
|
|
||||||
|
lat = (maxLatPoint.getLat() - minLatPoint.getLat()) / 2 + minLatPoint.getLat();
|
||||||
|
lng = (maxLonPoint.getLng() - minLonPoint.getLng()) / 2 + minLonPoint.getLng();
|
||||||
|
|
||||||
|
GeoPoint ref = new GeoPoint(lat, lng);
|
||||||
|
|
||||||
|
double vertDistance = GeoUtility.getDistance(
|
||||||
|
ref, new GeoPoint(ref.getLat(), maxLonPoint.getLng())
|
||||||
|
) * 2.1;
|
||||||
|
|
||||||
|
double horiDistance = GeoUtility.getDistance(
|
||||||
|
ref, new GeoPoint(maxLatPoint.getLat(), ref.getLng())
|
||||||
|
) * 2.1;
|
||||||
|
|
||||||
|
double vertScale = height / vertDistance;
|
||||||
|
|
||||||
|
if (horiDistance * vertScale > width) {
|
||||||
|
scaleFactor = width / horiDistance;
|
||||||
|
scaleDirection = ScaleDirection.HORIZONTAL;
|
||||||
|
} else {
|
||||||
|
scaleFactor = vertScale;
|
||||||
|
scaleDirection = ScaleDirection.VERTICAL;
|
||||||
|
}
|
||||||
|
return new ScaledPoint(lat, lng, referencePointX, referencePointY, scaleFactor, scaleDirection);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,498 @@
|
|||||||
|
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 boatTypeSpeedMultiplier = 1.0;
|
||||||
|
private Double boatTypeTurnStepMultiplier = 1.0;
|
||||||
|
private Double boatTypeAccelerationMultiplier = 1.0;
|
||||||
|
private Integer sourceId;
|
||||||
|
private String hullID; //matches HullNum in the XML spec.
|
||||||
|
private String shortName;
|
||||||
|
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;
|
||||||
|
private Double powerUpSpeedMultiplier;
|
||||||
|
private Integer powerUpHandlingMultiplier;
|
||||||
|
|
||||||
|
//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.powerUpSpeedMultiplier = 1d;
|
||||||
|
this.powerUpHandlingMultiplier = 1;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Powers up a yacht with a given yacht, only after powering it down first to avoid double power
|
||||||
|
* ups
|
||||||
|
*
|
||||||
|
* @param powerUp The given power up
|
||||||
|
*/
|
||||||
|
public void powerUp(TokenType powerUp) {
|
||||||
|
powerDown();
|
||||||
|
switch (powerUp) {
|
||||||
|
case BOOST:
|
||||||
|
powerUpSpeedMultiplier = GameState.VELOCITY_BOOST_MULTIPLIER;
|
||||||
|
break;
|
||||||
|
case HANDLING:
|
||||||
|
powerUpHandlingMultiplier = GameState.HANDLING_BOOST_MULTIPLIER;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
this.powerUp = powerUp;
|
||||||
|
powerUpStartTime = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Powers down a yacht, returning its power multipliers back to 1
|
||||||
|
*/
|
||||||
|
public void powerDown() {
|
||||||
|
this.powerUp = null;
|
||||||
|
this.powerUpSpeedMultiplier = 1d;
|
||||||
|
this.powerUpHandlingMultiplier = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getPowerUpStartTime() {
|
||||||
|
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 * powerUpHandlingMultiplier * boatTypeTurnStepMultiplier;
|
||||||
|
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.boatTypeAccelerationMultiplier = boatType.accelerationMultiplier;
|
||||||
|
this.boatTypeSpeedMultiplier = boatType.maxSpeedMultiplier;
|
||||||
|
this.boatTypeTurnStepMultiplier = boatType.turnStep;
|
||||||
|
this.boatType = boatType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Double getBoatTypeSpeedMultiplier() {
|
||||||
|
return boatTypeSpeedMultiplier;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Double getBoatTypeAccelerationMultiplier() {
|
||||||
|
return boatTypeAccelerationMultiplier;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public BoatMeshType getBoatType() {
|
||||||
|
return boatType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setContinuouslyTurning(Boolean continuouslyTurning) {
|
||||||
|
this.continuouslyTurning = continuouslyTurning;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Double getPowerUpSpeedMultiplier() {
|
||||||
|
return powerUpSpeedMultiplier;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPowerUpSpeedMultiplier(Double powerUpSpeedMultiplier) {
|
||||||
|
this.powerUpSpeedMultiplier = powerUpSpeedMultiplier;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getPowerUpHandlingMultiplier() {
|
||||||
|
return powerUpHandlingMultiplier;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPowerUpHandlingMultiplier(Integer powerUpHandlingMultiplier) {
|
||||||
|
this.powerUpHandlingMultiplier = powerUpHandlingMultiplier;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
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,79 @@
|
|||||||
|
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 List<CompoundMark> orderedUniqueCompoundMarks;
|
||||||
|
private Logger logger = LoggerFactory.getLogger(MarkOrder.class);
|
||||||
|
private List<Mark> allMarks;
|
||||||
|
|
||||||
|
|
||||||
|
public MarkOrder(RaceXMLData raceXMLData){
|
||||||
|
raceMarkOrder = new ArrayList<>();
|
||||||
|
for (Corner corner : raceXMLData.getMarkSequence()){
|
||||||
|
CompoundMark compoundMark = raceXMLData.getCompoundMarks().get(corner.getCompoundMarkID());
|
||||||
|
compoundMark.setRoundingSide(
|
||||||
|
RoundingSide.getRoundingSide(corner.getRounding())
|
||||||
|
);
|
||||||
|
raceMarkOrder.add(compoundMark);
|
||||||
|
}
|
||||||
|
orderedUniqueCompoundMarks = new ArrayList<>(raceXMLData.getCompoundMarks().values());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<CompoundMark> getOrderedUniqueCompoundMarks() {
|
||||||
|
return orderedUniqueCompoundMarks;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param seqID The seqID of the current mark the boat is heading to
|
||||||
|
* @return A Boolean indicating if this coming mark is the last one (finish line)
|
||||||
|
*/
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
package seng302.model.stream.packets;
|
||||||
|
|
||||||
|
public enum PacketType {
|
||||||
|
HEARTBEAT,
|
||||||
|
RACE_STATUS,
|
||||||
|
DISPLAY_TEXT_MESSAGE,
|
||||||
|
RACE_XML,
|
||||||
|
REGATTA_XML,
|
||||||
|
BOAT_XML,
|
||||||
|
RACE_START_STATUS,
|
||||||
|
YACHT_EVENT_CODE,
|
||||||
|
YACHT_ACTION_CODE,
|
||||||
|
CHATTER_TEXT,
|
||||||
|
BOAT_LOCATION,
|
||||||
|
MARK_ROUNDING,
|
||||||
|
COURSE_WIND,
|
||||||
|
AVG_WIND,
|
||||||
|
BOAT_ACTION,
|
||||||
|
OTHER,
|
||||||
|
RACE_REGISTRATION_REQUEST,
|
||||||
|
RACE_REGISTRATION_RESPONSE,
|
||||||
|
RACE_CUSTOMIZATION_REQUEST,
|
||||||
|
RACE_CUSTOMIZATION_RESPONSE,
|
||||||
|
|
||||||
|
SERVER_REGISTRATION, ROOM_CODE_REQUEST, LOBBY_REQUEST;
|
||||||
|
|
||||||
|
public static PacketType assignPacketType(int packetType, byte[] payload){
|
||||||
|
switch(packetType){
|
||||||
|
case 1:
|
||||||
|
return HEARTBEAT;
|
||||||
|
case 12:
|
||||||
|
return RACE_STATUS;
|
||||||
|
case 20:
|
||||||
|
return DISPLAY_TEXT_MESSAGE;
|
||||||
|
case 26:
|
||||||
|
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:
|
||||||
|
return YACHT_EVENT_CODE;
|
||||||
|
case 31:
|
||||||
|
return YACHT_ACTION_CODE;
|
||||||
|
case 36:
|
||||||
|
return CHATTER_TEXT;
|
||||||
|
case 37:
|
||||||
|
return BOAT_LOCATION;
|
||||||
|
case 38:
|
||||||
|
return MARK_ROUNDING;
|
||||||
|
case 44:
|
||||||
|
return COURSE_WIND;
|
||||||
|
case 47:
|
||||||
|
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;
|
||||||
|
case 201:
|
||||||
|
return SERVER_REGISTRATION;
|
||||||
|
case 202:
|
||||||
|
return ROOM_CODE_REQUEST;
|
||||||
|
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.
|
* Created by kre39 on 23/04/17.
|
||||||
@@ -13,7 +13,7 @@ public class StreamPacket {
|
|||||||
private byte[] payload;
|
private byte[] payload;
|
||||||
|
|
||||||
public StreamPacket(int type, long messageLength, long timeStamp, 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.messageLength = messageLength;
|
||||||
this.timeStamp = timeStamp;
|
this.timeStamp = timeStamp;
|
||||||
this.payload = payload;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user