mirror of
https://github.com/michaelrausch/Party-Parrots-At-Sea.git
synced 2026-05-09 14:28:43 +00:00
Compare commits
955 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 81b2d285e9 | |||
| 5d7f307260 | |||
| 00e2af9c14 | |||
| 73861b2ef3 | |||
| 4018dd783e | |||
| 8b7407bf89 | |||
| 00ddf117b2 | |||
| a266779709 | |||
| df24fe072a | |||
| a1a34b9bd7 | |||
| 42d490d6fd | |||
| cd4a2f8da3 | |||
| 287cfd77d0 | |||
| a08a38c985 | |||
| 810dde6a21 | |||
| 7a76efa2ce | |||
| ff4c2cd5b6 | |||
| c5e6302f86 | |||
| cd199767ae | |||
| 5cc4898ab5 | |||
| 22e1e57c24 | |||
| c5d2016733 | |||
| efc71f2003 | |||
| be72062c8e | |||
| 0d212a4a1d | |||
| 0e9b818071 | |||
| 452e83c1c3 | |||
| aded794a67 | |||
| 2b3a972ed5 | |||
| 2a523a5664 | |||
| 67f3124cfb | |||
| 7db387bdec | |||
| 2ceca2fd42 | |||
| 00ff771fc3 | |||
| d963785679 | |||
| 78f64557c3 | |||
| 982fac38a0 | |||
| edbfb2f84f | |||
| c01111038f | |||
| fd53fd52a4 | |||
| 6f62efcc93 | |||
| daf3867433 | |||
| e5af7bf666 | |||
| 85ca91db96 | |||
| 2eb7e603f1 | |||
| 658a342118 | |||
| c0bd498f1b | |||
| ea52977aeb | |||
| 10fa51a105 | |||
| b18d9e8573 | |||
| e9881bb24a | |||
| c54a1e141d | |||
| 6d51ea3574 | |||
| d56468e4aa | |||
| ab5ad58237 | |||
| df7264cc1f | |||
| 5248921576 | |||
| 330ccd272d | |||
| 4b7dfe38c4 | |||
| ab07c7f298 | |||
| b5076bc976 | |||
| 2dcdd1c248 | |||
| 9b00f76907 | |||
| f11c457d28 | |||
| 9e4fa30787 | |||
| 99ce4fa11d | |||
| 69d1fa9488 | |||
| 66e6a8a2a4 | |||
| 671efcaf08 | |||
| 8ba44d7476 | |||
| dd43097677 | |||
| 98abe64f00 | |||
| 1a53579317 | |||
| 132a729758 | |||
| 870d7a6e82 | |||
| 2e7487fdfc | |||
| 1bd4db73cd | |||
| 7a4cdbe0c9 | |||
| 735699dc85 | |||
| 81e791bd1a | |||
| 06e5f4ae00 | |||
| cd2b4cb93c | |||
| dde1c82dbb | |||
| d9c832168b | |||
| 9cfb3b9e5d | |||
| e990c68d40 | |||
| 83871a0336 | |||
| 6cba024d64 | |||
| 51747e2d13 | |||
| b3981b19e0 | |||
| 3d7a64068f | |||
| c12f7408ad | |||
| 7027de80c4 | |||
| 35b50d1436 | |||
| d0844e861d | |||
| 5d32d76d9d | |||
| 9ca39d1a7c | |||
| 30dad8509e | |||
| 0211f2df38 | |||
| dba5a5680f | |||
| 5c50e77efa | |||
| 0e93be7b36 | |||
| 191b818e38 | |||
| 00b09997b0 | |||
| d250c635d8 | |||
| 376c4d25a8 | |||
| e66abb4340 | |||
| a19e191684 | |||
| 29b97a194d | |||
| 8a0ad8d6a9 | |||
| 19db6668da | |||
| 44275aec04 | |||
| ca320f7fb8 | |||
| 64245833cd | |||
| aa0149b9a7 | |||
| f6b41f0513 | |||
| 9b00ba654a | |||
| 8dfdb228e9 | |||
| 1042817e4e | |||
| 066557584f | |||
| 4011295b8b | |||
| 78259f8e33 | |||
| c47e5b1450 | |||
| 0a885dd8fd | |||
| e9b50038a9 | |||
| 364264377a | |||
| 9112183ac3 | |||
| 8c7f9a878d | |||
| ecb3d4ecbf | |||
| e61b6d50a1 | |||
| 957821f1f2 | |||
| 061e49bab9 | |||
| 094eb4c1cf | |||
| a3c555d5fe | |||
| 607acff7c6 | |||
| 22fdf1e4ac | |||
| da8c91f5c1 | |||
| 52dc7a956d | |||
| 9f64b2380d | |||
| b05580f018 | |||
| c20c6fb264 | |||
| faeece27ff | |||
| 5e3ae40d03 | |||
| 95ad7a4840 | |||
| 6ca75b2cac | |||
| 6ff309a40c | |||
| 40a7f9bc5b | |||
| c4a6113f6c | |||
| 307e79ecfc | |||
| e17e9749d8 | |||
| 7d8a6afa5f | |||
| ea0be5e952 | |||
| 3be8cd264d | |||
| 7197bc2bee | |||
| 66d9a06f9e | |||
| fba522d0c3 | |||
| 0e829874c2 | |||
| c5d56065b6 | |||
| 410d765745 | |||
| fe76e85c71 | |||
| 9d61a43bd7 | |||
| c39582de5c | |||
| 034e4c252a | |||
| 6cde016401 | |||
| d4d7ddf8e2 | |||
| a1933c2869 | |||
| 8084a61333 | |||
| 52d3cea592 | |||
| 03f5f91043 | |||
| 9ed52a1225 | |||
| 027324cc4f | |||
| 78596ea111 | |||
| da263355f4 | |||
| ebecd25ed2 | |||
| 0f5137c2b6 | |||
| 73799954e4 | |||
| edfeb2b287 | |||
| 0355784000 | |||
| 02df69b7b4 | |||
| 242132b800 | |||
| 3a671d4ed0 | |||
| 482d987839 | |||
| cc124b2d19 | |||
| b3320ad805 | |||
| 33779ad5c1 | |||
| 3a41c27d8d | |||
| e24203904b | |||
| eb188495ce | |||
| 62a7e2b8fa | |||
| acd0e790fe | |||
| 46013474c0 | |||
| bf427f24d3 | |||
| 7d0a47446d | |||
| 889098bb50 | |||
| 6223a8fc0b | |||
| 391bd33548 | |||
| 7e0c2abbfd | |||
| 42a5e86bf8 | |||
| 20d73d8f2f | |||
| 0a62c538ca | |||
| 24a04aa530 | |||
| 7ee6a09626 | |||
| 47798b19fe | |||
| d9247eb031 | |||
| 7b0d31438e | |||
| 1e74321aab | |||
| f0fc75feec | |||
| c8dc448a52 | |||
| e518d13fd5 | |||
| 167545cbef | |||
| 20b656b16d | |||
| 90a54beb8d | |||
| e375efb8e9 | |||
| 06e9c55d2c | |||
| 9cba76f979 | |||
| d5ce61a0ff | |||
| 634322de3f | |||
| 6f534a430d | |||
| 17b0864815 | |||
| 3455321f01 | |||
| 7e9e96c091 | |||
| b25c3367a9 | |||
| ec7ee34305 | |||
| 9dad88e56a | |||
| 6a4547f3f9 | |||
| 71f626f57e | |||
| 1c343ec02d | |||
| 5046ca6ffa | |||
| bd31bae586 | |||
| 2f973deacc | |||
| f4c5e483ce | |||
| bb3ee424cc | |||
| 99f38fa05f | |||
| 6a8c77c670 | |||
| 659a521cc9 | |||
| da3613fe36 | |||
| 8dc3e54186 | |||
| 8fd35392b0 | |||
| bc9f0ea924 | |||
| 6d9864e677 | |||
| 8c2125276e | |||
| 235d6c9cef | |||
| 5dd936f8f1 | |||
| d223d393fa | |||
| aa098569f3 | |||
| d49e84e6d2 | |||
| 75bf92a67f | |||
| 0d7201e235 | |||
| 0197de6fe3 | |||
| d79ff0f1cf | |||
| 18f36d7ea4 | |||
| 131cd80e02 | |||
| 3d0209300e | |||
| 6b4f7eb42b | |||
| 8b9b3a31bd | |||
| 0bf83aa858 | |||
| dff261cf41 | |||
| 83ee217cef | |||
| d4826739e3 | |||
| fdfb5959fa | |||
| 878c0e0f43 | |||
| 1ce8df976c | |||
| 6fcd4ae4cc | |||
| 534eaee8ce | |||
| ab35e3506d | |||
| 1fec620427 | |||
| 00100eb991 | |||
| 58512fdbdf | |||
| 76a1a3c7a0 | |||
| 1b8c503712 | |||
| 334e13295f | |||
| 0c4d001510 | |||
| 24a3a54ccb | |||
| 40408cff27 | |||
| 9fcb8915c2 | |||
| caf910c4c5 | |||
| 4f07786449 | |||
| 2b53e0d5b4 | |||
| 1210f9342b | |||
| f136a970db | |||
| 800ae2864f | |||
| e764caee60 | |||
| 78b4786482 | |||
| e3ccb570ed | |||
| 470bf121a5 | |||
| f077486e22 | |||
| 717f7558d9 | |||
| 05236337ba | |||
| 1b76d59acb | |||
| 02e6d2a98b | |||
| 06a4dde216 | |||
| 1516e817b7 | |||
| bd7ea920b6 | |||
| 4a170f8179 | |||
| 1f9e6154ae | |||
| 5595e0439b | |||
| 08f7127b69 | |||
| 8ba28ffda0 | |||
| edd52a07a7 | |||
| 875a6b4e98 | |||
| faefcc7938 | |||
| 0c956f93c8 | |||
| 0e2946f20b | |||
| f66ef3c208 | |||
| cf4f8813d2 | |||
| e2bc613c9d | |||
| c2c3c9eb53 | |||
| cadf995bf7 | |||
| 62f139c604 | |||
| 0bf6dd9e6b | |||
| eed5f56690 | |||
| b35126ff4e | |||
| c39499cee7 | |||
| cff15ba07d | |||
| 6be7c17c40 | |||
| 64811354e3 | |||
| 3364f8b516 | |||
| 8cc725616c | |||
| 0feccdc8b9 | |||
| 1a755cec33 | |||
| d565552fcc | |||
| 26a8d76f8b | |||
| d87dcaa4fe | |||
| 0b61dffe71 | |||
| 4e69157c09 | |||
| d67566e217 | |||
| 56f1dd2efb | |||
| 302bc91461 | |||
| 488ab47c8d | |||
| 0650ba30e2 | |||
| 04518c35b0 | |||
| f33e4cc137 | |||
| 7a4b3f0ad9 | |||
| 1619c95098 | |||
| 396098e009 | |||
| e4dbbd05c7 | |||
| a1e3ec54c7 | |||
| 47f3f6e27d | |||
| fce7aa4f9a | |||
| 81afad1bcc | |||
| 5026c568a7 | |||
| 88d1e91b6f | |||
| 67f39e9049 | |||
| eb1d3f1a60 | |||
| 54ca12f3b6 | |||
| 3f910b8db7 | |||
| b346d5a706 | |||
| 45fa73595f | |||
| 01ca3f3453 | |||
| ba8e333d81 | |||
| 24d4c1df15 | |||
| 0c5d661995 | |||
| 262f27fa8a | |||
| 0fcdf41419 | |||
| 4ebf7d6104 | |||
| 353dd48829 | |||
| 4bd7291a4a | |||
| 0d0b2e59d5 | |||
| ace48a8404 | |||
| 201405d070 | |||
| dc19310849 | |||
| 1c866ea8c2 | |||
| 23027705da | |||
| c15f13bc2c | |||
| 6ee2517f74 | |||
| 75155fe481 | |||
| 2fcff65dd6 | |||
| 4c730ea890 | |||
| 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 | |||
| 762829e5ff | |||
| 835f79b113 | |||
| 3bd8added4 | |||
| e0854bc68c | |||
| cec7014856 | |||
| ba527a1979 | |||
| b73e4c89db | |||
| f1a9da83fc | |||
| 6e903bfbed | |||
| 23d62f552e | |||
| 53f6a6b8c5 | |||
| 945acb6071 | |||
| af81bf5891 | |||
| e72ac1def8 | |||
| f163dfdd11 | |||
| a1eda8d91d | |||
| 5ed02a1fe1 | |||
| 66d4a4b958 | |||
| adbb9ffe3b | |||
| 0cd2867ac0 | |||
| 4d29354797 | |||
| a6d9c66fc9 | |||
| 99588c7ff8 | |||
| 68c3e3e999 | |||
| 67b5650288 | |||
| 3085125f3e | |||
| 5e26ad7c36 | |||
| 765ea06c3b | |||
| ffe70a8313 | |||
| 331e0fc6ab | |||
| b3fd735f5c | |||
| 3cbbdb070f | |||
| d2e55bf964 | |||
| 05cdadac79 | |||
| e6aed88188 | |||
| d032314ddb | |||
| 6301fd2fb7 | |||
| 34c3899ec4 | |||
| 46f5fc5172 | |||
| 81c021b59a | |||
| 8ab57e4e61 | |||
| 14a7305a2d | |||
| 13ff179840 | |||
| e7060d4b6f | |||
| 641039720e | |||
| 6f132f1e38 | |||
| 76c0d34760 | |||
| 71637d7286 | |||
| 1cac7cc189 | |||
| c42942430f | |||
| 7abb36c362 | |||
| a1e8d29b9c | |||
| c449da2916 | |||
| 3fd0c0a2dd | |||
| 397f7d003a | |||
| f85d3bf5fe | |||
| d22d758757 | |||
| acbde5aad8 | |||
| 8f93956ff1 | |||
| 4fe4ac1079 | |||
| 8a2f0a9f45 | |||
| 5cc865f0af | |||
| 189ba93e64 | |||
| 89464e033e | |||
| 6d7c36e31f | |||
| ca8ea03870 | |||
| 3f57adb9cf | |||
| 2686dac62e | |||
| ffd40fef6d | |||
| e1b8e19966 | |||
| dec742cf54 | |||
| 6f1b0b06c3 | |||
| 408d70c420 | |||
| e51c966969 | |||
| 08eacacfd4 | |||
| 87150b3c72 | |||
| e385ac5c09 | |||
| c30629542b | |||
| 3f9fa24c69 | |||
| 78573fa837 | |||
| d4837cacda | |||
| 0367805f0f | |||
| e0750f5341 | |||
| 80528c9c42 | |||
| e26f2af93d | |||
| be633c0e60 | |||
| 97f1ccb6c1 | |||
| 68a243725b | |||
| 8f81060a18 | |||
| 07c76f12e1 | |||
| c6ab96a86f | |||
| 059c0de1fa | |||
| 8e147bd1bd | |||
| 4d3cfe71f7 | |||
| 5adb7c3762 | |||
| 937b309b07 | |||
| 48d58ea660 | |||
| aaf2e6a3f0 | |||
| 422dcd4501 | |||
| 951a726309 | |||
| b692ddcbe6 | |||
| 5d6b356602 | |||
| 08057edb28 | |||
| 390aabc78f | |||
| a2123df0c5 | |||
| 6a6ed3ed44 | |||
| b87008f590 | |||
| ecc0e722b5 | |||
| 6f9a8e5581 | |||
| b17bba3629 | |||
| cf4d7e03f5 | |||
| 73eeeb0ef9 | |||
| 0f79353936 | |||
| 38b44fa92b | |||
| 3fd13ddc0a | |||
| 2e375978bd | |||
| 45db731a60 | |||
| 95e353c14e | |||
| 8a3a41294a | |||
| f41858e2c7 | |||
| aaa3dc93f1 | |||
| e5eab0a6c8 | |||
| 7c39368126 | |||
| ade926e2f2 | |||
| c63c8e4d73 | |||
| 3c418b2aa4 | |||
| d34a158c34 | |||
| 6e3d037021 | |||
| 7d160eaf54 | |||
| 4fc99edbd6 | |||
| 4da8c1645e | |||
| 1c01aab1e7 | |||
| 6a85b0800f | |||
| 1acb0fbac4 | |||
| 2b294702a9 | |||
| afd97d6e05 | |||
| 2d5a7a8a49 | |||
| 9e3036e134 | |||
| 8dec458ba9 | |||
| da07d885da | |||
| a9de005e1a | |||
| e03e121da4 | |||
| fc3ca70e5d | |||
| 9c7144c918 | |||
| ed8d70c3b3 | |||
| fa501460cb | |||
| 110143ae6e | |||
| eda3d76077 | |||
| 51f090324a | |||
| 335540ff4a | |||
| 5fa47ff65b | |||
| 3a1c1a5e43 | |||
| 4c7f530458 | |||
| 2e914a7704 | |||
| 8fbb9d6d4e | |||
| 23bc643c91 | |||
| c4fe116267 | |||
| 081d7e3dcb | |||
| 764ae37ce4 | |||
| e62a609b6b | |||
| 4b1a4aae87 | |||
| ccda5f2a2e | |||
| 3fd8b1b855 | |||
| 94d1982670 | |||
| 39efafc75f | |||
| afe0c9f1a6 | |||
| 04b105d74b | |||
| 1ab6351d48 | |||
| 9c348df5a5 | |||
| 256ec046fc | |||
| 85d4d63287 | |||
| 213d36ed56 | |||
| ff6bfc9516 | |||
| ae5678482b | |||
| 63514cfafb | |||
| 03ca60f2e1 | |||
| 89ef6e5277 | |||
| 47880d09bc | |||
| 5472765b95 | |||
| 14d975dce4 | |||
| a23bdd0c53 | |||
| b0d8c3db0a | |||
| 4a75c062ce | |||
| fe90a3bf13 | |||
| 711f6f4c45 | |||
| 8fa7829a3c | |||
| 6d7697a0eb | |||
| cdd80af27b | |||
| 2cb09b81f8 | |||
| ac3f3bfd55 | |||
| 3a72409fb8 | |||
| fd092bb7e1 | |||
| 9c60521d00 |
@@ -0,0 +1,17 @@
|
|||||||
|
engines:
|
||||||
|
pmd:
|
||||||
|
enabled: true
|
||||||
|
channel: "beta"
|
||||||
|
|
||||||
|
fixme:
|
||||||
|
enabled: true
|
||||||
|
config:
|
||||||
|
strings:
|
||||||
|
- FIXME
|
||||||
|
- TODO
|
||||||
|
- BUG
|
||||||
|
- FIX
|
||||||
|
|
||||||
|
ratings:
|
||||||
|
paths:
|
||||||
|
- "**.java"
|
||||||
+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
|
||||||
@@ -16,10 +16,12 @@
|
|||||||
# https://www.kernel.org/pub/software/scm/git/docs/git-shortlog.html
|
# https://www.kernel.org/pub/software/scm/git/docs/git-shortlog.html
|
||||||
# http://stacktoheap.com/blog/2013/01/06/using-mailmap-to-fix-authors-list-in-git/
|
# http://stacktoheap.com/blog/2013/01/06/using-mailmap-to-fix-authors-list-in-git/
|
||||||
|
|
||||||
Michael Rausch <mra106@uclive.ac.nz> <me@michaelrausch.nz> <michael@michaelrausch.net>
|
Michael Rausch <mra106@uclive.ac.nz> <me@michaelrausch.nz>
|
||||||
|
Michael Rausch <mra106@uclive.ac.nz> <michael@michaelrausch.net>
|
||||||
Kusal Ekanayake <kre39@uclive.ac.nz> kre39 <kre39@uclive.ac.nz>
|
Kusal Ekanayake <kre39@uclive.ac.nz> kre39 <kre39@uclive.ac.nz>
|
||||||
Haoming Yin <hyi25@uclive.ac.nz> <haoming.y@icloud.com>
|
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>
|
||||||
|
Alistair McIntyre <ajm412@uclive.ac.nz> <alistairjmcintyre@gmail.com>
|
||||||
|
Calum <cir27@uclive.ac.nz> cir27 <cir27@uclive.ac.nz>
|
||||||
@@ -9,7 +9,7 @@ prints out event details, including time, involved boats and legs.
|
|||||||
|
|
||||||
- Configuration file
|
- 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,78 +1,109 @@
|
|||||||
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.parsers.StreamParser;
|
import org.apache.commons.cli.CommandLine;
|
||||||
import seng302.models.parsers.StreamReceiver;
|
import org.apache.commons.cli.CommandLineParser;
|
||||||
import seng302.server.ServerThread;
|
import org.apache.commons.cli.DefaultParser;
|
||||||
|
import org.apache.commons.cli.Options;
|
||||||
|
import org.apache.commons.cli.ParseException;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import seng302.discoveryServer.DiscoveryServer;
|
||||||
|
import seng302.visualiser.controllers.ViewManager;
|
||||||
|
|
||||||
public class App extends Application
|
public class App extends Application {
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public void start(Stage primaryStage) throws Exception {
|
|
||||||
Parent root = FXMLLoader.load(getClass().getResource("/views/MainView.fxml"));
|
|
||||||
primaryStage.setTitle("RaceVision");
|
|
||||||
primaryStage.setScene(new Scene(root));
|
|
||||||
primaryStage.setMaximized(true);
|
|
||||||
|
|
||||||
primaryStage.show();
|
private static Logger logger = LoggerFactory.getLogger(App.class);
|
||||||
primaryStage.setOnCloseRequest(e -> {
|
private static boolean isRunningAsCache = false;
|
||||||
StreamParser.appClose();
|
|
||||||
StreamReceiver.noMoreBytes();
|
|
||||||
System.out.println("[CLIENT] Exiting program");
|
|
||||||
System.exit(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
public static void parseArgs(String[] args) throws ParseException {
|
||||||
|
Options options = new Options();
|
||||||
|
CommandLineParser parser = new DefaultParser();
|
||||||
|
CommandLine cmd;
|
||||||
|
|
||||||
public static void main(String[] args) {
|
ch.qos.logback.classic.Logger rootLogger = (ch.qos.logback.classic.Logger) LoggerFactory
|
||||||
StreamReceiver sr = null;
|
.getLogger(Logger.ROOT_LOGGER_NAME);
|
||||||
|
|
||||||
new ServerThread("Racevision Test Server");
|
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");
|
||||||
|
|
||||||
try {
|
cmd = parser.parse(options, args);
|
||||||
Thread.sleep(2000);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (args.length == 1 && args[0].equals("-standalone")){
|
if (cmd.hasOption("runAsDiscoveryServer")){
|
||||||
|
isRunningAsCache = true;
|
||||||
|
rootLogger.setLevel(Level.ALL);
|
||||||
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 {
|
||||||
|
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,539 +0,0 @@
|
|||||||
package seng302.controllers;
|
|
||||||
|
|
||||||
import javafx.animation.*;
|
|
||||||
import javafx.beans.property.SimpleDoubleProperty;
|
|
||||||
import javafx.fxml.FXML;
|
|
||||||
import javafx.geometry.Point2D;
|
|
||||||
import javafx.scene.Group;
|
|
||||||
import javafx.scene.Node;
|
|
||||||
import javafx.scene.canvas.Canvas;
|
|
||||||
import javafx.scene.canvas.GraphicsContext;
|
|
||||||
import javafx.scene.layout.AnchorPane;
|
|
||||||
import javafx.scene.layout.Pane;
|
|
||||||
import javafx.scene.paint.Color;
|
|
||||||
import javafx.scene.shape.Polygon;
|
|
||||||
import javafx.scene.text.Font;
|
|
||||||
import javafx.stage.Stage;
|
|
||||||
import seng302.models.*;
|
|
||||||
import seng302.models.mark.*;
|
|
||||||
import seng302.models.parsers.StreamParser;
|
|
||||||
import seng302.models.parsers.StreamReceiver;
|
|
||||||
import seng302.models.parsers.packets.BoatPositionPacket;
|
|
||||||
import seng302.models.parsers.XMLParser;
|
|
||||||
import seng302.models.parsers.XMLParser.RaceXMLObject.CompoundMark;
|
|
||||||
import seng302.models.parsers.XMLParser.RaceXMLObject.Limit;
|
|
||||||
import seng302.models.mark.Mark;
|
|
||||||
|
|
||||||
import java.text.DecimalFormat;
|
|
||||||
import java.util.*;
|
|
||||||
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 final int MARK_SIZE = 10;
|
|
||||||
private final int BUFFER_SIZE = 50;
|
|
||||||
private final int CANVAS_WIDTH = 720;
|
|
||||||
private final int CANVAS_HEIGHT = 720;
|
|
||||||
private final int LHS_BUFFER = BUFFER_SIZE;
|
|
||||||
private final int RHS_BUFFER = BUFFER_SIZE + MARK_SIZE / 2;
|
|
||||||
private final int TOP_BUFFER = BUFFER_SIZE;
|
|
||||||
private final int BOT_BUFFER = TOP_BUFFER + MARK_SIZE / 2;
|
|
||||||
|
|
||||||
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 metersToPixels;
|
|
||||||
private List<RaceObject> raceObjects = new ArrayList<>();
|
|
||||||
private List<Mark> raceMarks = new ArrayList<>();
|
|
||||||
|
|
||||||
//FRAME RATE
|
|
||||||
private static final double UPDATE_TIME = 0.016666; // 1 / 60 ie 60fps
|
|
||||||
private final long[] frameTimes = new long[30];
|
|
||||||
private int frameTimeIndex = 0;
|
|
||||||
private boolean arrayFilled = false;
|
|
||||||
private DecimalFormat decimalFormat2dp = new DecimalFormat("0.00");
|
|
||||||
|
|
||||||
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();
|
|
||||||
|
|
||||||
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));
|
|
||||||
//group.minWidth(CANVAS_WIDTH);
|
|
||||||
//group.minHeight(CANVAS_HEIGHT);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void initializeCanvas (){
|
|
||||||
|
|
||||||
gc = canvas.getGraphicsContext2D();
|
|
||||||
gc.save();
|
|
||||||
gc.setFill(Color.SKYBLUE);
|
|
||||||
gc.fillRect(0,0, CANVAS_WIDTH, CANVAS_HEIGHT);
|
|
||||||
gc.restore();
|
|
||||||
fitMarksToCanvas();
|
|
||||||
|
|
||||||
|
|
||||||
// TODO: 1/05/17 wmu16 - Change this call to now draw the marks as from the xml
|
|
||||||
drawBoats();
|
|
||||||
timer = new AnimationTimer() {
|
|
||||||
|
|
||||||
@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 ;
|
|
||||||
Double frameRate = 1_000_000_000.0 / elapsedNanosPerFrame ;
|
|
||||||
drawFps(frameRate.intValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: 1/05/17 cir27 - Make the RaceObjects update on the actual delay.
|
|
||||||
elapsedNanos = 1000 / 60;
|
|
||||||
updateRaceObjects();
|
|
||||||
if (StreamParser.isRaceFinished()) {
|
|
||||||
this.stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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());
|
|
||||||
Limit thisPoint2 = courseLimits.get(i+1);
|
|
||||||
SingleMark thisMark2 = new SingleMark("", thisPoint2.getLat(), thisPoint2.getLng(), 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());
|
|
||||||
Limit thisPoint2 = courseLimits.get(0);
|
|
||||||
SingleMark thisMark2 = new SingleMark("", thisPoint2.getLat(), thisPoint2.getLng(), 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds the course 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 addCourseMarks() {
|
|
||||||
XMLParser.RaceXMLObject raceXMLObject = StreamParser.getXmlObject().getRaceXML();
|
|
||||||
ArrayList<CompoundMark> compoundMarks = raceXMLObject.getCompoundMarks();
|
|
||||||
RaceObject markGroup;
|
|
||||||
|
|
||||||
for (CompoundMark compoundMark : compoundMarks) {
|
|
||||||
|
|
||||||
//If the compound mark has 2 marks then its a gate mark
|
|
||||||
if (compoundMark.getMarks().size() == 2) {
|
|
||||||
CompoundMark.Mark mark1 = compoundMark.getMarks().get(0);
|
|
||||||
CompoundMark.Mark mark2 = compoundMark.getMarks().get(1);
|
|
||||||
SingleMark singleMark1 = new SingleMark(mark1.getMarkName(), mark1.getTargetLat(), mark1.getTargetLng(), mark1.getSourceID());
|
|
||||||
SingleMark singleMark2 = new SingleMark(mark1.getMarkName(), mark2.getTargetLat(), mark2.getTargetLng(), mark2.getSourceID());
|
|
||||||
GateMark thisGateMark = new GateMark(compoundMark.getcMarkName(),
|
|
||||||
(compoundMark.getMarkID().equals(1)) ? MarkType.OPEN_GATE : MarkType.CLOSED_GATE,
|
|
||||||
singleMark1,
|
|
||||||
singleMark2,
|
|
||||||
singleMark1.getLatitude(),
|
|
||||||
singleMark1.getLongitude());
|
|
||||||
|
|
||||||
markGroup = new MarkGroup(thisGateMark,
|
|
||||||
findScaledXY(thisGateMark.getSingleMark1()),
|
|
||||||
findScaledXY(thisGateMark.getSingleMark2()));
|
|
||||||
|
|
||||||
raceObjects.add(markGroup);
|
|
||||||
raceMarks.add(thisGateMark);
|
|
||||||
|
|
||||||
//Otherwise its a single mark
|
|
||||||
} else {
|
|
||||||
CompoundMark.Mark singleMark = compoundMark.getMarks().get(0);
|
|
||||||
Mark thisSingleMark = new SingleMark(singleMark.getMarkName(),
|
|
||||||
singleMark.getTargetLat(),
|
|
||||||
singleMark.getTargetLng(),
|
|
||||||
singleMark.getSourceID());
|
|
||||||
|
|
||||||
markGroup = new MarkGroup(thisSingleMark, findScaledXY(thisSingleMark));
|
|
||||||
raceObjects.add(markGroup);
|
|
||||||
raceMarks.add(thisSingleMark);
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateRaceObjects(){
|
|
||||||
for (RaceObject raceObject : raceObjects) {
|
|
||||||
raceObject.updatePosition(1000 / 60);
|
|
||||||
// some raceObjects will have multiply ID's (for instance gate marks)
|
|
||||||
for (long id : raceObject.getRaceIds()) {
|
|
||||||
//checking if the current "ID" has any updates associated with it
|
|
||||||
if (StreamParser.boatPositions.containsKey(id)) {
|
|
||||||
move(id, raceObject);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void move(long id, RaceObject raceObject){
|
|
||||||
PriorityBlockingQueue<BoatPositionPacket> movementQueue = StreamParser.boatPositions.get(id);
|
|
||||||
if (movementQueue.size() > 0){
|
|
||||||
BoatPositionPacket positionPacket = movementQueue.peek();
|
|
||||||
|
|
||||||
//this code adds a delay to reading from the movementQueue
|
|
||||||
//in case things being put into the movement queue are slightly
|
|
||||||
//out of order
|
|
||||||
int delayTime = 1000;
|
|
||||||
int loopTime = delayTime * 10;
|
|
||||||
long timeDiff = (System.currentTimeMillis()%loopTime - positionPacket.getTimeValid()%loopTime);
|
|
||||||
if (timeDiff < 0){
|
|
||||||
timeDiff = loopTime + timeDiff;
|
|
||||||
}
|
|
||||||
if (timeDiff > delayTime) {
|
|
||||||
try {
|
|
||||||
positionPacket = movementQueue.take();
|
|
||||||
Point2D p2d = latLonToXY(positionPacket.getLat(), positionPacket.getLon());
|
|
||||||
double heading = 360.0 / 0xffff * positionPacket.getHeading();
|
|
||||||
raceObject.setDestination(p2d.getX(), p2d.getY(), heading, positionPacket.getGroundSpeed(), (int) id);
|
|
||||||
} catch (InterruptedException e){
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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,50,20);
|
|
||||||
gc.setFill(Color.SKYBLUE);
|
|
||||||
gc.fillRect(4,4,51,21);
|
|
||||||
gc.setFill(Color.BLACK);
|
|
||||||
gc.setFont(new Font(14));
|
|
||||||
gc.setLineWidth(3);
|
|
||||||
gc.fillText(fps + " FPS", 5, 20);
|
|
||||||
} else {
|
|
||||||
gc.clearRect(5,5,50,20);
|
|
||||||
gc.setFill(Color.SKYBLUE);
|
|
||||||
gc.fillRect(4,4,51,21);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Draws all the boats.
|
|
||||||
*/
|
|
||||||
private void drawBoats() {
|
|
||||||
// Map<Boat, TimelineInfo> timelineInfos = raceViewController.getTimelineInfos();
|
|
||||||
// List<Boat> boats = raceViewController.getStartingBoats();
|
|
||||||
Map<Integer, Yacht> boats = StreamParser.getBoats();
|
|
||||||
Double startingX = raceObjects.get(0).getLayoutX();
|
|
||||||
Double startingY = raceObjects.get(0).getLayoutY();
|
|
||||||
Group boatAnnotations = new Group();
|
|
||||||
|
|
||||||
for (Yacht boat : boats.values()) {
|
|
||||||
// for (Boat boat : boats) {
|
|
||||||
boat.setColour(Colors.getColor());
|
|
||||||
BoatGroup boatGroup = new BoatGroup(boat, boat.getColour());
|
|
||||||
boatGroup.moveTo(startingX, startingY, 0d);
|
|
||||||
//boatGroup.setStage(raceViewController.getStage());
|
|
||||||
raceObjects.add(boatGroup);
|
|
||||||
boatAnnotations.getChildren().add(boatGroup.getLowPriorityAnnotations());
|
|
||||||
}
|
|
||||||
group.getChildren().add(boatAnnotations);
|
|
||||||
group.getChildren().addAll(raceObjects);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculates x and y location for every marker that fits it to the canvas the race will be drawn on.
|
|
||||||
*/
|
|
||||||
private void fitMarksToCanvas() {
|
|
||||||
findMinMaxPoint();
|
|
||||||
double minLonToMaxLon = scaleRaceExtremities();
|
|
||||||
calculateReferencePointLocation(minLonToMaxLon);
|
|
||||||
givePointsXY();
|
|
||||||
addRaceBorder();
|
|
||||||
findMetersToPixels();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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());
|
|
||||||
maxLatPoint = new SingleMark(maxLatMark.toString(), maxLatMark.getLat(), maxLatMark.getLng(), maxLatMark.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);
|
|
||||||
SingleMark thisMinLon = new SingleMark(minLonMark.toString(), minLonMark.getLat(), minLonMark.getLng(), minLonMark.getSeqID());
|
|
||||||
SingleMark thisMaxLon = new SingleMark(maxLonMark.toString(), maxLonMark.getLat(), maxLonMark.getLng(), maxLonMark.getSeqID());
|
|
||||||
// TODO: 30/03/17 cir27 - Correctly account for longitude wrapping around.
|
|
||||||
if (thisMaxLon.getLongitude() - thisMinLon.getLongitude() > 180) {
|
|
||||||
SingleMark temp = thisMinLon;
|
|
||||||
thisMinLon = thisMaxLon;
|
|
||||||
thisMaxLon = temp;
|
|
||||||
}
|
|
||||||
minLonPoint = thisMinLon;
|
|
||||||
maxLonPoint = thisMaxLon;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 = LHS_BUFFER + distanceScaleFactor * Math.sin(referenceAngle) * Mark.calculateDistance(referencePoint, minLonPoint);
|
|
||||||
|
|
||||||
referenceAngle = Math.abs(Mark.calculateHeadingRad(referencePoint, maxLatPoint));
|
|
||||||
referencePointY = CANVAS_HEIGHT - (TOP_BUFFER + BOT_BUFFER);
|
|
||||||
referencePointY -= distanceScaleFactor * Math.cos(referenceAngle) * Mark.calculateDistance(referencePoint, maxLatPoint);
|
|
||||||
referencePointY = referencePointY / 2;
|
|
||||||
referencePointY += TOP_BUFFER;
|
|
||||||
referencePointY += distanceScaleFactor * Math.cos(referenceAngle) * Mark.calculateDistance(referencePoint, maxLatPoint);
|
|
||||||
} else {
|
|
||||||
referencePointY = CANVAS_HEIGHT - BOT_BUFFER;
|
|
||||||
|
|
||||||
referenceAngle = Math.abs(Mark.calculateHeadingRad(referencePoint, minLonPoint));
|
|
||||||
referencePointX = LHS_BUFFER;
|
|
||||||
referencePointX += distanceScaleFactor * Math.sin(referenceAngle) * Mark.calculateDistance(referencePoint, minLonPoint);
|
|
||||||
referencePointX += ((CANVAS_WIDTH - (LHS_BUFFER + RHS_BUFFER)) - (minLonToMaxLon * distanceScaleFactor)) / 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 - (TOP_BUFFER + BOT_BUFFER)) / vertDistance;
|
|
||||||
|
|
||||||
if ((horiDistance * vertScale) > (CANVAS_WIDTH - (RHS_BUFFER + LHS_BUFFER))) {
|
|
||||||
distanceScaleFactor = (CANVAS_WIDTH - (RHS_BUFFER + LHS_BUFFER)) / horiDistance;
|
|
||||||
scaleDirection = ScaleDirection.HORIZONTAL;
|
|
||||||
} else {
|
|
||||||
distanceScaleFactor = vertScale;
|
|
||||||
scaleDirection = ScaleDirection.VERTICAL;
|
|
||||||
}
|
|
||||||
return horiDistance;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Give all markers in the course an x,y location relative to a given reference with a known x,y location. Distances
|
|
||||||
* are scaled according to the distanceScaleFactor variable.
|
|
||||||
*/
|
|
||||||
private void givePointsXY() {
|
|
||||||
List<Mark> allPoints = new ArrayList<>(raceViewController.getRace().getCourse());
|
|
||||||
List<Mark> processed = new ArrayList<>();
|
|
||||||
RaceObject markGroup;
|
|
||||||
|
|
||||||
for (Mark mark : allPoints) {
|
|
||||||
if (!processed.contains(mark)) {
|
|
||||||
if (mark.getMarkType() != MarkType.SINGLE_MARK) {
|
|
||||||
GateMark gateMark = (GateMark) mark;
|
|
||||||
markGroup = new MarkGroup(mark, findScaledXY(gateMark.getSingleMark1()), findScaledXY(gateMark.getSingleMark2()));
|
|
||||||
raceObjects.add(markGroup);
|
|
||||||
} else {
|
|
||||||
markGroup = new MarkGroup(mark, findScaledXY(mark));
|
|
||||||
raceObjects.add(markGroup);
|
|
||||||
}
|
|
||||||
processed.add(mark);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Point2D findScaledXY (Mark unscaled) {
|
|
||||||
return findScaledXY (minLatPoint.getLatitude(), minLatPoint.getLongitude(),
|
|
||||||
unscaled.getLatitude(), unscaled.getLongitude());
|
|
||||||
}
|
|
||||||
|
|
||||||
private Point2D findScaledXY (double latA, double lonA, double latB, double lonB) {
|
|
||||||
double distanceFromReference;
|
|
||||||
double angleFromReference;
|
|
||||||
int xAxisLocation = (int) referencePointX;
|
|
||||||
int yAxisLocation = (int) referencePointY;
|
|
||||||
|
|
||||||
angleFromReference = Mark.calculateHeadingRad(latA, lonA, latB, lonB);
|
|
||||||
distanceFromReference = Mark.calculateDistance(latA, lonA, latB, lonB);
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
return new Point2D(xAxisLocation, yAxisLocation);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Find the number of meters per pixel.
|
|
||||||
*/
|
|
||||||
private void findMetersToPixels () {
|
|
||||||
Double angularDistance;
|
|
||||||
Double angle;
|
|
||||||
Double straightLineDistance;
|
|
||||||
if (scaleDirection == ScaleDirection.HORIZONTAL) {
|
|
||||||
angularDistance = Mark.calculateDistance(minLonPoint, maxLonPoint);
|
|
||||||
angle = Mark.calculateHeadingRad(minLonPoint, maxLonPoint);
|
|
||||||
if (angle > Math.PI / 2) {
|
|
||||||
straightLineDistance = Math.cos(angle - Math.PI) * angularDistance;
|
|
||||||
} else {
|
|
||||||
straightLineDistance = Math.cos(angle) * angularDistance;
|
|
||||||
}
|
|
||||||
metersToPixels = (CANVAS_WIDTH - RHS_BUFFER - LHS_BUFFER) / straightLineDistance;
|
|
||||||
} else {
|
|
||||||
angularDistance = Mark.calculateDistance(minLatPoint, maxLatPoint);
|
|
||||||
angle = Mark.calculateHeadingRad(minLatPoint, maxLatPoint);
|
|
||||||
if (angle < Math.PI / 2) {
|
|
||||||
straightLineDistance = Math.cos(angle) * angularDistance;
|
|
||||||
} else {
|
|
||||||
straightLineDistance = Math.cos(-angle + Math.PI * 2) * angularDistance;
|
|
||||||
}
|
|
||||||
metersToPixels = (CANVAS_HEIGHT - TOP_BUFFER - BOT_BUFFER) / straightLineDistance;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Point2D latLonToXY (double latitude, double longitude) {
|
|
||||||
return findScaledXY(minLatPoint.getLatitude(), minLatPoint.getLongitude(), latitude, longitude);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<RaceObject> getRaceObjects() {
|
|
||||||
return raceObjects;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,164 +0,0 @@
|
|||||||
package seng302.controllers;
|
|
||||||
|
|
||||||
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.Pane;
|
|
||||||
import javafx.scene.paint.Color;
|
|
||||||
import seng302.models.Yacht;
|
|
||||||
import seng302.models.parsers.StreamParser;
|
|
||||||
import seng302.models.parsers.XMLParser;
|
|
||||||
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.text.DateFormat;
|
|
||||||
import java.text.SimpleDateFormat;
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.ResourceBundle;
|
|
||||||
import java.util.Timer;
|
|
||||||
import java.util.TimerTask;
|
|
||||||
|
|
||||||
public class Controller implements Initializable {
|
|
||||||
@FXML
|
|
||||||
private AnchorPane contentPane;
|
|
||||||
@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 XMLParser xmlParser;
|
|
||||||
|
|
||||||
private void setContentPane(String jfxUrl){
|
|
||||||
try{
|
|
||||||
contentPane.getChildren().removeAll();
|
|
||||||
contentPane.getChildren().clear();
|
|
||||||
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) {
|
|
||||||
//DateFormat format = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
|
|
||||||
//format.setTimeZone(TimeZone.getTimeZone("GMT-8"));
|
|
||||||
//realTime.setText(format.format(new Date()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Running a timer to update the livestream status on welcome screen. Update interval is 1 second.
|
|
||||||
*/
|
|
||||||
public void startStream() {
|
|
||||||
if (StreamParser.isStreamStatus()) {
|
|
||||||
xmlParser = StreamParser.getXmlObject();
|
|
||||||
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.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() {
|
|
||||||
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")
|
|
||||||
);
|
|
||||||
// if (StreamParser.isRaceStarted()) {
|
|
||||||
data.addAll(StreamParser.getBoatsPos().values());
|
|
||||||
// } else {
|
|
||||||
// for (Yacht boat : StreamParser.getBoats().values()) {
|
|
||||||
// boat.setPosition("-");
|
|
||||||
// data.add(boat);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
teamList.refresh();
|
|
||||||
|
|
||||||
// posCol.setSortType(TableColumn.SortType.ASCENDING);
|
|
||||||
// teamList.getSortOrder().add(posCol);
|
|
||||||
// posCol.setSortable(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
package seng302.controllers;
|
|
||||||
|
|
||||||
import seng302.models.Race;
|
|
||||||
import seng302.models.Yacht;
|
|
||||||
import seng302.models.parsers.ConfigParser;
|
|
||||||
import seng302.models.parsers.CourseParser;
|
|
||||||
import seng302.models.parsers.StreamParser;
|
|
||||||
|
|
||||||
import java.lang.reflect.Array;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Random;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by zyt10 on 17/03/17.
|
|
||||||
* run before CanvasController to initialize race events
|
|
||||||
* the CanvasController then uses the event data to make the animations
|
|
||||||
*/
|
|
||||||
public class RaceController {
|
|
||||||
Race race = null;
|
|
||||||
|
|
||||||
public void initializeRace() {
|
|
||||||
String raceConfigFile = "/config/config.xml";
|
|
||||||
String teamsConfigFile = "/config/teams.xml";
|
|
||||||
|
|
||||||
try {
|
|
||||||
race = createRace(raceConfigFile, teamsConfigFile);
|
|
||||||
} catch (Exception e) {
|
|
||||||
System.out.println("There was an error creating the race.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (race != null) {
|
|
||||||
race.startRace();
|
|
||||||
} else {
|
|
||||||
System.out.println("There was an error creating the race. Exiting.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Race createRace(String configFile, String teamsConfigFile) throws Exception {
|
|
||||||
Race race = new Race();
|
|
||||||
// StreamParser.xmlObject
|
|
||||||
// Read team names from file
|
|
||||||
// TeamsParser tp = new TeamsParser(teamsConfigFile);
|
|
||||||
|
|
||||||
// Read course from file
|
|
||||||
// ConfigParser config = new ConfigParser(configFile);
|
|
||||||
|
|
||||||
ArrayList<String> boatNames = new ArrayList<>();
|
|
||||||
// ArrayList<Boat> teams = tp.getBoats();
|
|
||||||
Map<Long, Yacht> teams = StreamParser.getBoatsPos();
|
|
||||||
|
|
||||||
//get race size
|
|
||||||
int numberOfBoats = teams.size();
|
|
||||||
|
|
||||||
//get time scale
|
|
||||||
// double timeScale = config.getTimeScale();
|
|
||||||
// race.setTimeScale(timeScale);
|
|
||||||
|
|
||||||
for (Yacht boat : teams.values()) {
|
|
||||||
boatNames.add(boat.getBoatName());
|
|
||||||
race.addBoat(boat);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shuffle team names
|
|
||||||
long seed = System.nanoTime();
|
|
||||||
Collections.shuffle(boatNames, new Random(seed));
|
|
||||||
|
|
||||||
if (numberOfBoats > Array.getLength(boatNames.toArray())) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
CourseParser course = new CourseParser("/config/course.xml");
|
|
||||||
race.addCourse(course.getCourse());
|
|
||||||
|
|
||||||
return race;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Race getRace() {
|
|
||||||
return race;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
package seng302.controllers;
|
|
||||||
|
|
||||||
import javafx.fxml.FXML;
|
|
||||||
import javafx.fxml.Initializable;
|
|
||||||
import javafx.scene.layout.AnchorPane;
|
|
||||||
import javafx.scene.layout.VBox;
|
|
||||||
import javafx.scene.text.Text;
|
|
||||||
import seng302.models.Race;
|
|
||||||
|
|
||||||
import java.net.URL;
|
|
||||||
import java.util.ResourceBundle;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by ptg19 on 20/03/17.
|
|
||||||
*/
|
|
||||||
public class RaceResultController implements Initializable{
|
|
||||||
@FXML private AnchorPane window;
|
|
||||||
@FXML private VBox resultsVBox;
|
|
||||||
private Race race;
|
|
||||||
|
|
||||||
RaceResultController(Race race){
|
|
||||||
this.race = race;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void initialize(URL location, ResourceBundle resources) {
|
|
||||||
int boatPosition = this.race.getFinishedBoats().length;
|
|
||||||
|
|
||||||
for (int i = this.race.getFinishedBoats().length - 1; i >= 0; i--){
|
|
||||||
resultsVBox.getChildren().add(0, new Text(boatPosition + ": " + this.race.getFinishedBoats()[i].getBoatName()));
|
|
||||||
boatPosition--;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,437 +0,0 @@
|
|||||||
package seng302.controllers;
|
|
||||||
|
|
||||||
import com.sun.scenario.effect.impl.sw.sse.SSEBlend_SRC_OUTPeer;
|
|
||||||
import javafx.animation.Animation;
|
|
||||||
import javafx.animation.KeyFrame;
|
|
||||||
import javafx.animation.Timeline;
|
|
||||||
import javafx.beans.value.ChangeListener;
|
|
||||||
import javafx.beans.value.ObservableValue;
|
|
||||||
import javafx.event.ActionEvent;
|
|
||||||
import javafx.fxml.FXML;
|
|
||||||
import javafx.fxml.FXMLLoader;
|
|
||||||
import javafx.scene.control.CheckBox;
|
|
||||||
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.text.Text;
|
|
||||||
import javafx.stage.Stage;
|
|
||||||
import javafx.util.Duration;
|
|
||||||
import javafx.util.StringConverter;
|
|
||||||
import seng302.models.*;
|
|
||||||
import seng302.models.parsers.ConfigParser;
|
|
||||||
import seng302.models.parsers.StreamParser;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by ptg19 on 29/03/17.
|
|
||||||
*/
|
|
||||||
public class RaceViewController extends Thread{
|
|
||||||
@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 CanvasController includedCanvasController;
|
|
||||||
|
|
||||||
private ArrayList<Yacht> startingBoats = new ArrayList<>();
|
|
||||||
private boolean displayFps;
|
|
||||||
private Timeline timerTimeline;
|
|
||||||
private Map<Yacht, TimelineInfo> timelineInfos = new HashMap<>();
|
|
||||||
private ArrayList<Yacht> boatOrder = new ArrayList<>();
|
|
||||||
private Race race;
|
|
||||||
private Stage stage;
|
|
||||||
|
|
||||||
public void initialize() {
|
|
||||||
|
|
||||||
RaceController raceController = new RaceController();
|
|
||||||
raceController.initializeRace();
|
|
||||||
race = raceController.getRace();
|
|
||||||
for (Yacht boat : race.getBoats()) {
|
|
||||||
startingBoats.add(boat);
|
|
||||||
}
|
|
||||||
// try{
|
|
||||||
// initializeTimelines();
|
|
||||||
// }
|
|
||||||
// catch (Exception e){
|
|
||||||
// e.printStackTrace();
|
|
||||||
// }
|
|
||||||
|
|
||||||
includedCanvasController.setup(this);
|
|
||||||
includedCanvasController.initializeCanvas();
|
|
||||||
initializeTimer();
|
|
||||||
initializeSettings();
|
|
||||||
initialiseWindDirection();
|
|
||||||
initialisePositionVBox();
|
|
||||||
//set wind direction!!!!!!! can't find another place to put my code --haoming
|
|
||||||
// double windDirection = new ConfigParser("/config/config.xml").getWindDirection();
|
|
||||||
// windDirectionText.setText(String.format("%.1f°", windDirection));
|
|
||||||
// windArrowText.setRotate(windDirection);
|
|
||||||
includedCanvasController.timer.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private void initializeSettings() {
|
|
||||||
displayFps = true;
|
|
||||||
|
|
||||||
toggleFps.selectedProperty().addListener(new ChangeListener<Boolean>() {
|
|
||||||
@Override
|
|
||||||
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
|
|
||||||
displayFps = !displayFps;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
//SLIFER STUFF BELOW
|
|
||||||
annotationSlider.setLabelFormatter(new StringConverter<Double>() {
|
|
||||||
@Override
|
|
||||||
public String toString(Double n) {
|
|
||||||
if (n == 0) return "None";
|
|
||||||
if (n == 1) return "Low";
|
|
||||||
if (n == 2) return "Medium";
|
|
||||||
if (n == 3) return "All";
|
|
||||||
|
|
||||||
return "All";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Double fromString(String s) {
|
|
||||||
switch (s) {
|
|
||||||
case "None":
|
|
||||||
return 0d;
|
|
||||||
case "Low":
|
|
||||||
return 1d;
|
|
||||||
case "Medium":
|
|
||||||
return 2d;
|
|
||||||
case "All":
|
|
||||||
return 3d;
|
|
||||||
|
|
||||||
default:
|
|
||||||
return 3d;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
annotationSlider.valueProperty().addListener((obs, oldval, newVal) ->
|
|
||||||
setAnnotations((int)annotationSlider.getValue()));
|
|
||||||
|
|
||||||
annotationSlider.setValue(3);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initializeTimer(){
|
|
||||||
timerTimeline = new Timeline();
|
|
||||||
timerTimeline.setCycleCount(Timeline.INDEFINITE);
|
|
||||||
// Run timer update every second
|
|
||||||
timerTimeline.getKeyFrames().add(
|
|
||||||
new KeyFrame(Duration.seconds(1),
|
|
||||||
event -> {
|
|
||||||
if (StreamParser.isRaceFinished()) {
|
|
||||||
timerLabel.setFill(Color.RED);
|
|
||||||
timerLabel.setText("Race Finished!");
|
|
||||||
} else {
|
|
||||||
timerLabel.setText(currentTimer());
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
// Start the timer
|
|
||||||
timerTimeline.playFromStart();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initialiseWindDirection() {
|
|
||||||
Timeline windDirTimeline = new Timeline();
|
|
||||||
windDirTimeline.setCycleCount(Timeline.INDEFINITE);
|
|
||||||
windDirTimeline.getKeyFrames().add(
|
|
||||||
new KeyFrame(Duration.seconds(1),
|
|
||||||
event -> {
|
|
||||||
windDirectionText.setText(String.format("%.1f°", StreamParser.getWindDirection()));
|
|
||||||
windArrowText.setRotate(StreamParser.getWindDirection());
|
|
||||||
})
|
|
||||||
);
|
|
||||||
windDirTimeline.playFromStart();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initialisePositionVBox() {
|
|
||||||
|
|
||||||
Timeline posVBoxTimeline = new Timeline();
|
|
||||||
posVBoxTimeline.setCycleCount(Timeline.INDEFINITE);
|
|
||||||
posVBoxTimeline.getKeyFrames().add(
|
|
||||||
new KeyFrame(Duration.seconds(1),
|
|
||||||
event -> {
|
|
||||||
showOrder();
|
|
||||||
})
|
|
||||||
);
|
|
||||||
posVBoxTimeline.playFromStart();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates time line for each boat, and stores time time into timelineInfos hash map
|
|
||||||
*/
|
|
||||||
private void initializeTimelines() {
|
|
||||||
HashMap<Yacht, List> boat_events = race.getEvents();
|
|
||||||
for (Yacht boat : boat_events.keySet()) {
|
|
||||||
startingBoats.add(boat);
|
|
||||||
// // x, y are the real time coordinates
|
|
||||||
// DoubleProperty x = new SimpleDoubleProperty();
|
|
||||||
// DoubleProperty y = new SimpleDoubleProperty();
|
|
||||||
//
|
|
||||||
// List<KeyFrame> keyFrames = new ArrayList<>();
|
|
||||||
// List<Event> events = boat_events.get(boat);
|
|
||||||
//
|
|
||||||
// // iterates all events and convert each event to keyFrame, then add them into a list
|
|
||||||
// for (Event event : events) {
|
|
||||||
// if (event.getIsFinishingEvent()) {
|
|
||||||
// keyFrames.add(
|
|
||||||
// new KeyFrame(Duration.seconds(event.getTime()),
|
|
||||||
// onFinished -> {race.setBoatFinished(boat); handleEvent(event);},
|
|
||||||
// new KeyValue(x, event.getThisMark().getLatitude()),
|
|
||||||
// new KeyValue(y, event.getThisMark().getLongitude())
|
|
||||||
// )
|
|
||||||
// );
|
|
||||||
// } else {
|
|
||||||
// keyFrames.add(
|
|
||||||
// new KeyFrame(Duration.seconds(event.getTime()),
|
|
||||||
// onFinished ->{
|
|
||||||
// handleEvent(event);
|
|
||||||
// boat.setHeading(event.getBoatHeading());
|
|
||||||
// },
|
|
||||||
// new KeyValue(x, event.getThisMark().getLatitude()),
|
|
||||||
// new KeyValue(y, event.getThisMark().getLongitude())
|
|
||||||
// )
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// timelineInfos.put(boat, new TimelineInfo(new Timeline(keyFrames.toArray(new KeyFrame[keyFrames.size()])), x, y));
|
|
||||||
}
|
|
||||||
setRaceDuration();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setRaceDuration(){
|
|
||||||
Double maxDuration = 0.0;
|
|
||||||
Timeline maxTimeline = null;
|
|
||||||
|
|
||||||
for (TimelineInfo timelineInfo : timelineInfos.values()) {
|
|
||||||
|
|
||||||
Timeline timeline = timelineInfo.getTimeline();
|
|
||||||
if (timeline.getTotalDuration().toMillis() >= maxDuration) {
|
|
||||||
maxDuration = timeline.getTotalDuration().toMillis();
|
|
||||||
maxTimeline = timeline;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Timelines are paused by default
|
|
||||||
timeline.play();
|
|
||||||
timeline.pause();
|
|
||||||
}
|
|
||||||
|
|
||||||
maxTimeline.setOnFinished(event -> {
|
|
||||||
race.setRaceFinished();
|
|
||||||
loadRaceResultView();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Play each boats timerTimeline
|
|
||||||
*/
|
|
||||||
public void playTimelines(){
|
|
||||||
for (TimelineInfo timelineInfo : timelineInfos.values()){
|
|
||||||
Timeline timeline = timelineInfo.getTimeline();
|
|
||||||
|
|
||||||
if (timeline.getStatus() == Animation.Status.PAUSED){
|
|
||||||
timeline.play();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Pause each boats timerTimeline
|
|
||||||
*/
|
|
||||||
public void pauseTimelines(){
|
|
||||||
for (TimelineInfo timelineInfo : timelineInfos.values()){
|
|
||||||
Timeline timeline = timelineInfo.getTimeline();
|
|
||||||
|
|
||||||
if (timeline.getStatus() == Animation.Status.RUNNING){
|
|
||||||
timeline.pause();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Display the list of boats in the order they finished the race
|
|
||||||
*/
|
|
||||||
private void loadRaceResultView() {
|
|
||||||
FXMLLoader loader = new FXMLLoader(getClass().getResource("/views/FinishView.fxml"));
|
|
||||||
loader.setController(new RaceResultController(race));
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void handleEvent(Event event) {
|
|
||||||
Yacht boat = event.getBoat();
|
|
||||||
boatOrder.remove(boat);
|
|
||||||
boat.setMarkLastPast(event.getMarkPosInRace());
|
|
||||||
boatOrder.add(boat);
|
|
||||||
boatOrder.sort(new Comparator<Yacht>() {
|
|
||||||
@Override
|
|
||||||
public int compare(Yacht b1, Yacht b2) {
|
|
||||||
return b2.getMarkLastPast() - b1.getMarkLastPast();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
showOrder();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showOrder() {
|
|
||||||
positionVbox.getChildren().clear();
|
|
||||||
positionVbox.getChildren().removeAll();
|
|
||||||
|
|
||||||
// for (Boat boat : boatOrder) {
|
|
||||||
// positionVbox.getChildren().add(new Text(boat.getShortName() + " " + boat.getSpeedInKnots() + " Knots"));
|
|
||||||
// }
|
|
||||||
|
|
||||||
for (Yacht boat : StreamParser.getBoatsPos().values()) {
|
|
||||||
// System.out.println(boat.getBoatStatus());
|
|
||||||
if (boat.getBoatStatus() == 3) { // 3 is finish status
|
|
||||||
positionVbox.getChildren().add(new Text(boat.getPosition() + ". " +
|
|
||||||
boat.getShortName() + " (Finished)"));
|
|
||||||
} else {
|
|
||||||
positionVbox.getChildren().add(new Text(boat.getPosition() + ". " +
|
|
||||||
boat.getShortName() + " "));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 currentTimer() {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void stopTimer() {
|
|
||||||
timerTimeline.stop();
|
|
||||||
}
|
|
||||||
public void startTimer() {
|
|
||||||
timerTimeline.play();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isDisplayFps() {
|
|
||||||
return displayFps;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Race getRace() {
|
|
||||||
return race;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Map<Yacht, TimelineInfo> getTimelineInfos() {
|
|
||||||
return timelineInfos;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ArrayList<Yacht> getStartingBoats(){
|
|
||||||
return startingBoats;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void setAnnotations(Integer annotationLevel) {
|
|
||||||
switch (annotationLevel) {
|
|
||||||
case 0:
|
|
||||||
for (RaceObject ro : includedCanvasController.getRaceObjects()) {
|
|
||||||
if(ro instanceof BoatGroup) {
|
|
||||||
BoatGroup bg = (BoatGroup) ro;
|
|
||||||
bg.setTeamNameObjectVisible(false);
|
|
||||||
bg.setVelocityObjectVisible(false);
|
|
||||||
bg.setLineGroupVisible(false);
|
|
||||||
bg.setWakeVisible(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
for (RaceObject ro : includedCanvasController.getRaceObjects()) {
|
|
||||||
if(ro instanceof BoatGroup) {
|
|
||||||
BoatGroup bg = (BoatGroup) ro;
|
|
||||||
bg.setTeamNameObjectVisible(true);
|
|
||||||
bg.setVelocityObjectVisible(false);
|
|
||||||
bg.setLineGroupVisible(false);
|
|
||||||
bg.setWakeVisible(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
for (RaceObject ro : includedCanvasController.getRaceObjects()) {
|
|
||||||
if(ro instanceof BoatGroup) {
|
|
||||||
BoatGroup bg = (BoatGroup) ro;
|
|
||||||
bg.setTeamNameObjectVisible(true);
|
|
||||||
bg.setVelocityObjectVisible(false);
|
|
||||||
bg.setLineGroupVisible(true);
|
|
||||||
bg.setWakeVisible(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
for (RaceObject ro : includedCanvasController.getRaceObjects()) {
|
|
||||||
if(ro instanceof BoatGroup) {
|
|
||||||
BoatGroup bg = (BoatGroup) ro;
|
|
||||||
bg.setTeamNameObjectVisible(true);
|
|
||||||
bg.setVelocityObjectVisible(true);
|
|
||||||
bg.setLineGroupVisible(true);
|
|
||||||
bg.setWakeVisible(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void setStage (Stage stage) {
|
|
||||||
this.stage = stage;
|
|
||||||
}
|
|
||||||
|
|
||||||
Stage getStage () {
|
|
||||||
return stage;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,170 @@
|
|||||||
|
package seng302.discoveryServer;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import seng302.gameServer.messages.Message;
|
||||||
|
import seng302.gameServer.messages.RoomCodeRequest;
|
||||||
|
import seng302.gameServer.messages.ServerRegistrationMessage;
|
||||||
|
import seng302.model.stream.packets.PacketType;
|
||||||
|
import seng302.discoveryServer.util.ServerListing;
|
||||||
|
import seng302.discoveryServer.util.ServerRepoStreamParser;
|
||||||
|
import seng302.discoveryServer.util.ServerTable;
|
||||||
|
import seng302.visualiser.ServerListener;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.ServerSocket;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.Timer;
|
||||||
|
|
||||||
|
public class DiscoveryServer {
|
||||||
|
public static final String ANSI_GREEN = "\u001B[32m";
|
||||||
|
public static final String ANSI_YELLOW = "\u001B[33m";
|
||||||
|
public static final String ANSI_BLUE = "\u001B[34m";
|
||||||
|
public static final String ANSI_RESET = "\u001B[0m";
|
||||||
|
private static final int MAX_SERVER_TRIES = 10;
|
||||||
|
public static String DISCOVERY_SERVER = "party.sydney.srv.michaelrausch.nz";
|
||||||
|
|
||||||
|
private ServerTable serverTable;
|
||||||
|
public static final Integer PORT_NUMBER = 9969;
|
||||||
|
private ServerSocket serverSocket;
|
||||||
|
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(DiscoveryServer.class);
|
||||||
|
|
||||||
|
private void displayHeader(){
|
||||||
|
String selectedColor = Arrays.asList(ANSI_BLUE, ANSI_GREEN, ANSI_YELLOW).get(new Random().nextInt(2));
|
||||||
|
System.out.println(selectedColor);
|
||||||
|
System.out.println(" .ccccc. \n" +
|
||||||
|
" .cc;'coooxkl;. \n" +
|
||||||
|
" .:c:::c:,,,,,;c;;,.'. \n" +
|
||||||
|
" .clc,',:,..:xxocc;'..c; \n" +
|
||||||
|
" .c:,';:ox:..:c,,,,,,...cd, \n" +
|
||||||
|
" .c:'.,oxxxxl::l:.,loll;..;ol. \n" +
|
||||||
|
" ;Oc..:xxxxxxxxx:.,llll,....oc \n" +
|
||||||
|
" .,;,',:loxxxxxxxxx:.,llll;.,,.'ld, \n" +
|
||||||
|
" .lo;..:xxxxxxxxxxxx:.'cllc,.:l:'cO; \n" +
|
||||||
|
" .:;...'cxxxxxxxxxxxxoc;,::,..cdl;;l' \n" +
|
||||||
|
" .cl;':,'';oxxxxxxdxxxxxx:....,cooc,cO; \n" +
|
||||||
|
" .,,,::;,lxoc:,,:lxxxxxxxxxxxo:,,;lxxl;'oNc \n" +
|
||||||
|
" .cdxo;':lxxxxxxc'';cccccoxxxxxxxxxxxxo,.;lc. " + ANSI_YELLOW + "Party-Parrots-At-Sea Discovery Server v0.1 " + selectedColor +"\n" +
|
||||||
|
" .loc'.'lxxxxxxxxocc;''''';ccoxxxxxxxxx:..oc \n" +
|
||||||
|
"olc,..',:cccccccccccc:;;;;;;;;:ccccccccc,.'c, \n" +
|
||||||
|
"Ol;......................................;l' ");
|
||||||
|
System.out.println(ANSI_RESET);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DiscoveryServer() throws Exception {
|
||||||
|
displayHeader();
|
||||||
|
serverTable = new ServerTable();
|
||||||
|
|
||||||
|
try{
|
||||||
|
serverSocket = new ServerSocket(PORT_NUMBER);
|
||||||
|
}
|
||||||
|
catch(java.net.BindException e){
|
||||||
|
logger.error("FATAL - Could not bind socket, are you sure there isn't already an instance running?");
|
||||||
|
System.exit(1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info("Started successfully - Now accepting connections");
|
||||||
|
|
||||||
|
try{
|
||||||
|
while (true){
|
||||||
|
Socket clientSocket = serverSocket.accept();
|
||||||
|
|
||||||
|
parseRequest(clientSocket);
|
||||||
|
|
||||||
|
clientSocket.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e){
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void parseRequest(Socket clientSocket) throws Exception {
|
||||||
|
ServerRepoStreamParser parser = new ServerRepoStreamParser(clientSocket.getInputStream());
|
||||||
|
|
||||||
|
if (clientSocket.isConnected() && !clientSocket.isClosed()){
|
||||||
|
PacketType parsePacketResult = parser.parse();
|
||||||
|
|
||||||
|
switch (parsePacketResult){
|
||||||
|
case SERVER_REGISTRATION:
|
||||||
|
ServerListing listing = parser.getServerListing();
|
||||||
|
|
||||||
|
if (!serverTable.getAllServers().contains(listing)){
|
||||||
|
listing.setRoomCode(serverTable.getNextRoomCode().toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
serverTable.addServer(listing);
|
||||||
|
|
||||||
|
Message serverRegMessage = new RoomCodeRequest(listing.getRoomCode());
|
||||||
|
clientSocket.getOutputStream().write(serverRegMessage.getBuffer());
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ROOM_CODE_REQUEST:
|
||||||
|
String desiredRoomCode = parser.getRoomCode();
|
||||||
|
ServerListing serverListing;
|
||||||
|
|
||||||
|
if (desiredRoomCode.equals("0000")){
|
||||||
|
serverListing = getRandomFreeServer();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
serverListing = serverTable.getServerByRoomCode(desiredRoomCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
Message response;
|
||||||
|
|
||||||
|
if (serverListing != null){
|
||||||
|
response = new ServerRegistrationMessage(serverListing.getServerName(), serverListing.getMapName(), serverListing.getAddress(), serverListing.getPortNumber(), 0, 0, desiredRoomCode);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
response = ServerRegistrationMessage.getEmptyRegistration();
|
||||||
|
}
|
||||||
|
|
||||||
|
clientSocket.getOutputStream().write(response.getBuffer());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServerListing getRandomFreeServer() {
|
||||||
|
ServerListing serverToJoin;
|
||||||
|
|
||||||
|
List<ServerListing> servers = serverTable.getAllServers();
|
||||||
|
|
||||||
|
if (servers.size() <= 0){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (servers.size() == 1){
|
||||||
|
return servers.get(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
serverToJoin = servers.get(new Random().nextInt(servers.size()));
|
||||||
|
|
||||||
|
int tries = 0;
|
||||||
|
|
||||||
|
while (serverToJoin != null && serverToJoin.isMaxPlayersReached() && tries < MAX_SERVER_TRIES){
|
||||||
|
serverToJoin = servers.get(new Random().nextInt(servers.size()));
|
||||||
|
tries++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (serverToJoin != null && serverToJoin.isMaxPlayersReached()){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return serverToJoin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void close(){
|
||||||
|
try {
|
||||||
|
serverSocket.close();
|
||||||
|
} catch (IOException ignored) {
|
||||||
|
;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,195 @@
|
|||||||
|
package seng302.discoveryServer;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import seng302.discoveryServer.util.ServerListing;
|
||||||
|
import seng302.discoveryServer.util.ServerRepoStreamParser;
|
||||||
|
import seng302.gameServer.messages.Message;
|
||||||
|
import seng302.gameServer.messages.RoomCodeRequest;
|
||||||
|
import seng302.gameServer.messages.ServerRegistrationMessage;
|
||||||
|
import seng302.model.stream.packets.PacketType;
|
||||||
|
import seng302.visualiser.controllers.ViewManager;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.Timer;
|
||||||
|
import java.util.TimerTask;
|
||||||
|
|
||||||
|
public class DiscoveryServerClient {
|
||||||
|
private final Integer UPDATE_INTERVAL_MS = 5000;
|
||||||
|
|
||||||
|
private static String roomCode = null;
|
||||||
|
private Timer serverListingUpdateTimer;
|
||||||
|
private Logger logger = LoggerFactory.getLogger(DiscoveryServerClient.class);
|
||||||
|
private String ip = "";
|
||||||
|
private Boolean isInInvalidState = false;
|
||||||
|
|
||||||
|
public DiscoveryServerClient() {
|
||||||
|
try {
|
||||||
|
ip = getInetIpAddr();
|
||||||
|
} catch (Exception e) {
|
||||||
|
failError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getInetIp(){
|
||||||
|
return ip;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void failError() {
|
||||||
|
isInInvalidState = true;
|
||||||
|
ViewManager.getInstance().showErrorSnackBar("You do not appear to be able to connect to the internet. Matchmaking will be unavailable.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean didFail(){
|
||||||
|
return isInInvalidState;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register the server with the discovery server
|
||||||
|
* @param serverListing The listing to register
|
||||||
|
*/
|
||||||
|
public void register(ServerListing serverListing){
|
||||||
|
if (isInInvalidState) return;
|
||||||
|
|
||||||
|
if (serverListingUpdateTimer != null){
|
||||||
|
serverListingUpdateTimer.cancel();
|
||||||
|
serverListingUpdateTimer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
serverListingUpdateTimer = new Timer();
|
||||||
|
|
||||||
|
serverListingUpdateTimer.schedule(new TimerTask() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
sendRegistrationUpdate(serverListing);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.debug("Could not update server listing");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 0, UPDATE_INTERVAL_MS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop updating the server registration updates
|
||||||
|
*/
|
||||||
|
public void unregister(){
|
||||||
|
if (serverListingUpdateTimer != null)
|
||||||
|
serverListingUpdateTimer.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the connection information for a server given a room code
|
||||||
|
*
|
||||||
|
* @param roomCode The room code to search for
|
||||||
|
* @return The ServerListing, or null if there was an error
|
||||||
|
* @throws Exception .
|
||||||
|
*/
|
||||||
|
public ServerListing getServerForRoomCode(String roomCode) throws Exception {
|
||||||
|
Socket socket = new Socket(DiscoveryServer.DISCOVERY_SERVER, DiscoveryServer.PORT_NUMBER);
|
||||||
|
ServerRepoStreamParser parser = new ServerRepoStreamParser(socket.getInputStream());
|
||||||
|
|
||||||
|
Message request = new RoomCodeRequest(roomCode); //roomCode);
|
||||||
|
socket.getOutputStream().write(request.getBuffer());
|
||||||
|
|
||||||
|
PacketType packetType = parser.parse();
|
||||||
|
|
||||||
|
if (packetType != PacketType.SERVER_REGISTRATION){
|
||||||
|
logger.debug("Wrong packet received in response to a room code request");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.close();
|
||||||
|
|
||||||
|
return parser.getServerListing();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServerListing getRandomServer() throws Exception {
|
||||||
|
Socket socket = new Socket(DiscoveryServer.DISCOVERY_SERVER, DiscoveryServer.PORT_NUMBER);
|
||||||
|
ServerRepoStreamParser parser = new ServerRepoStreamParser(socket.getInputStream());
|
||||||
|
|
||||||
|
Message request = new RoomCodeRequest("0000");
|
||||||
|
socket.getOutputStream().write(request.getBuffer());
|
||||||
|
|
||||||
|
PacketType packetType = parser.parse();
|
||||||
|
|
||||||
|
if (packetType != PacketType.SERVER_REGISTRATION){
|
||||||
|
logger.error("Incorrect packet type received");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.close();
|
||||||
|
|
||||||
|
ServerListing serverListing = parser.getServerListing();
|
||||||
|
|
||||||
|
if (serverListing == null || serverListing.equals(ServerRegistrationMessage.getEmptyRegistration())){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return serverListing;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a registration update to the discovery server.
|
||||||
|
*
|
||||||
|
* @param serverListing The server listing to send
|
||||||
|
* @throws Exception IF there was an error sending the update
|
||||||
|
*/
|
||||||
|
private void sendRegistrationUpdate(ServerListing serverListing) throws Exception {
|
||||||
|
Socket socket = new Socket(DiscoveryServer.DISCOVERY_SERVER, DiscoveryServer.PORT_NUMBER);
|
||||||
|
ServerRepoStreamParser parser = new ServerRepoStreamParser(socket.getInputStream());
|
||||||
|
|
||||||
|
Message req = new ServerRegistrationMessage(serverListing);
|
||||||
|
|
||||||
|
socket.getOutputStream().write(req.getBuffer());
|
||||||
|
|
||||||
|
PacketType packetType = parser.parse();
|
||||||
|
|
||||||
|
if (packetType != PacketType.ROOM_CODE_REQUEST){
|
||||||
|
socket.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String roomCode = parser.getRoomCode();
|
||||||
|
|
||||||
|
if (roomCode.length() != 0){
|
||||||
|
DiscoveryServerClient.roomCode = roomCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The last room code received by the client
|
||||||
|
*/
|
||||||
|
public static String getRoomCode(){
|
||||||
|
return roomCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getInetIpAddr() throws Exception {
|
||||||
|
URL myIp = new URL("http://checkip.amazonaws.com");
|
||||||
|
BufferedReader in = null;
|
||||||
|
try {
|
||||||
|
in = new BufferedReader(new InputStreamReader(
|
||||||
|
myIp.openStream()));
|
||||||
|
String ip = in.readLine();
|
||||||
|
return ip;
|
||||||
|
} finally {
|
||||||
|
if (in != null) {
|
||||||
|
try {
|
||||||
|
in.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package seng302.discoveryServer.util;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
public class ReadableByteInputStream {
|
||||||
|
private InputStream is;
|
||||||
|
|
||||||
|
public ReadableByteInputStream(InputStream is){
|
||||||
|
this.is = is;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get n bytes from the input stream
|
||||||
|
* @param n number of bytes
|
||||||
|
* @return the bytes read
|
||||||
|
* @throws Exception .
|
||||||
|
*/
|
||||||
|
public byte[] getBytes(int n) throws Exception {
|
||||||
|
byte[] bytes = new byte[n];
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
bytes[i] = (byte) readByte();
|
||||||
|
}
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Skip n bytes
|
||||||
|
* @param n number of bytes to skip
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public void skipBytes(long n) throws Exception {
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
readByte();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read the next byte from the stream
|
||||||
|
* @return The byte that was read
|
||||||
|
* @throws Exception .
|
||||||
|
*/
|
||||||
|
public int readByte() throws Exception {
|
||||||
|
int currentByte = is.read();
|
||||||
|
|
||||||
|
if (currentByte == -1) {
|
||||||
|
throw new Exception();
|
||||||
|
}
|
||||||
|
return currentByte;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
package seng302.discoveryServer.util;
|
||||||
|
|
||||||
|
public class ServerListing {
|
||||||
|
public final static int SERVER_TTL_DEFAULT = 10;
|
||||||
|
|
||||||
|
private String serverName = "";
|
||||||
|
private String mapName = "";
|
||||||
|
private String address = "";
|
||||||
|
private int portNumber = 0;
|
||||||
|
private int capacity = 0;
|
||||||
|
private int players = 0;
|
||||||
|
private String roomCode = "";
|
||||||
|
private int ttl = SERVER_TTL_DEFAULT;
|
||||||
|
|
||||||
|
|
||||||
|
public ServerListing(String serverName, String mapName, String address, int portNumber, int capacity){
|
||||||
|
this.serverName = serverName;
|
||||||
|
this.mapName = mapName;
|
||||||
|
this.address = address;
|
||||||
|
this.portNumber = portNumber;
|
||||||
|
this.capacity = capacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServerListing setNumberOfPlayers(int players){
|
||||||
|
this.players = players;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServerListing setRoomCode(String roomCode){
|
||||||
|
this.roomCode = roomCode;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void refreshTtl(){
|
||||||
|
ttl = SERVER_TTL_DEFAULT;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void decrementTtl(){
|
||||||
|
ttl--;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasTtlExpired(){
|
||||||
|
return ttl < 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (obj == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!ServerListing.class.isAssignableFrom(obj.getClass())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
final ServerListing other = (ServerListing) obj;
|
||||||
|
|
||||||
|
if (this.getPortNumber() != other.getPortNumber()){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.getMapName().equals(other.getMapName())){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.getServerName().equals(other.getServerName())){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.getCapacity() != other.getCapacity()){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.getAddress().equals(other.getAddress())){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return this.getServerName().hashCode() +
|
||||||
|
this.getAddress().hashCode() + this.getMapName().hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRoomCode() {
|
||||||
|
return roomCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPortNumber() {
|
||||||
|
return portNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMapName() {
|
||||||
|
return mapName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getServerName() {
|
||||||
|
return serverName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCapacity() {
|
||||||
|
return capacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAddress() {
|
||||||
|
return address;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTtl(Integer ttl){
|
||||||
|
this.ttl = ttl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isMaxPlayersReached() {
|
||||||
|
return players >= capacity;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
package seng302.discoveryServer.util;
|
||||||
|
|
||||||
|
|
||||||
|
import seng302.gameServer.messages.Message;
|
||||||
|
import seng302.model.stream.packets.PacketType;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
public class ServerRepoStreamParser {
|
||||||
|
private ReadableByteInputStream inputStream;
|
||||||
|
|
||||||
|
private String roomCode;
|
||||||
|
private String mapName;
|
||||||
|
private ServerListing serverListing;
|
||||||
|
|
||||||
|
public ServerRepoStreamParser(InputStream is){
|
||||||
|
inputStream = new ReadableByteInputStream(is);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PacketType parse() throws Exception {
|
||||||
|
int sync1 = inputStream.readByte();
|
||||||
|
int sync2 = inputStream.readByte();
|
||||||
|
|
||||||
|
PacketType packetType = null;
|
||||||
|
|
||||||
|
if (sync1 == 0x47 && sync2 == 0x83) {
|
||||||
|
int type = inputStream.readByte();
|
||||||
|
inputStream.skipBytes(10);
|
||||||
|
long payloadLength = Message.bytesToLong(inputStream.getBytes(2));
|
||||||
|
byte[] payload = inputStream.getBytes((int) payloadLength);
|
||||||
|
inputStream.skipBytes(4);
|
||||||
|
|
||||||
|
packetType = PacketType.assignPacketType(type, payload);
|
||||||
|
|
||||||
|
switch (packetType) {
|
||||||
|
case ROOM_CODE_REQUEST:
|
||||||
|
roomCode = parseRoomCodeRequest(payload);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LOBBY_REQUEST:
|
||||||
|
mapName = parseLobbyRequest(payload);
|
||||||
|
|
||||||
|
case SERVER_REGISTRATION:
|
||||||
|
serverListing = parseServerRegistration(payload);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return packetType;
|
||||||
|
}
|
||||||
|
private String parseLobbyRequest(byte[] payload) {
|
||||||
|
int mapNameLength = (int) Message.bytesToLong(Arrays.copyOfRange(payload, 0 ,4));
|
||||||
|
|
||||||
|
return new String(Arrays.copyOfRange(payload, 4, 4+mapNameLength));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String parseRoomCodeRequest(byte[] payload) {
|
||||||
|
int roomCodeLength = (int) Message.bytesToLong(Arrays.copyOfRange(payload, 0 ,6));
|
||||||
|
|
||||||
|
return new String(Arrays.copyOfRange(payload, 6, 6+roomCodeLength));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ServerListing parseServerRegistration(byte[] payload) {
|
||||||
|
int nameLength = (int) Message.bytesToLong(Arrays.copyOfRange(payload, 0, 6));
|
||||||
|
int mapNameLength = (int) Message.bytesToLong(Arrays.copyOfRange(payload, 6, 12));
|
||||||
|
int addressLength = (int) Message.bytesToLong(Arrays.copyOfRange(payload, 12, 18));
|
||||||
|
int roomCodeLength = (int) Message.bytesToLong(Arrays.copyOfRange(payload, 18, 24));
|
||||||
|
|
||||||
|
int portNumber = (int) Message.bytesToLong(Arrays.copyOfRange(payload, 24, 28));
|
||||||
|
int players = (int) Message.bytesToLong(Arrays.copyOfRange(payload, 28, 32));
|
||||||
|
int capacity = (int) Message.bytesToLong(Arrays.copyOfRange(payload, 32, 36));
|
||||||
|
|
||||||
|
int currentPos = 36;
|
||||||
|
int nextPos = currentPos + nameLength;
|
||||||
|
String serverName = new String(Arrays.copyOfRange(payload, currentPos, nextPos));
|
||||||
|
|
||||||
|
currentPos = nextPos;
|
||||||
|
nextPos = currentPos + mapNameLength;
|
||||||
|
String mapName = new String(Arrays.copyOfRange(payload, currentPos, nextPos));
|
||||||
|
|
||||||
|
currentPos = nextPos;
|
||||||
|
nextPos = currentPos + addressLength;
|
||||||
|
String address = new String(Arrays.copyOfRange(payload, currentPos, nextPos));
|
||||||
|
|
||||||
|
currentPos = nextPos;
|
||||||
|
nextPos = currentPos + roomCodeLength;
|
||||||
|
String roomCode = new String(Arrays.copyOfRange(payload, currentPos, nextPos));
|
||||||
|
|
||||||
|
ServerListing serverListing = new ServerListing(serverName, mapName, address, portNumber, capacity);
|
||||||
|
serverListing.setNumberOfPlayers(players);
|
||||||
|
serverListing.setRoomCode(roomCode);
|
||||||
|
|
||||||
|
return serverListing;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRoomCode() {
|
||||||
|
return roomCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMapName() {
|
||||||
|
return mapName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServerListing getServerListing() {
|
||||||
|
return serverListing;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
package seng302.discoveryServer.util;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
public class ServerTable {
|
||||||
|
private List<ServerListing> servers;
|
||||||
|
private int lastRoomCode = 4020;
|
||||||
|
private Logger logger = LoggerFactory.getLogger(ServerTable.class);
|
||||||
|
|
||||||
|
public ServerTable(){
|
||||||
|
servers = new ArrayList<>();
|
||||||
|
|
||||||
|
new Timer().schedule(new TimerTask() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
updateServers();
|
||||||
|
}
|
||||||
|
}, 0, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the servers TTL values, and then remove expired servers
|
||||||
|
*/
|
||||||
|
private void updateServers() {
|
||||||
|
List<ServerListing> serversToRemove = new ArrayList<>();
|
||||||
|
|
||||||
|
for (ServerListing server : servers){
|
||||||
|
server.decrementTtl();
|
||||||
|
|
||||||
|
if (server.hasTtlExpired()){
|
||||||
|
logger.debug("Removed expired server - " + server.getServerName());
|
||||||
|
serversToRemove.add(server);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
servers.removeAll(serversToRemove);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a server to the table
|
||||||
|
* @param server The server to add
|
||||||
|
*/
|
||||||
|
public void addServer(ServerListing server){
|
||||||
|
if (servers.contains(server)){
|
||||||
|
updateTtlForServer(server);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
logger.debug("Added new server - " + server.getServerName() + " at address: " + server.getAddress() + ":" + server.getPortNumber());
|
||||||
|
servers.add(server);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the TTL for a given server to the default TTL value
|
||||||
|
* @param server The server to update
|
||||||
|
*/
|
||||||
|
private void updateTtlForServer(ServerListing server) {
|
||||||
|
for (ServerListing serverListing : servers){
|
||||||
|
if (server.equals(serverListing)){
|
||||||
|
serverListing.refreshTtl();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return All the servers in the table
|
||||||
|
*/
|
||||||
|
public List<ServerListing> getAllServers(){
|
||||||
|
return Collections.unmodifiableList(servers);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a server from the table given its room code
|
||||||
|
* @param roomCode The room code to search for
|
||||||
|
* @return The ServerListing of the found server, or null
|
||||||
|
* the server wasn't found
|
||||||
|
*/
|
||||||
|
public ServerListing getServerByRoomCode(String roomCode){
|
||||||
|
for (ServerListing serverListing : servers){
|
||||||
|
if (serverListing.getRoomCode().equals(roomCode)){
|
||||||
|
return serverListing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The next available room code
|
||||||
|
*/
|
||||||
|
public Integer getNextRoomCode(){
|
||||||
|
lastRoomCode += 1;
|
||||||
|
return lastRoomCode;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,462 @@
|
|||||||
|
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.Random;
|
||||||
|
import java.util.Timer;
|
||||||
|
import java.util.TimerTask;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import seng302.gameServer.messages.Message;
|
||||||
|
import seng302.model.GeoPoint;
|
||||||
|
import seng302.model.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;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.ServerSocket;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.Timer;
|
||||||
|
import java.util.TimerTask;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class describing the overall server, which creates and collects server threads for each client
|
||||||
|
* Created by wmu16 on 13/07/17.
|
||||||
|
*/
|
||||||
|
public class MainServerThread implements Runnable, ClientConnectionDelegate {
|
||||||
|
|
||||||
|
private Logger logger = LoggerFactory.getLogger(MainServerThread.class);
|
||||||
|
|
||||||
|
private static final int PORT = 4942;
|
||||||
|
private static final Integer CLIENT_UPDATES_PER_SECOND = 60;
|
||||||
|
|
||||||
|
private boolean terminated;
|
||||||
|
|
||||||
|
private Thread thread;
|
||||||
|
private boolean hasStarted = false;
|
||||||
|
|
||||||
|
private ServerSocket serverSocket = null;
|
||||||
|
private ArrayList<ServerToClientThread> serverToClientThreads = new ArrayList<>();
|
||||||
|
private static Integer capacity;
|
||||||
|
private RaceXMLData raceXMLData;
|
||||||
|
private RegattaXMLData regattaXMLData;
|
||||||
|
private boolean serverStarted = false;
|
||||||
|
|
||||||
|
private void startAdvertisingServer() {
|
||||||
|
Integer capacity = GameState.getCapacity();
|
||||||
|
Integer numPlayers = GameState.getNumberOfPlayers();
|
||||||
|
Integer spacesLeft = capacity - numPlayers;
|
||||||
|
|
||||||
|
// No spaces left on server
|
||||||
|
if (spacesLeft < 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start advertising server
|
||||||
|
try {
|
||||||
|
ServerAdvertiser.getInstance()
|
||||||
|
.setMapName(regattaXMLData.getCourseName())
|
||||||
|
.setCapacity(capacity)
|
||||||
|
.setNumberOfPlayers(numPlayers - 1)
|
||||||
|
.registerGame(PORT, regattaXMLData.getRegattaName());
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.warn("Could not register server");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public MainServerThread() {
|
||||||
|
new GameState();
|
||||||
|
try {
|
||||||
|
serverSocket = new ServerSocket(PORT);
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.trace("IO error in server thread handler upon trying to make new server socket",
|
||||||
|
0);
|
||||||
|
}
|
||||||
|
terminated = false;
|
||||||
|
thread = new Thread(this, "MainServer");
|
||||||
|
thread.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startServer() {
|
||||||
|
PolarTable.parsePolarFile(getClass().getResourceAsStream("/server_config/acc_polars.csv"));
|
||||||
|
MessageFactory.updateXMLGenerator(raceXMLData, regattaXMLData);
|
||||||
|
GameState.setRace(raceXMLData);
|
||||||
|
MessageFactory.updateBoats(new ArrayList<>(GameState.getYachts().values()));
|
||||||
|
startAdvertisingServer();
|
||||||
|
GameState.addMessageEventListener(this::broadcastMessage);
|
||||||
|
sendSetupMessages();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
//serverToClientThread.addConnectionListener(this::sendSetupMessages);
|
||||||
|
}
|
||||||
|
serverToClientThreads.add(serverToClientThread);
|
||||||
|
|
||||||
|
try {
|
||||||
|
ServerAdvertiser.getInstance().setNumberOfPlayers(GameState.getNumberOfPlayers());
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.warn("Couldn't update advertisement");
|
||||||
|
}
|
||||||
|
|
||||||
|
while (regattaXMLData == null && raceXMLData == null){
|
||||||
|
try {
|
||||||
|
Thread.sleep(50);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
serverToClientThread.addConnectionListener(this::sendSetupMessages);
|
||||||
|
serverToClientThread.addDisconnectListener(this::clientDisconnected);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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");
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
|
||||||
|
// if (GameState.getCurrentStage() == GameStages.LOBBYING) {
|
||||||
|
// sendSetupMessages();
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
public void terminate() {
|
||||||
|
terminated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialise boats to specific spaced out geopoints behind starting line.
|
||||||
|
*/
|
||||||
|
private void initialiseBoatPositions() {
|
||||||
|
// CompoundMark cm = GameState.getMarkOrder().getMarkOrder().get(0);
|
||||||
|
// GeoPoint startMark1 = cm.getSubMark(1);
|
||||||
|
// GeoPoint startMark2 = cm.getSubMark(2);
|
||||||
|
//
|
||||||
|
// // Calculating midpoint
|
||||||
|
// Double perpendicularAngle = GeoUtility.getBearing(startMark1, startMark2);
|
||||||
|
// Double length = GeoUtility.getDistance(startMark1, startMark2);
|
||||||
|
// GeoPoint midpoint = GeoUtility.getGeoCoordinate(startMark1, perpendicularAngle, length / 2);
|
||||||
|
//
|
||||||
|
// // Setting each boats position side by side
|
||||||
|
// final double SEPARATION = 50.0; // distance apart in meters
|
||||||
|
//
|
||||||
|
// int boatIndex = 0;
|
||||||
|
// for (ServerYacht yacht : GameState.getYachts().values()) {
|
||||||
|
// int distanceApart = boatIndex / 2;
|
||||||
|
//
|
||||||
|
// if (boatIndex % 2 == 1 && boatIndex != 0) {
|
||||||
|
// distanceApart++;
|
||||||
|
// distanceApart *= -1;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// GeoPoint spawnMark = GeoUtility
|
||||||
|
// .getGeoCoordinate(midpoint, perpendicularAngle, distanceApart * SEPARATION);
|
||||||
|
//
|
||||||
|
// if (yacht.getHeading() < perpendicularAngle) {
|
||||||
|
// spawnMark = GeoUtility
|
||||||
|
// .getGeoCoordinate(spawnMark, perpendicularAngle + 90, SEPARATION);
|
||||||
|
// } else {
|
||||||
|
// spawnMark = GeoUtility
|
||||||
|
// .getGeoCoordinate(spawnMark, perpendicularAngle + 270, SEPARATION);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// yacht.setLocation(spawnMark);
|
||||||
|
// boatIndex++;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// final double SEPARATION = 50.0; // distance apart in meters
|
||||||
|
//
|
||||||
|
// //Reverse of the angle from start to first mark
|
||||||
|
// double angleToFirstMark = 360 - GeoUtility.getBearing(
|
||||||
|
// GameState.getMarkOrder().getMarkOrder().get(0).getMidPoint(),
|
||||||
|
// GameState.getMarkOrder().getMarkOrder().get(1).getMidPoint()
|
||||||
|
// );
|
||||||
|
//
|
||||||
|
// //Length of start line
|
||||||
|
// double startLineLength = GeoUtility.getDistance(
|
||||||
|
// GameState.getMarkOrder().getMarkOrder().get(0).getSubMark(1),
|
||||||
|
// GameState.getMarkOrder().getMarkOrder().get(0).getSubMark(2)
|
||||||
|
// );
|
||||||
|
//
|
||||||
|
// //Angle of start line
|
||||||
|
// double startMarkToMarkAngle = GeoUtility.getBearing(
|
||||||
|
// GameState.getMarkOrder().getMarkOrder().get(0).getSubMark(1),
|
||||||
|
// GameState.getMarkOrder().getMarkOrder().get(0).getSubMark(2)
|
||||||
|
// );
|
||||||
|
//
|
||||||
|
// //How many yachts can fit along the start line
|
||||||
|
// int spacesAlongLine = (int) Math.round(startLineLength / SEPARATION);
|
||||||
|
// //The free space left by the boats.
|
||||||
|
// double buffer = (startLineLength % SEPARATION) / 2;
|
||||||
|
//
|
||||||
|
// //Randomize starting order.
|
||||||
|
// List<ServerYacht> serverYachtList = new ArrayList<>(GameState.getYachts().values());
|
||||||
|
// Collections.shuffle(serverYachtList);
|
||||||
|
//
|
||||||
|
// //set the starting point away from start line.
|
||||||
|
// GeoPoint startingPoint = GeoUtility.getGeoCoordinate(
|
||||||
|
// GameState.getMarkOrder().getMarkOrder().get(0).getSubMark(1),
|
||||||
|
// angleToFirstMark, SEPARATION
|
||||||
|
// );
|
||||||
|
//
|
||||||
|
// //Move it along the start line
|
||||||
|
// startingPoint = GeoUtility.getGeoCoordinate(
|
||||||
|
// startingPoint, startMarkToMarkAngle, buffer
|
||||||
|
// );
|
||||||
|
//
|
||||||
|
// int yachtCount = 0;
|
||||||
|
// int repeats = 0;
|
||||||
|
//
|
||||||
|
// GeoPoint yachtLocation;
|
||||||
|
//
|
||||||
|
// for (ServerYacht serverYacht : serverYachtList) {
|
||||||
|
//
|
||||||
|
// //Move away from start line
|
||||||
|
// yachtLocation = GeoUtility.getGeoCoordinate(
|
||||||
|
// startingPoint, angleToFirstMark,repeats * SEPARATION
|
||||||
|
// );
|
||||||
|
// //Move along start line
|
||||||
|
// yachtLocation = GeoUtility.getGeoCoordinate(
|
||||||
|
// yachtLocation, startMarkToMarkAngle, yachtCount * SEPARATION
|
||||||
|
// );
|
||||||
|
// serverYacht.setLocation(yachtLocation);
|
||||||
|
// serverYacht.setHeading(GeoUtility.getBearing(
|
||||||
|
// yachtLocation, GameState.getMarkOrder().getMarkOrder().get(1).getMidPoint()
|
||||||
|
// ));
|
||||||
|
// //Set location for next yacht
|
||||||
|
// yachtCount++;
|
||||||
|
// if (yachtCount > spacesAlongLine) {
|
||||||
|
// yachtCount = 0;
|
||||||
|
// repeats++;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
final double DISTANCE_TO_START = 75d;
|
||||||
|
final double YACHT_SEPARATION = 20d;
|
||||||
|
|
||||||
|
//Length of start line
|
||||||
|
double startLineLength = GeoUtility.getDistance(
|
||||||
|
GameState.getMarkOrder().getMarkOrder().get(0).getSubMark(1),
|
||||||
|
GameState.getMarkOrder().getMarkOrder().get(0).getSubMark(2)
|
||||||
|
);
|
||||||
|
|
||||||
|
//How many yachts can fit along the start line
|
||||||
|
int spacesAlongLine = (int) Math.round(startLineLength / YACHT_SEPARATION);
|
||||||
|
|
||||||
|
//Angle of start line
|
||||||
|
double startMarkToMarkAngle = GeoUtility.getBearing(
|
||||||
|
GameState.getMarkOrder().getMarkOrder().get(0).getSubMark(1),
|
||||||
|
GameState.getMarkOrder().getMarkOrder().get(0).getSubMark(2)
|
||||||
|
);
|
||||||
|
|
||||||
|
//angle from first mark to the start
|
||||||
|
double angleToStart = GeoUtility.getBearing(
|
||||||
|
GameState.getMarkOrder().getMarkOrder().get(1).getMidPoint(),
|
||||||
|
GameState.getMarkOrder().getMarkOrder().get(0).getMidPoint()
|
||||||
|
);
|
||||||
|
|
||||||
|
double angleFromStart = GeoUtility.getBearing(
|
||||||
|
GameState.getMarkOrder().getMarkOrder().get(0).getMidPoint(),
|
||||||
|
GameState.getMarkOrder().getMarkOrder().get(1).getMidPoint()
|
||||||
|
);
|
||||||
|
|
||||||
|
GeoPoint startingPoint = GeoUtility.getGeoCoordinate(
|
||||||
|
GameState.getMarkOrder().getMarkOrder().get(0).getMidPoint(),
|
||||||
|
angleToStart, DISTANCE_TO_START
|
||||||
|
);
|
||||||
|
|
||||||
|
List<ServerYacht> randomisedYachts = new ArrayList<>(GameState.getYachts().values());
|
||||||
|
Collections.shuffle(randomisedYachts);
|
||||||
|
while (randomisedYachts.size() > 0) {
|
||||||
|
|
||||||
|
int numYachtsInLine = spacesAlongLine > randomisedYachts.size() ? randomisedYachts.size() : spacesAlongLine;
|
||||||
|
double yachtSpace = numYachtsInLine * YACHT_SEPARATION / 2;
|
||||||
|
|
||||||
|
GeoPoint firstYachtPoint = GeoUtility.getGeoCoordinate(
|
||||||
|
startingPoint, startMarkToMarkAngle + 180, yachtSpace
|
||||||
|
);
|
||||||
|
|
||||||
|
for (int i=0; i<numYachtsInLine; i++){
|
||||||
|
randomisedYachts.get(0).setHeading(angleFromStart);
|
||||||
|
randomisedYachts.get(0).setLocation(firstYachtPoint);
|
||||||
|
firstYachtPoint = GeoUtility.getGeoCoordinate(
|
||||||
|
firstYachtPoint, startMarkToMarkAngle, yachtSpace
|
||||||
|
);
|
||||||
|
randomisedYachts.remove(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
startingPoint = GeoUtility.getGeoCoordinate(
|
||||||
|
startingPoint, angleToStart, DISTANCE_TO_START
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasStarted() {
|
||||||
|
return hasStarted;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,232 @@
|
|||||||
|
package seng302.gameServer;
|
||||||
|
|
||||||
|
import seng302.gameServer.messages.*;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import seng302.gameServer.messages.BoatLocationMessage;
|
||||||
|
import seng302.gameServer.messages.BoatSubMessage;
|
||||||
|
import seng302.gameServer.messages.ChatterMessage;
|
||||||
|
import seng302.gameServer.messages.RaceStartNotificationType;
|
||||||
|
import seng302.gameServer.messages.RaceStartStatusMessage;
|
||||||
|
import seng302.gameServer.messages.RaceStatus;
|
||||||
|
import seng302.gameServer.messages.RaceStatusMessage;
|
||||||
|
import seng302.gameServer.messages.RaceType;
|
||||||
|
import seng302.gameServer.messages.XMLMessage;
|
||||||
|
import seng302.gameServer.messages.XMLMessageSubType;
|
||||||
|
import seng302.gameServer.messages.YachtEventCodeMessage;
|
||||||
|
import seng302.gameServer.messages.YachtEventType;
|
||||||
|
import seng302.model.Player;
|
||||||
|
import seng302.model.ServerYacht;
|
||||||
|
import seng302.model.stream.xml.generator.RaceXMLTemplate;
|
||||||
|
import seng302.model.stream.xml.generator.RegattaXMLTemplate;
|
||||||
|
import seng302.model.stream.xml.parser.RaceXMLData;
|
||||||
|
import seng302.model.stream.xml.parser.RegattaXMLData;
|
||||||
|
import seng302.model.token.Token;
|
||||||
|
import seng302.model.token.TokenType;
|
||||||
|
import seng302.utilities.XMLGenerator;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Class for interfacing between the data we have in the GameState to the messages we need to send
|
||||||
|
* through the MainServerThread.
|
||||||
|
*
|
||||||
|
* WARNING DO NOT USE THIS CLASS IF GAMESTATE HAS NOT BEEN INSTANTIATED. (Main Server has not started)
|
||||||
|
* // TODO: 29/08/17 wmu16 - Make GameState non static to fix this ¯\_(ツ)_/¯
|
||||||
|
* Created by wmu16 on 29/08/17.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
Ideally this class would be created with an instance of the GameState (I tried implementing this for
|
||||||
|
a bit) but it was too difficult to properly make GameState non static without doing some proper
|
||||||
|
re working. To do later.
|
||||||
|
*/
|
||||||
|
public class MessageFactory {
|
||||||
|
|
||||||
|
private static XMLGenerator xmlGenerator = new XMLGenerator();
|
||||||
|
private static XMLMessage race;
|
||||||
|
private static XMLMessage regatta;
|
||||||
|
private static XMLMessage boats;
|
||||||
|
|
||||||
|
public static void updateXMLGenerator(RaceXMLData race, RegattaXMLData regatta) {
|
||||||
|
xmlGenerator.setRegattaTemplate(
|
||||||
|
new RegattaXMLTemplate(
|
||||||
|
regatta.getRegattaName(),
|
||||||
|
regatta.getCourseName(),
|
||||||
|
regatta.getCentralLat(),
|
||||||
|
regatta.getCentralLng()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
xmlGenerator.setRaceTemplate(
|
||||||
|
new RaceXMLTemplate(
|
||||||
|
new ArrayList<>(),
|
||||||
|
new ArrayList<>(),
|
||||||
|
race.getMarkSequence(),
|
||||||
|
race.getCourseLimit(),
|
||||||
|
new ArrayList<>(race.getCompoundMarks().values()),
|
||||||
|
GameState.getCapacity(), true
|
||||||
|
)
|
||||||
|
);
|
||||||
|
String xmlStr = xmlGenerator.getRaceAsXml();
|
||||||
|
MessageFactory.race = new XMLMessage(xmlStr, XMLMessageSubType.RACE, xmlStr.length());
|
||||||
|
xmlStr = xmlGenerator.getRegattaAsXml();
|
||||||
|
MessageFactory.regatta = new XMLMessage(xmlStr, XMLMessageSubType.REGATTA, xmlStr.length());
|
||||||
|
xmlStr = xmlGenerator.getBoatsAsXml();
|
||||||
|
MessageFactory.boats = new XMLMessage(xmlStr, XMLMessageSubType.BOAT, xmlStr.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void updateBoats(List<ServerYacht> yachts) {
|
||||||
|
// for (ServerYacht serverYacht : yachts) {
|
||||||
|
// System.out.println(serverYacht);
|
||||||
|
// }
|
||||||
|
xmlGenerator.getRace().setBoats(yachts);
|
||||||
|
String xmlStr = xmlGenerator.getBoatsAsXml();
|
||||||
|
MessageFactory.boats = new XMLMessage(xmlStr, XMLMessageSubType.BOAT, xmlStr.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void updateTokens(List<Token> tokens) {
|
||||||
|
xmlGenerator.getRace().setTokens(tokens);
|
||||||
|
String xmlStr = xmlGenerator.getRaceAsXml();
|
||||||
|
MessageFactory.race = new XMLMessage(xmlStr, XMLMessageSubType.RACE, xmlStr.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static RaceStartStatusMessage getRaceStartStatusMessage() {
|
||||||
|
return new RaceStartStatusMessage(
|
||||||
|
1,
|
||||||
|
GameState.getStartTime(),
|
||||||
|
1,
|
||||||
|
RaceStartNotificationType.SET_RACE_START_TIME);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RaceStatusMessage getRaceStatusMessage() {
|
||||||
|
// variables taken from GameServerThread
|
||||||
|
|
||||||
|
List<BoatSubMessage> boatSubMessages = new ArrayList<>();
|
||||||
|
RaceStatus raceStatus;
|
||||||
|
|
||||||
|
for (Player player : GameState.getPlayers()) {
|
||||||
|
ServerYacht y = player.getYacht();
|
||||||
|
BoatSubMessage m = new BoatSubMessage(y.getSourceId(), y.getBoatStatus(),
|
||||||
|
y.getLegNumber(),
|
||||||
|
0, 0, 1234L,
|
||||||
|
1234L);
|
||||||
|
boatSubMessages.add(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
long timeTillStart = System.currentTimeMillis() - GameState.getStartTime();
|
||||||
|
|
||||||
|
if (GameState.getCurrentStage() == GameStages.LOBBYING) {
|
||||||
|
raceStatus = RaceStatus.PRESTART;
|
||||||
|
} else if (GameState.getCurrentStage() == GameStages.PRE_RACE) {
|
||||||
|
raceStatus = RaceStatus.PRESTART;
|
||||||
|
|
||||||
|
if (timeTillStart > GameState.WARNING_TIME) {
|
||||||
|
raceStatus = RaceStatus.WARNING;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timeTillStart > GameState.PREPATORY_TIME) {
|
||||||
|
raceStatus = RaceStatus.PREPARATORY;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
raceStatus = RaceStatus.STARTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new RaceStatusMessage(1, raceStatus, GameState.getStartTime(),
|
||||||
|
GameState.getWindDirection(),
|
||||||
|
GameState.getWindSpeedMMS().longValue(), GameState.getPlayers().size(),
|
||||||
|
RaceType.MATCH_RACE, 1, boatSubMessages);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BoatLocationMessage getBoatLocationMessage(ServerYacht yacht) {
|
||||||
|
return new BoatLocationMessage(
|
||||||
|
yacht.getSourceId(),
|
||||||
|
0, // TODO: 29/08/17 wmu16 - Work out what to do with seqNo. Currently not used
|
||||||
|
yacht.getLocation().getLat(),
|
||||||
|
yacht.getLocation().getLng(),
|
||||||
|
yacht.getHeading(),
|
||||||
|
yacht.getCurrentVelocity().longValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static XMLMessage getRaceXML() {
|
||||||
|
return race;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static XMLMessage getRegattaXML() {
|
||||||
|
return regatta;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static XMLMessage getBoatXML() {
|
||||||
|
return boats;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static YachtEventCodeMessage makeCollisionMessage(ServerYacht serverYacht) {
|
||||||
|
return new YachtEventCodeMessage(serverYacht.getSourceId(), YachtEventType.COLLISION);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a message to be sent out whenever a yacht picks up a boost
|
||||||
|
*
|
||||||
|
* @param serverYacht The yacht that has picked up a power up
|
||||||
|
* @param token The token which they picked up
|
||||||
|
* @return The corresponding YachtEventCodeMessage
|
||||||
|
*/
|
||||||
|
public static YachtEventCodeMessage makePickupMessage(ServerYacht serverYacht, Token token) {
|
||||||
|
YachtEventType yachtEventType = null;
|
||||||
|
switch (token.getTokenType()) {
|
||||||
|
case BOOST:
|
||||||
|
yachtEventType = YachtEventType.TOKEN_VELOCITY;
|
||||||
|
break;
|
||||||
|
case HANDLING:
|
||||||
|
yachtEventType = YachtEventType.TOKEN_HANDLING;
|
||||||
|
break;
|
||||||
|
case WIND_WALKER:
|
||||||
|
yachtEventType = YachtEventType.TOKEN_WIND_WALKER;
|
||||||
|
break;
|
||||||
|
case BUMPER:
|
||||||
|
yachtEventType = YachtEventType.TOKEN_BUMPER;
|
||||||
|
break;
|
||||||
|
case RANDOM:
|
||||||
|
yachtEventType = YachtEventType.TOKEN_RANDOM;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return new YachtEventCodeMessage(serverYacht.getSourceId(), yachtEventType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a message representing a certain buff / debuff for a given yacht. For now this is
|
||||||
|
* just for the bumper debuff so the affected boat is aware that it has been crashed. This could
|
||||||
|
* however be extended to render affects for all boats given a certain debuff.
|
||||||
|
*
|
||||||
|
* @param yacht The yacht affected by some status
|
||||||
|
* @param token The token indicating what status they have
|
||||||
|
* @return A YachtEventCodeMessage
|
||||||
|
*/
|
||||||
|
public static YachtEventCodeMessage makeStatusEffectMessage(ServerYacht yacht,
|
||||||
|
TokenType token) {
|
||||||
|
YachtEventType yachtEventType = null;
|
||||||
|
switch (token) {
|
||||||
|
case BUMPER:
|
||||||
|
yachtEventType = YachtEventType.BUMPER_CRASH;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return new YachtEventCodeMessage(yacht.getSourceId(), yachtEventType);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a message to be sent out when a given yacht powers down (From a boost of any type)
|
||||||
|
*
|
||||||
|
* @param yacht The yacht that is powering down
|
||||||
|
* @return A YachtEventCodeMessage representing this action
|
||||||
|
*/
|
||||||
|
public static YachtEventCodeMessage makePowerDownMessage(ServerYacht yacht) {
|
||||||
|
return new YachtEventCodeMessage(yacht.getSourceId(), YachtEventType.POWER_DOWN);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ChatterMessage makeChatterMessage(Integer messageType, String message) {
|
||||||
|
return new ChatterMessage(messageType, "SERVER: " + message);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,185 @@
|
|||||||
|
package seng302.gameServer;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import seng302.discoveryServer.DiscoveryServerClient;
|
||||||
|
import seng302.discoveryServer.util.ServerListing;
|
||||||
|
|
||||||
|
import javax.jmdns.JmDNS;
|
||||||
|
import javax.jmdns.ServiceInfo;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.Inet4Address;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.NetworkInterface;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
import java.util.Hashtable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Advertises the game server on the local network
|
||||||
|
*/
|
||||||
|
public class ServerAdvertiser {
|
||||||
|
/*
|
||||||
|
Our service name & protocol
|
||||||
|
|
||||||
|
This must be in the format _Service._Proto.Name as per http://www.ietf.org/rfc/rfc2782.txt
|
||||||
|
Where Service is unique on the network, and protocol is usually _tcp.
|
||||||
|
|
||||||
|
The pseudo-domain 'local.' must end in a full-stop. This is used to indicate that
|
||||||
|
the lookup should be performed using an IP multicast query on the local IP network.
|
||||||
|
|
||||||
|
Read this before changing any of the following values
|
||||||
|
https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/NetServices/Articles/domainnames.html#//apple_ref/doc/uid/TP40002460-SW1
|
||||||
|
*/
|
||||||
|
private static String SERVICE = "_partyatsea";
|
||||||
|
private static String PROTOCOL = "_tcp";
|
||||||
|
public static String SERVICE_TYPE = SERVICE + "." + PROTOCOL + ".local.";
|
||||||
|
|
||||||
|
private static ServerAdvertiser instance = null;
|
||||||
|
private static JmDNS jmdnsInstance = null;
|
||||||
|
private ServiceInfo serviceInfo; // Note: Whenever this is changed, our service will be re-registered on the network.
|
||||||
|
private DiscoveryServerClient repositoryClient;
|
||||||
|
|
||||||
|
private Hashtable<String ,String> props;
|
||||||
|
private Logger logger = LoggerFactory.getLogger(ServerAdvertiser.class);
|
||||||
|
|
||||||
|
private ServerAdvertiser() throws IOException{
|
||||||
|
jmdnsInstance = JmDNS.create(InetAddress.getByName(getLocalHostIp()));
|
||||||
|
|
||||||
|
repositoryClient = new DiscoveryServerClient();
|
||||||
|
|
||||||
|
props = new Hashtable<>();
|
||||||
|
props.put("map", "");
|
||||||
|
props.put("spacesLeft", "0");
|
||||||
|
props.put("capacity", "0");
|
||||||
|
props.put("players", "0");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an instance of the ServerAdvertiser, create an instance if there isn't already one
|
||||||
|
* @return A ServerAdvertiser Instance
|
||||||
|
* @throws IOException If there was an exception creating the instance
|
||||||
|
*/
|
||||||
|
public static ServerAdvertiser getInstance() throws IOException {
|
||||||
|
if (instance == null){
|
||||||
|
instance = new ServerAdvertiser();
|
||||||
|
}
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the map name and broadcast an update on the network
|
||||||
|
* @param mapName The new map name
|
||||||
|
* @return The current ServerAdvertiser instance
|
||||||
|
*/
|
||||||
|
public ServerAdvertiser setMapName(String mapName){
|
||||||
|
props.replace("map", mapName);
|
||||||
|
|
||||||
|
if (serviceInfo != null){
|
||||||
|
serviceInfo.setText(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the number of players on the server and broadcast an update on the network
|
||||||
|
* @param numPlayers The number of players on the server
|
||||||
|
* @return The current ServerAdvertiser instance
|
||||||
|
*/
|
||||||
|
public ServerAdvertiser setNumberOfPlayers(Integer numPlayers){
|
||||||
|
props.replace("players", numPlayers.toString());
|
||||||
|
|
||||||
|
if (serviceInfo != null){
|
||||||
|
serviceInfo.setText(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the max capacity of the server and broadcast an update on the network
|
||||||
|
* @param capacity The maximum capacity of the server
|
||||||
|
* @return The current ServerAdvertiser instance
|
||||||
|
*/
|
||||||
|
public ServerAdvertiser setCapacity(Integer capacity){
|
||||||
|
props.replace("capacity", capacity.toString());
|
||||||
|
|
||||||
|
if (serviceInfo != null){
|
||||||
|
serviceInfo.setText(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register this service on the network
|
||||||
|
*
|
||||||
|
* Note: other parameters (map name/spaces left etc) are set after the
|
||||||
|
* service has been registered
|
||||||
|
* @param portNo The servers port number
|
||||||
|
* @param serverName The servers name
|
||||||
|
*/
|
||||||
|
public void registerGame(Integer portNo, String serverName) {
|
||||||
|
|
||||||
|
serviceInfo = ServiceInfo.create(SERVICE_TYPE, serverName, portNo, 0, 0, props);
|
||||||
|
|
||||||
|
new java.util.Timer().schedule(
|
||||||
|
new java.util.TimerTask() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
jmdnsInstance.registerService(serviceInfo);
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.warn("Failed to register service info");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
ServerListing serverListing = new ServerListing(serverName, props.get("map"), new DiscoveryServerClient().getInetIp(), portNo, Integer.parseInt(props.get("capacity")));
|
||||||
|
repositoryClient.register(serverListing);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unregister the service
|
||||||
|
*/
|
||||||
|
public void unregister(){
|
||||||
|
if (serviceInfo != null)
|
||||||
|
jmdnsInstance.unregisterService(serviceInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the local host ip address.
|
||||||
|
*
|
||||||
|
* @return the localhost ip address
|
||||||
|
*/
|
||||||
|
public static String getLocalHostIp() {
|
||||||
|
String ipAddress = null;
|
||||||
|
try {
|
||||||
|
Enumeration<NetworkInterface> e = NetworkInterface.getNetworkInterfaces();
|
||||||
|
while (e.hasMoreElements()) {
|
||||||
|
NetworkInterface ni = e.nextElement();
|
||||||
|
if (ni.isLoopback())
|
||||||
|
continue;
|
||||||
|
if(ni.isPointToPoint())
|
||||||
|
continue;
|
||||||
|
if(ni.isVirtual())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
Enumeration<InetAddress> addresses = ni.getInetAddresses();
|
||||||
|
while(addresses.hasMoreElements()) {
|
||||||
|
InetAddress address = addresses.nextElement();
|
||||||
|
if(address instanceof Inet4Address) { // skip all ipv6
|
||||||
|
ipAddress = address.getHostAddress();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
if (ipAddress == null) {
|
||||||
|
System.out.println("[HOST] Cannot obtain local host ip address.");
|
||||||
|
}
|
||||||
|
return ipAddress;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
package seng302.gameServer;
|
||||||
|
|
||||||
|
public class ServerDescription {
|
||||||
|
private Integer capacity;
|
||||||
|
private String address;
|
||||||
|
private Integer portNum;
|
||||||
|
private String serverName;
|
||||||
|
private String mapName;
|
||||||
|
private Integer numPlayers;
|
||||||
|
private Long lastUpdated;
|
||||||
|
private Long lastRefreshed;
|
||||||
|
|
||||||
|
private static Long EXPIRY_INTERVAL = 5000L;
|
||||||
|
|
||||||
|
public ServerDescription(String serverName, String mapName, Integer numPlayers, Integer capacity, String address, Integer portNum){
|
||||||
|
this.serverName = serverName;
|
||||||
|
this.mapName = mapName;
|
||||||
|
this.numPlayers = numPlayers;
|
||||||
|
this.address = address;
|
||||||
|
this.portNum = portNum;
|
||||||
|
this.capacity = capacity;
|
||||||
|
lastUpdated = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return serverName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMapName() {
|
||||||
|
return mapName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer portNumber() {
|
||||||
|
return portNum;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAddress(){
|
||||||
|
return address;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getNumPlayers() {
|
||||||
|
return numPlayers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getCapacity(){
|
||||||
|
return capacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (obj == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!ServerDescription.class.isAssignableFrom(obj.getClass())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final ServerDescription other = (ServerDescription) obj;
|
||||||
|
|
||||||
|
if (!this.getAddress().equals(other.getAddress()) ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.portNumber().equals(other.portNumber())){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.getMapName().equals(other.getMapName())){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.getName().equals(other.getName())){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.getCapacity().equals(other.getCapacity())){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return this.getName().hashCode() + this.getAddress().hashCode() +
|
||||||
|
this.portNumber().hashCode() + this.getMapName().hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean hasExpired(){
|
||||||
|
return System.currentTimeMillis() - lastUpdated > EXPIRY_INTERVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean serverShouldBeRemoved() {
|
||||||
|
if (lastRefreshed == null) return false;
|
||||||
|
return System.currentTimeMillis() - lastRefreshed > EXPIRY_INTERVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void hasBeenRefreshed(){
|
||||||
|
lastRefreshed = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
+72
-77
@@ -1,12 +1,7 @@
|
|||||||
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 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;
|
||||||
@@ -34,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
|
||||||
@@ -41,10 +37,10 @@ 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() / 1000L;
|
time = System.currentTimeMillis();
|
||||||
this.sourceId = sourceId;
|
this.sourceId = sourceId;
|
||||||
this.sequenceNum = sequenceNum;
|
this.sequenceNum = sequenceNum;
|
||||||
this.deviceType = DeviceType.RACING_YACHT;
|
this.deviceType = DeviceType.RACING_YACHT;
|
||||||
@@ -56,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;
|
||||||
@@ -67,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);
|
||||||
@@ -161,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
|
||||||
+26
-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,10 @@ public abstract class Message {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return The current buffer
|
* @return The current buffer as a byte array
|
||||||
*/
|
*/
|
||||||
public ByteBuffer getBuffer(){
|
public byte[] getBuffer(){
|
||||||
return buffer;
|
return buffer.array();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -178,7 +172,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 +187,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,389 @@
|
|||||||
|
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.beans.value.ObservableObjectValue;
|
||||||
|
import javafx.collections.FXCollections;
|
||||||
|
import javafx.scene.paint.Color;
|
||||||
|
import jdk.nashorn.internal.objects.annotations.Function;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import seng302.model.token.TokenType;
|
||||||
|
import seng302.visualiser.fxObjects.assets_3D.BoatMeshType;
|
||||||
|
import seng302.model.token.TokenType;
|
||||||
|
import seng302.visualiser.fxObjects.assets_3D.BoatObject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Yacht class for the racing boat. <p> Class created to store more variables (eg. boat statuses)
|
||||||
|
* 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;
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,215 @@
|
|||||||
|
package seng302.model;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A static class for parsing and storing the polars. Will parse the whole polar table and also store the optimised
|
||||||
|
* upwind and downwind in separate tables here as well
|
||||||
|
* Created by wmu16 on 22/05/17.
|
||||||
|
*/
|
||||||
|
public final class PolarTable {
|
||||||
|
|
||||||
|
//A Polar table will consist of a wind speed key to a hashmap value of pairs of wind angles and boat speeds
|
||||||
|
private static HashMap<Double, HashMap<Double, Double>> polarTable;
|
||||||
|
private static HashMap<Double, HashMap<Double, Double>> upwindOptimal;
|
||||||
|
private static HashMap<Double, HashMap<Double, Double>> downwindOptimal;
|
||||||
|
private static Double optimalAngle;
|
||||||
|
|
||||||
|
private static int upTwaIndex;
|
||||||
|
private static int dnTwaIndex;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Iterates through each row of the polar table, in pairs, to extract the row into a hashmap of angle to boat speed.
|
||||||
|
* These angle boatspeed hashmaps are then added to an outer hashmap at the end of wind speed key to each row hashmap
|
||||||
|
* as a value
|
||||||
|
* @param polarFile polarFile to be parsed
|
||||||
|
*/
|
||||||
|
public static void parsePolarFile(InputStream polarFile) {
|
||||||
|
polarTable = new HashMap<>();
|
||||||
|
upwindOptimal = new HashMap<>();
|
||||||
|
downwindOptimal = new HashMap<>();
|
||||||
|
|
||||||
|
String line = null;
|
||||||
|
String check;
|
||||||
|
Boolean isHeaderLine = true;
|
||||||
|
|
||||||
|
try (BufferedReader br = new BufferedReader(new InputStreamReader(polarFile))) {
|
||||||
|
while ((check = br.readLine()) != null) {
|
||||||
|
line = check;
|
||||||
|
String[] thisLine = line.split(",");
|
||||||
|
|
||||||
|
//Initial line in file
|
||||||
|
if (isHeaderLine) {
|
||||||
|
deduceHeaders(thisLine);
|
||||||
|
isHeaderLine = false;
|
||||||
|
} else {
|
||||||
|
HashMap<Double, Double> thisPolar = new HashMap<>();
|
||||||
|
HashMap<Double, Double> thisUpWindPolar = new HashMap<>();
|
||||||
|
HashMap<Double, Double> thisDnWindPolar = new HashMap<>();
|
||||||
|
Double thisWindSpeed = Double.parseDouble(thisLine[0]);
|
||||||
|
|
||||||
|
// -3 <== -1 for length -1, and a further -2 as we iterate in pairs of 2 so finish before final 2
|
||||||
|
for (int i = 1; i < thisLine.length; i += 2) {
|
||||||
|
Double thisWindAngle = Double.parseDouble(thisLine[i]);
|
||||||
|
Double thisBoatSpeed = Double.parseDouble(thisLine[i + 1]);
|
||||||
|
thisPolar.put(thisWindAngle, thisBoatSpeed);
|
||||||
|
if (i == upTwaIndex) {
|
||||||
|
thisUpWindPolar.put(thisWindAngle, thisBoatSpeed);
|
||||||
|
} else if (i == dnTwaIndex) {
|
||||||
|
thisDnWindPolar.put(thisWindAngle, thisBoatSpeed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
polarTable.put(thisWindSpeed, thisPolar);
|
||||||
|
upwindOptimal.put(thisWindSpeed, thisUpWindPolar);
|
||||||
|
downwindOptimal.put(thisWindSpeed, thisDnWindPolar);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
getMaxSpeedAngle(line);
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
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
|
||||||
|
* @param thisLine The line which is the header of a polar file
|
||||||
|
*/
|
||||||
|
private static void deduceHeaders(String[] thisLine) {
|
||||||
|
|
||||||
|
for (int i = 0; i < thisLine.length; i++) {
|
||||||
|
String thisItem = thisLine[i];
|
||||||
|
if (thisItem.toLowerCase().startsWith("uptwa")) {
|
||||||
|
upTwaIndex = i;
|
||||||
|
} else if (thisItem.toLowerCase().startsWith("dntwa")) {
|
||||||
|
dnTwaIndex = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static Double getOptimalAngle() {
|
||||||
|
return optimalAngle;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The entire polar table
|
||||||
|
*/
|
||||||
|
public static HashMap<Double, HashMap<Double, Double>> getPolarTable() {
|
||||||
|
return polarTable;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The polar table just containing the optimal upwind values
|
||||||
|
*/
|
||||||
|
public static HashMap<Double, HashMap<Double, Double>> getUpwindOptimal() {
|
||||||
|
return upwindOptimal;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The polar table just containing the optimal downwind values
|
||||||
|
*/
|
||||||
|
public static HashMap<Double, HashMap<Double, Double>> getDownwindOptimal() {
|
||||||
|
return downwindOptimal;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will raise an exception if a polar table has just one row of data
|
||||||
|
* @param thisWindSpeed The current wind speed
|
||||||
|
* @return HashMap containing just the optimal upwind angle and resulting boat speed
|
||||||
|
*/
|
||||||
|
public static HashMap<Double, Double> getOptimalUpwindVMG(Double thisWindSpeed) {
|
||||||
|
|
||||||
|
Double polarWindSpeed = getClosestWindSpeedInPolar(thisWindSpeed);
|
||||||
|
return upwindOptimal.get(polarWindSpeed);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will raise an exception if a polar table has just one row of data
|
||||||
|
* @param thisWindSpeed The current wind speed
|
||||||
|
* @return HashMap containing just the optimal downwind angle and resulting boat speed
|
||||||
|
*/
|
||||||
|
public static HashMap<Double, Double> getOptimalDownwindVMG(Double thisWindSpeed) {
|
||||||
|
|
||||||
|
Double polarWindSpeed = getClosestWindSpeedInPolar(thisWindSpeed);
|
||||||
|
return downwindOptimal.get(polarWindSpeed);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static Double getBoatSpeed(Double thisWindSpeed, Double thisHeading) {
|
||||||
|
|
||||||
|
Double polarWindSpeed = getClosestWindSpeedInPolar(thisWindSpeed);
|
||||||
|
Double polarAngle = getClosestAngleInPolar(polarTable.get(polarWindSpeed), thisHeading);
|
||||||
|
|
||||||
|
return polarTable.get(polarWindSpeed).get(polarAngle);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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,137 @@
|
|||||||
|
package seng302.model;
|
||||||
|
|
||||||
|
import java.text.DateFormat;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.TimeZone;
|
||||||
|
import javafx.beans.property.ReadOnlyDoubleProperty;
|
||||||
|
import javafx.beans.property.ReadOnlyDoubleWrapper;
|
||||||
|
import javafx.collections.FXCollections;
|
||||||
|
import javafx.collections.ObservableList;
|
||||||
|
import seng302.model.stream.parser.RaceStartData;
|
||||||
|
import seng302.model.stream.parser.RaceStatusData;
|
||||||
|
import seng302.utilities.Sounds;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class for storing race data that does not relate to specific vessels or marks such as time or wind.
|
||||||
|
* Calculates the state of critical race attributes when relevant data is added.
|
||||||
|
*/
|
||||||
|
public class RaceState {
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface CollisionListener {
|
||||||
|
void notifyCollision(GeoPoint location);
|
||||||
|
}
|
||||||
|
|
||||||
|
// private final DateFormat DATE_TIME_FORMAT = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
|
||||||
|
private final DateFormat DATE_TIME_FORMAT = new SimpleDateFormat("HH:mm:ss");
|
||||||
|
private ReadOnlyDoubleWrapper windSpeed = new ReadOnlyDoubleWrapper();
|
||||||
|
private ReadOnlyDoubleWrapper windDirection = new ReadOnlyDoubleWrapper();
|
||||||
|
private long serverSystemTime;
|
||||||
|
private long expectedStartTime;
|
||||||
|
private boolean raceRunning = false;
|
||||||
|
private boolean gunFired = false;
|
||||||
|
private boolean raceFinished = false;
|
||||||
|
long timeTillStart;
|
||||||
|
private ObservableList<ClientYacht> playerPositions;
|
||||||
|
private List<ClientYacht> collisions = new ArrayList<>();
|
||||||
|
private List<CollisionListener> collisionListeners = new ArrayList<>();
|
||||||
|
|
||||||
|
public RaceState() {
|
||||||
|
playerPositions = FXCollections.observableArrayList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateState (RaceStatusData data) {
|
||||||
|
this.windSpeed.set(data.getWindSpeed());
|
||||||
|
this.windDirection.set(data.getWindDirection());
|
||||||
|
this.serverSystemTime = data.getCurrentTime();
|
||||||
|
this.expectedStartTime = data.getExpectedStartTime();
|
||||||
|
this.raceRunning = data.isRaceStarted();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTimeZone (TimeZone timeZone) {
|
||||||
|
DATE_TIME_FORMAT.setTimeZone(timeZone);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateState (RaceStartData data) {
|
||||||
|
this.timeTillStart = data.getRaceStartTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRaceTimeStr () {
|
||||||
|
long raceTime = serverSystemTime - expectedStartTime;
|
||||||
|
if (raceTime < 0) {
|
||||||
|
return "-" + DATE_TIME_FORMAT.format(-1 * (raceTime - 1000));
|
||||||
|
} else {
|
||||||
|
if (!gunFired) {
|
||||||
|
gunFired = true;
|
||||||
|
Sounds.playCapGunSound();
|
||||||
|
}
|
||||||
|
return DATE_TIME_FORMAT.format(serverSystemTime - expectedStartTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getTimeTillStart () {
|
||||||
|
return (expectedStartTime - serverSystemTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getWindSpeed() {
|
||||||
|
return windSpeed.doubleValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReadOnlyDoubleProperty windSpeedProperty() {
|
||||||
|
return windSpeed.getReadOnlyProperty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReadOnlyDoubleProperty windDirectionProperty() {
|
||||||
|
return windDirection.getReadOnlyProperty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getRaceTime() {
|
||||||
|
return serverSystemTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isRaceStarted () {
|
||||||
|
return raceRunning;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRaceStarted(Boolean value) {
|
||||||
|
this.raceRunning = value;
|
||||||
|
}
|
||||||
|
public void setBoats(Collection<ClientYacht> clientYachts) {
|
||||||
|
playerPositions.setAll(clientYachts);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sortPlayers() {
|
||||||
|
playerPositions.sort((yacht1, yacht2) -> Integer.compare(yacht2.getLegNumber(),
|
||||||
|
yacht1.getLegNumber()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObservableList<ClientYacht> getPlayerPositions() {
|
||||||
|
return playerPositions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void storeCollision(ClientYacht yacht) {
|
||||||
|
collisions.add(yacht);
|
||||||
|
for (CollisionListener collisionListener : collisionListeners) {
|
||||||
|
collisionListener.notifyCollision(yacht.getLocation());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addCollisionListener(CollisionListener collisionListener) {
|
||||||
|
collisionListeners.add(collisionListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeCollisionListener(CollisionListener collisionListener) {
|
||||||
|
collisionListeners.remove(collisionListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRaceFinished() {
|
||||||
|
raceFinished = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getRaceFinished() {
|
||||||
|
return raceFinished;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
package seng302.model;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
import javafx.geometry.Point2D;
|
||||||
|
import seng302.utilities.GeoUtility;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contains information on a scaled lat lon point for use with mapping geographical elements to a 2d plane.
|
||||||
|
*/
|
||||||
|
public class ScaledPoint extends GeoPoint {
|
||||||
|
|
||||||
|
public enum ScaleDirection {
|
||||||
|
HORIZONTAL,
|
||||||
|
VERTICAL
|
||||||
|
}
|
||||||
|
|
||||||
|
private double x, y, scaleFactor;
|
||||||
|
private ScaleDirection scaleDirection;
|
||||||
|
|
||||||
|
private ScaledPoint(double lat, double lng, double x, double y, double scaleFactor, ScaleDirection direction) {
|
||||||
|
super(lat, lng);
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
this.scaleFactor = scaleFactor;
|
||||||
|
this.scaleDirection = direction;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getX() {
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getY() {
|
||||||
|
return y;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getScaleFactor() {
|
||||||
|
return scaleFactor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ScaleDirection getScaleDirection() {
|
||||||
|
return scaleDirection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Point2D findScaledXY(GeoPoint unscaled) {
|
||||||
|
return findScaledXY(unscaled.getLat(), unscaled.getLng());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Point2D findScaledXY(double unscaledLat, double unscaledLon) {
|
||||||
|
double distanceFromReference;
|
||||||
|
double angleFromReference;
|
||||||
|
double xReference = this.getX();
|
||||||
|
double yReference = this.getY();
|
||||||
|
|
||||||
|
angleFromReference = GeoUtility.getBearingRad(
|
||||||
|
this, new GeoPoint(unscaledLat, unscaledLon)
|
||||||
|
);
|
||||||
|
distanceFromReference = GeoUtility.getDistance(
|
||||||
|
this, new GeoPoint(unscaledLat, unscaledLon)
|
||||||
|
);
|
||||||
|
if (angleFromReference >= 0 && angleFromReference <= Math.PI / 2) {
|
||||||
|
xReference += scaleFactor * Math.sin(angleFromReference) * distanceFromReference;
|
||||||
|
yReference -= scaleFactor * Math.cos(angleFromReference) * distanceFromReference;
|
||||||
|
} else if (angleFromReference >= 0) {
|
||||||
|
angleFromReference = angleFromReference - Math.PI / 2;
|
||||||
|
xReference += scaleFactor * Math.cos(angleFromReference) * distanceFromReference;
|
||||||
|
yReference += scaleFactor * Math.sin(angleFromReference) * distanceFromReference;
|
||||||
|
} else if (angleFromReference < 0 && angleFromReference >= -Math.PI / 2) {
|
||||||
|
angleFromReference = Math.abs(angleFromReference);
|
||||||
|
xReference -= scaleFactor * Math.sin(angleFromReference) * distanceFromReference;
|
||||||
|
yReference -= scaleFactor * Math.cos(angleFromReference) * distanceFromReference;
|
||||||
|
} else {
|
||||||
|
angleFromReference = Math.abs(angleFromReference) - Math.PI / 2;
|
||||||
|
xReference -= scaleFactor * Math.cos(angleFromReference) * distanceFromReference;
|
||||||
|
yReference += scaleFactor * Math.sin(angleFromReference) * distanceFromReference;
|
||||||
|
}
|
||||||
|
return new Point2D(xReference, yReference);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ScaledPoint makeScaledPoint(double width, double height,
|
||||||
|
List<? extends GeoPoint> points, boolean centered) {
|
||||||
|
|
||||||
|
double referencePointX, referencePointY, scaleFactor, lat, lng;
|
||||||
|
ScaleDirection scaleDirection;
|
||||||
|
points = new ArrayList<>(points);
|
||||||
|
points.sort(Comparator.comparingDouble(GeoPoint::getLat));
|
||||||
|
GeoPoint minLatPoint = points.get(0);
|
||||||
|
GeoPoint maxLatPoint = points.get(points.size() - 1);
|
||||||
|
|
||||||
|
points.sort(Comparator.comparingDouble(GeoPoint::getLng));
|
||||||
|
GeoPoint minLonPoint = points.get(0);
|
||||||
|
GeoPoint maxLonPoint = points.get(points.size() - 1);
|
||||||
|
|
||||||
|
referencePointX = centered ? 0 : width / 2;
|
||||||
|
referencePointY = centered ? 0 : height / 2;
|
||||||
|
|
||||||
|
lat = (maxLatPoint.getLat() - minLatPoint.getLat()) / 2 + minLatPoint.getLat();
|
||||||
|
lng = (maxLonPoint.getLng() - minLonPoint.getLng()) / 2 + minLonPoint.getLng();
|
||||||
|
|
||||||
|
GeoPoint ref = new GeoPoint(lat, lng);
|
||||||
|
|
||||||
|
double vertDistance = GeoUtility.getDistance(
|
||||||
|
ref, new GeoPoint(ref.getLat(), maxLonPoint.getLng())
|
||||||
|
) * 2.1;
|
||||||
|
|
||||||
|
double horiDistance = GeoUtility.getDistance(
|
||||||
|
ref, new GeoPoint(maxLatPoint.getLat(), ref.getLng())
|
||||||
|
) * 2.1;
|
||||||
|
|
||||||
|
double vertScale = height / vertDistance;
|
||||||
|
|
||||||
|
if (horiDistance * vertScale > width) {
|
||||||
|
scaleFactor = width / horiDistance;
|
||||||
|
scaleDirection = ScaleDirection.HORIZONTAL;
|
||||||
|
} else {
|
||||||
|
scaleFactor = vertScale;
|
||||||
|
scaleDirection = ScaleDirection.VERTICAL;
|
||||||
|
}
|
||||||
|
return new ScaledPoint(lat, lng, referencePointX, referencePointY, scaleFactor, scaleDirection);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,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,130 @@
|
|||||||
|
package seng302.model.mark;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import seng302.gameServer.messages.RoundingSide;
|
||||||
|
import seng302.model.GeoPoint;
|
||||||
|
import seng302.utilities.GeoUtility;
|
||||||
|
|
||||||
|
public class CompoundMark {
|
||||||
|
|
||||||
|
private int compoundMarkId;
|
||||||
|
private String name;
|
||||||
|
private List<Mark> marks;
|
||||||
|
private GeoPoint midPoint;
|
||||||
|
|
||||||
|
public CompoundMark(int markID, String name, List<Mark> marks) {
|
||||||
|
this.compoundMarkId = markID;
|
||||||
|
this.name = name;
|
||||||
|
this.marks = Collections.unmodifiableList(marks);
|
||||||
|
if (marks.size() > 1) {
|
||||||
|
this.midPoint = GeoUtility.getDirtyMidPoint(marks.get(0), marks.get(1));
|
||||||
|
} else {
|
||||||
|
this.midPoint = marks.get(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prints out compoundMark's info and its marks, good for testing
|
||||||
|
* @return a string showing its details
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String toString(){
|
||||||
|
String info = String.format(
|
||||||
|
"CompoundMark: %d (%s), [%s", compoundMarkId, name, marks.get(0).toString()
|
||||||
|
);
|
||||||
|
if (marks.size() > 1) {
|
||||||
|
info += String.format(", %s", marks.get(1).toString());
|
||||||
|
}
|
||||||
|
return info + "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getId() {
|
||||||
|
return compoundMarkId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId (int markID) {
|
||||||
|
this.compoundMarkId = markID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRoundingSide(RoundingSide roundingSide) {;
|
||||||
|
switch (roundingSide) {
|
||||||
|
case SP:
|
||||||
|
getSubMark(1).setRoundingSide(RoundingSide.STARBOARD);
|
||||||
|
getSubMark(2).setRoundingSide(RoundingSide.PORT);
|
||||||
|
break;
|
||||||
|
case PS:
|
||||||
|
getSubMark(1).setRoundingSide(RoundingSide.PORT);
|
||||||
|
getSubMark(2).setRoundingSide(RoundingSide.STARBOARD);
|
||||||
|
break;
|
||||||
|
case PORT:
|
||||||
|
getSubMark(1).setRoundingSide(RoundingSide.PORT);
|
||||||
|
break;
|
||||||
|
case STARBOARD:
|
||||||
|
getSubMark(1).setRoundingSide(RoundingSide.STARBOARD);
|
||||||
|
break;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the mark contained in the compound mark. Marks are numbered 1 to n;
|
||||||
|
* @param singleMarkId the id of the desired mark contained in this compound mark.
|
||||||
|
* @return the desired mark. Returns null if the ID is not in range (1, NUM_MARKS)
|
||||||
|
*/
|
||||||
|
public Mark getSubMark(int singleMarkId) {
|
||||||
|
try {
|
||||||
|
return marks.get(singleMarkId - 1);
|
||||||
|
} catch (IndexOutOfBoundsException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NOTE: This is a 'dirty' mid point as it is simply calculated as an xy point would be.
|
||||||
|
* NO CHECKING FOR LAT / LNG WRAPPING IS DONE IN CREATION OF THIS MIDPOINT
|
||||||
|
*
|
||||||
|
* @return GeoPoint of the midpoint of the two marks, or the one mark if there is only one
|
||||||
|
*/
|
||||||
|
public GeoPoint getMidPoint() {
|
||||||
|
return midPoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether or not this CompoundMark is a Gate. It is generally cleaner to program to a
|
||||||
|
* specific singleMark or the list of marks.
|
||||||
|
*
|
||||||
|
* @return True if the compound mark is a gate, false otherwise.
|
||||||
|
*/
|
||||||
|
public boolean isGate () {
|
||||||
|
return marks.size() > 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the list of marks in the compoundMark
|
||||||
|
*
|
||||||
|
* @return All marks contained in this mark.
|
||||||
|
*/
|
||||||
|
public List<Mark> getMarks () {
|
||||||
|
return marks;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
int hash = 0;
|
||||||
|
for (Mark mark : marks) {
|
||||||
|
hash += Double.hashCode(mark.getSourceID()) + Double.hashCode(mark.getLat())
|
||||||
|
+ Double.hashCode(mark.getLng()) + mark.getName().hashCode();
|
||||||
|
}
|
||||||
|
return hash + getName().hashCode() + Integer.hashCode(getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package seng302.model.mark;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores the data for the cornering of a mark.
|
||||||
|
*/
|
||||||
|
public class Corner {
|
||||||
|
|
||||||
|
private Integer seqID;
|
||||||
|
private Integer compoundMarkID;
|
||||||
|
private String rounding;
|
||||||
|
private Integer zoneSize;
|
||||||
|
|
||||||
|
public Corner(Integer seqID, Integer compoundMarkID, String rounding, Integer zoneSize) {
|
||||||
|
this.seqID = seqID;
|
||||||
|
this.compoundMarkID = compoundMarkID;
|
||||||
|
this.rounding = rounding;
|
||||||
|
this.zoneSize = zoneSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getSeqID() {
|
||||||
|
return seqID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getCompoundMarkID() {
|
||||||
|
return compoundMarkID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRounding() {
|
||||||
|
return rounding;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getZoneSize() {
|
||||||
|
return zoneSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Corner = {seqID=" + seqID + ", compoundMarkID=" + compoundMarkID + ", rounding="
|
||||||
|
+ rounding +", zoneSize=" + zoneSize + "}";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
package seng302.model.mark;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import seng302.gameServer.messages.RoundingSide;
|
||||||
|
import seng302.model.GeoPoint;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An abstract class to represent general marks
|
||||||
|
* Created by Haoming Yin (hyi25) on 17/3/17.
|
||||||
|
*/
|
||||||
|
public class Mark extends GeoPoint {
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface PositionListener {
|
||||||
|
void notifyPositionChange(Mark mark, double lat, double lon);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int seqID;
|
||||||
|
private String name;
|
||||||
|
private int sourceID;
|
||||||
|
private List<PositionListener> positionListeners = new ArrayList<>();
|
||||||
|
private RoundingSide roundingSide;
|
||||||
|
|
||||||
|
public Mark(String name, int seqID, double lat, double lng, int sourceID) {
|
||||||
|
super(lat, lng);
|
||||||
|
this.name = name;
|
||||||
|
this.sourceID = sourceID;
|
||||||
|
this.seqID = seqID;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prints out mark's info and its geo location, good for testing
|
||||||
|
* @return a string showing its details
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return String.format("Mark%d: %s, source: %d, lat: %f, lng: %f", seqID, name, sourceID, getLat(), getLng());
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSeqID() {
|
||||||
|
return seqID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSourceID() {
|
||||||
|
return sourceID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RoundingSide getRoundingSide() {
|
||||||
|
return roundingSide;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRoundingSide(RoundingSide roundingSide) {
|
||||||
|
this.roundingSide = roundingSide;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSourceID(int sourceID) {
|
||||||
|
this.sourceID = sourceID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updatePosition (double lat, double lon) {
|
||||||
|
this.setLat(lat);
|
||||||
|
this.setLng(lon);
|
||||||
|
for (PositionListener listener : positionListeners) {
|
||||||
|
listener.notifyPositionChange(this, lat, lon);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addPositionListener (PositionListener listener) {
|
||||||
|
positionListeners.add(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removePositionListener (PositionListener listener) {
|
||||||
|
positionListeners.remove(listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,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.parsers.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