mirror of
https://github.com/FlightControl-Master/MOOSE.git
synced 2026-07-19 09:15:12 +00:00
Compare commits
1857 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d52e427238 | |||
| f8e5efc874 | |||
| 2c61c695d5 | |||
| b6e806adbc | |||
| e74aa160af | |||
| 6793228fbe | |||
| e83ea8124e | |||
| 313f99d09d | |||
| 28de292107 | |||
| 10efb98eb3 | |||
| 8ac2241372 | |||
| 092434a103 | |||
| a777997f79 | |||
| 36cf7f718b | |||
| 7989267d51 | |||
| ef4398a5f1 | |||
| 0da8afc108 | |||
| 0e74d36227 | |||
| 290cc151bc | |||
| 21f93aa7e8 | |||
| 04c77e9760 | |||
| c6da6544da | |||
| 94d3e38bc7 | |||
| 5fcf992920 | |||
| 22cc9eabc0 | |||
| b191c0d341 | |||
| d2d431ce2e | |||
| 3a6b58ea8c | |||
| 2d8fc40307 | |||
| 68dce2f247 | |||
| bc03eb2e65 | |||
| f725709e5b | |||
| ba591c9dc5 | |||
| def5d33055 | |||
| 1aaa51c4be | |||
| 37206bcc83 | |||
| c2b86bac4b | |||
| 02dad179cd | |||
| adb94f8773 | |||
| 035110df75 | |||
| 5adb65899c | |||
| 764a373e7d | |||
| 8114327c7e | |||
| 6404417dad | |||
| d89cb8f1e9 | |||
| dee95c2f08 | |||
| 3a907ec7c2 | |||
| 851dff6e8c | |||
| 47e7364976 | |||
| 41f8207b3a | |||
| ed0a3a22ab | |||
| 3a513da942 | |||
| a4f1c60da2 | |||
| de0bf7d814 | |||
| 71fd72e462 | |||
| 619cc3c047 | |||
| 68c75a4e34 | |||
| 37671cefa3 | |||
| d9f409069a | |||
| fd5a190490 | |||
| 2f4d5b32b6 | |||
| 4c7ac68858 | |||
| ad3a3c5266 | |||
| 51d924c1b8 | |||
| 83491f535a | |||
| 380731a244 | |||
| 60c78da0f6 | |||
| 2d02f4f962 | |||
| 982bd89924 | |||
| 04f60747d9 | |||
| 28c107b089 | |||
| e6cc8757ac | |||
| 9169bfc608 | |||
| 610e33e4a4 | |||
| 77dd40fb4a | |||
| 5a63c51316 | |||
| eabc8b5854 | |||
| 1a798886a2 | |||
| 8735dadbca | |||
| 2c53ebc0e4 | |||
| f93033695d | |||
| 2d0f5817d0 | |||
| 2033e22216 | |||
| bada6f64ae | |||
| bc141698c8 | |||
| 75fddd9349 | |||
| b1a417295f | |||
| 0522036b80 | |||
| 061469840b | |||
| 6e218ed908 | |||
| 626b48c3d1 | |||
| dd957237e2 | |||
| fc9e237dbb | |||
| faac265373 | |||
| 998935e2a3 | |||
| 24f8116fce | |||
| 3415330871 | |||
| eadeaae6db | |||
| 32bdaf4f24 | |||
| 7525fe9357 | |||
| b24f31922f | |||
| 4c79349b8d | |||
| 44f7a27387 | |||
| 369425bec1 | |||
| 3dc284335e | |||
| bfc284994a | |||
| 277409a807 | |||
| d1f1f14bc3 | |||
| 5ee1f5d3af | |||
| f4569fb5cc | |||
| 4ba0a2dafb | |||
| c2b1c2b1d8 | |||
| 2e119e2efd | |||
| 4641918486 | |||
| 407f6f2b0f | |||
| f01d7ecd96 | |||
| e3d987bbdf | |||
| 89845883d0 | |||
| 4e7ff94b33 | |||
| 2fb9a1b1d7 | |||
| 5c0ab36662 | |||
| fc6dac326d | |||
| 426dcff085 | |||
| e623f45c04 | |||
| 3b9a9cb0fa | |||
| c018a86bdb | |||
| 559be38baf | |||
| e3e05e5003 | |||
| 95649d7d46 | |||
| 61097de76d | |||
| e49fff5028 | |||
| 20ae7945e0 | |||
| c5a50d23b6 | |||
| ad37e5ca27 | |||
| e8303064b9 | |||
| f8a577749a | |||
| e113817293 | |||
| 0d15cdd370 | |||
| b5db08eec3 | |||
| c3591c1fae | |||
| e1a4d5497a | |||
| 4e83162adc | |||
| 42c6b8a016 | |||
| 48f9d808cf | |||
| 6546c27cf5 | |||
| fc0c0f87dc | |||
| 9d703c0af6 | |||
| 24559c475b | |||
| f92e8a285a | |||
| 0411120551 | |||
| 5f57d4ddcd | |||
| 4164bbeba8 | |||
| 14dea99ccd | |||
| 3ea1881ff5 | |||
| 5192c188f4 | |||
| c1ffa47e9d | |||
| 2808a9dcc5 | |||
| e1ab6b6c93 | |||
| 229868bb20 | |||
| 520eb4cd1d | |||
| 2d0b4d6ae5 | |||
| 5dae9a197a | |||
| ff1ebf9775 | |||
| e6f388518a | |||
| 3557706e3a | |||
| f3d0d55a2f | |||
| b0c2bad1d8 | |||
| 53c9ca3b3a | |||
| e41ba1be45 | |||
| 594febaece | |||
| 890dae8ba7 | |||
| 8a3120be39 | |||
| 6b4975559d | |||
| 3326550705 | |||
| 6be0d82fc6 | |||
| 44ad841d05 | |||
| 53f45ace4c | |||
| 42e9cae876 | |||
| e46922d341 | |||
| 21c242ee05 | |||
| 33b7a0cef2 | |||
| 9cddca1af5 | |||
| 8cdf0d9fce | |||
| a079ac4207 | |||
| 099238a2e5 | |||
| 595bc16d92 | |||
| e9875c8288 | |||
| ec600ab672 | |||
| c3689aee93 | |||
| 60a728d521 | |||
| 7bd7071ec5 | |||
| bb787aa9ca | |||
| d2f629d100 | |||
| 5d949de0ee | |||
| e21bc8a930 | |||
| af79f63870 | |||
| 4ff32342e3 | |||
| 98d55faf4e | |||
| bd532ea808 | |||
| d4bd57ee70 | |||
| 90dbfb31a6 | |||
| 55ca34cad3 | |||
| 36a5e919cd | |||
| 3263ba1440 | |||
| 9721c19e48 | |||
| de71213eed | |||
| 65abbf9563 | |||
| a94098494a | |||
| ec935356aa | |||
| 6ab419dfad | |||
| b1ffcff064 | |||
| 4ce09a3845 | |||
| 9686e74feb | |||
| e97e24762d | |||
| b3fd0805e8 | |||
| 300092d07d | |||
| 56ed3a11d6 | |||
| 6938b20ab6 | |||
| 77f2cf5089 | |||
| 19e3b015c5 | |||
| 2702d0fc2a | |||
| 472a4883f0 | |||
| 485e2e18bf | |||
| 40c0f69eff | |||
| 854a1e5723 | |||
| f4cd28cdb9 | |||
| 5bae790dd8 | |||
| d1d43c38cd | |||
| 7e1552eeb4 | |||
| 7306bf28e0 | |||
| 07e00a8faf | |||
| ab93866366 | |||
| d7801b59e7 | |||
| ea926f173a | |||
| f6b55da0c6 | |||
| ee4e562874 | |||
| abfe5ffc45 | |||
| 9629b0e1a3 | |||
| 80b03dd194 | |||
| 3f92f8d2aa | |||
| 823c94cace | |||
| 421ac6c427 | |||
| 72550d6d84 | |||
| 72999cc7b0 | |||
| 706cf641b3 | |||
| d42bdb2505 | |||
| 9082054ba9 | |||
| abc69bc838 | |||
| 23ea5d9d06 | |||
| 8d009d6366 | |||
| 8213b51bd6 | |||
| a18fc2080d | |||
| 9c0ac0ff57 | |||
| c34dc9f946 | |||
| 1c5c205614 | |||
| 897df1b8fa | |||
| 3fb22b7c55 | |||
| 7819a30c57 | |||
| 37d2c72945 | |||
| 5631e2c09f | |||
| c7ddd6ec35 | |||
| 7280ceac32 | |||
| f8b9128d8e | |||
| c482f1dccd | |||
| 7e306be8aa | |||
| 035e6913b5 | |||
| e12f233afc | |||
| 26c7ff8c18 | |||
| 5647364293 | |||
| 06fa585f69 | |||
| 035eac2f1e | |||
| f01889cbf7 | |||
| 73025bfd4b | |||
| aec6dd1246 | |||
| ab4411e5bb | |||
| 0ac00efda6 | |||
| f32eb8d710 | |||
| ec33dfb76c | |||
| 43dfbac2fa | |||
| 7a483abec2 | |||
| 35e6b6faf4 | |||
| 1066b58fb6 | |||
| b41b8f251f | |||
| a1426c3e81 | |||
| aa3967d88f | |||
| c610c4cba9 | |||
| abb3a2dbd2 | |||
| 1f035301d9 | |||
| ebf7f76d48 | |||
| 397ab77105 | |||
| f37abc69ea | |||
| ee85b0e057 | |||
| eab28abc86 | |||
| 16c5307fc2 | |||
| 8c8cae1e27 | |||
| 925c645821 | |||
| c1c97a86b7 | |||
| 1fc1016148 | |||
| e73545d296 | |||
| f2fb7d43d0 | |||
| 781d421e1c | |||
| a3a7464a29 | |||
| ac936f5650 | |||
| c0f5dc2dc8 | |||
| 31c113180e | |||
| 239df2deb2 | |||
| 31a8c4b0b9 | |||
| f394bd45c7 | |||
| 63999c9ec3 | |||
| 82c24cee7e | |||
| 5e94b0c009 | |||
| 452ff62134 | |||
| 3df494cacd | |||
| d62bb59df7 | |||
| b367f9320b | |||
| 4bc3dbbf6c | |||
| e7b7e3ac96 | |||
| f3c4aea12f | |||
| b9b5938a91 | |||
| e7fdcf0db4 | |||
| c1f22aa556 | |||
| 991b4089d6 | |||
| a8132552de | |||
| a0323aef19 | |||
| a38abc2f7f | |||
| a6beecf510 | |||
| c5dece0e59 | |||
| ce935bafc1 | |||
| fd426a96aa | |||
| fad8800842 | |||
| 273a473db1 | |||
| a7588e517d | |||
| 84eff098eb | |||
| f157f3b5d6 | |||
| 6cd00c60a7 | |||
| 2ea951db61 | |||
| 9d0bd0aabb | |||
| 3d88d16b4b | |||
| 02724dc26d | |||
| 21a93652cd | |||
| fb4caf1a42 | |||
| 7a2508bf17 | |||
| eeeab86952 | |||
| a149ff1705 | |||
| 2c73c9c815 | |||
| f6f29db9f1 | |||
| c823a68616 | |||
| e7e2184760 | |||
| 6fe2422bc9 | |||
| cd62776be6 | |||
| 037357efe9 | |||
| 8d3910ea4c | |||
| 3e5926f706 | |||
| a1d67bf539 | |||
| 3e65457041 | |||
| f70180eb66 | |||
| c0a558e229 | |||
| 801069f5ac | |||
| f9dcac8264 | |||
| 2e4fd72781 | |||
| 902c001aa4 | |||
| 0cf2a4353d | |||
| 13c8cc90f2 | |||
| 80ced88ef1 | |||
| db231fafeb | |||
| c3f7deb602 | |||
| 300e3f2f58 | |||
| 513406f0e5 | |||
| 27e21e77f9 | |||
| dadfd803f7 | |||
| 02b0ba2278 | |||
| 3e1005aef1 | |||
| e2eb13739c | |||
| c52e30ceae | |||
| 8af3f89c14 | |||
| 07754cdf5f | |||
| c74c475a29 | |||
| fe3079caad | |||
| 61ac6b4131 | |||
| ad36ab520b | |||
| bc32ff5db9 | |||
| 6009432933 | |||
| 99eaa37c76 | |||
| 36cb189512 | |||
| ed6d5f727a | |||
| c6995d6e58 | |||
| cb0f453a8d | |||
| d9866846ad | |||
| 15f9843878 | |||
| de5d9195f3 | |||
| fd1fe8562c | |||
| b1298223aa | |||
| 67f847dd16 | |||
| 8b9143d3f1 | |||
| 0388d47f23 | |||
| c48128d92e | |||
| 0f4d466953 | |||
| 1b752fcaf6 | |||
| d06db9909c | |||
| 921024035c | |||
| 3b1c8c3deb | |||
| 69862ab67d | |||
| b05c321306 | |||
| 9b74a58dde | |||
| de9b173d9b | |||
| d112ffaf6a | |||
| 554f063def | |||
| f6dce02203 | |||
| 653dfe82fa | |||
| 5dbf743052 | |||
| 2cecc526fb | |||
| f7e7e2e41c | |||
| b28b4db7fd | |||
| c34f30e4a3 | |||
| aa7f11185d | |||
| 1790d19809 | |||
| fa6fbca67b | |||
| cd79c57a27 | |||
| 968d178317 | |||
| 4c3a97e2b2 | |||
| f126dceeae | |||
| 414521acc5 | |||
| 9147422340 | |||
| 920646ea74 | |||
| c57d839927 | |||
| e7fd5db2c2 | |||
| 3c477b872a | |||
| 77e6088114 | |||
| b76486ef5f | |||
| 5c06584676 | |||
| 2ae2ee64be | |||
| 17ba35e237 | |||
| 53e98e70aa | |||
| edd6594953 | |||
| 359e18eb58 | |||
| f8c05c99d0 | |||
| 3377459df5 | |||
| 3e30d15405 | |||
| 2b50cf2243 | |||
| b72e2b6bf9 | |||
| 50f6d98b49 | |||
| 147eeb05f6 | |||
| d8cb15a577 | |||
| 0daac876ea | |||
| 1832125022 | |||
| c7a2b34f59 | |||
| b48958995a | |||
| c311c40b72 | |||
| 58c496eacd | |||
| 7bd867c9dd | |||
| be0558849c | |||
| a1c7ec2ac9 | |||
| db516a2077 | |||
| ff8766669c | |||
| 8bf073c0c6 | |||
| 994689f05a | |||
| 3e10f9f451 | |||
| 06dc9a732e | |||
| 972fa9f674 | |||
| 50c74d0852 | |||
| 8e64d8e334 | |||
| eccd81d40a | |||
| 1c97eb6f3c | |||
| 649136f98f | |||
| d7dae1366d | |||
| 19e2ebcd03 | |||
| 69449430d1 | |||
| 663cd34aa3 | |||
| cfed6f5153 | |||
| 021c907d16 | |||
| 8ac3e28fdf | |||
| c5af279730 | |||
| 2b22d5288c | |||
| a64424ecc8 | |||
| fd1b2ecb86 | |||
| e9377e93f1 | |||
| 6747c69511 | |||
| 9b6cae6c49 | |||
| 7148bd1d42 | |||
| 73940fffc6 | |||
| 09015449cd | |||
| 4dfdb99731 | |||
| bbbca04066 | |||
| e755bba608 | |||
| 9161cec238 | |||
| dc14b60fcc | |||
| da625d51f6 | |||
| ce69055758 | |||
| 589ebd5bca | |||
| 35b50e1a9d | |||
| f038564b1b | |||
| 1d0eb9806d | |||
| 6cae3e62cf | |||
| c50a9bd318 | |||
| cd9006d29b | |||
| 05ce7e4513 | |||
| 136bd19f19 | |||
| d3b88f9065 | |||
| 6a6cb1961d | |||
| 884c51a69a | |||
| 6f126e6cd4 | |||
| b0c2e5409a | |||
| 9516ec3cd2 | |||
| 8873504daf | |||
| a844a5d697 | |||
| aecb92ccd3 | |||
| bdf13f29f7 | |||
| 81120abfcb | |||
| a49f4eaa21 | |||
| e6e2651f8c | |||
| b93ba13644 | |||
| 08a8170990 | |||
| 316ea910c2 | |||
| 0b5edfc21f | |||
| e4a51951b0 | |||
| ad56e39942 | |||
| ea09dc5a6e | |||
| 8ecfd913a3 | |||
| 53367c786e | |||
| 76c08095ab | |||
| 538864519e | |||
| cd6631049b | |||
| 079f8030b7 | |||
| ad0b32c0ee | |||
| db5797bb4e | |||
| 8235b51a14 | |||
| 1bd5e7472f | |||
| 5e8fe97752 | |||
| 7433a7144c | |||
| 27632ecdd9 | |||
| 0b1e25b073 | |||
| 393fa0bfbb | |||
| 4f51884b9d | |||
| 19a93f0026 | |||
| 3256a6d596 | |||
| 4c5c320073 | |||
| 9098590568 | |||
| 555bb7e68b | |||
| c0a18957f0 | |||
| 2cf939560e | |||
| 9d3a7aae78 | |||
| 28ddfa5243 | |||
| 05c560be3b | |||
| 8ddd810fe2 | |||
| f20760bae5 | |||
| f6ed592f92 | |||
| c98757d13c | |||
| 17378f509e | |||
| cea7320a9b | |||
| 7f18ea0e7a | |||
| fe6826016c | |||
| f0167b3e88 | |||
| 0edbf7f517 | |||
| a8a8dcff3f | |||
| 1b0ad13529 | |||
| 5172619cb1 | |||
| 3962529698 | |||
| 418d6c882c | |||
| 1e6899c40b | |||
| 6481d5d41e | |||
| 259b201e65 | |||
| 8fb113a04e | |||
| c6ebbc6122 | |||
| 6cc3d73c04 | |||
| e541e39403 | |||
| 755903a08e | |||
| 34e4825cd0 | |||
| a68eeae9bd | |||
| e4e31b554e | |||
| eba6e3f5f1 | |||
| d73ebaca76 | |||
| 94167152e5 | |||
| c7ea45e5fd | |||
| 830f76e909 | |||
| 20f28b3d2c | |||
| 8ae04944fe | |||
| f3f63ab8aa | |||
| c55b8d29f7 | |||
| e91090cfff | |||
| 1a7fb3c13e | |||
| 59857ed79d | |||
| 30623f7d38 | |||
| 16964520df | |||
| 3050fdb3c3 | |||
| 77fc2c5cf1 | |||
| b21c3ed4e9 | |||
| 93f4b345c5 | |||
| 88e59a2739 | |||
| 86bb256bf1 | |||
| 9a05f5bc93 | |||
| bce28cf389 | |||
| 629c5e7739 | |||
| ed402e2f5f | |||
| 4a56c7523d | |||
| 218d9f48b0 | |||
| f67cf99477 | |||
| 2201b2adda | |||
| 6b36421bce | |||
| 8c573c65d4 | |||
| 730dabd9cf | |||
| 69175e1b19 | |||
| 4797665939 | |||
| a5ad58e516 | |||
| b89749036d | |||
| c6268488de | |||
| 82b4631341 | |||
| a1c9c1538d | |||
| 1a53f58540 | |||
| 1b414b840e | |||
| 642cc0e98f | |||
| 63431bb54b | |||
| d64de26ded | |||
| de04369703 | |||
| 8b45067226 | |||
| 05b6f19a87 | |||
| 2753df8216 | |||
| 061032e3d7 | |||
| 82432686aa | |||
| b66d2039be | |||
| d245a73d7f | |||
| f0037151e6 | |||
| 33d761503d | |||
| 52d494f573 | |||
| 36a2d71731 | |||
| 615a220acb | |||
| c718584755 | |||
| b52272e18e | |||
| 70fa1cf19f | |||
| 4db0ea616d | |||
| 7ca7caea75 | |||
| 5439e60078 | |||
| a6880d4569 | |||
| 65e852f341 | |||
| 4ffdf9e536 | |||
| 8f698e3e62 | |||
| 61481e6e9a | |||
| 49397df90b | |||
| 7b254a08fb | |||
| 5bebbcf5eb | |||
| 83fd48e2f4 | |||
| f5b25370b0 | |||
| f68d3209ac | |||
| 2ab7f784c9 | |||
| 1e8d99a591 | |||
| d517cba765 | |||
| 552eb5b9d8 | |||
| 684c4ea113 | |||
| 4dfaca610f | |||
| 8a53998251 | |||
| 6690f70b05 | |||
| 277c26821e | |||
| 96d1d3cb66 | |||
| 09785ef451 | |||
| 22826b4cd1 | |||
| 8cc1c24b64 | |||
| 25e118f3dc | |||
| 0db35a0e9f | |||
| 78b3e3c60b | |||
| efe41a5e21 | |||
| 835041e5f6 | |||
| 93a8086ff6 | |||
| b4707bb3eb | |||
| bbfc2e9ed7 | |||
| 2fb0ab1aed | |||
| 1ac40684de | |||
| 433d1bbf57 | |||
| e33de03522 | |||
| 86fedbfaae | |||
| 6fbf050b72 | |||
| c0f4eef896 | |||
| a2f3bf3a8c | |||
| 1b717e4683 | |||
| 29210f670c | |||
| 52e2ac7174 | |||
| 3d6b053eb4 | |||
| 268eb1d60d | |||
| 18b45c9621 | |||
| 9356d67d74 | |||
| 5bb2b05f71 | |||
| a69865b8c9 | |||
| 4a1df3d5cc | |||
| 6dfd757ea1 | |||
| fc1adf3b94 | |||
| be2d1d7895 | |||
| 9e7c360912 | |||
| 51acd33d19 | |||
| af3dbe2f86 | |||
| 97668e5413 | |||
| 89308f7d06 | |||
| 464fde0ed2 | |||
| 65bd7909e1 | |||
| 5d3ea57d4d | |||
| b7bc3cfbcf | |||
| 04ef32420b | |||
| a3d8a48b92 | |||
| ef86d1d7cb | |||
| 00b3e9b371 | |||
| 0d02f56855 | |||
| 48aa841add | |||
| 4ba52212a9 | |||
| a14bca1059 | |||
| a7e07af24f | |||
| e14d655447 | |||
| 74bdeaf4f7 | |||
| 9591c62175 | |||
| 39c46dcab0 | |||
| 299e08f53d | |||
| 12555a6ff1 | |||
| 1cc012c7d1 | |||
| 28c6810878 | |||
| 353d6dfec0 | |||
| 1ed36fdff4 | |||
| 26801cf206 | |||
| c80cebb824 | |||
| 3c6e8d6d01 | |||
| 76a53ab154 | |||
| fa3e387dd1 | |||
| dca626bbcb | |||
| 3289ad2817 | |||
| 5a022a2246 | |||
| b012c5b2aa | |||
| 3f5e322948 | |||
| a861f8d9d4 | |||
| 36669c80da | |||
| 2ff128f184 | |||
| 3b44aba341 | |||
| 978be4e383 | |||
| d4cdfcc48c | |||
| 5123ab0720 | |||
| 5021a1e1f3 | |||
| 576281a612 | |||
| b84c0aba59 | |||
| ee503a378e | |||
| f235037cb9 | |||
| 0cd1cd97a6 | |||
| 74513f1b5c | |||
| 231c5bfea7 | |||
| cfd0e4ba85 | |||
| 0e8732fd44 | |||
| bf33e4ed4f | |||
| 77a3c7369d | |||
| 6290db035d | |||
| cd1935be1d | |||
| 64262d6ecc | |||
| 270c69344f | |||
| 10a0793af7 | |||
| 1b37af321f | |||
| 7cde279be1 | |||
| 0396741f3d | |||
| 8d8070bbd7 | |||
| 117cf8888a | |||
| 83db7b32e8 | |||
| d496d3d16e | |||
| 72df687ce7 | |||
| 246935622c | |||
| 6b801247ab | |||
| 078573629d | |||
| 973b8323c9 | |||
| 4c6cd31bfc | |||
| 6d61c5ee94 | |||
| a8ef096f24 | |||
| 3e8db6a1fa | |||
| c8d2a7e833 | |||
| 832d6b1c08 | |||
| 783391838b | |||
| e7936950f4 | |||
| 7c37d8b204 | |||
| 831e986ee8 | |||
| 8a44fae3d4 | |||
| 0c9390914a | |||
| 4fa525a4ae | |||
| 65ed8825b0 | |||
| af9324dd5f | |||
| 858b00336b | |||
| 82d78c98bb | |||
| 06c3f7998b | |||
| 064f727676 | |||
| e03e87f501 | |||
| cf83abfe90 | |||
| 5a00f461e9 | |||
| 8025862fb9 | |||
| bb43b08190 | |||
| 05f95796f6 | |||
| cfcd7d7588 | |||
| 2544ec5587 | |||
| 6c823b0137 | |||
| 4c3d44a63b | |||
| 88d44fa654 | |||
| 8b6f9f9de7 | |||
| fcd75a34eb | |||
| 4827b73bb1 | |||
| 2d7e7d55a9 | |||
| 3129c8c8ea | |||
| cb14961dcd | |||
| dab486ec99 | |||
| 6e37300d9b | |||
| 6b747e924b | |||
| 75849fbfb5 | |||
| f984ff044b | |||
| 85fef96d00 | |||
| e55bb21401 | |||
| 7a3fb95851 | |||
| d39074bf3e | |||
| 47d814e409 | |||
| aaf5f295da | |||
| 8f612f2017 | |||
| a2808163a7 | |||
| e4aa23ce3d | |||
| 41b01a508d | |||
| 1b3cac9424 | |||
| 0410ae6877 | |||
| e49020b905 | |||
| 1ae41319fa | |||
| d68dcaf69a | |||
| 856c3578f6 | |||
| 0265152c12 | |||
| a893d46cb9 | |||
| b68f271fb7 | |||
| 057e231a9d | |||
| b6fedbd97d | |||
| 18c6ea39ce | |||
| 7cd29501a9 | |||
| 59e4f48726 | |||
| 20fe2ee505 | |||
| 1ce46c016c | |||
| 296944c23c | |||
| 4f772fecdc | |||
| 8b4ba430fb | |||
| cdd22444b9 | |||
| 3b0949de72 | |||
| 15848bc8c4 | |||
| ebb4623bb5 | |||
| 9eba6d607c | |||
| e9d3c7c2e7 | |||
| ad16c6d44f | |||
| 2046958bd6 | |||
| 0046533d53 | |||
| ce7e152e13 | |||
| 2309487429 | |||
| a3decc33c9 | |||
| b2d87a8780 | |||
| 5034005f6b | |||
| c045ba0c6d | |||
| d0fb4d44ef | |||
| edcdc057e6 | |||
| cfa851beea | |||
| d1f3e3f4bb | |||
| f5d8efe86e | |||
| 45f578b5a3 | |||
| 620f4b96b9 | |||
| 7c8e5033ec | |||
| 22ec94b374 | |||
| 5cd3a172f1 | |||
| 39d3f85b71 | |||
| a89a3b38ce | |||
| 3202702612 | |||
| 725e32f439 | |||
| 9a7df65faa | |||
| 596eb4c7e4 | |||
| 2bce305451 | |||
| 2bcc531e73 | |||
| 7da1905ad3 | |||
| 2e66a854b1 | |||
| 421005a3b6 | |||
| d5bf28f799 | |||
| 990f748e42 | |||
| a466365949 | |||
| 57f6096aa1 | |||
| b12f71c430 | |||
| 236a69e5dc | |||
| dc64b98c59 | |||
| e6dde7335a | |||
| bbb2174957 | |||
| 9458243926 | |||
| 41274e7801 | |||
| b37b50a0c6 | |||
| 459ff8038b | |||
| f2da862442 | |||
| 9c6c2ec5ca | |||
| aa13583aa7 | |||
| 002f1e09ef | |||
| 5091ed7d6d | |||
| 0259e41ce8 | |||
| 2475f22937 | |||
| e78f9cbd46 | |||
| 9d6851adec | |||
| 4668f072af | |||
| a1a414925f | |||
| f4f12f786c | |||
| eb659446df | |||
| 62c42adebc | |||
| 19eb9dca30 | |||
| 0fc91595b8 | |||
| 18fca6461b | |||
| 8464a2ba43 | |||
| a8549e2b7d | |||
| 869b819a4f | |||
| 2531a25c9e | |||
| 8ae6192a5f | |||
| 3b2af6ea07 | |||
| 82da7a8f24 | |||
| 3749686882 | |||
| b2eba4911a | |||
| 40700a387c | |||
| 0577940d58 | |||
| 1d6be07cff | |||
| 2d6545429a | |||
| 9f095a1d28 | |||
| 2f7b7eeddd | |||
| 91b6da70af | |||
| 235e40cb26 | |||
| ab1ec54c03 | |||
| 8ba3a30278 | |||
| 3675ddaf04 | |||
| 62a59e7b2a | |||
| 1f949463aa | |||
| 6d53a30b1f | |||
| 5aeb480069 | |||
| 4b6aa3b56f | |||
| a1ca605206 | |||
| 4209569f29 | |||
| 7106efbc04 | |||
| 4e878e49a7 | |||
| 3e470e557d | |||
| 5f567fcf66 | |||
| 2289e16d91 | |||
| 40415ef933 | |||
| 6519c30b3a | |||
| 42d253a9ca | |||
| 38b40e2931 | |||
| f7a7ea14a3 | |||
| 41e0f92cc0 | |||
| 2c5e9789db | |||
| e198aae1dd | |||
| a7b444dfea | |||
| 64b35b9cb3 | |||
| 4a8d52b328 | |||
| d040cc0d03 | |||
| 3cbf6fff90 | |||
| f078e94a7a | |||
| 9973099f57 | |||
| 9ca7785a64 | |||
| e7d5fd6c1b | |||
| 9eed35ace7 | |||
| c7ec1b4e5e | |||
| 2693e76483 | |||
| 34ffebb46a | |||
| b232a5da37 | |||
| e8a2167934 | |||
| e50fc00583 | |||
| 4b8802af37 | |||
| fa342a5b6b | |||
| ebd69fd10d | |||
| c1a8c08880 | |||
| 647fb6acbf | |||
| ede692ea2a | |||
| ca44e2762b | |||
| 4ebcaf913c | |||
| 7a4c9806a4 | |||
| bddc1d7fdc | |||
| cb813b70e1 | |||
| eda70742eb | |||
| b81e3e9e6d | |||
| e766eaeccc | |||
| bab61271f2 | |||
| b1465d89a6 | |||
| 05b1ddf013 | |||
| 903e8711f7 | |||
| 31b93fb42c | |||
| e16bbfc2b9 | |||
| 220e004d73 | |||
| f161f08fc1 | |||
| 3136861b24 | |||
| dc2693bead | |||
| 3e22411328 | |||
| d7da8c4253 | |||
| c1ff5831ad | |||
| b00778bb86 | |||
| ac5646075b | |||
| 5a1faba796 | |||
| 4ab16cb276 | |||
| eb1e885c0a | |||
| 00ff9a11b8 | |||
| 564f95781e | |||
| 8eabc024bf | |||
| a848af0753 | |||
| f4e7a3a314 | |||
| d83283c709 | |||
| a35112a6f3 | |||
| 6c2f8aa6ff | |||
| 66c012a30d | |||
| 2e342e4341 | |||
| 7b92b7853d | |||
| 6c9f10e6a8 | |||
| 6db2c4465d | |||
| c0cc960df5 | |||
| 968240ad44 | |||
| 50f3016711 | |||
| 56e2b06e9d | |||
| c02f69720a | |||
| 96df7d1e42 | |||
| 1d4caf7588 | |||
| b0e3201d3c | |||
| bab097958f | |||
| f4a3f6d433 | |||
| e73ab89a18 | |||
| 187643f6ae | |||
| 3c9bdb2915 | |||
| 5241642447 | |||
| 3c6089884e | |||
| 3b2cbea1c4 | |||
| 80e3da49cb | |||
| 1df7a12587 | |||
| 663a59bd71 | |||
| 884ea866e2 | |||
| ced859424b | |||
| 4a6377c204 | |||
| fd22558207 | |||
| a6faadea48 | |||
| 42a26100b7 | |||
| e8f4770abe | |||
| 7751d4ee88 | |||
| cdd829e7be | |||
| 1af1ada7dc | |||
| c72c3fd091 | |||
| 0848718313 | |||
| 832abd7860 | |||
| 16bf3a89b5 | |||
| 11cdae098b | |||
| a63ee3fa8b | |||
| 5cc023d1fe | |||
| c4084156ac | |||
| 302018feff | |||
| 12e745af54 | |||
| f18c818b8e | |||
| 4deca4f816 | |||
| c50f6b72e7 | |||
| b023ecfb99 | |||
| cdb491bb5f | |||
| c0401447dd | |||
| 3ac47c967e | |||
| e577062720 | |||
| 95dc7ad2b4 | |||
| fccf58cd6c | |||
| 43680c2fc3 | |||
| 6791e4d4ba | |||
| 11f8ac4820 | |||
| 8f2672f928 | |||
| fb66d74ab7 | |||
| 37c061bcbd | |||
| 9491b18ff4 | |||
| 2c92bb9d61 | |||
| 8cff2eaac0 | |||
| f64550f7c8 | |||
| 0addafa627 | |||
| b643d73f7f | |||
| 798c5b292a | |||
| 688c57d91d | |||
| d4c8d5d91b | |||
| 154cc9fbeb | |||
| 327bac3058 | |||
| c0e58e9c92 | |||
| e8bfab515c | |||
| dcf1a56756 | |||
| 5990ab1cc9 | |||
| 084499fa0e | |||
| 74e84a73dd | |||
| 96b0393f91 | |||
| 0656c33e05 | |||
| ec1ab49b54 | |||
| 1daa2857b2 | |||
| 8c55541d0e | |||
| c5a4776b3a | |||
| 8bb9f0d7c0 | |||
| 7d83c251a8 | |||
| ee3ead9aac | |||
| 54896e0e31 | |||
| 016198e7f2 | |||
| 0933eef4c3 | |||
| fe55555c67 | |||
| bcf8973eed | |||
| 50d2e42cc3 | |||
| 261d1cbeba | |||
| 242462b9ba | |||
| 695f204493 | |||
| a7202ccd0d | |||
| c71c6c5178 | |||
| 155b06edc8 | |||
| fd4e478dbf | |||
| 6b91e68668 | |||
| 64b8f2fb46 | |||
| 7a07e15032 | |||
| 3d74312d43 | |||
| 6baf85d429 | |||
| 7516d2af56 | |||
| c247a98402 | |||
| 311ab19389 | |||
| 83bcd55a7a | |||
| 5a43936e35 | |||
| e9f92d2250 | |||
| d469374ed5 | |||
| a6ff84c09a | |||
| 3ed8ca63a8 | |||
| 89aa08829e | |||
| 35b776179e | |||
| 5146106885 | |||
| f4cb6df8d4 | |||
| a259b88f1d | |||
| 5b4f3d1c98 | |||
| c62c21c386 | |||
| f05fc956db | |||
| d10adb5de8 | |||
| 17c56a11bd | |||
| 796561b8b6 | |||
| 209b7a64ed | |||
| 8f56299380 | |||
| 73b1394ca7 | |||
| f3a2cf7284 | |||
| 45aac21b63 | |||
| b52643bb36 | |||
| 036010c39d | |||
| 9440d2b2c3 | |||
| 29727ec9a6 | |||
| eb0b43cea8 | |||
| df79c765f8 | |||
| a6e16fb7ad | |||
| d09737c218 | |||
| 616b80710a | |||
| 94f206af68 | |||
| 6b0c42ae50 | |||
| ae6613fae1 | |||
| 7363da1b54 | |||
| b61cfc7390 | |||
| 684f19c060 | |||
| e2c5688670 | |||
| 1d016d9fce | |||
| cbc5b5bb89 | |||
| 218c7736e0 | |||
| 8e46e41b34 | |||
| bcf639d076 | |||
| 697d12aefc | |||
| d08e4db0eb | |||
| 89afd2b4c0 | |||
| 3358f98bc4 | |||
| 417af6a93c | |||
| 9a2f6c7faa | |||
| e52b0edc4e | |||
| c51a65f058 | |||
| 5b2e67df19 | |||
| 7b7605e501 | |||
| 49d7d9ee2e | |||
| 774c8971c8 | |||
| 545c0886fa | |||
| 721d027fb3 | |||
| f1e7d8bd06 | |||
| 5185ea35fe | |||
| ec039f2999 | |||
| c0fc649f8b | |||
| c49497e68c | |||
| 1328ac061e | |||
| 57bc6de12b | |||
| 1b80d68f50 | |||
| 787151597c | |||
| f725039da5 | |||
| a4f15a0830 | |||
| 391f0fc9af | |||
| 084f00afad | |||
| 38cf5be738 | |||
| 1bb70b8697 | |||
| 6e35b80daf | |||
| a171c22ce4 | |||
| f56457bc28 | |||
| 87f4ebee64 | |||
| a0276821d9 | |||
| 9708de70c7 | |||
| 01a5b523da | |||
| 13477e17ec | |||
| c32cec4c5b | |||
| d68589bb70 | |||
| e08df3f9ce | |||
| 16eb38782a | |||
| f958130b92 | |||
| 9a19dd254e | |||
| 2175e9c0d6 | |||
| 4869ae4baa | |||
| 1fd41eaa55 | |||
| 25a7260d7f | |||
| 478934ddd1 | |||
| 3d7234de19 | |||
| 9abd71f681 | |||
| d1dc829136 | |||
| fed1fb2839 | |||
| eda672bdc6 | |||
| 0e5dbbea79 | |||
| 08ba001b45 | |||
| f5809ef302 | |||
| 457ce1ece1 | |||
| ce5aaf0b48 | |||
| 9c9841332f | |||
| ee6954a4b9 | |||
| 9470d3a2ef | |||
| 8356271f8d | |||
| 6b6e845a18 | |||
| c01b94518f | |||
| 9cf5f74e5e | |||
| 4fda0414a0 | |||
| 42a6f3272d | |||
| effa4af3a1 | |||
| 80b7e49eed | |||
| 362b242c41 | |||
| 7ace7b49ca | |||
| 7cd902e924 | |||
| e3c14678e2 | |||
| 63748c3841 | |||
| 1c68b66047 | |||
| bfaca75a7d | |||
| 6c9dce70e7 | |||
| 5515aa43d6 | |||
| 47c461e504 | |||
| 65174ceaae | |||
| 5aecd8c255 | |||
| cfc0f1a1f7 | |||
| 2500cfb3c7 | |||
| 7e673271ee | |||
| e165cb156c | |||
| 82bfb0cc55 | |||
| b0fe0663f1 | |||
| 1bd5193786 | |||
| edf657c65c | |||
| 801d00fa26 | |||
| 64c3fb985a | |||
| 1536c94319 | |||
| eeffa31282 | |||
| 206528a530 | |||
| e7efd89d7a | |||
| c486167b01 | |||
| e5201f3363 | |||
| ff4927dbb7 | |||
| 41690fdf7b | |||
| 5b83f8e45b | |||
| 94863ed8ae | |||
| 5842562ce1 | |||
| 5f545fd3c5 | |||
| aab2f20280 | |||
| 76ac68ce51 | |||
| f034e3680c | |||
| e5c57269bb | |||
| e63b99c890 | |||
| e8542743b4 | |||
| 309e1208c2 | |||
| 3e0db05664 | |||
| 9573d92cc0 | |||
| 1abc0dcb6a | |||
| 770fb3228f | |||
| d6aa251c5e | |||
| 9305b870d2 | |||
| 824a98d7a8 | |||
| 7a55ba105c | |||
| 122425338e | |||
| ab5167717d | |||
| 176abf74a1 | |||
| da4f13d0d7 | |||
| 1d7f874a78 | |||
| afb63a7331 | |||
| 7ddec7609a | |||
| 53c3996aef | |||
| 0c07a660ee | |||
| 8ffda53e3d | |||
| 5a30c08ff6 | |||
| 9cac2674c4 | |||
| ec3f43c69f | |||
| 270dfab6a2 | |||
| 624b1f5647 | |||
| 84332a3ebf | |||
| 63ff79ef0b | |||
| 09452444d7 | |||
| 75be2fc0ba | |||
| 1e0a0f8dc5 | |||
| f3d9b9c5be | |||
| cef0e444c9 | |||
| bad8dc2220 | |||
| e05d57d908 | |||
| 45c153f7a0 | |||
| 0948f418cd | |||
| d027113b0b | |||
| ff00c2fb15 | |||
| cd5b4ee1be | |||
| ac337b8459 | |||
| 567f7cbc29 | |||
| d81ffcf119 | |||
| 6ed66a70a2 | |||
| 9ed3b045f7 | |||
| 2d6653c14a | |||
| ab0e7e735a | |||
| 3243555d3f | |||
| 8696ce0cc1 | |||
| 459ff26868 | |||
| 277eb2d09f | |||
| 187093fa5a | |||
| 6c9ce55759 | |||
| f9721e501a | |||
| 23c22651be | |||
| 2ad81ca499 | |||
| 7792381c36 | |||
| 591dd128ce | |||
| ce10390406 | |||
| bf299e5f8d | |||
| 7f98e703f1 | |||
| 421d27f640 | |||
| fb1e95ab87 | |||
| 3737592734 | |||
| 4b8b13dd68 | |||
| 6445cf01a2 | |||
| 8e07b93eac | |||
| 23f6752362 | |||
| 19b870c038 | |||
| ca7f15e622 | |||
| d497bb25f3 | |||
| 29d694722b | |||
| 40ffef035b | |||
| a00c198bcc | |||
| 717842b276 | |||
| d5c4c34759 | |||
| edb0032118 | |||
| 123144d06e | |||
| 38f5d45d5b | |||
| bcaf808b88 | |||
| 7b8db597ef | |||
| 18e192b235 | |||
| 5ad30277ba | |||
| 2e998dc315 | |||
| cda1658541 | |||
| 8b640912c3 | |||
| 81a773b861 | |||
| 444cc43971 | |||
| eb86d59203 | |||
| 5ee35f3fbb | |||
| 9a91145f89 | |||
| 637081ebdd | |||
| 7d2746e7be | |||
| c84a92c787 | |||
| b775aaa274 | |||
| a97d7abc24 | |||
| 7e73db505b | |||
| a126906776 | |||
| 2d6092c114 | |||
| caef309547 | |||
| f9ec21a2b5 | |||
| 06600b2a94 | |||
| 3f39ec0ae0 | |||
| cfc45cf068 | |||
| 45d809a641 | |||
| fcfbc5e17c | |||
| f30c66424c | |||
| f823fc6ee0 | |||
| aac291c0c6 | |||
| 61adeeeda3 | |||
| f94a5eed61 | |||
| 0695c48e7a | |||
| f4bfcf58fd | |||
| dee7307adc | |||
| 114032a743 | |||
| e9e6a63e6a | |||
| 2a4f6020c2 | |||
| 3ea8b3737f | |||
| ed47b4b3db | |||
| adc89feda9 | |||
| 32c9a59ff0 | |||
| 200c1dac85 | |||
| cf65c2af15 | |||
| fb6ebc42a5 | |||
| c7696c375e | |||
| 337e1a837f | |||
| 495a9fd3a0 | |||
| e6f7493ddd | |||
| 76e75505e5 | |||
| dafbd3f368 | |||
| 11ec70441a | |||
| d1a18913f3 | |||
| 775b9e9fde | |||
| 286e34e057 | |||
| da2aa00442 | |||
| ba90176bb1 | |||
| cf9248ecc8 | |||
| 0d42b12658 | |||
| 2f6d9b640f | |||
| 04923b65b2 | |||
| 2328f9a36b | |||
| 98971736f8 | |||
| ec809085b4 | |||
| af023c1994 | |||
| d9b7cc18f3 | |||
| ce1121a4c2 | |||
| 5fa75bf6b9 | |||
| 5cb1036618 | |||
| 9473dc2069 | |||
| 81f5184b43 | |||
| b6bc22f991 | |||
| 56fad29df9 | |||
| dfeea53cf6 | |||
| 71e6369960 | |||
| 0611db78b3 | |||
| 8c768cc517 | |||
| b67750d808 | |||
| 96f2ad06e2 | |||
| 5cf4833455 | |||
| f578fcfdc7 | |||
| 9838e1af19 | |||
| cee443e28d | |||
| 1fe57cab67 | |||
| 80b22a8444 | |||
| 71733babf8 | |||
| 9f793d6cf7 | |||
| 46e73a108f | |||
| b37a8f3217 | |||
| 1202187e2d | |||
| b13ddc958b | |||
| ba3954ad3f | |||
| f2e417b21f | |||
| d93d0afdf3 | |||
| 66d913e4bf | |||
| 207c9ed62e | |||
| e3f4aa3d64 | |||
| 02d30fa2b4 | |||
| c287a96c7f | |||
| 2392c9dc8e | |||
| 682c1f5ef2 | |||
| 50305f3d6f | |||
| 1129e34e01 | |||
| 76dfcf751c | |||
| 4227186383 | |||
| 18fd9cdc3d | |||
| 9d3341ab9f | |||
| fd432dc0d1 | |||
| ee12dc0a7d | |||
| fdcdf52d9a | |||
| 5dcefff28d | |||
| 6f940fbf46 | |||
| 5fbdeb7a78 | |||
| 64f278e2e7 | |||
| 6fcc3ef1e2 | |||
| 0e073f3f8b | |||
| b3b021550c | |||
| 42e05aa28a | |||
| fd726f1da8 | |||
| 44786c3ea4 | |||
| 12d69c057a | |||
| 4841a06c9f | |||
| 1ee03dfe8c | |||
| c1ae6be799 | |||
| e689fe0366 | |||
| 94fced4af0 | |||
| 5223a4beba | |||
| 3c7a90e2a2 | |||
| da2d56752d | |||
| b26fb5ac19 | |||
| 3bb8f35f63 | |||
| 3ca27c4852 | |||
| 32421bdb6a | |||
| 06076f4154 | |||
| 322e079486 | |||
| f2af1b1921 | |||
| c5d3db0bed | |||
| 66c6b8c2e4 | |||
| 9b72254ccc | |||
| 5afd5cae27 | |||
| 4537784ad9 | |||
| c2f6940c5e | |||
| a7011e33fa | |||
| 5686403f7f | |||
| 33859c2b31 | |||
| 8472a37cd8 | |||
| ed49348155 | |||
| ae36353aab | |||
| c30d9f3472 | |||
| 853989bfc9 | |||
| df2dbed441 | |||
| 7f0f7cee0c | |||
| 14cf4edc39 | |||
| c1144b063b | |||
| cd81911bfb | |||
| d2962005ee | |||
| c2d4f179ae | |||
| a51aeab811 | |||
| dbc8e8be67 | |||
| 1c93f06b9b | |||
| 422e111857 | |||
| c8d2104c39 | |||
| b2a903e077 | |||
| 74bd2c1cfc | |||
| 4ea9c8508d | |||
| f9e5785f52 | |||
| 25e58393ea | |||
| 3653199bfc | |||
| 443a37a9c5 | |||
| 78ef53fbea | |||
| 5e63deb717 | |||
| 5d8b0d150a | |||
| 0440982c83 | |||
| b683dd5352 | |||
| ac975e7e86 | |||
| 2745c6c1ca | |||
| 918337f202 | |||
| 5a2779c698 | |||
| 7429982bef | |||
| 4222c4d945 | |||
| 62bfdcff8f | |||
| 06213ab583 | |||
| 1a8f9b4aa2 | |||
| 7c07df19bf | |||
| d2629263c3 | |||
| 11fb5500f3 | |||
| 31697030ed | |||
| 54e799d3f6 | |||
| ca2ae9657c | |||
| efaf7e856c | |||
| eeb5555400 | |||
| d72b33551e | |||
| 5a6a9d1aed | |||
| 2810acc1f4 | |||
| 08e4b7baeb | |||
| 8d722c2ccc | |||
| afe5df922a | |||
| 93a0b0e19e | |||
| d52867c541 | |||
| f554642505 | |||
| a0ecf58c7f | |||
| 0ab54268e9 | |||
| fdf2605367 | |||
| 3548cea999 | |||
| 7f3ebe4f48 | |||
| f22837103b | |||
| 7465fd065f | |||
| 61d3035c76 | |||
| 92402fc0ca | |||
| 6f1861f057 | |||
| 94259ab75a | |||
| 2dbfe1d4ee | |||
| d06d23cba2 | |||
| 8b9e49cfc6 | |||
| 618b4d7ded | |||
| b409b8dfc1 | |||
| efb514e97f | |||
| 81977aa04d | |||
| 3e5b87411e | |||
| 717ef02c44 | |||
| c4b8e4a36c | |||
| 4ae4f76ce0 | |||
| c0a9d51f98 | |||
| d9f7be4f25 | |||
| 891235032a | |||
| dda74c471f | |||
| f5c419919c | |||
| a95f3f2fa1 | |||
| 0e78a11fd6 | |||
| 130e089d1c | |||
| e675c991c4 | |||
| fc79188fe2 | |||
| 56ec7f122f | |||
| e1d09bdd8b | |||
| 1ae2171885 | |||
| c2257729d5 | |||
| 4dd8e53c1c | |||
| 4991fc9cb4 | |||
| 1371445e94 | |||
| 8823b96b5f | |||
| 9cb551e6fa | |||
| b9fc1df2e4 | |||
| 3bc387c1c7 | |||
| fb85b94d2d | |||
| 3409030e1a | |||
| c0b816fd40 | |||
| 9f14ed7a5c | |||
| e147a4c604 | |||
| fd7827c264 | |||
| 6c0b6b3b95 | |||
| 04fb7f7684 | |||
| fa9adaf046 | |||
| 6305459056 | |||
| a477fc78bc | |||
| 781ebc7a42 | |||
| 59603e3027 | |||
| dc7c50b97b | |||
| 2eb4e87d32 | |||
| fd730206a6 | |||
| 7d9e721506 | |||
| 5f416b3271 | |||
| 589335e420 | |||
| 2214584e59 | |||
| 090d0e79f9 | |||
| ae3d1f6b2a | |||
| 9ea52fce28 | |||
| dc3350a1c8 | |||
| 905095add9 | |||
| ce4353d701 | |||
| 929f99d527 | |||
| 541db97a74 | |||
| cc567cf342 | |||
| 8ed6f8b193 | |||
| 1dec76d96d | |||
| 75216793f9 | |||
| 173e68676c | |||
| e67d5a6c9f | |||
| 47e4777df0 | |||
| e1c602583c | |||
| 8925bc11ca | |||
| 6e97433ec4 | |||
| bddc40ddb8 | |||
| 7d697dc584 | |||
| 02c63850a1 | |||
| 2d3ef543b3 | |||
| 78ccbd733b | |||
| 99ab12ed99 | |||
| fd7469b5f0 | |||
| 014b07ffc3 | |||
| 165c569a99 | |||
| 6cb59a8f39 | |||
| 2e49b72ab4 | |||
| 0da53c794e | |||
| c54a0968b9 | |||
| 5ba5dd5fd8 | |||
| 004591f8ad | |||
| b7543f24e8 | |||
| eb8853ddf4 | |||
| 3a0e154daa | |||
| e004da9f00 | |||
| ecd76dec7d | |||
| 29995e1584 | |||
| b97b197219 | |||
| 87b75b4269 | |||
| 2b96071884 | |||
| 4ee3d8fb5c | |||
| 30c21086ba | |||
| 5fda8c1c1b | |||
| 4bb96a7730 | |||
| 4ec757d87a | |||
| 745da36e78 | |||
| 3a2effb2dc | |||
| f45eef2dd2 | |||
| d3825ccda1 | |||
| aaabf5220e | |||
| 43034ed0ad | |||
| 50ee8865ed | |||
| 74a4b84b78 | |||
| 56feca1451 | |||
| d5cdb9ec34 | |||
| 37d2602f2d | |||
| fe7df16ac0 | |||
| 1c1f74e1ad | |||
| 33d86bd081 | |||
| e509eb3d35 | |||
| ebc080b452 | |||
| 25ddf06ad7 | |||
| 5c23b102df | |||
| 3e9ab6834e | |||
| d9bfc65862 | |||
| c661491772 | |||
| 0b2f6b4f3d | |||
| 75114effc1 | |||
| b513b2a049 | |||
| 82c606edc1 | |||
| 0550ed1566 | |||
| 9dad6fb9b9 | |||
| 5d4fb7c1ba | |||
| 2e15341f6b | |||
| 8be023a5ee | |||
| 6b66b0d567 | |||
| 53bde229c3 | |||
| 4eaff505df | |||
| bff55e29ea | |||
| 3950008d05 | |||
| bbc1da3c48 | |||
| a4c6a67d33 | |||
| dbb1fa05d7 | |||
| 859b33fa08 | |||
| abf8601f67 | |||
| cd33677de4 | |||
| 68fff65088 | |||
| 99827bce47 | |||
| 2583c5c175 | |||
| 61dafa7aa7 | |||
| abbe53c7fa | |||
| 5b57873dee | |||
| 1d9b114c95 | |||
| 88e7302838 | |||
| b9cb19e8e0 | |||
| 3e69143cfd | |||
| 7a53439408 | |||
| 6a6c7a3236 | |||
| f9a49ab7e7 | |||
| 5ad927d20c | |||
| 75a7f05761 | |||
| 70d8fa23fb | |||
| a1e6ee7a90 | |||
| b3fe6fe363 | |||
| e978ff69bb | |||
| 5fe9cb24ac | |||
| cc5db0ef6b | |||
| 3d1f0cc376 | |||
| 751cd8c75c | |||
| 26bb6471dc | |||
| 1822df778d | |||
| 57af94ef7b | |||
| f79ec32c2b | |||
| 8c91c753ee | |||
| f975fa8eae | |||
| d0d58b3f24 | |||
| 986459151f | |||
| 5cf97ac802 | |||
| a33c8219e1 | |||
| f5ec6c1cb4 | |||
| 6f1afa2de9 | |||
| 7e9042074d | |||
| 164bcc5557 | |||
| 50b61ea094 | |||
| b737da9c51 | |||
| b41e539b6e | |||
| a2f35d816c | |||
| b12d9e9a5d | |||
| 5ba8979d94 | |||
| 880fae604d | |||
| 0db24006e1 | |||
| 05b87659f4 | |||
| 3b58e1f127 | |||
| c5705e8b1a | |||
| 91dc8a9453 | |||
| 16ed06a10b | |||
| 4fb28b1bf5 | |||
| 7c2ad7e8d0 | |||
| a79b4ba3a9 | |||
| 7b92053277 | |||
| 9e742b1620 | |||
| b3be71c75b | |||
| 010ca59281 | |||
| 1293679401 | |||
| f1f3d69584 | |||
| 8a00a31d5b | |||
| f96a554a84 | |||
| 70f49a9600 | |||
| f968472c00 | |||
| c9f9ac7b0e | |||
| 6a3c0b00a1 | |||
| f194db0309 | |||
| b0342c048d | |||
| 6ae074cd10 | |||
| d37a16f3ef | |||
| 7efd2a3053 | |||
| 5c8a3f695e | |||
| fff30c21cd | |||
| 6eafba3032 | |||
| b96cd1909a | |||
| aa294cd9c6 | |||
| 8880bf8587 | |||
| b0b8d81e5c | |||
| de2f7148ee | |||
| 69c8d52f52 | |||
| aae67268df | |||
| 417e88cc6b | |||
| 6411b77284 | |||
| 367c67f269 | |||
| a3067740a3 | |||
| af3bf7dece | |||
| 553f14d2ea | |||
| 36e923cac7 | |||
| eb14fdc84b | |||
| 361854390f | |||
| c6fc6ab017 | |||
| 254dd92cd8 | |||
| 93af911891 | |||
| 1ecbf22f0a | |||
| 3531b6a3e6 | |||
| 91035064fb | |||
| 11944dab2f | |||
| 15cbdabbeb | |||
| 677a733767 | |||
| 657e3578e0 | |||
| f9389f4aed | |||
| c263221236 | |||
| c0a1df4682 | |||
| aae8ef8053 | |||
| d9d246e194 | |||
| 2d4520995a | |||
| 786e1ea784 | |||
| 9e2f312126 | |||
| 9797d47fbf | |||
| 594afc5b8e | |||
| d530ba5c3e | |||
| 94bbb8a9a4 | |||
| aade1eebf1 | |||
| f16db1d4e5 | |||
| 17372a16c6 | |||
| 059bc05527 | |||
| 296a7a808a | |||
| db3ab439d0 | |||
| a26c9f423c | |||
| d1e4295178 | |||
| 043e1dc74b | |||
| 10154a6e4e | |||
| 1e1e0758f8 | |||
| f069523ba6 | |||
| 6c846380c0 | |||
| c172c6d6d6 | |||
| acd0ec187f | |||
| 965ae51223 | |||
| 65557db444 | |||
| 414196afc6 | |||
| eb37f9b18c | |||
| 6f7f20f7a8 | |||
| 2533651215 | |||
| aa4e5d0e7b | |||
| aef5f0c11a | |||
| a43769981d | |||
| b0a67686e3 | |||
| 4622affe70 | |||
| 6b55f0e5dc | |||
| 92a75d488c | |||
| c76703207a | |||
| 0418dfaa32 | |||
| 15d7c36d8f | |||
| 305a738926 | |||
| ba1b42f209 | |||
| b45b3c9804 | |||
| fa048311ca | |||
| 23bffd8c1d | |||
| 731517dcf2 | |||
| 6ac1194746 | |||
| 9b2ed229b2 | |||
| 8ad430347c | |||
| 9c344eff6e | |||
| 557a652f73 | |||
| 1ce7ecf64e | |||
| 9c84e995ac | |||
| 93936a07d0 | |||
| 7a000b02ce | |||
| fbff95ed29 | |||
| 7a79d49659 | |||
| 614a1cc906 | |||
| 1e1335e2ed | |||
| 70bca0d142 | |||
| eb3037c913 | |||
| 8e92b8045b | |||
| b795d2fd09 | |||
| a73faca149 | |||
| 96a7a963e6 | |||
| e206fcd5d5 | |||
| bbc353a861 | |||
| ba1857950e | |||
| 43193cf7f6 | |||
| 43c95b6ab5 | |||
| da44f6ef22 | |||
| 6113455520 | |||
| 1150c81ab9 | |||
| 5aabdb7593 | |||
| 0a8f83b0fc | |||
| b0adfc6bb5 | |||
| e959b40805 | |||
| 274771b212 |
+19
-17
@@ -26,24 +26,25 @@ init:
|
||||
# - ps: iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
|
||||
|
||||
install:
|
||||
- cmd:
|
||||
# Outcomment if lua environment invalidates and needs to be reinstalled, otherwise all will run from the cache.
|
||||
# - call choco install 7zip.commandline
|
||||
# - call choco install lua51
|
||||
# - call choco install luarocks
|
||||
# - call refreshenv
|
||||
# - call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat"
|
||||
# - cmd: PATH = %PATH%;C:\ProgramData\chocolatey\lib\luarocks\luarocks-2.4.3-win32\systree\bin
|
||||
# - cmd: set LUA_PATH = %LUA_PATH%;C:\ProgramData\chocolatey\lib\luarocks\luarocks-2.4.3-win32\systree\share\lua\5.1\?.lua;C:\ProgramData\chocolatey\lib\luarocks\luarocks-2.4.3-win32\systree\share\lua\5.1\?\init.lua
|
||||
# - cmd: set LUA_CPATH = %LUA_CPATH%;C:\ProgramData\chocolatey\lib\luarocks\luarocks-2.4.3-win32\systree\lib\lua\5.1\?.dll
|
||||
# - call luarocks install luasrcdiet
|
||||
# - call luarocks install checks
|
||||
# - call luarocks install luadocumentor
|
||||
# - call luarocks install luacheck
|
||||
call choco install 7zip.commandline
|
||||
call choco install lua51
|
||||
call choco install luarocks
|
||||
call refreshenv
|
||||
call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat"
|
||||
cmd: PATH = %PATH%;C:\ProgramData\chocolatey\lib\luarocks\luarocks-2.4.3-win32\systree\bin
|
||||
cmd: set LUA_PATH = %LUA_PATH%;C:\ProgramData\chocolatey\lib\luarocks\luarocks-2.4.3-win32\systree\share\lua\5.1\?.lua;C:\ProgramData\chocolatey\lib\luarocks\luarocks-2.4.3-win32\systree\share\lua\5.1\?\init.lua
|
||||
cmd: set LUA_CPATH = %LUA_CPATH%;C:\ProgramData\chocolatey\lib\luarocks\luarocks-2.4.3-win32\systree\lib\lua\5.1\?.dll
|
||||
call luarocks install luasrcdiet
|
||||
call luarocks install checks
|
||||
call luarocks install luadocumentor
|
||||
call luarocks install luacheck
|
||||
|
||||
|
||||
#cache:
|
||||
# - C:\ProgramData\chocolatey\lib
|
||||
# - C:\ProgramData\chocolatey\bin
|
||||
cache:
|
||||
C:\ProgramData\chocolatey\lib
|
||||
C:\ProgramData\chocolatey\bin
|
||||
|
||||
|
||||
|
||||
@@ -51,8 +52,9 @@ build_script:
|
||||
- ps: |
|
||||
if( $env:appveyor_repo_branch -eq 'master' -or $env:appveyor_repo_branch -eq 'develop' )
|
||||
{
|
||||
echo "Hello World!"
|
||||
$apiUrl = 'https://ci.appveyor.com/api'
|
||||
$token = 'qts80b5kpq0ooj4x6vvw'
|
||||
$token = 'v2.6hcv3ige78kg3yvg4ge8'
|
||||
$headers = @{
|
||||
"Authorization" = "Bearer $token"
|
||||
"Content-type" = "application/json"
|
||||
@@ -65,7 +67,7 @@ build_script:
|
||||
if( $env:appveyor_repo_branch -eq 'master' -or $env:appveyor_repo_branch -eq 'develop' )
|
||||
{
|
||||
$apiUrl = 'https://ci.appveyor.com/api'
|
||||
$token = 'qts80b5kpq0ooj4x6vvw'
|
||||
$token = 'v2.6hcv3ige78kg3yvg4ge8'
|
||||
$headers = @{
|
||||
"Authorization" = "Bearer $token"
|
||||
"Content-type" = "application/json"
|
||||
|
||||
@@ -18,6 +18,8 @@ local.properties
|
||||
# External tool builders
|
||||
.externalToolBuilders/
|
||||
|
||||
# AppVeyor
|
||||
.appveyor/
|
||||
|
||||
# CDT-specific
|
||||
.cproject
|
||||
@@ -26,6 +28,13 @@ local.properties
|
||||
.buildpath
|
||||
|
||||
|
||||
#####################
|
||||
## Visual Studio Code
|
||||
#####################
|
||||
*.code-workspace
|
||||
.vscode/
|
||||
|
||||
|
||||
#################
|
||||
## Visual Studio
|
||||
#################
|
||||
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
ignore = {
|
||||
"011", -- A syntax error.
|
||||
"021", -- An invalid inline option.
|
||||
"022", -- An unpaired inline push directive.
|
||||
"023", -- An unpaired inline pop directive.
|
||||
"111", -- Setting an undefined global variable.
|
||||
"112", -- Mutating an undefined global variable.
|
||||
"113", -- Accessing an undefined global variable.
|
||||
"121", -- Setting a read-only global variable.
|
||||
"122", -- Setting a read-only field of a global variable.
|
||||
"131", -- Unused implicitly defined global variable.
|
||||
"142", -- Setting an undefined field of a global variable.
|
||||
"143", -- Accessing an undefined field of a global variable.
|
||||
"211", -- Unused local variable.
|
||||
"212", -- Unused argument.
|
||||
"213", -- Unused loop variable.
|
||||
"221", -- Local variable is accessed but never set.
|
||||
"231", -- Local variable is set but never accessed.
|
||||
"232", -- An argument is set but never accessed.
|
||||
"233", -- Loop variable is set but never accessed.
|
||||
"241", -- Local variable is mutated but never accessed.
|
||||
"311", -- Value assigned to a local variable is unused.
|
||||
"312", -- Value of an argument is unused.
|
||||
"313", -- Value of a loop variable is unused.
|
||||
"314", -- Value of a field in a table literal is unused.
|
||||
"321", -- Accessing uninitialized local variable.
|
||||
"331", -- Value assigned to a local variable is mutated but never accessed.
|
||||
"341", -- Mutating uninitialized local variable.
|
||||
"411", -- Redefining a local variable.
|
||||
"412", -- Redefining an argument.
|
||||
"413", -- Redefining a loop variable.
|
||||
"421", -- Shadowing a local variable.
|
||||
"422", -- Shadowing an argument.
|
||||
"423", -- Shadowing a loop variable.
|
||||
"431", -- Shadowing an upvalue.
|
||||
"432", -- Shadowing an upvalue argument.
|
||||
"433", -- Shadowing an upvalue loop variable.
|
||||
"511", -- Unreachable code.
|
||||
"512", -- Loop can be executed at most once.
|
||||
"521", -- Unused label.
|
||||
"531", -- Left-hand side of an assignment is too short.
|
||||
"532", -- Left-hand side of an assignment is too long.
|
||||
"541", -- An empty do end block.
|
||||
"542", -- An empty if branch.
|
||||
"551", -- An empty statement.
|
||||
"561", -- Cyclomatic complexity of a function is too high.
|
||||
"571", -- A numeric for loop goes from #(expr) down to 1 or less without negative step.
|
||||
"611", -- A line consists of nothing but whitespace.
|
||||
"612", -- A line contains trailing whitespace.
|
||||
"613", -- Trailing whitespace in a string.
|
||||
"614", -- Trailing whitespace in a comment.
|
||||
"621", -- Inconsistent indentation (SPACE followed by TAB).
|
||||
"631", -- Line is too long.
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
env.info( '*** MOOSE DYNAMIC INCLUDE START *** ' )
|
||||
|
||||
local base = _G
|
||||
|
||||
__Moose = {}
|
||||
|
||||
__Moose.Include = function( IncludeFile )
|
||||
if not __Moose.Includes[ IncludeFile ] then
|
||||
__Moose.Includes[IncludeFile] = IncludeFile
|
||||
local f = assert( base.loadfile( IncludeFile ) )
|
||||
if f == nil then
|
||||
error ("Moose: Could not load Moose file " .. IncludeFile )
|
||||
else
|
||||
env.info( "Moose: " .. IncludeFile .. " dynamically loaded." )
|
||||
return f()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
__Moose.Includes = {}
|
||||
|
||||
__Moose.Include( 'Scripts/Moose/Modules.lua' )
|
||||
BASE:TraceOnOff( true )
|
||||
env.info( '*** MOOSE INCLUDE END *** ' )
|
||||
File diff suppressed because it is too large
Load Diff
@@ -143,7 +143,7 @@ end
|
||||
-- @return #AI_A2A_CAP
|
||||
function AI_A2A_CAP:New( AICap, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, EngageMinSpeed, EngageMaxSpeed, PatrolAltType )
|
||||
|
||||
return self:New2( AICap, EngageMinSpeed, EngageMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolZone, PatrolMinSpeed, PatrolMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolAltType, PatrolAltType )
|
||||
return self:New2( AICap, EngageMinSpeed, EngageMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolAltType, PatrolZone, PatrolMinSpeed, PatrolMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolAltType )
|
||||
|
||||
end
|
||||
|
||||
|
||||
@@ -658,7 +658,9 @@ do -- AI_A2A_DISPATCHER
|
||||
-- of the race track will randomly selected between 90 (West to East) and 180 (North to South) degrees.
|
||||
-- After a random duration between 10 and 20 minutes, the flight will get a new random orbit location.
|
||||
--
|
||||
-- Note that all parameters except the squadron name are optional. If not specified, default values are taken. Speed and altitude are taken from the
|
||||
-- Note that all parameters except the squadron name are optional. If not specified, default values are taken. Speed and altitude are taken from the CAP command used earlier on, e.g.
|
||||
--
|
||||
-- A2ADispatcher:SetSquadronCap( "Mineralnye", CAPZoneEast, 4000, 10000, 500, 600, 800, 900 )
|
||||
--
|
||||
-- Also note that the center of the race track pattern is chosen randomly within the patrol zone and can be close the the boarder of the zone. Hence, it cannot be guaranteed that the
|
||||
-- whole pattern lies within the patrol zone.
|
||||
@@ -882,8 +884,9 @@ do -- AI_A2A_DISPATCHER
|
||||
--- Enumerator for spawns at airbases
|
||||
-- @type AI_A2A_DISPATCHER.Takeoff
|
||||
-- @extends Wrapper.Group#GROUP.Takeoff
|
||||
|
||||
--- @field #AI_A2A_DISPATCHER.Takeoff Takeoff
|
||||
|
||||
---
|
||||
-- @field #AI_A2A_DISPATCHER.Takeoff Takeoff
|
||||
AI_A2A_DISPATCHER.Takeoff = GROUP.Takeoff
|
||||
|
||||
--- Defnes Landing location.
|
||||
@@ -929,6 +932,8 @@ do -- AI_A2A_DISPATCHER
|
||||
self.DefenderTasks = {} -- The Defenders Tasks.
|
||||
self.DefenderDefault = {} -- The Defender Default Settings over all Squadrons.
|
||||
|
||||
self.SetSendPlayerMessages = false --#boolean Flash messages to player
|
||||
|
||||
-- TODO: Check detection through radar.
|
||||
self.Detection:FilterCategories( { Unit.Category.AIRPLANE, Unit.Category.HELICOPTER } )
|
||||
--self.Detection:InitDetectRadar( true )
|
||||
@@ -949,7 +954,6 @@ do -- AI_A2A_DISPATCHER
|
||||
self:SetDefaultCapTimeInterval( 180, 600 ) -- Between 180 and 600 seconds.
|
||||
self:SetDefaultCapLimit( 1 ) -- Maximum one CAP per squadron.
|
||||
|
||||
|
||||
self:AddTransition( "Started", "Assign", "Started" )
|
||||
|
||||
--- OnAfter Transition Handler for Event Assign.
|
||||
@@ -1707,7 +1711,7 @@ do -- AI_A2A_DISPATCHER
|
||||
end
|
||||
|
||||
|
||||
--- Set the Squadron visible before startup of the dispatcher.
|
||||
--- [DEPRECATED - Might create problems launching planes] Set the Squadron visible before startup of the dispatcher.
|
||||
-- All planes will be spawned as uncontrolled on the parking spot.
|
||||
-- They will lock the parking spot.
|
||||
-- @param #AI_A2A_DISPATCHER self
|
||||
@@ -2356,6 +2360,12 @@ do -- AI_A2A_DISPATCHER
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set flashing player messages on or off
|
||||
-- @param #AI_A2A_DISPATCHER self
|
||||
-- @param #boolean onoff Set messages on (true) or off (false)
|
||||
function AI_A2A_DISPATCHER:SetSendMessages( onoff )
|
||||
self.SetSendPlayerMessages = onoff
|
||||
end
|
||||
|
||||
--- Sets flights to take-off in the air, as part of the defense system.
|
||||
-- @param #AI_A2A_DISPATCHER self
|
||||
@@ -2919,9 +2929,13 @@ do -- AI_A2A_DISPATCHER
|
||||
-- @return #AI_A2A_DISPATCHER.Squadron Squadron The squadron.
|
||||
function AI_A2A_DISPATCHER:GetSquadronFromDefender( Defender )
|
||||
self.Defenders = self.Defenders or {}
|
||||
local DefenderName = Defender:GetName()
|
||||
self:F( { DefenderName = DefenderName } )
|
||||
return self.Defenders[ DefenderName ]
|
||||
if Defender ~= nil then
|
||||
local DefenderName = Defender:GetName()
|
||||
self:F( { DefenderName = DefenderName } )
|
||||
return self.Defenders[ DefenderName ]
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -3035,7 +3049,20 @@ do -- AI_A2A_DISPATCHER
|
||||
for FriendlyDistance, AIFriendly in UTILS.spairs( DefenderFriendlies or {} ) do
|
||||
-- We only allow to ENGAGE targets as long as the Units on both sides are balanced.
|
||||
if AttackerCount > DefenderCount then
|
||||
local Friendly = AIFriendly:GetGroup() -- Wrapper.Group#GROUP
|
||||
--self:I("***** AI_A2A_DISPATCHER:CountDefendersToBeEngaged() *****\nThis is supposed to be a UNIT:")
|
||||
if AIFriendly then
|
||||
local classname = AIFriendly.ClassName or "No Class Name"
|
||||
local unitname = AIFriendly.IdentifiableName or "No Unit Name"
|
||||
--self:I("Class Name: " .. classname)
|
||||
--self:I("Unit Name: " .. unitname)
|
||||
--self:I({AIFriendly})
|
||||
end
|
||||
local Friendly = nil
|
||||
if AIFriendly and AIFriendly:IsAlive() then
|
||||
--self:I("AIFriendly alive, getting GROUP")
|
||||
Friendly = AIFriendly:GetGroup() -- Wrapper.Group#GROUP
|
||||
end
|
||||
|
||||
if Friendly and Friendly:IsAlive() then
|
||||
-- Ok, so we have a friendly near the potential target.
|
||||
-- Now we need to check if the AIGroup has a Task.
|
||||
@@ -3240,7 +3267,9 @@ do -- AI_A2A_DISPATCHER
|
||||
local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup )
|
||||
|
||||
if Squadron then
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. " Wheels up.", DefenderGroup )
|
||||
if self.SetSendPlayerMessages then
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. " Wheels up.", DefenderGroup )
|
||||
end
|
||||
AI_A2A_Fsm:__Patrol( 2 ) -- Start Patrolling
|
||||
end
|
||||
end
|
||||
@@ -3252,10 +3281,10 @@ do -- AI_A2A_DISPATCHER
|
||||
self:GetParent(self).onafterPatrolRoute( self, DefenderGroup, From, Event, To )
|
||||
|
||||
local DefenderName = DefenderGroup:GetCallsign()
|
||||
local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER
|
||||
local Dispatcher = self:GetDispatcher() -- #AI_A2A_DISPATCHER
|
||||
local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup )
|
||||
if Squadron then
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", patrolling.", DefenderGroup )
|
||||
if Squadron and self.SetSendPlayerMessages then
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", patrolling.", DefenderGroup )
|
||||
end
|
||||
|
||||
Dispatcher:ClearDefenderTaskTarget( DefenderGroup )
|
||||
@@ -3271,7 +3300,7 @@ do -- AI_A2A_DISPATCHER
|
||||
local DefenderName = DefenderGroup:GetCallsign()
|
||||
local Dispatcher = self:GetDispatcher() -- #AI_A2A_DISPATCHER
|
||||
local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup )
|
||||
if Squadron then
|
||||
if Squadron and self.SetSendPlayerMessages then
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. " returning to base.", DefenderGroup )
|
||||
end
|
||||
Dispatcher:ClearDefenderTaskTarget( DefenderGroup )
|
||||
@@ -3459,10 +3488,10 @@ do -- AI_A2A_DISPATCHER
|
||||
local DefenderTarget = Dispatcher:GetDefenderTaskTarget( DefenderGroup )
|
||||
|
||||
if DefenderTarget then
|
||||
if Squadron.Language == "EN" then
|
||||
if Squadron.Language == "EN" and self.SetSendPlayerMessages then
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. " wheels up.", DefenderGroup )
|
||||
elseif Squadron.Language == "RU" then
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. " колеса вверх.", DefenderGroup )
|
||||
elseif Squadron.Language == "RU" and self.SetSendPlayerMessages then
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. " колёса вверх.", DefenderGroup )
|
||||
end
|
||||
--Fsm:__Engage( 2, DefenderTarget.Set ) -- Engage on the TargetSetUnit
|
||||
Fsm:EngageRoute( DefenderTarget.Set ) -- Engage on the TargetSetUnit
|
||||
@@ -3480,11 +3509,11 @@ do -- AI_A2A_DISPATCHER
|
||||
local FirstUnit = AttackSetUnit:GetFirst()
|
||||
local Coordinate = FirstUnit:GetCoordinate() -- Core.Point#COORDINATE
|
||||
|
||||
if Squadron.Language == "EN" then
|
||||
if Squadron.Language == "EN" and self.SetSendPlayerMessages then
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", intercepting bogeys at " .. Coordinate:ToStringA2A( DefenderGroup, nil, Squadron.Language ), DefenderGroup )
|
||||
elseif Squadron.Language == "RU" then
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", перехват самолетов в " .. Coordinate:ToStringA2A( DefenderGroup, nil, Squadron.Language ), DefenderGroup )
|
||||
elseif Squadron.Language == "DE" then
|
||||
elseif Squadron.Language == "RU" and self.SetSendPlayerMessages then
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", перехватывая боги в " .. Coordinate:ToStringA2A( DefenderGroup, nil, Squadron.Language ), DefenderGroup )
|
||||
elseif Squadron.Language == "DE" and self.SetSendPlayerMessages then
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", Eindringlinge abfangen bei" .. Coordinate:ToStringA2A( DefenderGroup, nil, Squadron.Language ), DefenderGroup )
|
||||
end
|
||||
end
|
||||
@@ -3502,10 +3531,10 @@ do -- AI_A2A_DISPATCHER
|
||||
local FirstUnit = AttackSetUnit:GetFirst()
|
||||
local Coordinate = FirstUnit:GetCoordinate() -- Core.Point#COORDINATE
|
||||
|
||||
if Squadron.Language == "EN" then
|
||||
if Squadron.Language == "EN" and self.SetSendPlayerMessages then
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", engaging bogeys at " .. Coordinate:ToStringA2A( DefenderGroup, nil, Squadron.Language ), DefenderGroup )
|
||||
elseif Squadron.Language == "RU" then
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", захватывающие самолеты в " .. Coordinate:ToStringA2A( DefenderGroup, nil, Squadron.Language ), DefenderGroup )
|
||||
elseif Squadron.Language == "RU" and self.SetSendPlayerMessages then
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", задействуя боги в " .. Coordinate:ToStringA2A( DefenderGroup, nil, Squadron.Language ), DefenderGroup )
|
||||
end
|
||||
end
|
||||
self:GetParent( Fsm ).onafterEngage( self, DefenderGroup, From, Event, To, AttackSetUnit )
|
||||
@@ -3520,10 +3549,10 @@ do -- AI_A2A_DISPATCHER
|
||||
local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup )
|
||||
|
||||
if Squadron then
|
||||
if Squadron.Language == "EN" then
|
||||
if Squadron.Language == "EN" and self.SetSendPlayerMessages then
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. " returning to base.", DefenderGroup )
|
||||
elseif Squadron.Language == "RU" then
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", возвращаясь на базу.", DefenderGroup )
|
||||
elseif Squadron.Language == "RU" and self.SetSendPlayerMessages then
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", возвращение на базу.", DefenderGroup )
|
||||
end
|
||||
end
|
||||
Dispatcher:ClearDefenderTaskTarget( DefenderGroup )
|
||||
@@ -3551,10 +3580,10 @@ do -- AI_A2A_DISPATCHER
|
||||
local Dispatcher = self:GetDispatcher() -- #AI_A2A_DISPATCHER
|
||||
local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup )
|
||||
|
||||
if Squadron.Language == "EN" then
|
||||
if Squadron.Language == "EN" and self.SetSendPlayerMessages then
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. " landing at base.", DefenderGroup )
|
||||
elseif Squadron.Language == "RU" then
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", захватывающие самолеты в посадка на базу.", DefenderGroup )
|
||||
elseif Squadron.Language == "RU" and self.SetSendPlayerMessages then
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", посадка на базу.", DefenderGroup )
|
||||
end
|
||||
|
||||
if Action and Action == "Destroy" then
|
||||
@@ -3947,6 +3976,30 @@ do
|
||||
function AI_A2A_DISPATCHER:SchedulerCAP( SquadronName )
|
||||
self:CAP( SquadronName )
|
||||
end
|
||||
|
||||
--- Add resources to a Squadron
|
||||
-- @param #AI_A2A_DISPATCHER self
|
||||
-- @param #string Squadron The squadron name.
|
||||
-- @param #number Amount Number of resources to add.
|
||||
function AI_A2A_DISPATCHER:AddToSquadron(Squadron,Amount)
|
||||
local Squadron = self:GetSquadron(Squadron)
|
||||
if Squadron.ResourceCount then
|
||||
Squadron.ResourceCount = Squadron.ResourceCount + Amount
|
||||
end
|
||||
self:T({Squadron = Squadron.Name,SquadronResourceCount = Squadron.ResourceCount})
|
||||
end
|
||||
|
||||
--- Remove resources from a Squadron
|
||||
-- @param #AI_A2A_DISPATCHER self
|
||||
-- @param #string Squadron The squadron name.
|
||||
-- @param #number Amount Number of resources to remove.
|
||||
function AI_A2A_DISPATCHER:RemoveFromSquadron(Squadron,Amount)
|
||||
local Squadron = self:GetSquadron(Squadron)
|
||||
if Squadron.ResourceCount then
|
||||
Squadron.ResourceCount = Squadron.ResourceCount - Amount
|
||||
end
|
||||
self:T({Squadron = Squadron.Name,SquadronResourceCount = Squadron.ResourceCount})
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -4523,5 +4576,5 @@ do
|
||||
return self
|
||||
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
@@ -431,7 +431,7 @@ do -- AI_A2G_DISPATCHER
|
||||
--
|
||||
-- ### 2.2. The **Defense Reactivity**.
|
||||
--
|
||||
-- There are 5 levels that can be configured to tweak the defense reactivity. As explained above, the threat to a defense coordinate is
|
||||
-- There are three levels that can be configured to tweak the defense reactivity. As explained above, the threat to a defense coordinate is
|
||||
-- also determined by the distance of the enemy ground target to the defense coordinate.
|
||||
-- If you want to have a **low** defense reactivity, that is, the probability that an A2G defense will engage to the enemy ground target, then
|
||||
-- use the @{#AI_A2G_DISPATCHER.SetDefenseReactivityLow}() method. For medium and high reactivity, use the methods
|
||||
@@ -999,7 +999,9 @@ do -- AI_A2G_DISPATCHER
|
||||
-- self.Detection:InitDetectRadar( false )
|
||||
-- self.Detection:InitDetectVisual( true )
|
||||
-- self.Detection:SetRefreshTimeInterval( 30 )
|
||||
|
||||
|
||||
self.SetSendPlayerMessages = false --flash messages to players
|
||||
|
||||
self:SetDefenseRadius()
|
||||
self:SetDefenseLimit( nil )
|
||||
self:SetDefenseApproach( AI_A2G_DISPATCHER.DefenseApproach.Random )
|
||||
@@ -3699,7 +3701,9 @@ do -- AI_A2G_DISPATCHER
|
||||
local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup )
|
||||
|
||||
if Squadron then
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", wheels up.", DefenderGroup )
|
||||
if self.SetSendPlayerMessages then
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", wheels up.", DefenderGroup )
|
||||
end
|
||||
AI_A2G_Fsm:Patrol() -- Engage on the TargetSetUnit
|
||||
end
|
||||
end
|
||||
@@ -3711,7 +3715,7 @@ do -- AI_A2G_DISPATCHER
|
||||
local DefenderName = DefenderGroup:GetCallsign()
|
||||
local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER
|
||||
local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup )
|
||||
if Squadron then
|
||||
if Squadron and self.SetSendPlayerMessages then
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", patrolling.", DefenderGroup )
|
||||
end
|
||||
|
||||
@@ -3730,8 +3734,9 @@ do -- AI_A2G_DISPATCHER
|
||||
if Squadron and AttackSetUnit:Count() > 0 then
|
||||
local FirstUnit = AttackSetUnit:GetFirst()
|
||||
local Coordinate = FirstUnit:GetCoordinate() -- Core.Point#COORDINATE
|
||||
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", moving on to ground target at " .. Coordinate:ToStringA2G( DefenderGroup ), DefenderGroup )
|
||||
if self.SetSendPlayerMessages then
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", moving on to ground target at " .. Coordinate:ToStringA2G( DefenderGroup ), DefenderGroup )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3745,8 +3750,9 @@ do -- AI_A2G_DISPATCHER
|
||||
local FirstUnit = AttackSetUnit:GetFirst()
|
||||
if FirstUnit then
|
||||
local Coordinate = FirstUnit:GetCoordinate()
|
||||
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", engaging ground target at " .. Coordinate:ToStringA2G( DefenderGroup ), DefenderGroup )
|
||||
if self.SetSendPlayerMessages then
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", engaging ground target at " .. Coordinate:ToStringA2G( DefenderGroup ), DefenderGroup )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3757,8 +3763,9 @@ do -- AI_A2G_DISPATCHER
|
||||
local DefenderName = DefenderGroup:GetCallsign()
|
||||
local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER
|
||||
local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup )
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", returning to base.", DefenderGroup )
|
||||
|
||||
if self.SetSendPlayerMessages then
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", returning to base.", DefenderGroup )
|
||||
end
|
||||
Dispatcher:ClearDefenderTaskTarget( DefenderGroup )
|
||||
end
|
||||
|
||||
@@ -3770,7 +3777,9 @@ do -- AI_A2G_DISPATCHER
|
||||
local DefenderName = DefenderGroup:GetCallsign()
|
||||
local Dispatcher = AI_A2G_Fsm:GetDispatcher() -- #AI_A2G_DISPATCHER
|
||||
local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup )
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", lost control." )
|
||||
if self.SetSendPlayerMessages then
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", lost control." )
|
||||
end
|
||||
if DefenderGroup:IsAboveRunway() then
|
||||
Dispatcher:RemoveDefenderFromSquadron( Squadron, DefenderGroup )
|
||||
DefenderGroup:Destroy()
|
||||
@@ -3785,8 +3794,9 @@ do -- AI_A2G_DISPATCHER
|
||||
local DefenderName = DefenderGroup:GetCallsign()
|
||||
local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER
|
||||
local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup )
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", landing at base.", DefenderGroup )
|
||||
|
||||
if self.SetSendPlayerMessages then
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", landing at base.", DefenderGroup )
|
||||
end
|
||||
if Action and Action == "Destroy" then
|
||||
Dispatcher:RemoveDefenderFromSquadron( Squadron, DefenderGroup )
|
||||
DefenderGroup:Destroy()
|
||||
@@ -3842,7 +3852,9 @@ do -- AI_A2G_DISPATCHER
|
||||
self:F( { DefenderTarget = DefenderTarget } )
|
||||
|
||||
if DefenderTarget then
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", wheels up.", DefenderGroup )
|
||||
if self.SetSendPlayerMessages then
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", wheels up.", DefenderGroup )
|
||||
end
|
||||
AI_A2G_Fsm:EngageRoute( DefenderTarget.Set ) -- Engage on the TargetSetUnit
|
||||
end
|
||||
end
|
||||
@@ -3857,8 +3869,9 @@ do -- AI_A2G_DISPATCHER
|
||||
if Squadron then
|
||||
local FirstUnit = AttackSetUnit:GetFirst()
|
||||
local Coordinate = FirstUnit:GetCoordinate() -- Core.Point#COORDINATE
|
||||
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", on route to ground target at " .. Coordinate:ToStringA2G( DefenderGroup ), DefenderGroup )
|
||||
if self.SetSendPlayerMessages then
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", on route to ground target at " .. Coordinate:ToStringA2G( DefenderGroup ), DefenderGroup )
|
||||
end
|
||||
end
|
||||
self:GetParent(self).onafterEngageRoute( self, DefenderGroup, From, Event, To, AttackSetUnit )
|
||||
end
|
||||
@@ -3873,8 +3886,9 @@ do -- AI_A2G_DISPATCHER
|
||||
local FirstUnit = AttackSetUnit:GetFirst()
|
||||
if FirstUnit then
|
||||
local Coordinate = FirstUnit:GetCoordinate()
|
||||
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", engaging ground target at " .. Coordinate:ToStringA2G( DefenderGroup ), DefenderGroup )
|
||||
if self.SetSendPlayerMessages then
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", engaging ground target at " .. Coordinate:ToStringA2G( DefenderGroup ), DefenderGroup )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3884,8 +3898,9 @@ do -- AI_A2G_DISPATCHER
|
||||
local DefenderName = DefenderGroup:GetCallsign()
|
||||
local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER
|
||||
local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup )
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", returning to base.", DefenderGroup )
|
||||
|
||||
if self.SetSendPlayerMessages then
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", returning to base.", DefenderGroup )
|
||||
end
|
||||
self:GetParent(self).onafterRTB( self, DefenderGroup, From, Event, To )
|
||||
|
||||
Dispatcher:ClearDefenderTaskTarget( DefenderGroup )
|
||||
@@ -3899,8 +3914,9 @@ do -- AI_A2G_DISPATCHER
|
||||
local DefenderName = DefenderGroup:GetCallsign()
|
||||
local Dispatcher = AI_A2G_Fsm:GetDispatcher() -- #AI_A2G_DISPATCHER
|
||||
local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup )
|
||||
--Dispatcher:MessageToPlayers( Squadron, "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " lost control." )
|
||||
|
||||
if self.SetSendPlayerMessages then
|
||||
Dispatcher:MessageToPlayers( Squadron, "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " lost control." )
|
||||
end
|
||||
if DefenderGroup:IsAboveRunway() then
|
||||
Dispatcher:RemoveDefenderFromSquadron( Squadron, DefenderGroup )
|
||||
DefenderGroup:Destroy()
|
||||
@@ -3915,8 +3931,9 @@ do -- AI_A2G_DISPATCHER
|
||||
local DefenderName = DefenderGroup:GetCallsign()
|
||||
local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER
|
||||
local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup )
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", landing at base.", DefenderGroup )
|
||||
|
||||
if self.SetSendPlayerMessages then
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", landing at base.", DefenderGroup )
|
||||
end
|
||||
if Action and Action == "Destroy" then
|
||||
Dispatcher:RemoveDefenderFromSquadron( Squadron, DefenderGroup )
|
||||
DefenderGroup:Destroy()
|
||||
@@ -3956,7 +3973,7 @@ do -- AI_A2G_DISPATCHER
|
||||
local EvaluateDistance = AttackCoordinate:Get2DDistance( DefenseCoordinate )
|
||||
|
||||
-- Now check if this coordinate is not in a danger zone, meaning, that the attack line is not crossing other coordinates.
|
||||
-- (y1 – y2)x + (x2 – x1)y + (x1y2 – x2y1) = 0
|
||||
-- (y1 - y2)x + (x2 - x1)y + (x1y2 - x2y1) = 0
|
||||
|
||||
local c1 = DefenseCoordinate
|
||||
local c2 = AttackCoordinate
|
||||
@@ -4017,7 +4034,7 @@ do -- AI_A2G_DISPATCHER
|
||||
for DefenderID, DefenderGroup in pairs( DefenderFriendlies or {} ) do
|
||||
|
||||
-- Here we check if the defenders have a defense line to the attackers.
|
||||
-- If the attackers are behind enemy lines or too close to an other defense line; then don´t engage.
|
||||
-- If the attackers are behind enemy lines or too close to an other defense line; then don't engage.
|
||||
local DefenseCoordinate = DefenderGroup:GetCoordinate()
|
||||
local HasDefenseLine = self:HasDefenseLine( DefenseCoordinate, DetectedItem )
|
||||
|
||||
@@ -4320,7 +4337,7 @@ do -- AI_A2G_DISPATCHER
|
||||
|
||||
-- Show tactical situation
|
||||
local ThreatLevel = DetectedItem.Set:CalculateThreatLevelA2G()
|
||||
Report:Add( string.format( " - %1s%s ( %04s ): ( #%02d - %-4s ) %s" , ( DetectedItem.IsDetected == true ) and "!" or " ", DetectedItem.ItemID, DetectedItem.Index, DetectedItem.Set:Count(), DetectedItem.Type or " --- ", string.rep( "■", ThreatLevel ) ) )
|
||||
Report:Add( string.format( " - %1s%s ( %04s ): ( #%02d - %-4s ) %s" , ( DetectedItem.IsDetected == true ) and "!" or " ", DetectedItem.ItemID, DetectedItem.Index, DetectedItem.Set:Count(), DetectedItem.Type or " --- ", string.rep( "■", ThreatLevel ) ) )
|
||||
for Defender, DefenderTask in pairs( self:GetDefenderTasks() ) do
|
||||
local Defender = Defender -- Wrapper.Group#GROUP
|
||||
if DefenderTask.Target and DefenderTask.Target.Index == DetectedItem.Index then
|
||||
@@ -4537,7 +4554,7 @@ do -- AI_A2G_DISPATCHER
|
||||
if self.TacticalDisplay then
|
||||
-- Show tactical situation
|
||||
local ThreatLevel = DetectedItem.Set:CalculateThreatLevelA2G()
|
||||
Report:Add( string.format( " - %1s%s ( %4s ): ( #%d - %4s ) %s" , ( DetectedItem.IsDetected == true ) and "!" or " ", DetectedItem.ItemID, DetectedItem.Index, DetectedItem.Set:Count(), DetectedItem.Type or " --- ", string.rep( "■", ThreatLevel ) ) )
|
||||
Report:Add( string.format( " - %1s%s ( %4s ): ( #%d - %4s ) %s" , ( DetectedItem.IsDetected == true ) and "!" or " ", DetectedItem.ItemID, DetectedItem.Index, DetectedItem.Set:Count(), DetectedItem.Type or " --- ", string.rep( "■", ThreatLevel ) ) )
|
||||
for Defender, DefenderTask in pairs( self:GetDefenderTasks() ) do
|
||||
local Defender = Defender -- Wrapper.Group#GROUP
|
||||
if DefenderTask.Target and DefenderTask.Target.Index == DetectedItem.Index then
|
||||
@@ -4706,6 +4723,35 @@ do
|
||||
local PatrolTaskType = PatrolTaskTypes[math.random(1,3)]
|
||||
self:Patrol( SquadronName, PatrolTaskType )
|
||||
end
|
||||
|
||||
|
||||
--- Set flashing player messages on or off
|
||||
-- @param #AI_A2G_DISPATCHER self
|
||||
-- @param #boolean onoff Set messages on (true) or off (false)
|
||||
function AI_A2G_DISPATCHER:SetSendMessages( onoff )
|
||||
self.SetSendPlayerMessages = onoff
|
||||
end
|
||||
end
|
||||
|
||||
--- Add resources to a Squadron
|
||||
-- @param #AI_A2G_DISPATCHER self
|
||||
-- @param #string Squadron The squadron name.
|
||||
-- @param #number Amount Number of resources to add.
|
||||
function AI_A2G_DISPATCHER:AddToSquadron(Squadron,Amount)
|
||||
local Squadron = self:GetSquadron(Squadron)
|
||||
if Squadron.ResourceCount then
|
||||
Squadron.ResourceCount = Squadron.ResourceCount + Amount
|
||||
end
|
||||
self:T({Squadron = Squadron.Name,SquadronResourceCount = Squadron.ResourceCount})
|
||||
end
|
||||
|
||||
--- Remove resources from a Squadron
|
||||
-- @param #AI_A2G_DISPATCHER self
|
||||
-- @param #string Squadron The squadron name.
|
||||
-- @param #number Amount Number of resources to remove.
|
||||
function AI_A2G_DISPATCHER:RemoveFromSquadron(Squadron,Amount)
|
||||
local Squadron = self:GetSquadron(Squadron)
|
||||
if Squadron.ResourceCount then
|
||||
Squadron.ResourceCount = Squadron.ResourceCount - Amount
|
||||
end
|
||||
self:T({Squadron = Squadron.Name,SquadronResourceCount = Squadron.ResourceCount})
|
||||
end
|
||||
@@ -585,29 +585,36 @@ function AI_AIR:onafterRTB( AIGroup, From, Event, To )
|
||||
|
||||
if AIGroup and AIGroup:IsAlive() then
|
||||
|
||||
self:I( "Group " .. AIGroup:GetName() .. " ... RTB! ( " .. self:GetState() .. " )" )
|
||||
self:T( "Group " .. AIGroup:GetName() .. " ... RTB! ( " .. self:GetState() .. " )" )
|
||||
|
||||
self:ClearTargetDistance()
|
||||
--AIGroup:ClearTasks()
|
||||
|
||||
|
||||
AIGroup:OptionProhibitAfterburner(true)
|
||||
|
||||
local EngageRoute = {}
|
||||
|
||||
--- Calculate the target route point.
|
||||
|
||||
local FromCoord = AIGroup:GetCoordinate()
|
||||
local ToTargetCoord = self.HomeAirbase:GetCoordinate()
|
||||
|
||||
if not self.RTBMinSpeed and not self.RTBMaxSpeed then
|
||||
local ToTargetCoord = self.HomeAirbase:GetCoordinate() -- coordinate is on land height(!)
|
||||
local ToTargetVec3 = ToTargetCoord:GetVec3()
|
||||
ToTargetVec3.y = ToTargetCoord:GetLandHeight()+1000 -- let's set this 1000m/3000 feet above ground
|
||||
local ToTargetCoord2 = COORDINATE:NewFromVec3( ToTargetVec3 )
|
||||
|
||||
if not self.RTBMinSpeed or not self.RTBMaxSpeed then
|
||||
local RTBSpeedMax = AIGroup:GetSpeedMax()
|
||||
self:SetRTBSpeed( RTBSpeedMax * 0.25, RTBSpeedMax * 0.25 )
|
||||
self:SetRTBSpeed( RTBSpeedMax * 0.5, RTBSpeedMax * 0.6 )
|
||||
end
|
||||
|
||||
local RTBSpeed = math.random( self.RTBMinSpeed, self.RTBMaxSpeed )
|
||||
local ToAirbaseAngle = FromCoord:GetAngleDegrees( FromCoord:GetDirectionVec3( ToTargetCoord ) )
|
||||
--local ToAirbaseAngle = FromCoord:GetAngleDegrees( FromCoord:GetDirectionVec3( ToTargetCoord2 ) )
|
||||
|
||||
local Distance = FromCoord:Get2DDistance( ToTargetCoord )
|
||||
local Distance = FromCoord:Get2DDistance( ToTargetCoord2 )
|
||||
|
||||
local ToAirbaseCoord = FromCoord:Translate( 5000, ToAirbaseAngle )
|
||||
--local ToAirbaseCoord = FromCoord:Translate( 5000, ToAirbaseAngle )
|
||||
local ToAirbaseCoord = ToTargetCoord2
|
||||
|
||||
if Distance < 5000 then
|
||||
self:I( "RTB and near the airbase!" )
|
||||
self:Home()
|
||||
|
||||
@@ -418,6 +418,7 @@ end
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @param Core.Set#SET_UNIT AttackSetUnit Unit set to be attacked.
|
||||
function AI_AIR_ENGAGE:onafterEngageRoute( DefenderGroup, From, Event, To, AttackSetUnit )
|
||||
self:I( { DefenderGroup, From, Event, To, AttackSetUnit } )
|
||||
|
||||
@@ -425,7 +426,7 @@ function AI_AIR_ENGAGE:onafterEngageRoute( DefenderGroup, From, Event, To, Attac
|
||||
|
||||
self.AttackSetUnit = AttackSetUnit -- Kept in memory in case of resume from refuel in air!
|
||||
|
||||
local AttackCount = AttackSetUnit:Count()
|
||||
local AttackCount = AttackSetUnit:CountAlive()
|
||||
|
||||
if AttackCount > 0 then
|
||||
|
||||
@@ -510,6 +511,7 @@ end
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @param Core.Set#SET_UNIT AttackSetUnit Set of units to be attacked.
|
||||
function AI_AIR_ENGAGE:onafterEngage( DefenderGroup, From, Event, To, AttackSetUnit )
|
||||
self:F( { DefenderGroup, From, Event, To, AttackSetUnit} )
|
||||
|
||||
@@ -517,8 +519,8 @@ function AI_AIR_ENGAGE:onafterEngage( DefenderGroup, From, Event, To, AttackSetU
|
||||
|
||||
self.AttackSetUnit = AttackSetUnit -- Kept in memory in case of resume from refuel in air!
|
||||
|
||||
local AttackCount = AttackSetUnit:Count()
|
||||
self:I({AttackCount = AttackCount})
|
||||
local AttackCount = AttackSetUnit:CountAlive()
|
||||
self:T({AttackCount = AttackCount})
|
||||
|
||||
if AttackCount > 0 then
|
||||
|
||||
@@ -531,6 +533,10 @@ function AI_AIR_ENGAGE:onafterEngage( DefenderGroup, From, Event, To, AttackSetU
|
||||
DefenderCoord:SetY( EngageAltitude ) -- Ground targets don't have an altitude.
|
||||
|
||||
local TargetCoord = AttackSetUnit:GetFirst():GetPointVec3()
|
||||
if not TargetCoord then
|
||||
self:Return()
|
||||
return
|
||||
end
|
||||
TargetCoord:SetY( EngageAltitude ) -- Ground targets don't have an altitude.
|
||||
|
||||
local TargetDistance = DefenderCoord:Get2DDistance( TargetCoord )
|
||||
|
||||
@@ -515,8 +515,8 @@ function AI_BAI_ZONE:onafterEngage( Controllable, From, Event, To,
|
||||
--- Calculate the current route point.
|
||||
local CurrentVec2 = self.Controllable:GetVec2()
|
||||
|
||||
--TODO: Create GetAltitude function for GROUP, and delete GetUnit(1).
|
||||
local CurrentAltitude = self.Controllable:GetUnit(1):GetAltitude()
|
||||
--DONE: Create GetAltitude function for GROUP, and delete GetUnit(1).
|
||||
local CurrentAltitude = self.Controllable:GetAltitude()
|
||||
local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y )
|
||||
local ToEngageZoneSpeed = self.PatrolMaxSpeed
|
||||
local CurrentRoutePoint = CurrentPointVec3:WaypointAir(
|
||||
|
||||
@@ -428,8 +428,10 @@ function AI_CAP_ZONE:onafterEngage( Controllable, From, Event, To )
|
||||
--- Calculate the current route point.
|
||||
local CurrentVec2 = self.Controllable:GetVec2()
|
||||
|
||||
--TODO: Create GetAltitude function for GROUP, and delete GetUnit(1).
|
||||
local CurrentAltitude = self.Controllable:GetUnit(1):GetAltitude()
|
||||
if not CurrentVec2 then return self end
|
||||
|
||||
--DONE: Create GetAltitude function for GROUP, and delete GetUnit(1).
|
||||
local CurrentAltitude = self.Controllable:GetAltitude()
|
||||
local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y )
|
||||
local ToEngageZoneSpeed = self.PatrolMaxSpeed
|
||||
local CurrentRoutePoint = CurrentPointVec3:WaypointAir(
|
||||
|
||||
@@ -459,8 +459,8 @@ function AI_CAS_ZONE:onafterEngage( Controllable, From, Event, To,
|
||||
--- Calculate the current route point.
|
||||
local CurrentVec2 = self.Controllable:GetVec2()
|
||||
|
||||
--TODO: Create GetAltitude function for GROUP, and delete GetUnit(1).
|
||||
local CurrentAltitude = self.Controllable:GetUnit(1):GetAltitude()
|
||||
--DONE: Create GetAltitude function for GROUP, and delete GetUnit(1).
|
||||
local CurrentAltitude = self.Controllable:GetAltitude()
|
||||
local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y )
|
||||
local ToEngageZoneSpeed = self.PatrolMaxSpeed
|
||||
local CurrentRoutePoint = CurrentPointVec3:WaypointAir(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
--- **AI** -- (R2.4) - Models the intelligent transportation of infantry and other cargo.
|
||||
--- **AI** - Models the intelligent transportation of infantry and other cargo.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
@@ -35,10 +35,9 @@ AI_CARGO = {
|
||||
|
||||
--- Creates a new AI_CARGO object.
|
||||
-- @param #AI_CARGO self
|
||||
-- @param Wrapper.Group#GROUP Carrier
|
||||
-- @param Core.Set#SET_CARGO CargoSet
|
||||
-- @param #number CombatRadius
|
||||
-- @return #AI_CARGO
|
||||
-- @param Wrapper.Group#GROUP Carrier Cargo carrier group.
|
||||
-- @param Core.Set#SET_CARGO CargoSet Set of cargo(s) to transport.
|
||||
-- @return #AI_CARGO self
|
||||
function AI_CARGO:New( Carrier, CargoSet )
|
||||
|
||||
local self = BASE:Inherit( self, FSM_CONTROLLABLE:New( Carrier ) ) -- #AI_CARGO
|
||||
@@ -48,18 +47,21 @@ function AI_CARGO:New( Carrier, CargoSet )
|
||||
|
||||
self:SetStartState( "Unloaded" )
|
||||
|
||||
self:AddTransition( "Unloaded", "Pickup", "*" )
|
||||
self:AddTransition( "Loaded", "Deploy", "*" )
|
||||
-- Board
|
||||
self:AddTransition( "Unloaded", "Pickup", "Unloaded" )
|
||||
self:AddTransition( "*", "Load", "*" )
|
||||
self:AddTransition( "*", "Reload", "*" )
|
||||
self:AddTransition( "*", "Board", "*" )
|
||||
self:AddTransition( "*", "Loaded", "Loaded" )
|
||||
self:AddTransition( "Loaded", "PickedUp", "Loaded" )
|
||||
|
||||
self:AddTransition( "*", "Load", "Boarding" )
|
||||
self:AddTransition( { "Boarding", "Loaded" }, "Board", "Boarding" )
|
||||
self:AddTransition( "Boarding", "Loaded", "Boarding" )
|
||||
self:AddTransition( "Boarding", "PickedUp", "Loaded" )
|
||||
|
||||
self:AddTransition( "Loaded", "Unload", "Unboarding" )
|
||||
self:AddTransition( "Unboarding", "Unboard", "Unboarding" )
|
||||
self:AddTransition( "Unboarding", "Unloaded", "Unboarding" )
|
||||
self:AddTransition( "Unboarding", "Deployed", "Unloaded" )
|
||||
-- Unload
|
||||
self:AddTransition( "Loaded", "Deploy", "*" )
|
||||
self:AddTransition( "*", "Unload", "*" )
|
||||
self:AddTransition( "*", "Unboard", "*" )
|
||||
self:AddTransition( "*", "Unloaded", "Unloaded" )
|
||||
self:AddTransition( "Unloaded", "Deployed", "Unloaded" )
|
||||
|
||||
|
||||
--- Pickup Handler OnBefore for AI_CARGO
|
||||
-- @function [parent=#AI_CARGO] OnBeforePickup
|
||||
@@ -580,4 +582,3 @@ function AI_CARGO:onafterDeployed( Carrier, From, Event, To, DeployZone, Defend
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
--- **AI** -- (R2.4) - Models the intelligent transportation of infantry and other cargo.
|
||||
--- **AI** - Models the intelligent transportation of cargo using ground vehicles.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
@@ -98,7 +98,8 @@ function AI_CARGO_APC:New( APC, CargoSet, CombatRadius )
|
||||
self:AddTransition( "*", "Guard", "Unloaded" )
|
||||
self:AddTransition( "*", "Home", "*" )
|
||||
self:AddTransition( "*", "Reload", "Boarding" )
|
||||
|
||||
self:AddTransition( "*", "Deployed", "*" )
|
||||
self:AddTransition( "*", "PickedUp", "*" )
|
||||
self:AddTransition( "*", "Destroyed", "Destroyed" )
|
||||
|
||||
self:SetCombatRadius( CombatRadius )
|
||||
@@ -157,6 +158,45 @@ function AI_CARGO_APC:SetCarrier( CargoCarrier )
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set whether or not the carrier will use roads to *pickup* and *deploy* the cargo.
|
||||
-- @param #AI_CARGO_APC self
|
||||
-- @param #boolean Offroad If true, carrier will not use roads. If `nil` or `false` the carrier will use roads when available.
|
||||
-- @param #number Formation Offroad formation used. Default is `ENUMS.Formation.Vehicle.Offroad`.
|
||||
-- @return #AI_CARGO_APC self
|
||||
function AI_CARGO_APC:SetOffRoad(Offroad, Formation)
|
||||
|
||||
self:SetPickupOffRoad(Offroad, Formation)
|
||||
self:SetDeployOffRoad(Offroad, Formation)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set whether the carrier will *not* use roads to *pickup* the cargo.
|
||||
-- @param #AI_CARGO_APC self
|
||||
-- @param #boolean Offroad If true, carrier will not use roads.
|
||||
-- @param #number Formation Offroad formation used. Default is `ENUMS.Formation.Vehicle.Offroad`.
|
||||
-- @return #AI_CARGO_APC self
|
||||
function AI_CARGO_APC:SetPickupOffRoad(Offroad, Formation)
|
||||
|
||||
self.pickupOffroad=Offroad
|
||||
self.pickupFormation=Formation or ENUMS.Formation.Vehicle.OffRoad
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set whether the carrier will *not* use roads to *deploy* the cargo.
|
||||
-- @param #AI_CARGO_APC self
|
||||
-- @param #boolean Offroad If true, carrier will not use roads.
|
||||
-- @param #number Formation Offroad formation used. Default is `ENUMS.Formation.Vehicle.Offroad`.
|
||||
-- @return #AI_CARGO_APC self
|
||||
function AI_CARGO_APC:SetDeployOffRoad(Offroad, Formation)
|
||||
|
||||
self.deployOffroad=Offroad
|
||||
self.deployFormation=Formation or ENUMS.Formation.Vehicle.OffRoad
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Find a free Carrier within a radius.
|
||||
-- @param #AI_CARGO_APC self
|
||||
@@ -350,10 +390,13 @@ function AI_CARGO_APC:onafterFollow( APC, From, Event, To )
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- @param #AI_CARGO_APC
|
||||
-- @param Wrapper.Group#GROUP APC
|
||||
function AI_CARGO_APC._Pickup( APC, self, Coordinate, Speed, PickupZone )
|
||||
--- Pickup task function. Triggers Load event.
|
||||
-- @param Wrapper.Group#GROUP APC The cargo carrier group.
|
||||
-- @param #AI_CARGO_APC sel `AI_CARGO_APC` class.
|
||||
-- @param Core.Point#COORDINATE Coordinate. The coordinate (not used).
|
||||
-- @param #number Speed Speed (not used).
|
||||
-- @param Core.Zone#ZONE PickupZone Pickup zone.
|
||||
function AI_CARGO_APC._Pickup(APC, self, Coordinate, Speed, PickupZone)
|
||||
|
||||
APC:F( { "AI_CARGO_APC._Pickup:", APC:GetName() } )
|
||||
|
||||
@@ -362,8 +405,12 @@ function AI_CARGO_APC._Pickup( APC, self, Coordinate, Speed, PickupZone )
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function AI_CARGO_APC._Deploy( APC, self, Coordinate, DeployZone )
|
||||
--- Deploy task function. Triggers Unload event.
|
||||
-- @param Wrapper.Group#GROUP APC The cargo carrier group.
|
||||
-- @param #AI_CARGO_APC self `AI_CARGO_APC` class.
|
||||
-- @param Core.Point#COORDINATE Coordinate. The coordinate (not used).
|
||||
-- @param Core.Zone#ZONE DeployZone Deploy zone.
|
||||
function AI_CARGO_APC._Deploy(APC, self, Coordinate, DeployZone)
|
||||
|
||||
APC:F( { "AI_CARGO_APC._Deploy:", APC } )
|
||||
|
||||
@@ -392,12 +439,20 @@ function AI_CARGO_APC:onafterPickup( APC, From, Event, To, Coordinate, Speed, He
|
||||
self.RoutePickup = true
|
||||
|
||||
local _speed=Speed or APC:GetSpeedMax()*0.5
|
||||
|
||||
-- Route on road.
|
||||
local Waypoints = {}
|
||||
|
||||
if self.pickupOffroad then
|
||||
Waypoints[1]=APC:GetCoordinate():WaypointGround(Speed, self.pickupFormation)
|
||||
Waypoints[2]=Coordinate:WaypointGround(_speed, self.pickupFormation, DCSTasks)
|
||||
else
|
||||
Waypoints=APC:TaskGroundOnRoad(Coordinate, _speed, ENUMS.Formation.Vehicle.OffRoad, true)
|
||||
end
|
||||
|
||||
local Waypoints = APC:TaskGroundOnRoad( Coordinate, _speed, "Line abreast", true )
|
||||
|
||||
local TaskFunction = APC:TaskFunction( "AI_CARGO_APC._Pickup", self, Coordinate, Speed, PickupZone )
|
||||
|
||||
self:F({Waypoints = Waypoints})
|
||||
|
||||
local Waypoint = Waypoints[#Waypoints]
|
||||
APC:SetTaskWaypoint( Waypoint, TaskFunction ) -- Set for the given Route at Waypoint 2 the TaskRouteToZone.
|
||||
|
||||
@@ -428,18 +483,34 @@ function AI_CARGO_APC:onafterDeploy( APC, From, Event, To, Coordinate, Speed, He
|
||||
|
||||
self.RouteDeploy = true
|
||||
|
||||
local _speed=Speed or APC:GetSpeedMax()*0.5
|
||||
|
||||
local Waypoints = APC:TaskGroundOnRoad( Coordinate, _speed, "Line abreast", true )
|
||||
-- Set speed in km/h.
|
||||
local speedmax=APC:GetSpeedMax()
|
||||
local _speed=Speed or speedmax*0.5
|
||||
_speed=math.min(_speed, speedmax)
|
||||
|
||||
-- Route on road.
|
||||
local Waypoints = {}
|
||||
|
||||
if self.deployOffroad then
|
||||
Waypoints[1]=APC:GetCoordinate():WaypointGround(Speed, self.deployFormation)
|
||||
Waypoints[2]=Coordinate:WaypointGround(_speed, self.deployFormation, DCSTasks)
|
||||
else
|
||||
Waypoints=APC:TaskGroundOnRoad(Coordinate, _speed, ENUMS.Formation.Vehicle.OffRoad, true)
|
||||
end
|
||||
|
||||
-- Task function
|
||||
local TaskFunction = APC:TaskFunction( "AI_CARGO_APC._Deploy", self, Coordinate, DeployZone )
|
||||
|
||||
self:F({Waypoints = Waypoints})
|
||||
-- Last waypoint
|
||||
local Waypoint = Waypoints[#Waypoints]
|
||||
APC:SetTaskWaypoint( Waypoint, TaskFunction ) -- Set for the given Route at Waypoint 2 the TaskRouteToZone.
|
||||
|
||||
-- Set task function
|
||||
APC:SetTaskWaypoint(Waypoint, TaskFunction) -- Set for the given Route at Waypoint 2 the TaskRouteToZone.
|
||||
|
||||
-- Route group
|
||||
APC:Route( Waypoints, 1 ) -- Move after a random seconds to the Route. See the Route method for details.
|
||||
|
||||
-- Call parent function.
|
||||
self:GetParent( self, AI_CARGO_APC ).onafterDeploy( self, APC, From, Event, To, Coordinate, Speed, Height, DeployZone )
|
||||
|
||||
end
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
--- **AI** -- (R2.4) - Models the intelligent transportation of infantry (cargo).
|
||||
--- **AI** - Models the intelligent transportation of cargo using airplanes.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
@@ -408,9 +408,6 @@ function AI_CARGO_AIRPLANE:onafterUnload( Airplane, From, Event, To, DeployZone
|
||||
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
--- Route the airplane from one airport or it's current position to another airbase.
|
||||
-- @param #AI_CARGO_AIRPLANE self
|
||||
-- @param Wrapper.Group#GROUP Airplane Airplane group to be routed.
|
||||
@@ -438,14 +435,10 @@ function AI_CARGO_AIRPLANE:Route( Airplane, Airbase, Speed, Height, Uncontrolled
|
||||
|
||||
-- To point.
|
||||
local AirbasePointVec2 = Airbase:GetPointVec2()
|
||||
local ToWaypoint = AirbasePointVec2:WaypointAir(
|
||||
POINT_VEC3.RoutePointAltType.BARO,
|
||||
"Land",
|
||||
"Landing",
|
||||
Speed or Airplane:GetSpeedMax()*0.8
|
||||
)
|
||||
ToWaypoint["airdromeId"] = Airbase:GetID()
|
||||
ToWaypoint["speed_locked"] = true
|
||||
local ToWaypoint = AirbasePointVec2:WaypointAir(POINT_VEC3.RoutePointAltType.BARO, "Land", "Landing", Speed or Airplane:GetSpeedMax()*0.8, true, Airbase)
|
||||
|
||||
--ToWaypoint["airdromeId"] = Airbase:GetID()
|
||||
--ToWaypoint["speed_locked"] = true
|
||||
|
||||
|
||||
-- If self.Airbase~=nil then group is currently at an airbase, where it should be respawned.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
--- **AI** -- (R2.4) - Models the intelligent transportation of infantry and other cargo.
|
||||
--- **AI** - Models the intelligent transportation of infantry and other cargo.
|
||||
--
|
||||
-- ## Features:
|
||||
--
|
||||
@@ -1104,7 +1104,7 @@ function AI_CARGO_DISPATCHER:onafterMonitor()
|
||||
-- The Pickup sequence ...
|
||||
-- Check if this Carrier need to go and Pickup something...
|
||||
-- So, if the cargo bay is not full yet with cargo to be loaded ...
|
||||
self:I( { Carrier = CarrierGroupName, IsRelocating = AI_Cargo:IsRelocating(), IsTransporting = AI_Cargo:IsTransporting() } )
|
||||
self:T( { Carrier = CarrierGroupName, IsRelocating = AI_Cargo:IsRelocating(), IsTransporting = AI_Cargo:IsTransporting() } )
|
||||
if AI_Cargo:IsRelocating() == false and AI_Cargo:IsTransporting() == false then
|
||||
-- ok, so there is a free Carrier
|
||||
-- now find the first cargo that is Unloaded
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
--- **AI** -- (2.4) - Models the intelligent transportation of infantry and other cargo using APCs.
|
||||
--- **AI** - Models the intelligent transportation of infantry and other cargo using APCs.
|
||||
--
|
||||
-- ## Features:
|
||||
--
|
||||
@@ -181,25 +181,36 @@ function AI_CARGO_DISPATCHER_APC:New( APCSet, CargoSet, PickupZoneSet, DeployZon
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- AI cargo
|
||||
-- @param #AI_CARGO_DISPATCHER_APC self
|
||||
-- @param Wrapper.Group#GROUP APC The APC carrier.
|
||||
-- @param Core.Set#SET_CARGO CargoSet Cargo set.
|
||||
-- @return AI.AI_Cargo_APC#AI_CARGO_DISPATCHER_APC AI cargo APC object.
|
||||
function AI_CARGO_DISPATCHER_APC:AICargo( APC, CargoSet )
|
||||
|
||||
return AI_CARGO_APC:New( APC, CargoSet, self.CombatRadius )
|
||||
local aicargoapc=AI_CARGO_APC:New(APC, CargoSet, self.CombatRadius)
|
||||
|
||||
aicargoapc:SetDeployOffRoad(self.deployOffroad, self.deployFormation)
|
||||
aicargoapc:SetPickupOffRoad(self.pickupOffroad, self.pickupFormation)
|
||||
|
||||
return aicargoapc
|
||||
end
|
||||
|
||||
--- Enable/Disable unboarding of cargo (infantry) when enemies are nearby (to help defend the carrier).
|
||||
-- This is only valid for APCs and trucks etc, thus ground vehicles.
|
||||
-- @param #AI_CARGO_DISPATCHER_APC self
|
||||
-- @param #number CombatRadius Provide the combat radius to defend the carrier by unboarding the cargo when enemies are nearby.
|
||||
-- When the combat radius is 0, no defense will happen of the carrier.
|
||||
-- When the combat radius is 0 (default), no defense will happen of the carrier.
|
||||
-- When the combat radius is not provided, no defense will happen!
|
||||
-- @return #AI_CARGO_DISPATCHER_APC
|
||||
-- @usage
|
||||
--
|
||||
-- -- Disembark the infantry when the carrier is under attack.
|
||||
-- AICargoDispatcher:SetCombatRadius( true )
|
||||
-- AICargoDispatcher:SetCombatRadius( 500 )
|
||||
--
|
||||
-- -- Keep the cargo in the carrier when the carrier is under attack.
|
||||
-- AICargoDispatcher:SetCombatRadius( false )
|
||||
-- AICargoDispatcher:SetCombatRadius( 0 )
|
||||
function AI_CARGO_DISPATCHER_APC:SetCombatRadius( CombatRadius )
|
||||
|
||||
self.CombatRadius = CombatRadius or 0
|
||||
@@ -207,3 +218,41 @@ function AI_CARGO_DISPATCHER_APC:SetCombatRadius( CombatRadius )
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set whether the carrier will *not* use roads to *pickup* and *deploy* the cargo.
|
||||
-- @param #AI_CARGO_DISPATCHER_APC self
|
||||
-- @param #boolean Offroad If true, carrier will not use roads.
|
||||
-- @param #number Formation Offroad formation used. Default is `ENUMS.Formation.Vehicle.Offroad`.
|
||||
-- @return #AI_CARGO_DISPATCHER_APC self
|
||||
function AI_CARGO_DISPATCHER_APC:SetOffRoad(Offroad, Formation)
|
||||
|
||||
self:SetPickupOffRoad(Offroad, Formation)
|
||||
self:SetDeployOffRoad(Offroad, Formation)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set whether the carrier will *not* use roads to *pickup* the cargo.
|
||||
-- @param #AI_CARGO_DISPATCHER_APC self
|
||||
-- @param #boolean Offroad If true, carrier will not use roads.
|
||||
-- @param #number Formation Offroad formation used. Default is `ENUMS.Formation.Vehicle.Offroad`.
|
||||
-- @return #AI_CARGO_DISPATCHER_APC self
|
||||
function AI_CARGO_DISPATCHER_APC:SetPickupOffRoad(Offroad, Formation)
|
||||
|
||||
self.pickupOffroad=Offroad
|
||||
self.pickupFormation=Formation or ENUMS.Formation.Vehicle.OffRoad
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set whether the carrier will *not* use roads to *deploy* the cargo.
|
||||
-- @param #AI_CARGO_DISPATCHER_APC self
|
||||
-- @param #boolean Offroad If true, carrier will not use roads.
|
||||
-- @param #number Formation Offroad formation used. Default is `ENUMS.Formation.Vehicle.Offroad`.
|
||||
-- @return #AI_CARGO_DISPATCHER_APC self
|
||||
function AI_CARGO_DISPATCHER_APC:SetDeployOffRoad(Offroad, Formation)
|
||||
|
||||
self.deployOffroad=Offroad
|
||||
self.deployFormation=Formation or ENUMS.Formation.Vehicle.OffRoad
|
||||
|
||||
return self
|
||||
end
|
||||
@@ -174,8 +174,8 @@ function AI_CARGO_DISPATCHER_HELICOPTER:New( HelicopterSet, CargoSet, PickupZone
|
||||
self:SetPickupSpeed( 350, 150 )
|
||||
self:SetDeploySpeed( 350, 150 )
|
||||
|
||||
self:SetPickupRadius( 0, 0 )
|
||||
self:SetDeployRadius( 0, 0 )
|
||||
self:SetPickupRadius( 40, 12 )
|
||||
self:SetDeployRadius( 40, 12 )
|
||||
|
||||
self:SetPickupHeight( 500, 200 )
|
||||
self:SetDeployHeight( 500, 200 )
|
||||
@@ -186,6 +186,9 @@ end
|
||||
|
||||
function AI_CARGO_DISPATCHER_HELICOPTER:AICargo( Helicopter, CargoSet )
|
||||
|
||||
return AI_CARGO_HELICOPTER:New( Helicopter, CargoSet )
|
||||
local dispatcher = AI_CARGO_HELICOPTER:New( Helicopter, CargoSet )
|
||||
dispatcher:SetLandingSpeedAndHeight(27, 6)
|
||||
return dispatcher
|
||||
|
||||
end
|
||||
|
||||
|
||||
@@ -0,0 +1,193 @@
|
||||
--- **AI** -- (2.5.1) - Models the intelligent transportation of infantry and other cargo using Ships
|
||||
--
|
||||
-- ## Features:
|
||||
--
|
||||
-- * Transport cargo to various deploy zones using naval vehicles.
|
||||
-- * Various @{Cargo.Cargo#CARGO} types can be transported, including infantry, vehicles, and crates.
|
||||
-- * Define a deploy zone of various types to determine the destination of the cargo.
|
||||
-- * Ships will follow shipping lanes as defined in the Mission Editor.
|
||||
-- * Multiple ships can transport multiple cargo as a single group.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ## Test Missions:
|
||||
--
|
||||
-- NEED TO DO
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **acrojason** (derived from AI_Cargo_Dispatcher_APC by FlightControl)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module AI.AI_Cargo_Dispatcher_Ship
|
||||
-- @image AI_Cargo_Dispatcher.JPG
|
||||
|
||||
--- @type AI_CARGO_DISPATCHER_SHIP
|
||||
-- @extends AI.AI_Cargo_Dispatcher#AI_CARGO_DISPATCHER
|
||||
|
||||
|
||||
--- A dynamic cargo transportation capability for AI groups.
|
||||
--
|
||||
-- Naval vessels can be mobilized to semi-intelligently transport cargo within the simulation.
|
||||
--
|
||||
-- The AI_CARGO_DISPATCHER_SHIP module is derived from the AI_CARGO_DISPATCHER module.
|
||||
--
|
||||
-- ## Note! In order to fully understand the mechanisms of the AI_CARGO_DISPATCHER_SHIP class, it is recommended that you first consult and READ the documentation of the @{AI.AI_Cargo_Dispatcher} module!!!
|
||||
--
|
||||
-- This will be particularly helpful in order to determine how to **Tailor the different cargo handling events**.
|
||||
--
|
||||
-- The AI_CARGO_DISPATCHER_SHIP class uses the @{Cargo.Cargo} capabilities within the MOOSE framwork.
|
||||
-- Also ensure that you fully understand how to declare and setup Cargo objects within the MOOSE framework before using this class.
|
||||
-- CARGO derived objects must generally be declared within the mission to make the AI_CARGO_DISPATCHER_SHIP object recognize the cargo.
|
||||
--
|
||||
--
|
||||
-- # 1) AI_CARGO_DISPATCHER_SHIP constructor.
|
||||
--
|
||||
-- * @{AI_CARGO_DISPATCHER_SHIP.New}(): Creates a new AI_CARGO_DISPATCHER_SHIP object.
|
||||
--
|
||||
-- ---
|
||||
--
|
||||
-- # 2) AI_CARGO_DISPATCHER_SHIP is a Finite State Machine.
|
||||
--
|
||||
-- This section must be read as follows... Each of the rows indicate a state transition, triggered through an event, and with an ending state of the event was executed.
|
||||
-- The first column is the **From** state, the second column the **Event**, and the third column the **To** state.
|
||||
--
|
||||
-- So, each of the rows have the following structure.
|
||||
--
|
||||
-- * **From** => **Event** => **To**
|
||||
--
|
||||
-- Important to know is that an event can only be executed if the **current state** is the **From** state.
|
||||
-- This, when an **Event** that is being triggered has a **From** state that is equal to the **Current** state of the state machine, the event will be executed,
|
||||
-- and the resulting state will be the **To** state.
|
||||
--
|
||||
-- These are the different possible state transitions of this state machine implementation:
|
||||
--
|
||||
-- * Idle => Start => Monitoring
|
||||
-- * Monitoring => Monitor => Monitoring
|
||||
-- * Monitoring => Stop => Idle
|
||||
--
|
||||
-- * Monitoring => Pickup => Monitoring
|
||||
-- * Monitoring => Load => Monitoring
|
||||
-- * Monitoring => Loading => Monitoring
|
||||
-- * Monitoring => Loaded => Monitoring
|
||||
-- * Monitoring => PickedUp => Monitoring
|
||||
-- * Monitoring => Deploy => Monitoring
|
||||
-- * Monitoring => Unload => Monitoring
|
||||
-- * Monitoring => Unloaded => Monitoring
|
||||
-- * Monitoring => Deployed => Monitoring
|
||||
-- * Monitoring => Home => Monitoring
|
||||
--
|
||||
--
|
||||
-- ## 2.1) AI_CARGO_DISPATCHER States.
|
||||
--
|
||||
-- * **Monitoring**: The process is dispatching.
|
||||
-- * **Idle**: The process is idle.
|
||||
--
|
||||
-- ## 2.2) AI_CARGO_DISPATCHER Events.
|
||||
--
|
||||
-- * **Start**: Start the transport process.
|
||||
-- * **Stop**: Stop the transport process.
|
||||
-- * **Monitor**: Monitor and take action.
|
||||
--
|
||||
-- * **Pickup**: Pickup cargo.
|
||||
-- * **Load**: Load the cargo.
|
||||
-- * **Loading**: The dispatcher is coordinating the loading of a cargo.
|
||||
-- * **Loaded**: Flag that the cargo is loaded.
|
||||
-- * **PickedUp**: The dispatcher has loaded all requested cargo into the CarrierGroup.
|
||||
-- * **Deploy**: Deploy cargo to a location.
|
||||
-- * **Unload**: Unload the cargo.
|
||||
-- * **Unloaded**: Flag that the cargo is unloaded.
|
||||
-- * **Deployed**: All cargo is unloaded from the carriers in the group.
|
||||
-- * **Home**: A Carrier is going home.
|
||||
--
|
||||
-- ## 2.3) Enhance your mission scripts with **Tailored** Event Handling!
|
||||
--
|
||||
-- Within your mission, you can capture these events when triggered, and tailor the events with your own code!
|
||||
-- Check out the @{AI.AI_Cargo_Dispatcher#AI_CARGO_DISPATCHER} class at chapter 3 for details on the different event handlers that are available and how to use them.
|
||||
--
|
||||
-- **There are a lot of templates available that allows you to quickly setup an event handler for a specific event type!**
|
||||
--
|
||||
-- ---
|
||||
--
|
||||
-- # 3) Set the pickup parameters.
|
||||
--
|
||||
-- Several parameters can be set to pickup cargo:
|
||||
--
|
||||
-- * @{#AI_CARGO_DISPATCHER_SHIP.SetPickupRadius}(): Sets or randomizes the pickup location for the Ship around the cargo coordinate in a radius defined an outer and optional inner radius.
|
||||
-- * @{#AI_CARGO_DISPATCHER_SHIP.SetPickupSpeed}(): Set the speed or randomizes the speed in km/h to pickup the cargo.
|
||||
--
|
||||
-- # 4) Set the deploy parameters.
|
||||
--
|
||||
-- Several parameters can be set to deploy cargo:
|
||||
--
|
||||
-- * @{#AI_CARGO_DISPATCHER_SHIP.SetDeployRadius}(): Sets or randomizes the deploy location for the Ship around the cargo coordinate in a radius defined an outer and an optional inner radius.
|
||||
-- * @{#AI_CARGO_DISPATCHER_SHIP.SetDeploySpeed}(): Set the speed or randomizes the speed in km/h to deploy the cargo.
|
||||
--
|
||||
-- # 5) Set the home zone when there isn't any more cargo to pickup.
|
||||
--
|
||||
-- A home zone can be specified to where the Ship will move when there isn't any cargo left for pickup.
|
||||
-- Use @{#AI_CARGO_DISPATCHER_SHIP.SetHomeZone}() to specify the home zone.
|
||||
--
|
||||
-- If no home zone is specified, the Ship will wait near the deploy zone for a new pickup command.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @field #AI_CARGO_DISPATCHER_SHIP
|
||||
AI_CARGO_DISPATCHER_SHIP = {
|
||||
ClassName = "AI_CARGO_DISPATCHER_SHIP"
|
||||
}
|
||||
|
||||
--- Creates a new AI_CARGO_DISPATCHER_SHIP object.
|
||||
-- @param #AI_CARGO_DISPATCHER_SHIP self
|
||||
-- @param Core.Set#SET_GROUP ShipSet The set of @{Wrapper.Group#GROUP} objects of Ships that will transport the cargo
|
||||
-- @param Core.Set#SET_CARGO CargoSet The set of @{Cargo.Cargo#CARGO} objects, which can be CARGO_GROUP, CARGO_CRATE, or CARGO_SLINGLOAD objects.
|
||||
-- @param Core.Set#SET_ZONE PickupZoneSet The set of pickup zones which are used to determine from where the cargo can be picked up by the Ship.
|
||||
-- @param Core.Set#SET_ZONE DeployZoneSet The set of deploy zones which determine where the cargo will be deployed by the Ship.
|
||||
-- @param #table ShippingLane Table containing list of Shipping Lanes to be used
|
||||
-- @return #AI_CARGO_DISPATCHER_SHIP
|
||||
-- @usage
|
||||
--
|
||||
-- -- An AI dispatcher object for a naval group, moving cargo from pickup zones to deploy zones via a predetermined Shipping Lane
|
||||
--
|
||||
-- local SetCargoInfantry = SET_CARGO:New():FilterTypes( "Infantry" ):FilterStart()
|
||||
-- local SetShip = SET_GROUP:New():FilterPrefixes( "Ship" ):FilterStart()
|
||||
-- local SetPickupZones = SET_ZONE:New():FilterPrefixes( "Pickup" ):FilterStart()
|
||||
-- local SetDeployZones = SET_ZONE:New():FilterPrefixes( "Deploy" ):FilterStart()
|
||||
-- NEED MORE THOUGHT - ShippingLane is part of Warehouse.......
|
||||
-- local ShippingLane = GROUP:New():FilterPrefixes( "ShippingLane" ):FilterStart()
|
||||
--
|
||||
-- AICargoDispatcherShip = AI_CARGO_DISPATCHER_SHIP:New( SetShip, SetCargoInfantry, SetPickupZones, SetDeployZones, ShippingLane )
|
||||
-- AICargoDispatcherShip:Start()
|
||||
--
|
||||
function AI_CARGO_DISPATCHER_SHIP:New( ShipSet, CargoSet, PickupZoneSet, DeployZoneSet, ShippingLane )
|
||||
|
||||
local self = BASE:Inherit( self, AI_CARGO_DISPATCHER:New( ShipSet, CargoSet, PickupZoneSet, DeployZoneSet ) )
|
||||
|
||||
self:SetPickupSpeed( 60, 10 )
|
||||
self:SetDeploySpeed( 60, 10 )
|
||||
|
||||
self:SetPickupRadius( 500, 6000 )
|
||||
self:SetDeployRadius( 500, 6000 )
|
||||
|
||||
self:SetPickupHeight( 0, 0 )
|
||||
self:SetDeployHeight( 0, 0 )
|
||||
|
||||
self:SetShippingLane( ShippingLane )
|
||||
|
||||
self:SetMonitorTimeInterval( 600 )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function AI_CARGO_DISPATCHER_SHIP:SetShippingLane( ShippingLane )
|
||||
self.ShippingLane = ShippingLane
|
||||
|
||||
return self
|
||||
|
||||
end
|
||||
|
||||
function AI_CARGO_DISPATCHER_SHIP:AICargo( Ship, CargoSet )
|
||||
|
||||
return AI_CARGO_SHIP:New( Ship, CargoSet, 0, self.ShippingLane )
|
||||
end
|
||||
@@ -1,4 +1,4 @@
|
||||
--- **AI** -- (R2.4) - Models the intelligent transportation of infantry (cargo).
|
||||
--- **AI** - Models the intelligent transportation of cargo using helicopters.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
@@ -33,7 +33,7 @@
|
||||
--
|
||||
-- ## Infantry health.
|
||||
--
|
||||
-- When infantry is unboarded from the APCs, the infantry is actually respawned into the battlefield.
|
||||
-- When infantry is unboarded from the helicopters, the infantry is actually respawned into the battlefield.
|
||||
-- As a result, the unboarding infantry is very _healthy_ every time it unboards.
|
||||
-- This is due to the limitation of the DCS simulator, which is not able to specify the health of new spawned units as a parameter.
|
||||
-- However, infantry that was destroyed when unboarded, won't be respawned again. Destroyed is destroyed.
|
||||
@@ -64,25 +64,24 @@ function AI_CARGO_HELICOPTER:New( Helicopter, CargoSet )
|
||||
self.Zone = ZONE_GROUP:New( Helicopter:GetName(), Helicopter, 300 )
|
||||
|
||||
self:SetStartState( "Unloaded" )
|
||||
-- Boarding
|
||||
self:AddTransition( "Unloaded", "Pickup", "Unloaded" )
|
||||
self:AddTransition( "*", "Landed", "*" )
|
||||
self:AddTransition( "*", "Load", "*" )
|
||||
self:AddTransition( "*", "Loaded", "Loaded" )
|
||||
self:AddTransition( "Loaded", "PickedUp", "Loaded" )
|
||||
|
||||
self:AddTransition( "Unloaded", "Pickup", "*" )
|
||||
self:AddTransition( "Loaded", "Deploy", "*" )
|
||||
-- Unboarding
|
||||
self:AddTransition( "Loaded", "Deploy", "*" )
|
||||
self:AddTransition( "*", "Queue", "*" )
|
||||
self:AddTransition( "*", "Orbit" , "*" )
|
||||
self:AddTransition( "*", "Destroyed", "*" )
|
||||
self:AddTransition( "*", "Unload", "*" )
|
||||
self:AddTransition( "*", "Unloaded", "Unloaded" )
|
||||
self:AddTransition( "Unloaded", "Deployed", "Unloaded" )
|
||||
|
||||
self:AddTransition( { "Unloaded", "Loading" }, "Load", "Boarding" )
|
||||
self:AddTransition( "Boarding", "Board", "Boarding" )
|
||||
self:AddTransition( "Boarding", "Loaded", "Boarding" )
|
||||
self:AddTransition( "Boarding", "PickedUp", "Loaded" )
|
||||
self:AddTransition( "Loaded", "Unload", "Unboarding" )
|
||||
self:AddTransition( "Unboarding", "Unboard", "Unboarding" )
|
||||
self:AddTransition( "Unboarding", "Unloaded", "Unboarding" )
|
||||
self:AddTransition( "Unboarding", "Deployed", "Unloaded" )
|
||||
|
||||
self:AddTransition( "*", "Landed", "*" )
|
||||
self:AddTransition( "*", "Queue", "*" )
|
||||
self:AddTransition( "*", "Orbit" , "*" )
|
||||
-- RTB
|
||||
self:AddTransition( "*", "Home" , "*" )
|
||||
|
||||
self:AddTransition( "*", "Destroyed", "Destroyed" )
|
||||
|
||||
--- Pickup Handler OnBefore for AI_CARGO_HELICOPTER
|
||||
-- @function [parent=#AI_CARGO_HELICOPTER] OnBeforePickup
|
||||
@@ -100,13 +99,31 @@ function AI_CARGO_HELICOPTER:New( Helicopter, CargoSet )
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param Core.Point#COORDINATE Coordinate
|
||||
-- @param #number Speed Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
-- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
|
||||
--- PickedUp Handler OnAfter for AI_CARGO_HELICOPTER - Cargo set has been picked up, ready to deploy
|
||||
-- @function [parent=#AI_CARGO_HELICOPTER] OnAfterPickedUp
|
||||
-- @param #AI_CARGO_HELICOPTER self
|
||||
-- @param Wrapper.Group#GROUP Helicopter The helicopter #GROUP object
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param Wrapper.Unit#UNIT Unit The helicopter #UNIT object
|
||||
|
||||
--- Unloaded Handler OnAfter for AI_CARGO_HELICOPTER - Cargo unloaded, carrier is empty
|
||||
-- @function [parent=#AI_CARGO_HELICOPTER] OnAfterUnloaded
|
||||
-- @param #AI_CARGO_HELICOPTER self
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param Cargo.CargoGroup#CARGO_GROUP Cargo The #CARGO_GROUP object.
|
||||
-- @param Wrapper.Unit#UNIT Unit The helicopter #UNIT object
|
||||
|
||||
--- Pickup Trigger for AI_CARGO_HELICOPTER
|
||||
-- @function [parent=#AI_CARGO_HELICOPTER] Pickup
|
||||
-- @param #AI_CARGO_HELICOPTER self
|
||||
-- @param Core.Point#COORDINATE Coordinate
|
||||
-- @param #number Speed Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
-- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
|
||||
--- Pickup Asynchronous Trigger for AI_CARGO_HELICOPTER
|
||||
-- @function [parent=#AI_CARGO_HELICOPTER] __Pickup
|
||||
@@ -122,7 +139,7 @@ function AI_CARGO_HELICOPTER:New( Helicopter, CargoSet )
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param Core.Point#COORDINATE Coordinate Place at which cargo is deployed.
|
||||
-- @param #number Speed Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
-- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
-- @return #boolean
|
||||
|
||||
--- Deploy Handler OnAfter for AI_CARGO_HELICOPTER
|
||||
@@ -132,21 +149,42 @@ function AI_CARGO_HELICOPTER:New( Helicopter, CargoSet )
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param Core.Point#COORDINATE Coordinate
|
||||
-- @param #number Speed Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
-- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
|
||||
--- Deployed Handler OnAfter for AI_CARGO_HELICOPTER
|
||||
-- @function [parent=#AI_CARGO_HELICOPTER] OnAfterDeployed
|
||||
-- @param #AI_CARGO_HELICOPTER self
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
|
||||
--- Deploy Trigger for AI_CARGO_HELICOPTER
|
||||
-- @function [parent=#AI_CARGO_HELICOPTER] Deploy
|
||||
-- @param #AI_CARGO_HELICOPTER self
|
||||
-- @param Core.Point#COORDINATE Coordinate Place at which the cargo is deployed.
|
||||
-- @param #number Speed Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
-- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
|
||||
--- Deploy Asynchronous Trigger for AI_CARGO_HELICOPTER
|
||||
-- @function [parent=#AI_CARGO_HELICOPTER] __Deploy
|
||||
-- @param #number Delay Delay in seconds.
|
||||
-- @param #AI_CARGO_HELICOPTER self
|
||||
-- @param Core.Point#COORDINATE Coordinate Place at which the cargo is deployed.
|
||||
-- @param #number Speed Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
-- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
|
||||
--- Home Trigger for AI_CARGO_HELICOPTER
|
||||
-- @function [parent=#AI_CARGO_HELICOPTER] Home
|
||||
-- @param #AI_CARGO_HELICOPTER self
|
||||
-- @param Core.Point#COORDINATE Coordinate Place to which the helicopter will go.
|
||||
-- @param #number Speed (optional) Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
-- @param #number Height (optional) Height the Helicopter should be flying at.
|
||||
|
||||
--- Home Asynchronous Trigger for AI_CARGO_HELICOPTER
|
||||
-- @function [parent=#AI_CARGO_HELICOPTER] __Home
|
||||
-- @param #number Delay Delay in seconds.
|
||||
-- @param #AI_CARGO_HELICOPTER self
|
||||
-- @param Core.Point#COORDINATE Coordinate Place to which the helicopter will go.
|
||||
-- @param #number Speed (optional) Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
-- @param #number Height (optional) Height the Helicopter should be flying at.
|
||||
|
||||
-- We need to capture the Crash events for the helicopters.
|
||||
-- The helicopter reference is used in the semaphore AI_CARGO_QUEUE.
|
||||
@@ -173,6 +211,9 @@ function AI_CARGO_HELICOPTER:New( Helicopter, CargoSet )
|
||||
|
||||
self:SetCarrier( Helicopter )
|
||||
|
||||
self.landingspeed = 15 -- kph
|
||||
self.landingheight = 5.5 -- meter
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -221,6 +262,25 @@ function AI_CARGO_HELICOPTER:SetCarrier( Helicopter )
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set landingspeed and -height for helicopter landings. Adjust after tracing if your helis get stuck after landing.
|
||||
-- @param #AI_CARGO_HELICOPTER self
|
||||
-- @param #number speed Landing speed in kph(!), e.g. 15
|
||||
-- @param #number height Landing height in meters(!), e.g. 5.5
|
||||
-- @return #AI_CARGO_HELICOPTER self
|
||||
-- @usage If your choppers get stuck, add tracing to your script to determine if they hit the right parameters like so:
|
||||
--
|
||||
-- BASE:TraceOn()
|
||||
-- BASE:TraceClass("AI_CARGO_HELICOPTER")
|
||||
--
|
||||
-- Watch the DCS.log for entries stating `Helicopter:<name>, Height = Helicopter:<number>, Velocity = Helicopter:<number>`
|
||||
-- Adjust if necessary.
|
||||
function AI_CARGO_HELICOPTER:SetLandingSpeedAndHeight(speed, height)
|
||||
local _speed = speed or 15
|
||||
local _height = height or 5.5
|
||||
self.landingheight = _height
|
||||
self.landingspeed = _speed
|
||||
return self
|
||||
end
|
||||
|
||||
--- @param #AI_CARGO_HELICOPTER self
|
||||
-- @param Wrapper.Group#GROUP Helicopter
|
||||
@@ -228,7 +288,7 @@ end
|
||||
-- @param Event
|
||||
-- @param To
|
||||
function AI_CARGO_HELICOPTER:onafterLanded( Helicopter, From, Event, To )
|
||||
|
||||
self:F({From, Event, To})
|
||||
Helicopter:F( { Name = Helicopter:GetName() } )
|
||||
|
||||
if Helicopter and Helicopter:IsAlive() then
|
||||
@@ -237,13 +297,13 @@ function AI_CARGO_HELICOPTER:onafterLanded( Helicopter, From, Event, To )
|
||||
-- 1 - When the helo lands normally on the ground.
|
||||
-- 2 - when the helo is hit and goes RTB or even when it is destroyed.
|
||||
-- For point 2, this is an issue, the infantry may not unload in this case!
|
||||
-- So we check if the helo is on the ground, and velocity< 5.
|
||||
-- So we check if the helo is on the ground, and velocity< 15.
|
||||
-- Only then the infantry can unload (and load too, for consistency)!
|
||||
|
||||
self:F( { Helicopter:GetName(), Height = Helicopter:GetHeight( true ), Velocity = Helicopter:GetVelocityKMH() } )
|
||||
self:T( { Helicopter:GetName(), Height = Helicopter:GetHeight( true ), Velocity = Helicopter:GetVelocityKMH() } )
|
||||
|
||||
if self.RoutePickup == true then
|
||||
if Helicopter:GetHeight( true ) <= 5 and Helicopter:GetVelocityKMH() < 10 then
|
||||
if Helicopter:GetHeight( true ) <= self.landingheight then --and Helicopter:GetVelocityKMH() < self.landingspeed then
|
||||
--self:Load( Helicopter:GetPointVec2() )
|
||||
self:Load( self.PickupZone )
|
||||
self.RoutePickup = false
|
||||
@@ -251,7 +311,7 @@ function AI_CARGO_HELICOPTER:onafterLanded( Helicopter, From, Event, To )
|
||||
end
|
||||
|
||||
if self.RouteDeploy == true then
|
||||
if Helicopter:GetHeight( true ) <= 5 and Helicopter:GetVelocityKMH() < 10 then
|
||||
if Helicopter:GetHeight( true ) <= self.landingheight then --and Helicopter:GetVelocityKMH() < self.landingspeed then
|
||||
self:Unload( self.DeployZone )
|
||||
self.RouteDeploy = false
|
||||
end
|
||||
@@ -269,7 +329,7 @@ end
|
||||
-- @param Core.Point#COORDINATE Coordinate
|
||||
-- @param #number Speed
|
||||
function AI_CARGO_HELICOPTER:onafterQueue( Helicopter, From, Event, To, Coordinate, Speed, DeployZone )
|
||||
|
||||
self:F({From, Event, To, Coordinate, Speed, DeployZone})
|
||||
local HelicopterInZone = false
|
||||
|
||||
if Helicopter and Helicopter:IsAlive() == true then
|
||||
@@ -308,7 +368,11 @@ function AI_CARGO_HELICOPTER:onafterQueue( Helicopter, From, Event, To, Coordina
|
||||
-- true
|
||||
-- )
|
||||
-- Route[#Route+1] = WaypointFrom
|
||||
local CoordinateTo = Coordinate
|
||||
local CoordinateTo = Coordinate
|
||||
|
||||
local landheight = CoordinateTo:GetLandHeight() -- get target height
|
||||
CoordinateTo.y = landheight + 50 -- flight height should be 50m above ground
|
||||
|
||||
local WaypointTo = CoordinateTo:WaypointAir(
|
||||
"RADIO",
|
||||
POINT_VEC3.RoutePointType.TurningPoint,
|
||||
@@ -348,28 +412,17 @@ end
|
||||
-- @param Core.Point#COORDINATE Coordinate
|
||||
-- @param #number Speed
|
||||
function AI_CARGO_HELICOPTER:onafterOrbit( Helicopter, From, Event, To, Coordinate )
|
||||
|
||||
self:F({From, Event, To, Coordinate})
|
||||
|
||||
if Helicopter and Helicopter:IsAlive() then
|
||||
|
||||
local Route = {}
|
||||
|
||||
-- local CoordinateFrom = Helicopter:GetCoordinate()
|
||||
-- local WaypointFrom = CoordinateFrom:WaypointAir(
|
||||
-- "RADIO",
|
||||
-- POINT_VEC3.RoutePointType.TurningPoint,
|
||||
-- POINT_VEC3.RoutePointAction.TurningPoint,
|
||||
-- Speed,
|
||||
-- true
|
||||
-- )
|
||||
-- Route[#Route+1] = WaypointFrom
|
||||
local CoordinateTo = Coordinate
|
||||
local WaypointTo = CoordinateTo:WaypointAir(
|
||||
"RADIO",
|
||||
POINT_VEC3.RoutePointType.TurningPoint,
|
||||
POINT_VEC3.RoutePointAction.TurningPoint,
|
||||
50,
|
||||
true
|
||||
)
|
||||
local CoordinateTo = Coordinate
|
||||
local landheight = CoordinateTo:GetLandHeight() -- get target height
|
||||
CoordinateTo.y = landheight + 50 -- flight height should be 50m above ground
|
||||
|
||||
local WaypointTo = CoordinateTo:WaypointAir("RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, 50, true)
|
||||
Route[#Route+1] = WaypointTo
|
||||
|
||||
local Tasks = {}
|
||||
@@ -379,7 +432,7 @@ function AI_CARGO_HELICOPTER:onafterOrbit( Helicopter, From, Event, To, Coordina
|
||||
Route[#Route+1] = WaypointTo
|
||||
|
||||
-- Now route the helicopter
|
||||
Helicopter:Route( Route, 0 )
|
||||
Helicopter:Route(Route, 0)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -395,7 +448,7 @@ end
|
||||
-- @param #boolean Deployed Cargo is deployed.
|
||||
-- @return #boolean True if all cargo has been unloaded.
|
||||
function AI_CARGO_HELICOPTER:onafterDeployed( Helicopter, From, Event, To, DeployZone )
|
||||
self:F( { Helicopter, From, Event, To, DeployZone = DeployZone } )
|
||||
self:F( { From, Event, To, DeployZone = DeployZone } )
|
||||
|
||||
self:Orbit( Helicopter:GetCoordinate(), 50 )
|
||||
|
||||
@@ -408,7 +461,6 @@ function AI_CARGO_HELICOPTER:onafterDeployed( Helicopter, From, Event, To, Deplo
|
||||
|
||||
self:GetParent( self, AI_CARGO_HELICOPTER ).onafterDeployed( self, Helicopter, From, Event, To, DeployZone )
|
||||
|
||||
|
||||
end
|
||||
|
||||
--- On after Pickup event.
|
||||
@@ -418,11 +470,12 @@ end
|
||||
-- @param Event
|
||||
-- @param To
|
||||
-- @param Core.Point#COORDINATE Coordinate Pickup place.
|
||||
-- @param #number Speed Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
-- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
-- @param #number Height Height in meters to move to the pickup coordinate. This parameter is ignored for APCs.
|
||||
-- @param Core.Zone#ZONE PickupZone (optional) The zone where the cargo will be picked up. The PickupZone can be nil, if there wasn't any PickupZoneSet provided.
|
||||
function AI_CARGO_HELICOPTER:onafterPickup( Helicopter, From, Event, To, Coordinate, Speed, Height, PickupZone )
|
||||
|
||||
self:F({Coordinate, Speed, Height, PickupZone })
|
||||
|
||||
if Helicopter and Helicopter:IsAlive() ~= nil then
|
||||
|
||||
Helicopter:Activate()
|
||||
@@ -436,25 +489,16 @@ function AI_CARGO_HELICOPTER:onafterPickup( Helicopter, From, Event, To, Coordin
|
||||
|
||||
--- Calculate the target route point.
|
||||
local CoordinateFrom = Helicopter:GetCoordinate()
|
||||
local CoordinateTo = Coordinate
|
||||
|
||||
--- Create a route point of type air.
|
||||
local WaypointFrom = CoordinateFrom:WaypointAir(
|
||||
"RADIO",
|
||||
POINT_VEC3.RoutePointType.TurningPoint,
|
||||
POINT_VEC3.RoutePointAction.TurningPoint,
|
||||
_speed,
|
||||
true
|
||||
)
|
||||
local WaypointFrom = CoordinateFrom:WaypointAir("RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, _speed, true)
|
||||
|
||||
--- Create a route point of type air.
|
||||
local WaypointTo = CoordinateTo:WaypointAir(
|
||||
"RADIO",
|
||||
POINT_VEC3.RoutePointType.TurningPoint,
|
||||
POINT_VEC3.RoutePointAction.TurningPoint,
|
||||
_speed,
|
||||
true
|
||||
)
|
||||
local CoordinateTo = Coordinate
|
||||
local landheight = CoordinateTo:GetLandHeight() -- get target height
|
||||
CoordinateTo.y = landheight + 50 -- flight height should be 50m above ground
|
||||
|
||||
local WaypointTo = CoordinateTo:WaypointAir("RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint,_speed, true)
|
||||
|
||||
Route[#Route+1] = WaypointFrom
|
||||
Route[#Route+1] = WaypointTo
|
||||
@@ -495,10 +539,10 @@ end
|
||||
-- @param Event
|
||||
-- @param To
|
||||
-- @param Core.Point#COORDINATE Coordinate Place at which the cargo is deployed.
|
||||
-- @param #number Speed Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
-- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
-- @param #number Height Height in meters to move to the deploy coordinate.
|
||||
function AI_CARGO_HELICOPTER:onafterDeploy( Helicopter, From, Event, To, Coordinate, Speed, Height, DeployZone )
|
||||
|
||||
self:F({From, Event, To, Coordinate, Speed, Height, DeployZone})
|
||||
if Helicopter and Helicopter:IsAlive() ~= nil then
|
||||
|
||||
self.RouteDeploy = true
|
||||
@@ -514,25 +558,17 @@ function AI_CARGO_HELICOPTER:onafterDeploy( Helicopter, From, Event, To, Coordin
|
||||
|
||||
--- Create a route point of type air.
|
||||
local CoordinateFrom = Helicopter:GetCoordinate()
|
||||
local WaypointFrom = CoordinateFrom:WaypointAir(
|
||||
"RADIO",
|
||||
POINT_VEC3.RoutePointType.TurningPoint,
|
||||
POINT_VEC3.RoutePointAction.TurningPoint,
|
||||
_speed,
|
||||
true
|
||||
)
|
||||
local WaypointFrom = CoordinateFrom:WaypointAir("RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, _speed, true)
|
||||
Route[#Route+1] = WaypointFrom
|
||||
Route[#Route+1] = WaypointFrom
|
||||
|
||||
--- Create a route point of type air.
|
||||
local CoordinateTo = Coordinate
|
||||
local WaypointTo = CoordinateTo:WaypointAir(
|
||||
"RADIO",
|
||||
POINT_VEC3.RoutePointType.TurningPoint,
|
||||
POINT_VEC3.RoutePointAction.TurningPoint,
|
||||
_speed,
|
||||
true
|
||||
)
|
||||
|
||||
local CoordinateTo = Coordinate
|
||||
local landheight = CoordinateTo:GetLandHeight() -- get target height
|
||||
CoordinateTo.y = landheight + 50 -- flight height should be 50m above ground
|
||||
|
||||
local WaypointTo = CoordinateTo:WaypointAir("RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, _speed, true)
|
||||
|
||||
Route[#Route+1] = WaypointTo
|
||||
Route[#Route+1] = WaypointTo
|
||||
@@ -542,7 +578,9 @@ function AI_CARGO_HELICOPTER:onafterDeploy( Helicopter, From, Event, To, Coordin
|
||||
|
||||
local Tasks = {}
|
||||
|
||||
-- The _Deploy function does not exist.
|
||||
Tasks[#Tasks+1] = Helicopter:TaskFunction( "AI_CARGO_HELICOPTER._Deploy", self, Coordinate, DeployZone )
|
||||
|
||||
Tasks[#Tasks+1] = Helicopter:TaskOrbitCircle( math.random( 30, 100 ), _speed, CoordinateTo:GetRandomCoordinateInRadius( 800, 500 ) )
|
||||
|
||||
--Tasks[#Tasks+1] = Helicopter:TaskLandAtVec2( CoordinateTo:GetVec2() )
|
||||
@@ -566,11 +604,12 @@ end
|
||||
-- @param Event
|
||||
-- @param To
|
||||
-- @param Core.Point#COORDINATE Coordinate Home place.
|
||||
-- @param #number Speed Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
-- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
-- @param #number Height Height in meters to move to the home coordinate.
|
||||
-- @param Core.Zone#ZONE HomeZone The zone wherein the carrier will return when all cargo has been transported. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE.
|
||||
function AI_CARGO_HELICOPTER:onafterHome( Helicopter, From, Event, To, Coordinate, Speed, Height, HomeZone )
|
||||
|
||||
self:F({From, Event, To, Coordinate, Speed, Height})
|
||||
|
||||
if Helicopter and Helicopter:IsAlive() ~= nil then
|
||||
|
||||
self.RouteHome = true
|
||||
@@ -579,30 +618,23 @@ function AI_CARGO_HELICOPTER:onafterHome( Helicopter, From, Event, To, Coordinat
|
||||
|
||||
--- Calculate the target route point.
|
||||
|
||||
Coordinate.y = Height
|
||||
--Coordinate.y = Height
|
||||
Height = Height or 50
|
||||
|
||||
Speed = Speed or Helicopter:GetSpeedMax()*0.5
|
||||
|
||||
--- Create a route point of type air.
|
||||
local CoordinateFrom = Helicopter:GetCoordinate()
|
||||
local WaypointFrom = CoordinateFrom:WaypointAir(
|
||||
"RADIO",
|
||||
POINT_VEC3.RoutePointType.TurningPoint,
|
||||
POINT_VEC3.RoutePointAction.TurningPoint,
|
||||
Speed ,
|
||||
true
|
||||
)
|
||||
|
||||
local WaypointFrom = CoordinateFrom:WaypointAir("RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, Speed, true)
|
||||
Route[#Route+1] = WaypointFrom
|
||||
|
||||
--- Create a route point of type air.
|
||||
local CoordinateTo = Coordinate
|
||||
local WaypointTo = CoordinateTo:WaypointAir(
|
||||
"RADIO",
|
||||
POINT_VEC3.RoutePointType.TurningPoint,
|
||||
POINT_VEC3.RoutePointAction.TurningPoint,
|
||||
Speed ,
|
||||
true
|
||||
)
|
||||
local CoordinateTo = Coordinate
|
||||
local landheight = CoordinateTo:GetLandHeight() -- get target height
|
||||
CoordinateTo.y = landheight + Height -- flight height should be 50m above ground
|
||||
|
||||
local WaypointTo = CoordinateTo:WaypointAir("RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, Speed, true)
|
||||
|
||||
Route[#Route+1] = WaypointTo
|
||||
|
||||
@@ -613,12 +645,11 @@ function AI_CARGO_HELICOPTER:onafterHome( Helicopter, From, Event, To, Coordinat
|
||||
|
||||
Tasks[#Tasks+1] = Helicopter:TaskLandAtVec2( CoordinateTo:GetVec2() )
|
||||
Route[#Route].task = Helicopter:TaskCombo( Tasks )
|
||||
|
||||
|
||||
Route[#Route+1] = WaypointTo
|
||||
|
||||
-- Now route the helicopter
|
||||
Helicopter:Route( Route, 0 )
|
||||
|
||||
Helicopter:Route(Route, 0)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -0,0 +1,397 @@
|
||||
--- **AI** -- (R2.5.1) - Models the intelligent transportation of infantry and other cargo.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **acrojason** (derived from AI_Cargo_APC by FlightControl)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module AI.AI_Cargo_Ship
|
||||
-- @image AI_Cargo_Dispatcher.JPG
|
||||
|
||||
--- @type AI_CARGO_SHIP
|
||||
-- @extends AI.AI_Cargo#AI_CARGO
|
||||
|
||||
--- Brings a dynamic cargo handling capability for an AI naval group.
|
||||
--
|
||||
-- Naval ships can be utilized to transport cargo around the map following naval shipping lanes.
|
||||
-- The AI_CARGO_SHIP class uses the @{Cargo.Cargo} capabilities within the MOOSE framework.
|
||||
-- @{Cargo.Cargo} must be declared within the mission or warehouse to make the AI_CARGO_SHIP recognize the cargo.
|
||||
-- Please consult the @{Cargo.Cargo} module for more information.
|
||||
--
|
||||
-- ## Cargo loading.
|
||||
--
|
||||
-- The module will automatically load cargo when the Ship is within boarding or loading radius.
|
||||
-- The boarding or loading radius is specified when the cargo is created in the simulation and depends on the type of
|
||||
-- cargo and the specified boarding radius.
|
||||
--
|
||||
-- ## Defending the Ship when enemies are nearby
|
||||
-- This is not supported for naval cargo because most tanks don't float. Protect your transports...
|
||||
--
|
||||
-- ## Infantry or cargo **health**.
|
||||
-- When cargo is unboarded from the Ship, the cargo is actually respawned into the battlefield.
|
||||
-- As a result, the unboarding cargo is very _healthy_ every time it unboards.
|
||||
-- This is due to the limitation of the DCS simulator, which is not able to specify the health of newly spawned units as a parameter.
|
||||
-- However, cargo that was destroyed when unboarded and following the Ship won't be respawned again (this is likely not a thing for
|
||||
-- naval cargo due to the lack of support for defending the Ship mentioned above). Destroyed is destroyed.
|
||||
-- As a result, there is some additional strength that is gained when an unboarding action happens, but in terms of simulation balance
|
||||
-- this has marginal impact on the overall battlefield simulation. Given the relatively short duration of DCS missions and the somewhat
|
||||
-- lengthy naval transport times, most units entering the Ship as cargo will be freshly en route to an amphibious landing or transporting
|
||||
-- between warehouses.
|
||||
--
|
||||
-- ## Control the Ships on the map.
|
||||
--
|
||||
-- Currently, naval transports can only be controlled via scripts due to their reliance upon predefined Shipping Lanes created in the Mission
|
||||
-- Editor. An interesting future enhancement could leverage new pathfinding functionality for ships in the Ops module.
|
||||
--
|
||||
-- ## Cargo deployment.
|
||||
--
|
||||
-- Using the @{AI_CARGO_SHIP.Deploy}() method, you are able to direct the Ship towards a Deploy zone to unboard/unload the cargo at the
|
||||
-- specified coordinate. The Ship will follow the Shipping Lane to ensure consistent cargo transportation within the simulation environment.
|
||||
--
|
||||
-- ## Cargo pickup.
|
||||
--
|
||||
-- Using the @{AI_CARGO_SHIP.Pickup}() method, you are able to direct the Ship towards a Pickup zone to board/load the cargo at the specified
|
||||
-- coordinate. The Ship will follow the Shipping Lane to ensure consistent cargo transportation within the simulation environment.
|
||||
--
|
||||
--
|
||||
-- @field #AI_CARGO_SHIP
|
||||
AI_CARGO_SHIP = {
|
||||
ClassName = "AI_CARGO_SHIP",
|
||||
Coordinate = nil -- Core.Point#COORDINATE
|
||||
}
|
||||
|
||||
--- Creates a new AI_CARGO_SHIP object.
|
||||
-- @param #AI_CARGO_SHIP self
|
||||
-- @param Wrapper.Group#GROUP Ship The carrier Ship group
|
||||
-- @param Core.Set#SET_CARGO CargoSet The set of cargo to be transported
|
||||
-- @param #number CombatRadius Provide the combat radius to defend the carrier by unboarding the cargo when enemies are nearby. When CombatRadius is 0, no defense will occur.
|
||||
-- @param #table ShippingLane Table containing list of Shipping Lanes to be used
|
||||
-- @return #AI_CARGO_SHIP
|
||||
function AI_CARGO_SHIP:New( Ship, CargoSet, CombatRadius, ShippingLane )
|
||||
|
||||
local self = BASE:Inherit( self, AI_CARGO:New( Ship, CargoSet ) ) -- #AI_CARGO_SHIP
|
||||
|
||||
self:AddTransition( "*", "Monitor", "*" )
|
||||
self:AddTransition( "*", "Destroyed", "Destroyed" )
|
||||
self:AddTransition( "*", "Home", "*" )
|
||||
|
||||
self:SetCombatRadius( 0 ) -- Don't want to deploy cargo in middle of water to defend Ship, so set CombatRadius to 0
|
||||
self:SetShippingLane ( ShippingLane )
|
||||
|
||||
self:SetCarrier( Ship )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set the Carrier
|
||||
-- @param #AI_CARGO_SHIP self
|
||||
-- @param Wrapper.Group#GROUP CargoCarrier
|
||||
-- @return #AI_CARGO_SHIP
|
||||
function AI_CARGO_SHIP:SetCarrier( CargoCarrier )
|
||||
self.CargoCarrier = CargoCarrier -- Wrapper.Group#GROUIP
|
||||
self.CargoCarrier:SetState( self.CargoCarrier, "AI_CARGO_SHIP", self )
|
||||
|
||||
CargoCarrier:HandleEvent( EVENTS.Dead )
|
||||
|
||||
function CargoCarrier:OnEventDead( EventData )
|
||||
self:F({"dead"})
|
||||
local AICargoTroops = self:GetState( self, "AI_CARGO_SHIP" )
|
||||
self:F({AICargoTroops=AICargoTroops})
|
||||
if AICargoTroops then
|
||||
self:F({})
|
||||
if not AICargoTroops:Is( "Loaded" ) then
|
||||
-- Better hope they can swim!
|
||||
AICargoTroops:Destroyed()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
self.Zone = ZONE_UNIT:New( self.CargoCarrier:GetName() .. "-Zone", self.CargoCarrier, self.CombatRadius )
|
||||
self.Coalition = self.CargoCarrier:GetCoalition()
|
||||
|
||||
self:SetControllable( CargoCarrier )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- FInd a free Carrier within a radius
|
||||
-- @param #AI_CARGO_SHIP self
|
||||
-- @param Core.Point#COORDINATE Coordinate
|
||||
-- @param #number Radius
|
||||
-- @return Wrapper.Group#GROUP NewCarrier
|
||||
function AI_CARGO_SHIP:FindCarrier( Coordinate, Radius )
|
||||
|
||||
local CoordinateZone = ZONE_RADIUS:New( "Zone", Coordinate:GetVec2(), Radius )
|
||||
CoordinateZone:Scan( { Object.Category.UNIT } )
|
||||
for _, DCSUnit in pairs( CoordinateZone:GetScannedUnits() ) do
|
||||
local NearUnit = UNIT:Find( DCSUnit )
|
||||
self:F({NearUnit=NearUnit})
|
||||
if not NearUnit:GetState( NearUnit, "AI_CARGO_SHIP" ) then
|
||||
local Attributes = NearUnit:GetDesc()
|
||||
self:F({Desc=Attributes})
|
||||
if NearUnit:HasAttributes( "Trucks" ) then
|
||||
return NearUnit:GetGroup()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
function AI_CARGO_SHIP:SetShippingLane( ShippingLane )
|
||||
self.ShippingLane = ShippingLane
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function AI_CARGO_SHIP:SetCombatRadius( CombatRadius )
|
||||
self.CombatRadius = CombatRadius or 0
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Follow Infantry to the Carrier
|
||||
-- @param #AI_CARGO_SHIP self
|
||||
-- @param #AI_CARGO_SHIP Me
|
||||
-- @param Wrapper.Unit#UNIT ShipUnit
|
||||
-- @param Cargo.CargoGroup#CARGO_GROUP Cargo
|
||||
-- @return #AI_CARGO_SHIP
|
||||
function AI_CARGO_SHIP:FollowToCarrier( Me, ShipUnit, CargoGroup )
|
||||
|
||||
local InfantryGroup = CargoGroup:GetGroup()
|
||||
|
||||
self:F( { self=self:GetClassNameAndID(), InfantryGroup = InfantryGroup:GetName() } )
|
||||
|
||||
if ShipUnit:IsAlive() then
|
||||
-- Check if the Cargo is near the CargoCarrier
|
||||
if InfantryGroup:IsPartlyInZone( ZONE_UNIT:New( "Radius", ShipUnit, 1000 ) ) then
|
||||
|
||||
-- Cargo does not need to navigate to Carrier
|
||||
Me:Guard()
|
||||
else
|
||||
|
||||
self:F( { InfantryGroup = InfantryGroup:GetName() } )
|
||||
if InfantryGroup:IsAlive() then
|
||||
|
||||
self:F( { InfantryGroup = InfantryGroup:GetName() } )
|
||||
local Waypoints = {}
|
||||
|
||||
-- Calculate new route
|
||||
local FromCoord = InfantryGroup:GetCoordinate()
|
||||
local FromGround = FromCoord:WaypointGround( 10, "Diamond" )
|
||||
self:F({FromGround=FromGround})
|
||||
table.insert( Waypoints, FromGround )
|
||||
|
||||
local ToCoord = ShipUnit:GetCoordinate():GetRandomCoordinateInRadius( 10, 5 )
|
||||
local ToGround = ToCoord:WaypointGround( 10, "Diamond" )
|
||||
self:F({ToGround=ToGround})
|
||||
table.insert( Waypoints, ToGround )
|
||||
|
||||
local TaskRoute = InfantryGroup:TaskFunction( "AI_CARGO_SHIP.FollowToCarrier", Me, ShipUnit, CargoGroup )
|
||||
|
||||
self:F({Waypoints=Waypoints})
|
||||
local Waypoint = Waypoints[#Waypoints]
|
||||
InfantryGroup:SetTaskWaypoint( Waypoint, TaskRoute ) -- Set for the given Route at Waypoint 2 the TaskRouteToZone
|
||||
|
||||
InfantryGroup:Route( Waypoints, 1 ) -- Move after a random number of seconds to the Route. See Route method for details
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function AI_CARGO_SHIP:onafterMonitor( Ship, From, Event, To )
|
||||
self:F( { Ship, From, Event, To, IsTransporting = self:IsTransporting() } )
|
||||
|
||||
if self.CombatRadius > 0 then
|
||||
-- We really shouldn't find ourselves in here for Ships since the CombatRadius should always be 0.
|
||||
-- This is to avoid Unloading the Ship in the middle of the sea.
|
||||
if Ship and Ship:IsAlive() then
|
||||
if self.CarrierCoordinate then
|
||||
if self:IsTransporting() == true then
|
||||
local Coordinate = Ship:GetCoordinate()
|
||||
if self:Is( "Unloaded" ) or self:Is( "Loaded" ) then
|
||||
self.Zone:Scan( { Object.Category.UNIT } )
|
||||
if self.Zone:IsAllInZoneOfCoalition( self.Coalition ) then
|
||||
if self:Is( "Unloaded" ) then
|
||||
-- There are no enemies within combat radius. Reload the CargoCarrier.
|
||||
self:Reload()
|
||||
end
|
||||
else
|
||||
if self:Is( "Loaded" ) then
|
||||
-- There are enemies within combat radius. Unload the CargoCarrier.
|
||||
self:__Unload( 1, nil, true ) -- The 2nd parameter is true, which means that the unload is for defending the carrier, not to deploy!
|
||||
else
|
||||
if self:Is( "Unloaded" ) then
|
||||
--self:Follow()
|
||||
end
|
||||
self:F( "I am here" .. self:GetCurrentState() )
|
||||
if self:Is( "Following" ) then
|
||||
for Cargo, ShipUnit in pairs( self.Carrier_Cargo ) do
|
||||
local Cargo = Cargo -- Cargo.Cargo#CARGO
|
||||
local ShipUnit = ShipUnit -- Wrapper.Unit#UNIT
|
||||
if Cargo:IsAlive() then
|
||||
if not Cargo:IsNear( ShipUnit, 40 ) then
|
||||
ShipUnit:RouteStop()
|
||||
self.CarrierStopped = true
|
||||
else
|
||||
if self.CarrierStopped then
|
||||
if Cargo:IsNear( ShipUnit, 25 ) then
|
||||
ShipUnit:RouteResume()
|
||||
self.CarrierStopped = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
self.CarrierCoordinate = Ship:GetCoordinate()
|
||||
end
|
||||
self:__Monitor( -5 )
|
||||
end
|
||||
end
|
||||
|
||||
--- Check if cargo ship is alive and trigger Load event
|
||||
-- @param Wrapper.Group#Group Ship
|
||||
-- @param #AI_CARGO_SHIP self
|
||||
function AI_CARGO_SHIP._Pickup( Ship, self, Coordinate, Speed, PickupZone )
|
||||
|
||||
Ship:F( { "AI_CARGO_Ship._Pickup:", Ship:GetName() } )
|
||||
|
||||
if Ship:IsAlive() then
|
||||
self:Load( PickupZone )
|
||||
end
|
||||
end
|
||||
|
||||
--- Check if cargo ship is alive and trigger Unload event. Good time to remind people that Lua is case sensitive and Unload != UnLoad
|
||||
-- @param Wrapper.Group#GROUP Ship
|
||||
-- @param #AI_CARGO_SHIP self
|
||||
function AI_CARGO_SHIP._Deploy( Ship, self, Coordinate, DeployZone )
|
||||
Ship:F( { "AI_CARGO_Ship._Deploy:", Ship } )
|
||||
|
||||
if Ship:IsAlive() then
|
||||
self:Unload( DeployZone )
|
||||
end
|
||||
end
|
||||
|
||||
--- on after Pickup event.
|
||||
-- @param AI_CARGO_SHIP Ship
|
||||
-- @param From
|
||||
-- @param Event
|
||||
-- @param To
|
||||
-- @param Core.Point#COORDINATE Coordinate of the pickup point
|
||||
-- @param #number Speed Speed in km/h to sail to the pickup coordinate. Default is 50% of max speed for the unit
|
||||
-- @param #number Height Altitude in meters to move to the pickup coordinate. This parameter is ignored for Ships
|
||||
-- @param Core.Zone#ZONE PickupZone (optional) The zone where the cargo will be picked up. The PickupZone can be nil if there was no PickupZoneSet provided
|
||||
function AI_CARGO_SHIP:onafterPickup( Ship, From, Event, To, Coordinate, Speed, Height, PickupZone )
|
||||
|
||||
if Ship and Ship:IsAlive() then
|
||||
AI_CARGO_SHIP._Pickup( Ship, self, Coordinate, Speed, PickupZone )
|
||||
self:GetParent( self, AI_CARGO_SHIP ).onafterPickup( self, Ship, From, Event, To, Coordinate, Speed, Height, PickupZone )
|
||||
end
|
||||
end
|
||||
|
||||
--- On after Deploy event.
|
||||
-- @param #AI_CARGO_SHIP self
|
||||
-- @param Wrapper.Group#GROUP SHIP
|
||||
-- @param From
|
||||
-- @param Event
|
||||
-- @param To
|
||||
-- @param Core.Point#COORDINATE Coordinate Coordinate of the deploy point
|
||||
-- @param #number Speed Speed in km/h to sail to the deploy coordinate. Default is 50% of max speed for the unit
|
||||
-- @param #number Height Altitude in meters to move to the deploy coordinate. This parameter is ignored for Ships
|
||||
-- @param Core.Zone#ZONE DeployZone The zone where the cargo will be deployed.
|
||||
function AI_CARGO_SHIP:onafterDeploy( Ship, From, Event, To, Coordinate, Speed, Height, DeployZone )
|
||||
|
||||
if Ship and Ship:IsAlive() then
|
||||
|
||||
Speed = Speed or Ship:GetSpeedMax()*0.8
|
||||
local lane = self.ShippingLane
|
||||
|
||||
if lane then
|
||||
local Waypoints = {}
|
||||
|
||||
for i=1, #lane do
|
||||
local coord = lane[i]
|
||||
local Waypoint = coord:WaypointGround(_speed)
|
||||
table.insert(Waypoints, Waypoint)
|
||||
end
|
||||
|
||||
local TaskFunction = Ship:TaskFunction( "AI_CARGO_SHIP._Deploy", self, Coordinate, DeployZone )
|
||||
local Waypoint = Waypoints[#Waypoints]
|
||||
Ship:SetTaskWaypoint( Waypoint, TaskFunction )
|
||||
Ship:Route(Waypoints, 1)
|
||||
self:GetParent( self, AI_CARGO_SHIP ).onafterDeploy( self, Ship, From, Event, To, Coordinate, Speed, Height, DeployZone )
|
||||
else
|
||||
self:E(self.lid.."ERROR: No shipping lane defined for Naval Transport!")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- On after Unload event.
|
||||
-- @param #AI_CARGO_SHIP self
|
||||
-- @param Wrapper.Group#GROUP Ship
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE.
|
||||
function AI_CARGO_SHIP:onafterUnload( Ship, From, Event, To, DeployZone, Defend )
|
||||
self:F( { Ship, From, Event, To, DeployZone, Defend = Defend } )
|
||||
|
||||
local UnboardInterval = 5
|
||||
local UnboardDelay = 5
|
||||
|
||||
if Ship and Ship:IsAlive() then
|
||||
for _, ShipUnit in pairs( Ship:GetUnits() ) do
|
||||
local ShipUnit = ShipUnit -- Wrapper.Unit#UNIT
|
||||
Ship:RouteStop()
|
||||
for _, Cargo in pairs( ShipUnit:GetCargo() ) do
|
||||
self:F( { Cargo = Cargo:GetName(), Isloaded = Cargo:IsLoaded() } )
|
||||
if Cargo:IsLoaded() then
|
||||
local unboardCoord = DeployZone:GetRandomPointVec2()
|
||||
Cargo:__UnBoard( UnboardDelay, unboardCoord, 1000)
|
||||
UnboardDelay = UnboardDelay + Cargo:GetCount() * UnboardInterval
|
||||
self:__Unboard( UnboardDelay, Cargo, ShipUnit, DeployZone, Defend )
|
||||
if not Defend == true then
|
||||
Cargo:SetDeployed( true )
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function AI_CARGO_SHIP:onafterHome( Ship, From, Event, To, Coordinate, Speed, Height, HomeZone )
|
||||
if Ship and Ship:IsAlive() then
|
||||
|
||||
self.RouteHome = true
|
||||
Speed = Speed or Ship:GetSpeedMax()*0.8
|
||||
local lane = self.ShippingLane
|
||||
|
||||
if lane then
|
||||
local Waypoints = {}
|
||||
|
||||
-- Need to find a more generalized way to do this instead of reversing the shipping lane.
|
||||
-- This only works if the Source/Dest route waypoints are numbered 1..n and not n..1
|
||||
for i=#lane, 1, -1 do
|
||||
local coord = lane[i]
|
||||
local Waypoint = coord:WaypointGround(_speed)
|
||||
table.insert(Waypoints, Waypoint)
|
||||
end
|
||||
|
||||
local Waypoint = Waypoints[#Waypoints]
|
||||
Ship:Route(Waypoints, 1)
|
||||
|
||||
else
|
||||
self:E(self.lid.."ERROR: No shipping lane defined for Naval Transport!")
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1140,8 +1140,8 @@ end
|
||||
-- @param DCS#Vec3 CV2 Vec3
|
||||
function AI_FORMATION:FollowMe(FollowGroup, ClientUnit, CT1, CV1, CT2, CV2)
|
||||
|
||||
if FollowGroup:GetState( FollowGroup, "Mode" ) == self.__Enum.Mode.Formation then
|
||||
|
||||
if FollowGroup:GetState( FollowGroup, "Mode" ) == self.__Enum.Mode.Formation and not self:Is("Stopped") then
|
||||
|
||||
self:T({Mode=FollowGroup:GetState( FollowGroup, "Mode" )})
|
||||
|
||||
FollowGroup:OptionROTEvadeFire()
|
||||
|
||||
@@ -725,8 +725,8 @@ function AI_PATROL_ZONE:onafterRoute( Controllable, From, Event, To )
|
||||
return
|
||||
end
|
||||
|
||||
|
||||
if self.Controllable:IsAlive() then
|
||||
local life = self.Controllable:GetLife() or 0
|
||||
if self.Controllable:IsAlive() and life > 1 then
|
||||
-- Determine if the AIControllable is within the PatrolZone.
|
||||
-- If not, make a waypoint within the to that the AIControllable will fly at maximum speed to that point.
|
||||
|
||||
@@ -743,8 +743,9 @@ function AI_PATROL_ZONE:onafterRoute( Controllable, From, Event, To )
|
||||
if self.Controllable:InAir() == false then
|
||||
self:T( "Not in the air, finding route path within PatrolZone" )
|
||||
local CurrentVec2 = self.Controllable:GetVec2()
|
||||
--TODO: Create GetAltitude function for GROUP, and delete GetUnit(1).
|
||||
local CurrentAltitude = self.Controllable:GetUnit(1):GetAltitude()
|
||||
if not CurrentVec2 then return end
|
||||
--Done: Create GetAltitude function for GROUP, and delete GetUnit(1).
|
||||
local CurrentAltitude = self.Controllable:GetAltitude()
|
||||
local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y )
|
||||
local ToPatrolZoneSpeed = self.PatrolMaxSpeed
|
||||
local CurrentRoutePoint = CurrentPointVec3:WaypointAir(
|
||||
@@ -758,8 +759,9 @@ function AI_PATROL_ZONE:onafterRoute( Controllable, From, Event, To )
|
||||
else
|
||||
self:T( "In the air, finding route path within PatrolZone" )
|
||||
local CurrentVec2 = self.Controllable:GetVec2()
|
||||
--TODO: Create GetAltitude function for GROUP, and delete GetUnit(1).
|
||||
local CurrentAltitude = self.Controllable:GetUnit(1):GetAltitude()
|
||||
if not CurrentVec2 then return end
|
||||
--DONE: Create GetAltitude function for GROUP, and delete GetUnit(1).
|
||||
local CurrentAltitude = self.Controllable:GetAltitude()
|
||||
local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y )
|
||||
local ToPatrolZoneSpeed = self.PatrolMaxSpeed
|
||||
local CurrentRoutePoint = CurrentPointVec3:WaypointAir(
|
||||
@@ -870,9 +872,10 @@ function AI_PATROL_ZONE:onafterRTB()
|
||||
|
||||
--- Calculate the current route point.
|
||||
local CurrentVec2 = self.Controllable:GetVec2()
|
||||
|
||||
--TODO: Create GetAltitude function for GROUP, and delete GetUnit(1).
|
||||
local CurrentAltitude = self.Controllable:GetUnit(1):GetAltitude()
|
||||
if not CurrentVec2 then return end
|
||||
--DONE: Create GetAltitude function for GROUP, and delete GetUnit(1).
|
||||
--local CurrentAltitude = self.Controllable:GetUnit(1):GetAltitude()
|
||||
local CurrentAltitude = self.Controllable:GetAltitude()
|
||||
local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y )
|
||||
local ToPatrolZoneSpeed = self.PatrolMaxSpeed
|
||||
local CurrentRoutePoint = CurrentPointVec3:WaypointAir(
|
||||
|
||||
@@ -190,7 +190,7 @@ do -- ACT_ROUTE
|
||||
self:F( { ZoneName = ZoneName } )
|
||||
local Zone = Zone -- Core.Zone#ZONE
|
||||
local ZoneCoord = Zone:GetCoordinate()
|
||||
local ZoneDistance = ZoneCoord:Get2DDistance( self.Coordinate )
|
||||
local ZoneDistance = ZoneCoord:Get2DDistance( Coordinate )
|
||||
self:F( { ShortestDistance, ShortestReferenceName } )
|
||||
if ShortestDistance == 0 or ZoneDistance < ShortestDistance then
|
||||
ShortestDistance = ZoneDistance
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
--- **Core** -- Management of CARGO logistics, that can be transported from and to transportation carriers.
|
||||
--- **Cargo** - Management of CARGO logistics, that can be transported from and to transportation carriers.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
@@ -1066,18 +1066,26 @@ do -- CARGO_REPRESENTABLE
|
||||
|
||||
--- CARGO_REPRESENTABLE Constructor.
|
||||
-- @param #CARGO_REPRESENTABLE self
|
||||
-- @param #string Type
|
||||
-- @param #string Name
|
||||
-- @param #number LoadRadius (optional)
|
||||
-- @param #number NearRadius (optional)
|
||||
-- @param Wrapper.Positionable#POSITIONABLE CargoObject The cargo object.
|
||||
-- @param #string Type Type name
|
||||
-- @param #string Name Name.
|
||||
-- @param #number LoadRadius (optional) Radius in meters.
|
||||
-- @param #number NearRadius (optional) Radius in meters when the cargo is loaded into the carrier.
|
||||
-- @return #CARGO_REPRESENTABLE
|
||||
function CARGO_REPRESENTABLE:New( CargoObject, Type, Name, LoadRadius, NearRadius )
|
||||
|
||||
-- Inherit CARGO.
|
||||
local self = BASE:Inherit( self, CARGO:New( Type, Name, 0, LoadRadius, NearRadius ) ) -- #CARGO_REPRESENTABLE
|
||||
self:F( { Type, Name, LoadRadius, NearRadius } )
|
||||
|
||||
local Desc = CargoObject:GetDesc()
|
||||
self:I( { Desc = Desc } )
|
||||
-- Descriptors.
|
||||
local Desc=CargoObject:GetDesc()
|
||||
self:T({Desc=Desc})
|
||||
|
||||
-- Weight.
|
||||
local Weight = math.random( 80, 120 )
|
||||
|
||||
-- Adjust weight..
|
||||
if Desc then
|
||||
if Desc.typeName == "2B11 mortar" then
|
||||
Weight = 210
|
||||
@@ -1086,13 +1094,8 @@ do -- CARGO_REPRESENTABLE
|
||||
end
|
||||
end
|
||||
|
||||
-- Set weight.
|
||||
self:SetWeight( Weight )
|
||||
|
||||
-- local Box = CargoUnit:GetBoundingBox()
|
||||
-- local VolumeUnit = ( Box.max.x - Box.min.x ) * ( Box.max.y - Box.min.y ) * ( Box.max.z - Box.min.z )
|
||||
-- self:I( { VolumeUnit = VolumeUnit, WeightUnit = WeightUnit } )
|
||||
--self:SetVolume( VolumeUnit )
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
--- **Cargo** -- Management of grouped cargo logistics, which are based on a @{Wrapper.Group} object.
|
||||
--- **Cargo** - Management of grouped cargo logistics, which are based on a @{Wrapper.Group} object.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
@@ -56,6 +56,8 @@ do -- CARGO_GROUP
|
||||
-- @param #number NearRadius (optional) Once the units are within this radius of the carrier, they are actually loaded, i.e. disappear from the scene.
|
||||
-- @return #CARGO_GROUP Cargo group object.
|
||||
function CARGO_GROUP:New( CargoGroup, Type, Name, LoadRadius, NearRadius )
|
||||
|
||||
-- Inherit CAROG_REPORTABLE
|
||||
local self = BASE:Inherit( self, CARGO_REPORTABLE:New( Type, Name, 0, LoadRadius, NearRadius ) ) -- #CARGO_GROUP
|
||||
self:F( { Type, Name, LoadRadius } )
|
||||
|
||||
@@ -76,6 +78,9 @@ do -- CARGO_GROUP
|
||||
local GroupName = CargoGroup:GetName()
|
||||
self.CargoName = Name
|
||||
self.CargoTemplate = UTILS.DeepCopy( _DATABASE:GetGroupTemplate( GroupName ) )
|
||||
|
||||
-- Deactivate late activation.
|
||||
self.CargoTemplate.lateActivation=false
|
||||
|
||||
self.GroupTemplate = UTILS.DeepCopy( self.CargoTemplate )
|
||||
self.GroupTemplate.name = self.CargoName .. "#CARGO"
|
||||
@@ -481,7 +486,7 @@ do -- CARGO_GROUP
|
||||
-- @param #string Event
|
||||
-- @param #string From
|
||||
-- @param #string To
|
||||
-- @param Core.Point#POINT_VEC2
|
||||
-- @param Core.Point#POINT_VEC2 ToPointVec2
|
||||
function CARGO_GROUP:onafterUnLoad( From, Event, To, ToPointVec2, ... )
|
||||
--self:F( { From, Event, To, ToPointVec2 } )
|
||||
|
||||
@@ -491,7 +496,10 @@ do -- CARGO_GROUP
|
||||
self.CargoSet:ForEach(
|
||||
function( Cargo )
|
||||
--Cargo:UnLoad( ToPointVec2 )
|
||||
local RandomVec2=ToPointVec2:GetRandomPointVec2InRadius(20, 10)
|
||||
local RandomVec2=nil
|
||||
if ToPointVec2 then
|
||||
RandomVec2=ToPointVec2:GetRandomPointVec2InRadius(20, 10)
|
||||
end
|
||||
Cargo:UnBoard( RandomVec2 )
|
||||
end
|
||||
)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
--- **Cargo** -- Management of single cargo logistics, which are based on a @{Wrapper.Unit} object.
|
||||
--- **Cargo** - Management of single cargo logistics, which are based on a @{Wrapper.Unit} object.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
@@ -46,14 +46,17 @@ do -- CARGO_UNIT
|
||||
-- @param #number NearRadius (optional)
|
||||
-- @return #CARGO_UNIT
|
||||
function CARGO_UNIT:New( CargoUnit, Type, Name, LoadRadius, NearRadius )
|
||||
local self = BASE:Inherit( self, CARGO_REPRESENTABLE:New( CargoUnit, Type, Name, LoadRadius, NearRadius ) ) -- #CARGO_UNIT
|
||||
self:I( { Type, Name, LoadRadius, NearRadius } )
|
||||
|
||||
self:T( CargoUnit )
|
||||
-- Inherit CARGO_REPRESENTABLE.
|
||||
local self = BASE:Inherit( self, CARGO_REPRESENTABLE:New( CargoUnit, Type, Name, LoadRadius, NearRadius ) ) -- #CARGO_UNIT
|
||||
|
||||
-- Debug info.
|
||||
self:T({Type=Type, Name=Name, LoadRadius=LoadRadius, NearRadius=NearRadius})
|
||||
|
||||
-- Set cargo object.
|
||||
self.CargoObject = CargoUnit
|
||||
|
||||
self:T( self.ClassName )
|
||||
|
||||
-- Set event prio.
|
||||
self:SetEventPriority( 5 )
|
||||
|
||||
return self
|
||||
@@ -100,7 +103,12 @@ do -- CARGO_UNIT
|
||||
|
||||
-- Respawn the group...
|
||||
if self.CargoObject then
|
||||
self.CargoObject:ReSpawnAt( FromPointVec2, CargoDeployHeading )
|
||||
if CargoCarrier:IsShip() then
|
||||
-- If CargoCarrier is a ship, we don't want to spawn the units in the water next to the boat. Use destination coord instead.
|
||||
self.CargoObject:ReSpawnAt( ToPointVec2, CargoDeployHeading )
|
||||
else
|
||||
self.CargoObject:ReSpawnAt( FromPointVec2, CargoDeployHeading )
|
||||
end
|
||||
self:F( { "CargoUnits:", self.CargoObject:GetGroup():GetName() } )
|
||||
self.CargoCarrier = nil
|
||||
|
||||
|
||||
@@ -0,0 +1,943 @@
|
||||
--- **Core** - A* Pathfinding.
|
||||
--
|
||||
-- **Main Features:**
|
||||
--
|
||||
-- * Find path from A to B.
|
||||
-- * Pre-defined as well as custom valid neighbour functions.
|
||||
-- * Pre-defined as well as custom cost functions.
|
||||
-- * Easy rectangular grid setup.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **funkyfranky**
|
||||
--
|
||||
-- ===
|
||||
-- @module Core.Astar
|
||||
-- @image CORE_Astar.png
|
||||
|
||||
|
||||
--- ASTAR class.
|
||||
-- @type ASTAR
|
||||
-- @field #string ClassName Name of the class.
|
||||
-- @field #boolean Debug Debug mode. Messages to all about status.
|
||||
-- @field #string lid Class id string for output to DCS log file.
|
||||
-- @field #table nodes Table of nodes.
|
||||
-- @field #number counter Node counter.
|
||||
-- @field #number Nnodes Number of nodes.
|
||||
-- @field #number nvalid Number of nvalid calls.
|
||||
-- @field #number nvalidcache Number of cached valid evals.
|
||||
-- @field #number ncost Number of cost evaluations.
|
||||
-- @field #number ncostcache Number of cached cost evals.
|
||||
-- @field #ASTAR.Node startNode Start node.
|
||||
-- @field #ASTAR.Node endNode End node.
|
||||
-- @field Core.Point#COORDINATE startCoord Start coordinate.
|
||||
-- @field Core.Point#COORDINATE endCoord End coordinate.
|
||||
-- @field #function ValidNeighbourFunc Function to check if a node is valid.
|
||||
-- @field #table ValidNeighbourArg Optional arguments passed to the valid neighbour function.
|
||||
-- @field #function CostFunc Function to calculate the heuristic "cost" to go from one node to another.
|
||||
-- @field #table CostArg Optional arguments passed to the cost function.
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
--- *When nothing goes right... Go left!*
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- # The ASTAR Concept
|
||||
--
|
||||
-- Pathfinding algorithm.
|
||||
--
|
||||
--
|
||||
-- # Start and Goal
|
||||
--
|
||||
-- The first thing we need to define is obviously the place where we want to start and where we want to go eventually.
|
||||
--
|
||||
-- ## Start
|
||||
--
|
||||
-- The start
|
||||
--
|
||||
-- ## Goal
|
||||
--
|
||||
--
|
||||
-- # Nodes
|
||||
--
|
||||
-- ## Rectangular Grid
|
||||
--
|
||||
-- A rectangular grid can be created using the @{#ASTAR.CreateGrid}(*ValidSurfaceTypes, BoxHY, SpaceX, deltaX, deltaY, MarkGrid*), where
|
||||
--
|
||||
-- * *ValidSurfaceTypes* is a table of valid surface types. By default all surface types are valid.
|
||||
-- * *BoxXY* is the width of the grid perpendicular the the line between start and end node. Default is 40,000 meters (40 km).
|
||||
-- * *SpaceX* is the additional space behind the start and end nodes. Default is 20,000 meters (20 km).
|
||||
-- * *deltaX* is the grid spacing between nodes in the direction of start and end node. Default is 2,000 meters (2 km).
|
||||
-- * *deltaY* is the grid spacing perpendicular to the direction of start and end node. Default is the same as *deltaX*.
|
||||
-- * *MarkGrid* If set to *true*, this places marker on the F10 map on each grid node. Note that this can stall DCS if too many nodes are created.
|
||||
--
|
||||
-- ## Valid Surfaces
|
||||
--
|
||||
-- Certain unit types can only travel on certain surfaces types, for example
|
||||
--
|
||||
-- * Naval units can only travel on water (that also excludes shallow water in DCS currently),
|
||||
-- * Ground units can only traval on land.
|
||||
--
|
||||
-- By restricting the surface type in the grid construction, we also reduce the number of nodes, which makes the algorithm more efficient.
|
||||
--
|
||||
-- ## Box Width (BoxHY)
|
||||
--
|
||||
-- The box width needs to be large enough to capture all paths you want to consider.
|
||||
--
|
||||
-- ## Space in X
|
||||
--
|
||||
-- The space in X value is important if the algorithm needs to to backwards from the start node or needs to extend even further than the end node.
|
||||
--
|
||||
-- ## Grid Spacing
|
||||
--
|
||||
-- The grid spacing is an important factor as it determines the number of nodes and hence the performance of the algorithm. It should be as large as possible.
|
||||
-- However, if the value is too large, the algorithm might fail to get a valid path.
|
||||
--
|
||||
-- A good estimate of the grid spacing is to set it to be smaller (~ half the size) of the smallest gap you need to path.
|
||||
--
|
||||
-- # Valid Neighbours
|
||||
--
|
||||
-- The A* algorithm needs to know if a transition from one node to another is allowed or not. By default, hopping from one node to another is always possible.
|
||||
--
|
||||
-- ## Line of Sight
|
||||
--
|
||||
-- For naval
|
||||
--
|
||||
--
|
||||
-- # Heuristic Cost
|
||||
--
|
||||
-- In order to determine the optimal path, the pathfinding algorithm needs to know, how costly it is to go from one node to another.
|
||||
-- Often, this can simply be determined by the distance between two nodes. Therefore, the default cost function is set to be the 2D distance between two nodes.
|
||||
--
|
||||
--
|
||||
-- # Calculate the Path
|
||||
--
|
||||
-- Finally, we have to calculate the path. This is done by the @{ASTAR.GetPath}(*ExcludeStart, ExcludeEnd*) function. This function returns a table of nodes, which
|
||||
-- describe the optimal path from the start node to the end node.
|
||||
--
|
||||
-- By default, the start and end node are include in the table that is returned.
|
||||
--
|
||||
-- Note that a valid path must not always exist. So you should check if the function returns *nil*.
|
||||
--
|
||||
-- Common reasons that a path cannot be found are:
|
||||
--
|
||||
-- * The grid is too small ==> increase grid size, e.g. *BoxHY* and/or *SpaceX* if you use a rectangular grid.
|
||||
-- * The grid spacing is too large ==> decrease *deltaX* and/or *deltaY*
|
||||
-- * There simply is no valid path ==> you are screwed :(
|
||||
--
|
||||
--
|
||||
-- # Examples
|
||||
--
|
||||
-- ## Strait of Hormuz
|
||||
--
|
||||
-- Carrier Group finds its way through the Stait of Hormuz.
|
||||
--
|
||||
-- ##
|
||||
--
|
||||
--
|
||||
--
|
||||
-- @field #ASTAR
|
||||
ASTAR = {
|
||||
ClassName = "ASTAR",
|
||||
Debug = nil,
|
||||
lid = nil,
|
||||
nodes = {},
|
||||
counter = 1,
|
||||
Nnodes = 0,
|
||||
ncost = 0,
|
||||
ncostcache = 0,
|
||||
nvalid = 0,
|
||||
nvalidcache = 0,
|
||||
}
|
||||
|
||||
--- Node data.
|
||||
-- @type ASTAR.Node
|
||||
-- @field #number id Node id.
|
||||
-- @field Core.Point#COORDINATE coordinate Coordinate of the node.
|
||||
-- @field #number surfacetype Surface type.
|
||||
-- @field #table valid Cached valid/invalid nodes.
|
||||
-- @field #table cost Cached cost.
|
||||
|
||||
--- ASTAR infinity.
|
||||
-- @field #number INF
|
||||
ASTAR.INF=1/0
|
||||
|
||||
--- ASTAR class version.
|
||||
-- @field #string version
|
||||
ASTAR.version="0.4.0"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- TODO list
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
-- TODO: Add more valid neighbour functions.
|
||||
-- TODO: Write docs.
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Constructor
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Create a new ASTAR object.
|
||||
-- @param #ASTAR self
|
||||
-- @return #ASTAR self
|
||||
function ASTAR:New()
|
||||
|
||||
-- Inherit everything from INTEL class.
|
||||
local self=BASE:Inherit(self, BASE:New()) --#ASTAR
|
||||
|
||||
self.lid="ASTAR | "
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- User functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Set coordinate from where to start.
|
||||
-- @param #ASTAR self
|
||||
-- @param Core.Point#COORDINATE Coordinate Start coordinate.
|
||||
-- @return #ASTAR self
|
||||
function ASTAR:SetStartCoordinate(Coordinate)
|
||||
|
||||
self.startCoord=Coordinate
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set coordinate where you want to go.
|
||||
-- @param #ASTAR self
|
||||
-- @param Core.Point#COORDINATE Coordinate end coordinate.
|
||||
-- @return #ASTAR self
|
||||
function ASTAR:SetEndCoordinate(Coordinate)
|
||||
|
||||
self.endCoord=Coordinate
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Create a node from a given coordinate.
|
||||
-- @param #ASTAR self
|
||||
-- @param Core.Point#COORDINATE Coordinate The coordinate where to create the node.
|
||||
-- @return #ASTAR.Node The node.
|
||||
function ASTAR:GetNodeFromCoordinate(Coordinate)
|
||||
|
||||
local node={} --#ASTAR.Node
|
||||
|
||||
node.coordinate=Coordinate
|
||||
node.surfacetype=Coordinate:GetSurfaceType()
|
||||
node.id=self.counter
|
||||
|
||||
node.valid={}
|
||||
node.cost={}
|
||||
|
||||
self.counter=self.counter+1
|
||||
|
||||
return node
|
||||
end
|
||||
|
||||
|
||||
--- Add a node to the table of grid nodes.
|
||||
-- @param #ASTAR self
|
||||
-- @param #ASTAR.Node Node The node to be added.
|
||||
-- @return #ASTAR self
|
||||
function ASTAR:AddNode(Node)
|
||||
|
||||
self.nodes[Node.id]=Node
|
||||
self.Nnodes=self.Nnodes+1
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Add a node to the table of grid nodes specifying its coordinate.
|
||||
-- @param #ASTAR self
|
||||
-- @param Core.Point#COORDINATE Coordinate The coordinate where the node is created.
|
||||
-- @return #ASTAR.Node The node.
|
||||
function ASTAR:AddNodeFromCoordinate(Coordinate)
|
||||
|
||||
local node=self:GetNodeFromCoordinate(Coordinate)
|
||||
|
||||
self:AddNode(node)
|
||||
|
||||
return node
|
||||
end
|
||||
|
||||
--- Check if the coordinate of a node has is at a valid surface type.
|
||||
-- @param #ASTAR self
|
||||
-- @param #ASTAR.Node Node The node to be added.
|
||||
-- @param #table SurfaceTypes Surface types, for example `{land.SurfaceType.WATER}`. By default all surface types are valid.
|
||||
-- @return #boolean If true, surface type of node is valid.
|
||||
function ASTAR:CheckValidSurfaceType(Node, SurfaceTypes)
|
||||
|
||||
if SurfaceTypes then
|
||||
|
||||
if type(SurfaceTypes)~="table" then
|
||||
SurfaceTypes={SurfaceTypes}
|
||||
end
|
||||
|
||||
for _,surface in pairs(SurfaceTypes) do
|
||||
if surface==Node.surfacetype then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
|
||||
else
|
||||
return true
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Add a function to determine if a neighbour of a node is valid.
|
||||
-- @param #ASTAR self
|
||||
-- @param #function NeighbourFunction Function that needs to return *true* for a neighbour to be valid.
|
||||
-- @param ... Condition function arguments if any.
|
||||
-- @return #ASTAR self
|
||||
function ASTAR:SetValidNeighbourFunction(NeighbourFunction, ...)
|
||||
|
||||
self.ValidNeighbourFunc=NeighbourFunction
|
||||
|
||||
self.ValidNeighbourArg={}
|
||||
if arg then
|
||||
self.ValidNeighbourArg=arg
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Set valid neighbours to require line of sight between two nodes.
|
||||
-- @param #ASTAR self
|
||||
-- @param #number CorridorWidth Width of LoS corridor in meters.
|
||||
-- @return #ASTAR self
|
||||
function ASTAR:SetValidNeighbourLoS(CorridorWidth)
|
||||
|
||||
self:SetValidNeighbourFunction(ASTAR.LoS, CorridorWidth)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set valid neighbours to be in a certain distance.
|
||||
-- @param #ASTAR self
|
||||
-- @param #number MaxDistance Max distance between nodes in meters. Default is 2000 m.
|
||||
-- @return #ASTAR self
|
||||
function ASTAR:SetValidNeighbourDistance(MaxDistance)
|
||||
|
||||
self:SetValidNeighbourFunction(ASTAR.DistMax, MaxDistance)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set valid neighbours to be in a certain distance.
|
||||
-- @param #ASTAR self
|
||||
-- @param #number MaxDistance Max distance between nodes in meters. Default is 2000 m.
|
||||
-- @return #ASTAR self
|
||||
function ASTAR:SetValidNeighbourRoad(MaxDistance)
|
||||
|
||||
self:SetValidNeighbourFunction(ASTAR.Road, MaxDistance)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set the function which calculates the "cost" to go from one to another node.
|
||||
-- The first to arguments of this function are always the two nodes under consideration. But you can add optional arguments.
|
||||
-- Very often the distance between nodes is a good measure for the cost.
|
||||
-- @param #ASTAR self
|
||||
-- @param #function CostFunction Function that returns the "cost".
|
||||
-- @param ... Condition function arguments if any.
|
||||
-- @return #ASTAR self
|
||||
function ASTAR:SetCostFunction(CostFunction, ...)
|
||||
|
||||
self.CostFunc=CostFunction
|
||||
|
||||
self.CostArg={}
|
||||
if arg then
|
||||
self.CostArg=arg
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set heuristic cost to go from one node to another to be their 2D distance.
|
||||
-- @param #ASTAR self
|
||||
-- @return #ASTAR self
|
||||
function ASTAR:SetCostDist2D()
|
||||
|
||||
self:SetCostFunction(ASTAR.Dist2D)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set heuristic cost to go from one node to another to be their 3D distance.
|
||||
-- @param #ASTAR self
|
||||
-- @return #ASTAR self
|
||||
function ASTAR:SetCostDist3D()
|
||||
|
||||
self:SetCostFunction(ASTAR.Dist3D)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set heuristic cost to go from one node to another to be their 3D distance.
|
||||
-- @param #ASTAR self
|
||||
-- @return #ASTAR self
|
||||
function ASTAR:SetCostRoad()
|
||||
|
||||
self:SetCostFunction(ASTAR)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Grid functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Create a rectangular grid of nodes between star and end coordinate.
|
||||
-- The coordinate system is oriented along the line between start and end point.
|
||||
-- @param #ASTAR self
|
||||
-- @param #table ValidSurfaceTypes Valid surface types. By default is all surfaces are allowed.
|
||||
-- @param #number BoxHY Box "height" in meters along the y-coordinate. Default 40000 meters (40 km).
|
||||
-- @param #number SpaceX Additional space in meters before start and after end coordinate. Default 10000 meters (10 km).
|
||||
-- @param #number deltaX Increment in the direction of start to end coordinate in meters. Default 2000 meters.
|
||||
-- @param #number deltaY Increment perpendicular to the direction of start to end coordinate in meters. Default is same as deltaX.
|
||||
-- @param #boolean MarkGrid If true, create F10 map markers at grid nodes.
|
||||
-- @return #ASTAR self
|
||||
function ASTAR:CreateGrid(ValidSurfaceTypes, BoxHY, SpaceX, deltaX, deltaY, MarkGrid)
|
||||
|
||||
-- Note that internally
|
||||
-- x coordinate is z: x-->z Line from start to end
|
||||
-- y coordinate is x: y-->x Perpendicular
|
||||
|
||||
-- Grid length and width.
|
||||
local Dz=SpaceX or 10000
|
||||
local Dx=BoxHY and BoxHY/2 or 20000
|
||||
|
||||
-- Increments.
|
||||
local dz=deltaX or 2000
|
||||
local dx=deltaY or dz
|
||||
|
||||
-- Heading from start to end coordinate.
|
||||
local angle=self.startCoord:HeadingTo(self.endCoord)
|
||||
|
||||
--Distance between start and end.
|
||||
local dist=self.startCoord:Get2DDistance(self.endCoord)+2*Dz
|
||||
|
||||
-- Origin of map. Needed to translate back to wanted position.
|
||||
local co=COORDINATE:New(0, 0, 0)
|
||||
local do1=co:Get2DDistance(self.startCoord)
|
||||
local ho1=co:HeadingTo(self.startCoord)
|
||||
|
||||
-- Start of grid.
|
||||
local xmin=-Dx
|
||||
local zmin=-Dz
|
||||
|
||||
-- Number of grid points.
|
||||
local nz=dist/dz+1
|
||||
local nx=2*Dx/dx+1
|
||||
|
||||
-- Debug info.
|
||||
local text=string.format("Building grid with nx=%d ny=%d => total=%d nodes", nx, nz, nx*nz)
|
||||
self:T(self.lid..text)
|
||||
|
||||
-- Loop over x and z coordinate to create a 2D grid.
|
||||
for i=1,nx do
|
||||
|
||||
-- x coordinate perpendicular to z.
|
||||
local x=xmin+dx*(i-1)
|
||||
|
||||
for j=1,nz do
|
||||
|
||||
-- z coordinate connecting start and end.
|
||||
local z=zmin+dz*(j-1)
|
||||
|
||||
-- Rotate 2D.
|
||||
local vec3=UTILS.Rotate2D({x=x, y=0, z=z}, angle)
|
||||
|
||||
-- Coordinate of the node.
|
||||
local c=COORDINATE:New(vec3.z, vec3.y, vec3.x):Translate(do1, ho1, true)
|
||||
|
||||
-- Create a node at this coordinate.
|
||||
local node=self:GetNodeFromCoordinate(c)
|
||||
|
||||
-- Check if node has valid surface type.
|
||||
if self:CheckValidSurfaceType(node, ValidSurfaceTypes) then
|
||||
|
||||
if MarkGrid then
|
||||
c:MarkToAll(string.format("i=%d, j=%d surface=%d", i, j, node.surfacetype))
|
||||
end
|
||||
|
||||
-- Add node to grid.
|
||||
self:AddNode(node)
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
-- Debug info.
|
||||
local text=string.format("Done building grid!")
|
||||
self:T2(self.lid..text)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Valid neighbour functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Function to check if two nodes have line of sight (LoS).
|
||||
-- @param #ASTAR.Node nodeA First node.
|
||||
-- @param #ASTAR.Node nodeB Other node.
|
||||
-- @param #number corridor (Optional) Width of corridor in meters.
|
||||
-- @return #boolean If true, two nodes have LoS.
|
||||
function ASTAR.LoS(nodeA, nodeB, corridor)
|
||||
|
||||
local offset=1
|
||||
|
||||
local dx=corridor and corridor/2 or nil
|
||||
local dy=dx
|
||||
|
||||
local cA=nodeA.coordinate:GetVec3()
|
||||
local cB=nodeB.coordinate:GetVec3()
|
||||
cA.y=offset
|
||||
cB.y=offset
|
||||
|
||||
local los=land.isVisible(cA, cB)
|
||||
|
||||
if los and corridor then
|
||||
|
||||
-- Heading from A to B.
|
||||
local heading=nodeA.coordinate:HeadingTo(nodeB.coordinate)
|
||||
|
||||
local Ap=UTILS.VecTranslate(cA, dx, heading+90)
|
||||
local Bp=UTILS.VecTranslate(cB, dx, heading+90)
|
||||
|
||||
los=land.isVisible(Ap, Bp)
|
||||
|
||||
if los then
|
||||
|
||||
local Am=UTILS.VecTranslate(cA, dx, heading-90)
|
||||
local Bm=UTILS.VecTranslate(cB, dx, heading-90)
|
||||
|
||||
los=land.isVisible(Am, Bm)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
return los
|
||||
end
|
||||
|
||||
--- Function to check if two nodes have a road connection.
|
||||
-- @param #ASTAR.Node nodeA First node.
|
||||
-- @param #ASTAR.Node nodeB Other node.
|
||||
-- @return #boolean If true, two nodes are connected via a road.
|
||||
function ASTAR.Road(nodeA, nodeB)
|
||||
|
||||
local path=land.findPathOnRoads("roads", nodeA.coordinate.x, nodeA.coordinate.z, nodeB.coordinate.x, nodeB.coordinate.z)
|
||||
|
||||
if path then
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Function to check if distance between two nodes is less than a threshold distance.
|
||||
-- @param #ASTAR.Node nodeA First node.
|
||||
-- @param #ASTAR.Node nodeB Other node.
|
||||
-- @param #number distmax Max distance in meters. Default is 2000 m.
|
||||
-- @return #boolean If true, distance between the two nodes is below threshold.
|
||||
function ASTAR.DistMax(nodeA, nodeB, distmax)
|
||||
|
||||
distmax=distmax or 2000
|
||||
|
||||
local dist=nodeA.coordinate:Get2DDistance(nodeB.coordinate)
|
||||
|
||||
return dist<=distmax
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Heuristic cost functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Heuristic cost is given by the 2D distance between the nodes.
|
||||
-- @param #ASTAR.Node nodeA First node.
|
||||
-- @param #ASTAR.Node nodeB Other node.
|
||||
-- @return #number Distance between the two nodes.
|
||||
function ASTAR.Dist2D(nodeA, nodeB)
|
||||
local dist=nodeA.coordinate:Get2DDistance(nodeB)
|
||||
return dist
|
||||
end
|
||||
|
||||
--- Heuristic cost is given by the 3D distance between the nodes.
|
||||
-- @param #ASTAR.Node nodeA First node.
|
||||
-- @param #ASTAR.Node nodeB Other node.
|
||||
-- @return #number Distance between the two nodes.
|
||||
function ASTAR.Dist3D(nodeA, nodeB)
|
||||
local dist=nodeA.coordinate:Get3DDistance(nodeB.coordinate)
|
||||
return dist
|
||||
end
|
||||
|
||||
--- Heuristic cost is given by the distance between the nodes on road.
|
||||
-- @param #ASTAR.Node nodeA First node.
|
||||
-- @param #ASTAR.Node nodeB Other node.
|
||||
-- @return #number Distance between the two nodes.
|
||||
function ASTAR.DistRoad(nodeA, nodeB)
|
||||
|
||||
-- Get the path.
|
||||
local path=land.findPathOnRoads("roads", nodeA.coordinate.x, nodeA.coordinate.z, nodeB.coordinate.x, nodeB.coordinate.z)
|
||||
|
||||
if path then
|
||||
|
||||
local dist=0
|
||||
|
||||
for i=2,#path do
|
||||
local b=path[i] --DCS#Vec2
|
||||
local a=path[i-1] --DCS#Vec2
|
||||
|
||||
dist=dist+UTILS.VecDist2D(a,b)
|
||||
|
||||
end
|
||||
|
||||
return dist
|
||||
end
|
||||
|
||||
|
||||
return math.huge
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Misc functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Find the closest node from a given coordinate.
|
||||
-- @param #ASTAR self
|
||||
-- @param Core.Point#COORDINATE Coordinate.
|
||||
-- @return #ASTAR.Node Cloest node to the coordinate.
|
||||
-- @return #number Distance to closest node in meters.
|
||||
function ASTAR:FindClosestNode(Coordinate)
|
||||
|
||||
local distMin=math.huge
|
||||
local closeNode=nil
|
||||
|
||||
for _,_node in pairs(self.nodes) do
|
||||
local node=_node --#ASTAR.Node
|
||||
|
||||
local dist=node.coordinate:Get2DDistance(Coordinate)
|
||||
|
||||
if dist<distMin then
|
||||
distMin=dist
|
||||
closeNode=node
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
return closeNode, distMin
|
||||
end
|
||||
|
||||
--- Find the start node.
|
||||
-- @param #ASTAR self
|
||||
-- @param #ASTAR.Node Node The node to be added to the nodes table.
|
||||
-- @return #ASTAR self
|
||||
function ASTAR:FindStartNode()
|
||||
|
||||
local node, dist=self:FindClosestNode(self.startCoord)
|
||||
|
||||
self.startNode=node
|
||||
|
||||
if dist>1000 then
|
||||
self:T(self.lid.."Adding start node to node grid!")
|
||||
self:AddNode(node)
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Add a node.
|
||||
-- @param #ASTAR self
|
||||
-- @param #ASTAR.Node Node The node to be added to the nodes table.
|
||||
-- @return #ASTAR self
|
||||
function ASTAR:FindEndNode()
|
||||
|
||||
local node, dist=self:FindClosestNode(self.endCoord)
|
||||
|
||||
self.endNode=node
|
||||
|
||||
if dist>1000 then
|
||||
self:T(self.lid.."Adding end node to node grid!")
|
||||
self:AddNode(node)
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Main A* pathfinding function
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- A* pathfinding function. This seaches the path along nodes between start and end nodes/coordinates.
|
||||
-- @param #ASTAR self
|
||||
-- @param #boolean ExcludeStartNode If *true*, do not include start node in found path. Default is to include it.
|
||||
-- @param #boolean ExcludeEndNode If *true*, do not include end node in found path. Default is to include it.
|
||||
-- @return #table Table of nodes from start to finish.
|
||||
function ASTAR:GetPath(ExcludeStartNode, ExcludeEndNode)
|
||||
|
||||
self:FindStartNode()
|
||||
self:FindEndNode()
|
||||
|
||||
local nodes=self.nodes
|
||||
local start=self.startNode
|
||||
local goal=self.endNode
|
||||
|
||||
-- Sets.
|
||||
local openset = {}
|
||||
local closedset = {}
|
||||
local came_from = {}
|
||||
local g_score = {}
|
||||
local f_score = {}
|
||||
|
||||
openset[start.id]=true
|
||||
local Nopen=1
|
||||
|
||||
-- Initial scores.
|
||||
g_score[start.id]=0
|
||||
f_score[start.id]=g_score[start.id]+self:_HeuristicCost(start, goal)
|
||||
|
||||
-- Set start time.
|
||||
local T0=timer.getAbsTime()
|
||||
|
||||
-- Debug message.
|
||||
local text=string.format("Starting A* pathfinding with %d Nodes", self.Nnodes)
|
||||
self:T(self.lid..text)
|
||||
|
||||
local Tstart=UTILS.GetOSTime()
|
||||
|
||||
-- Loop while we still have an open set.
|
||||
while Nopen > 0 do
|
||||
|
||||
-- Get current node.
|
||||
local current=self:_LowestFscore(openset, f_score)
|
||||
|
||||
-- Check if we are at the end node.
|
||||
if current.id==goal.id then
|
||||
|
||||
local path=self:_UnwindPath({}, came_from, goal)
|
||||
|
||||
if not ExcludeEndNode then
|
||||
table.insert(path, goal)
|
||||
end
|
||||
|
||||
if ExcludeStartNode then
|
||||
table.remove(path, 1)
|
||||
end
|
||||
|
||||
local Tstop=UTILS.GetOSTime()
|
||||
|
||||
local dT=nil
|
||||
if Tstart and Tstop then
|
||||
dT=Tstop-Tstart
|
||||
end
|
||||
|
||||
-- Debug message.
|
||||
local text=string.format("Found path with %d nodes (%d total)", #path, self.Nnodes)
|
||||
if dT then
|
||||
text=text..string.format(", OS Time %.6f sec", dT)
|
||||
end
|
||||
text=text..string.format(", Nvalid=%d [%d cached]", self.nvalid, self.nvalidcache)
|
||||
text=text..string.format(", Ncost=%d [%d cached]", self.ncost, self.ncostcache)
|
||||
self:T(self.lid..text)
|
||||
|
||||
return path
|
||||
end
|
||||
|
||||
-- Move Node from open to closed set.
|
||||
openset[current.id]=nil
|
||||
Nopen=Nopen-1
|
||||
closedset[current.id]=true
|
||||
|
||||
-- Get neighbour nodes.
|
||||
local neighbors=self:_NeighbourNodes(current, nodes)
|
||||
|
||||
-- Loop over neighbours.
|
||||
for _,neighbor in pairs(neighbors) do
|
||||
|
||||
if self:_NotIn(closedset, neighbor.id) then
|
||||
|
||||
local tentative_g_score=g_score[current.id]+self:_DistNodes(current, neighbor)
|
||||
|
||||
if self:_NotIn(openset, neighbor.id) or tentative_g_score < g_score[neighbor.id] then
|
||||
|
||||
came_from[neighbor]=current
|
||||
|
||||
g_score[neighbor.id]=tentative_g_score
|
||||
f_score[neighbor.id]=g_score[neighbor.id]+self:_HeuristicCost(neighbor, goal)
|
||||
|
||||
if self:_NotIn(openset, neighbor.id) then
|
||||
-- Add to open set.
|
||||
openset[neighbor.id]=true
|
||||
Nopen=Nopen+1
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Debug message.
|
||||
local text=string.format("WARNING: Could NOT find valid path!")
|
||||
self:E(self.lid..text)
|
||||
MESSAGE:New(text, 60, "ASTAR"):ToAllIf(self.Debug)
|
||||
|
||||
return nil -- no valid path
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- A* pathfinding helper functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Heuristic "cost" function to go from node A to node B. Default is the distance between the nodes.
|
||||
-- @param #ASTAR self
|
||||
-- @param #ASTAR.Node nodeA Node A.
|
||||
-- @param #ASTAR.Node nodeB Node B.
|
||||
-- @return #number "Cost" to go from node A to node B.
|
||||
function ASTAR:_HeuristicCost(nodeA, nodeB)
|
||||
|
||||
-- Counter.
|
||||
self.ncost=self.ncost+1
|
||||
|
||||
-- Get chached cost if available.
|
||||
local cost=nodeA.cost[nodeB.id]
|
||||
if cost~=nil then
|
||||
self.ncostcache=self.ncostcache+1
|
||||
return cost
|
||||
end
|
||||
|
||||
local cost=nil
|
||||
if self.CostFunc then
|
||||
cost=self.CostFunc(nodeA, nodeB, unpack(self.CostArg))
|
||||
else
|
||||
cost=self:_DistNodes(nodeA, nodeB)
|
||||
end
|
||||
|
||||
nodeA.cost[nodeB.id]=cost
|
||||
nodeB.cost[nodeA.id]=cost -- Symmetric problem.
|
||||
|
||||
return cost
|
||||
end
|
||||
|
||||
--- Check if going from a node to a neighbour is possible.
|
||||
-- @param #ASTAR self
|
||||
-- @param #ASTAR.Node node A node.
|
||||
-- @param #ASTAR.Node neighbor Neighbour node.
|
||||
-- @return #boolean If true, transition between nodes is possible.
|
||||
function ASTAR:_IsValidNeighbour(node, neighbor)
|
||||
|
||||
-- Counter.
|
||||
self.nvalid=self.nvalid+1
|
||||
|
||||
local valid=node.valid[neighbor.id]
|
||||
if valid~=nil then
|
||||
--env.info(string.format("Node %d has valid=%s neighbour %d", node.id, tostring(valid), neighbor.id))
|
||||
self.nvalidcache=self.nvalidcache+1
|
||||
return valid
|
||||
end
|
||||
|
||||
local valid=nil
|
||||
if self.ValidNeighbourFunc then
|
||||
valid=self.ValidNeighbourFunc(node, neighbor, unpack(self.ValidNeighbourArg))
|
||||
else
|
||||
valid=true
|
||||
end
|
||||
|
||||
node.valid[neighbor.id]=valid
|
||||
neighbor.valid[node.id]=valid -- Symmetric problem.
|
||||
|
||||
return valid
|
||||
end
|
||||
|
||||
--- Calculate 2D distance between two nodes.
|
||||
-- @param #ASTAR self
|
||||
-- @param #ASTAR.Node nodeA Node A.
|
||||
-- @param #ASTAR.Node nodeB Node B.
|
||||
-- @return #number Distance between nodes in meters.
|
||||
function ASTAR:_DistNodes(nodeA, nodeB)
|
||||
return nodeA.coordinate:Get2DDistance(nodeB.coordinate)
|
||||
end
|
||||
|
||||
--- Function that calculates the lowest F score.
|
||||
-- @param #ASTAR self
|
||||
-- @param #table set The set of nodes IDs.
|
||||
-- @param #number f_score F score.
|
||||
-- @return #ASTAR.Node Best node.
|
||||
function ASTAR:_LowestFscore(set, f_score)
|
||||
|
||||
local lowest, bestNode = ASTAR.INF, nil
|
||||
|
||||
for nid,node in pairs(set) do
|
||||
|
||||
local score=f_score[nid]
|
||||
|
||||
if score<lowest then
|
||||
lowest, bestNode = score, nid
|
||||
end
|
||||
end
|
||||
|
||||
return self.nodes[bestNode]
|
||||
end
|
||||
|
||||
--- Function to get valid neighbours of a node.
|
||||
-- @param #ASTAR self
|
||||
-- @param #ASTAR.Node theNode The node.
|
||||
-- @param #table nodes Possible neighbours.
|
||||
-- @param #table Valid neighbour nodes.
|
||||
function ASTAR:_NeighbourNodes(theNode, nodes)
|
||||
|
||||
local neighbors = {}
|
||||
|
||||
for _,node in pairs(nodes) do
|
||||
|
||||
if theNode.id~=node.id then
|
||||
|
||||
local isvalid=self:_IsValidNeighbour(theNode, node)
|
||||
|
||||
if isvalid then
|
||||
table.insert(neighbors, node)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
return neighbors
|
||||
end
|
||||
|
||||
--- Function to check if a node is not in a set.
|
||||
-- @param #ASTAR self
|
||||
-- @param #table set Set of nodes.
|
||||
-- @param #ASTAR.Node theNode The node to check.
|
||||
-- @return #boolean If true, the node is not in the set.
|
||||
function ASTAR:_NotIn(set, theNode)
|
||||
return set[theNode]==nil
|
||||
end
|
||||
|
||||
--- Unwind path function.
|
||||
-- @param #ASTAR self
|
||||
-- @param #table flat_path Flat path.
|
||||
-- @param #table map Map.
|
||||
-- @param #ASTAR.Node current_node The current node.
|
||||
-- @return #table Unwinded path.
|
||||
function ASTAR:_UnwindPath( flat_path, map, current_node )
|
||||
|
||||
if map [current_node] then
|
||||
table.insert (flat_path, 1, map[current_node])
|
||||
return self:_UnwindPath(flat_path, map, map[current_node])
|
||||
else
|
||||
return flat_path
|
||||
end
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
@@ -234,7 +234,8 @@ FORMATION = {
|
||||
-- @param #BASE self
|
||||
-- @return #BASE
|
||||
function BASE:New()
|
||||
local self = routines.utils.deepCopy( self ) -- Create a new self instance
|
||||
--local self = routines.utils.deepCopy( self ) -- Create a new self instance
|
||||
local self = UTILS.DeepCopy(self)
|
||||
|
||||
_ClassID = _ClassID + 1
|
||||
self.ClassID = _ClassID
|
||||
@@ -472,14 +473,16 @@ do -- Event Handling
|
||||
return self
|
||||
end
|
||||
|
||||
-- Event handling function prototypes
|
||||
-- Event handling function prototypes - Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
|
||||
|
||||
--- Occurs whenever any unit in a mission fires a weapon. But not any machine gun or autocannon based weapon, those are handled by EVENT.ShootingStart.
|
||||
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
|
||||
-- @function [parent=#BASE] OnEventShot
|
||||
-- @param #BASE self
|
||||
-- @param Core.Event#EVENTDATA EventData The EventData structure.
|
||||
|
||||
--- Occurs whenever an object is hit by a weapon.
|
||||
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
|
||||
-- initiator : The unit object the fired the weapon
|
||||
-- weapon: Weapon object that hit the target
|
||||
-- target: The Object that was hit.
|
||||
@@ -488,6 +491,7 @@ do -- Event Handling
|
||||
-- @param Core.Event#EVENTDATA EventData The EventData structure.
|
||||
|
||||
--- Occurs when an aircraft takes off from an airbase, farp, or ship.
|
||||
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
|
||||
-- initiator : The unit that tookoff
|
||||
-- place: Object from where the AI took-off from. Can be an Airbase Object, FARP, or Ships
|
||||
-- @function [parent=#BASE] OnEventTakeoff
|
||||
@@ -495,6 +499,7 @@ do -- Event Handling
|
||||
-- @param Core.Event#EVENTDATA EventData The EventData structure.
|
||||
|
||||
--- Occurs when an aircraft lands at an airbase, farp or ship
|
||||
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
|
||||
-- initiator : The unit that has landed
|
||||
-- place: Object that the unit landed on. Can be an Airbase Object, FARP, or Ships
|
||||
-- @function [parent=#BASE] OnEventLand
|
||||
@@ -502,101 +507,118 @@ do -- Event Handling
|
||||
-- @param Core.Event#EVENTDATA EventData The EventData structure.
|
||||
|
||||
--- Occurs when any aircraft crashes into the ground and is completely destroyed.
|
||||
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
|
||||
-- initiator : The unit that has crashed
|
||||
-- @function [parent=#BASE] OnEventCrash
|
||||
-- @param #BASE self
|
||||
-- @param Core.Event#EVENTDATA EventData The EventData structure.
|
||||
|
||||
--- Occurs when a pilot ejects from an aircraft
|
||||
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
|
||||
-- initiator : The unit that has ejected
|
||||
-- @function [parent=#BASE] OnEventEjection
|
||||
-- @param #BASE self
|
||||
-- @param Core.Event#EVENTDATA EventData The EventData structure.
|
||||
|
||||
--- Occurs when an aircraft connects with a tanker and begins taking on fuel.
|
||||
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
|
||||
-- initiator : The unit that is receiving fuel.
|
||||
-- @function [parent=#BASE] OnEventRefueling
|
||||
-- @param #BASE self
|
||||
-- @param Core.Event#EVENTDATA EventData The EventData structure.
|
||||
|
||||
--- Occurs when an object is dead.
|
||||
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
|
||||
-- initiator : The unit that is dead.
|
||||
-- @function [parent=#BASE] OnEventDead
|
||||
-- @param #BASE self
|
||||
-- @param Core.Event#EVENTDATA EventData The EventData structure.
|
||||
|
||||
--- Occurs when an object is completely destroyed.
|
||||
-- initiator : The unit that is was destroyed.
|
||||
--- Occurs when an Event for an object is triggered.
|
||||
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
|
||||
-- initiator : The unit that triggered the event.
|
||||
-- @function [parent=#BASE] OnEvent
|
||||
-- @param #BASE self
|
||||
-- @param Core.Event#EVENTDATA EventData The EventData structure.
|
||||
|
||||
--- Occurs when the pilot of an aircraft is killed. Can occur either if the player is alive and crashes or if a weapon kills the pilot without completely destroying the plane.
|
||||
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
|
||||
-- initiator : The unit that the pilot has died in.
|
||||
-- @function [parent=#BASE] OnEventPilotDead
|
||||
-- @param #BASE self
|
||||
-- @param Core.Event#EVENTDATA EventData The EventData structure.
|
||||
|
||||
--- Occurs when a ground unit captures either an airbase or a farp.
|
||||
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
|
||||
-- initiator : The unit that captured the base
|
||||
-- place: The airbase that was captured, can be a FARP or Airbase. When calling place:getCoalition() the faction will already be the new owning faction.
|
||||
-- @function [parent=#BASE] OnEventBaseCaptured
|
||||
-- @param #BASE self
|
||||
-- @param Core.Event#EVENTDATA EventData The EventData structure.
|
||||
|
||||
--- Occurs when a mission starts
|
||||
--- Occurs when a mission starts
|
||||
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
|
||||
-- @function [parent=#BASE] OnEventMissionStart
|
||||
-- @param #BASE self
|
||||
-- @param Core.Event#EVENTDATA EventData The EventData structure.
|
||||
|
||||
--- Occurs when a mission ends
|
||||
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
|
||||
-- @function [parent=#BASE] OnEventMissionEnd
|
||||
-- @param #BASE self
|
||||
-- @param Core.Event#EVENTDATA EventData The EventData structure.
|
||||
|
||||
--- Occurs when an aircraft is finished taking fuel.
|
||||
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
|
||||
-- initiator : The unit that was receiving fuel.
|
||||
-- @function [parent=#BASE] OnEventRefuelingStop
|
||||
-- @param #BASE self
|
||||
-- @param Core.Event#EVENTDATA EventData The EventData structure.
|
||||
|
||||
--- Occurs when any object is spawned into the mission.
|
||||
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
|
||||
-- initiator : The unit that was spawned
|
||||
-- @function [parent=#BASE] OnEventBirth
|
||||
-- @param #BASE self
|
||||
-- @param Core.Event#EVENTDATA EventData The EventData structure.
|
||||
|
||||
--- Occurs when any system fails on a human controlled aircraft.
|
||||
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
|
||||
-- initiator : The unit that had the failure
|
||||
-- @function [parent=#BASE] OnEventHumanFailure
|
||||
-- @param #BASE self
|
||||
-- @param Core.Event#EVENTDATA EventData The EventData structure.
|
||||
|
||||
--- Occurs when any aircraft starts its engines.
|
||||
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
|
||||
-- initiator : The unit that is starting its engines.
|
||||
-- @function [parent=#BASE] OnEventEngineStartup
|
||||
-- @param #BASE self
|
||||
-- @param Core.Event#EVENTDATA EventData The EventData structure.
|
||||
|
||||
--- Occurs when any aircraft shuts down its engines.
|
||||
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
|
||||
-- initiator : The unit that is stopping its engines.
|
||||
-- @function [parent=#BASE] OnEventEngineShutdown
|
||||
-- @param #BASE self
|
||||
-- @param Core.Event#EVENTDATA EventData The EventData structure.
|
||||
|
||||
--- Occurs when any player assumes direct control of a unit.
|
||||
--- Occurs when any player assumes direct control of a unit. Note - not Mulitplayer safe. Use PlayerEnterAircraft.
|
||||
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
|
||||
-- initiator : The unit that is being taken control of.
|
||||
-- @function [parent=#BASE] OnEventPlayerEnterUnit
|
||||
-- @param #BASE self
|
||||
-- @param Core.Event#EVENTDATA EventData The EventData structure.
|
||||
|
||||
--- Occurs when any player relieves control of a unit to the AI.
|
||||
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
|
||||
-- initiator : The unit that the player left.
|
||||
-- @function [parent=#BASE] OnEventPlayerLeaveUnit
|
||||
-- @param #BASE self
|
||||
-- @param Core.Event#EVENTDATA EventData The EventData structure.
|
||||
|
||||
--- Occurs when any unit begins firing a weapon that has a high rate of fire. Most common with aircraft cannons (GAU-8), autocannons, and machine guns.
|
||||
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
|
||||
-- initiator : The unit that is doing the shooting.
|
||||
-- target: The unit that is being targeted.
|
||||
-- @function [parent=#BASE] OnEventShootingStart
|
||||
@@ -604,24 +626,28 @@ do -- Event Handling
|
||||
-- @param Core.Event#EVENTDATA EventData The EventData structure.
|
||||
|
||||
--- Occurs when any unit stops firing its weapon. Event will always correspond with a shooting start event.
|
||||
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
|
||||
-- initiator : The unit that was doing the shooting.
|
||||
-- @function [parent=#BASE] OnEventShootingEnd
|
||||
-- @param #BASE self
|
||||
-- @param Core.Event#EVENTDATA EventData The EventData structure.
|
||||
|
||||
--- Occurs when a new mark was added.
|
||||
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
|
||||
-- MarkID: ID of the mark.
|
||||
-- @function [parent=#BASE] OnEventMarkAdded
|
||||
-- @param #BASE self
|
||||
-- @param Core.Event#EVENTDATA EventData The EventData structure.
|
||||
|
||||
--- Occurs when a mark was removed.
|
||||
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
|
||||
-- MarkID: ID of the mark.
|
||||
-- @function [parent=#BASE] OnEventMarkRemoved
|
||||
-- @param #BASE self
|
||||
-- @param Core.Event#EVENTDATA EventData The EventData structure.
|
||||
|
||||
--- Occurs when a mark text was changed.
|
||||
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
|
||||
-- MarkID: ID of the mark.
|
||||
-- @function [parent=#BASE] OnEventMarkChange
|
||||
-- @param #BASE self
|
||||
@@ -638,11 +664,13 @@ do -- Event Handling
|
||||
|
||||
--- Occurs when any modification to the "Score" as seen on the debrief menu would occur.
|
||||
-- There is no information on what values the score was changed to. Event is likely similar to player_comment in this regard.
|
||||
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
|
||||
-- @function [parent=#BASE] OnEventScore
|
||||
-- @param #BASE self
|
||||
-- @param Core.Event#EVENTDATA EventData The EventData structure.
|
||||
|
||||
--- Occurs on the death of a unit. Contains more and different information. Similar to unit_lost it will occur for aircraft before the aircraft crash event occurs.
|
||||
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
|
||||
--
|
||||
-- * initiator: The unit that killed the target
|
||||
-- * target: Target Object
|
||||
@@ -654,11 +682,13 @@ do -- Event Handling
|
||||
|
||||
--- Occurs when any modification to the "Score" as seen on the debrief menu would occur.
|
||||
-- There is no information on what values the score was changed to. Event is likely similar to player_comment in this regard.
|
||||
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
|
||||
-- @function [parent=#BASE] OnEventScore
|
||||
-- @param #BASE self
|
||||
-- @param Core.Event#EVENTDATA EventData The EventData structure.
|
||||
|
||||
--- Occurs when the game thinks an object is destroyed.
|
||||
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
|
||||
--
|
||||
-- * initiator: The unit that is was destroyed.
|
||||
--
|
||||
@@ -667,6 +697,7 @@ do -- Event Handling
|
||||
-- @param Core.Event#EVENTDATA EventData The EventData structure.
|
||||
|
||||
--- Occurs shortly after the landing animation of an ejected pilot touching the ground and standing up. Event does not occur if the pilot lands in the water and sub combs to Davey Jones Locker.
|
||||
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
|
||||
--
|
||||
-- * initiator: Static object representing the ejected pilot. Place : Aircraft that the pilot ejected from.
|
||||
-- * place: may not return as a valid object if the aircraft has crashed into the ground and no longer exists.
|
||||
@@ -676,6 +707,51 @@ do -- Event Handling
|
||||
-- @param #BASE self
|
||||
-- @param Core.Event#EVENTDATA EventData The EventData structure.
|
||||
|
||||
--- Paratrooper landing.
|
||||
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
|
||||
-- @function [parent=#BASE] OnEventParatrooperLanding
|
||||
-- @param #BASE self
|
||||
-- @param Core.Event#EVENTDATA EventData The EventData structure.
|
||||
|
||||
--- Discard chair after ejection.
|
||||
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
|
||||
-- @function [parent=#BASE] OnEventDiscardChairAfterEjection
|
||||
-- @param #BASE self
|
||||
-- @param Core.Event#EVENTDATA EventData The EventData structure.
|
||||
|
||||
--- Weapon add. Fires when entering a mission per pylon with the name of the weapon (double pylons not counted, infinite wep reload not counted.
|
||||
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
|
||||
-- @function [parent=#BASE] OnEventParatrooperLanding
|
||||
-- @param #BASE self
|
||||
-- @param Core.Event#EVENTDATA EventData The EventData structure.
|
||||
|
||||
--- Trigger zone.
|
||||
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
|
||||
-- @function [parent=#BASE] OnEventTriggerZone
|
||||
-- @param #BASE self
|
||||
-- @param Core.Event#EVENTDATA EventData The EventData structure.
|
||||
|
||||
--- Landing quality mark.
|
||||
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
|
||||
-- @function [parent=#BASE] OnEventLandingQualityMark
|
||||
-- @param #BASE self
|
||||
-- @param Core.Event#EVENTDATA EventData The EventData structure.
|
||||
|
||||
--- BDA.
|
||||
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
|
||||
-- @function [parent=#BASE] OnEventBDA
|
||||
-- @param #BASE self
|
||||
-- @param Core.Event#EVENTDATA EventData The EventData structure.
|
||||
|
||||
|
||||
--- Occurs when a player enters a slot and takes control of an aircraft.
|
||||
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
|
||||
-- **NOTE**: This is a workaround of a long standing DCS bug with the PLAYER_ENTER_UNIT event.
|
||||
-- initiator : The unit that is being taken control of.
|
||||
-- @function [parent=#BASE] OnEventPlayerEnterAircraft
|
||||
-- @param #BASE self
|
||||
-- @param Core.Event#EVENTDATA EventData The EventData structure.
|
||||
|
||||
end
|
||||
|
||||
|
||||
@@ -717,17 +793,34 @@ function BASE:CreateEventCrash( EventTime, Initiator )
|
||||
world.onEvent( Event )
|
||||
end
|
||||
|
||||
--- Creation of a Crash Event.
|
||||
-- @param #BASE self
|
||||
-- @param DCS#Time EventTime The time stamp of the event.
|
||||
-- @param DCS#Object Initiator The initiating object of the event.
|
||||
function BASE:CreateEventUnitLost(EventTime, Initiator)
|
||||
self:F( { EventTime, Initiator } )
|
||||
|
||||
local Event = {
|
||||
id = world.event.S_EVENT_UNIT_LOST,
|
||||
time = EventTime,
|
||||
initiator = Initiator,
|
||||
}
|
||||
|
||||
world.onEvent( Event )
|
||||
end
|
||||
|
||||
--- Creation of a Dead Event.
|
||||
-- @param #BASE self
|
||||
-- @param DCS#Time EventTime The time stamp of the event.
|
||||
-- @param DCS#Object Initiator The initiating object of the event.
|
||||
function BASE:CreateEventDead( EventTime, Initiator )
|
||||
self:F( { EventTime, Initiator } )
|
||||
function BASE:CreateEventDead( EventTime, Initiator, IniObjectCategory )
|
||||
self:F( { EventTime, Initiator, IniObjectCategory } )
|
||||
|
||||
local Event = {
|
||||
id = world.event.S_EVENT_DEAD,
|
||||
time = EventTime,
|
||||
initiator = Initiator,
|
||||
IniObjectCategory = IniObjectCategory,
|
||||
}
|
||||
|
||||
world.onEvent( Event )
|
||||
@@ -765,31 +858,47 @@ function BASE:CreateEventTakeoff( EventTime, Initiator )
|
||||
world.onEvent( Event )
|
||||
end
|
||||
|
||||
-- TODO: Complete DCS#Event structure.
|
||||
--- Creation of a `S_EVENT_PLAYER_ENTER_AIRCRAFT` event.
|
||||
-- @param #BASE self
|
||||
-- @param Wrapper.Unit#UNIT PlayerUnit The aircraft unit the player entered.
|
||||
function BASE:CreateEventPlayerEnterAircraft( PlayerUnit )
|
||||
self:F( { PlayerUnit } )
|
||||
|
||||
local Event = {
|
||||
id = EVENTS.PlayerEnterAircraft,
|
||||
time = timer.getTime(),
|
||||
initiator = PlayerUnit:GetDCSObject()
|
||||
}
|
||||
|
||||
world.onEvent(Event)
|
||||
end
|
||||
|
||||
--- The main event handling function... This function captures all events generated for the class.
|
||||
-- @param #BASE self
|
||||
-- @param DCS#Event event
|
||||
function BASE:onEvent(event)
|
||||
--self:F( { BaseEventCodes[event.id], event } )
|
||||
|
||||
if self then
|
||||
for EventID, EventObject in pairs( self.Events ) do
|
||||
|
||||
for EventID, EventObject in pairs(self.Events) do
|
||||
if EventObject.EventEnabled then
|
||||
--env.info( 'onEvent Table EventObject.Self = ' .. tostring(EventObject.Self) )
|
||||
--env.info( 'onEvent event.id = ' .. tostring(event.id) )
|
||||
--env.info( 'onEvent EventObject.Event = ' .. tostring(EventObject.Event) )
|
||||
|
||||
if event.id == EventObject.Event then
|
||||
|
||||
if self == EventObject.Self then
|
||||
|
||||
if event.initiator and event.initiator:isExist() then
|
||||
event.IniUnitName = event.initiator:getName()
|
||||
end
|
||||
|
||||
if event.target and event.target:isExist() then
|
||||
event.TgtUnitName = event.target:getName()
|
||||
end
|
||||
--self:T( { BaseEventCodes[event.id], event } )
|
||||
--EventObject.EventFunction( self, event )
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -802,20 +911,22 @@ do -- Scheduling
|
||||
-- @param #number Start Specifies the amount of seconds that will be waited before the scheduling is started, and the event function is called.
|
||||
-- @param #function SchedulerFunction The event function to be called when a timer event occurs. The event function needs to accept the parameters specified in SchedulerArguments.
|
||||
-- @param #table ... Optional arguments that can be given as part of scheduler. The arguments need to be given as a table { param1, param 2, ... }.
|
||||
-- @return #number The ScheduleID of the planned schedule.
|
||||
-- @return #string The Schedule ID of the planned schedule.
|
||||
function BASE:ScheduleOnce( Start, SchedulerFunction, ... )
|
||||
self:F2( { Start } )
|
||||
self:T3( { ... } )
|
||||
|
||||
-- Object name.
|
||||
local ObjectName = "-"
|
||||
ObjectName = self.ClassName .. self.ClassID
|
||||
|
||||
-- Debug info.
|
||||
self:F3( { "ScheduleOnce: ", ObjectName, Start } )
|
||||
|
||||
if not self.Scheduler then
|
||||
self.Scheduler = SCHEDULER:New( self )
|
||||
end
|
||||
|
||||
-- FF this was wrong!
|
||||
--[[
|
||||
local ScheduleID = _SCHEDULEDISPATCHER:AddSchedule(
|
||||
self,
|
||||
SchedulerFunction,
|
||||
@@ -825,6 +936,10 @@ do -- Scheduling
|
||||
nil,
|
||||
nil
|
||||
)
|
||||
]]
|
||||
|
||||
-- NOTE: MasterObject (first parameter) needs to be nil or it will be the first argument passed to the SchedulerFunction!
|
||||
local ScheduleID = self.Scheduler:Schedule(nil, SchedulerFunction, {...}, Start)
|
||||
|
||||
self._.Schedules[#self._.Schedules+1] = ScheduleID
|
||||
|
||||
@@ -839,7 +954,7 @@ do -- Scheduling
|
||||
-- @param #number Stop Specifies the amount of seconds when the scheduler will be stopped.
|
||||
-- @param #function SchedulerFunction The event function to be called when a timer event occurs. The event function needs to accept the parameters specified in SchedulerArguments.
|
||||
-- @param #table ... Optional arguments that can be given as part of scheduler. The arguments need to be given as a table { param1, param 2, ... }.
|
||||
-- @return #number The ScheduleID of the planned schedule.
|
||||
-- @return #string The Schedule ID of the planned schedule.
|
||||
function BASE:ScheduleRepeat( Start, Repeat, RandomizeFactor, Stop, SchedulerFunction, ... )
|
||||
self:F2( { Start } )
|
||||
self:T3( { ... } )
|
||||
@@ -853,8 +968,9 @@ do -- Scheduling
|
||||
self.Scheduler = SCHEDULER:New( self )
|
||||
end
|
||||
|
||||
local ScheduleID = self.Scheduler:Schedule(
|
||||
self,
|
||||
-- NOTE: MasterObject (first parameter) should(!) be nil as it will be the first argument passed to the SchedulerFunction!
|
||||
local ScheduleID = self.Scheduler:Schedule(
|
||||
nil,
|
||||
SchedulerFunction,
|
||||
{ ... },
|
||||
Start,
|
||||
@@ -871,13 +987,13 @@ do -- Scheduling
|
||||
|
||||
--- Stops the Schedule.
|
||||
-- @param #BASE self
|
||||
-- @param #function SchedulerFunction The event function to be called when a timer event occurs. The event function needs to accept the parameters specified in SchedulerArguments.
|
||||
function BASE:ScheduleStop( SchedulerFunction )
|
||||
|
||||
-- @param #string SchedulerID (Optional) Scheduler ID to be stopped. If nil, all pending schedules are stopped.
|
||||
function BASE:ScheduleStop( SchedulerID )
|
||||
self:F3( { "ScheduleStop:" } )
|
||||
|
||||
|
||||
if self.Scheduler then
|
||||
_SCHEDULEDISPATCHER:Stop( self.Scheduler, self._.Schedules[SchedulerFunction] )
|
||||
--_SCHEDULEDISPATCHER:Stop( self.Scheduler, self._.Schedules[SchedulerFunction] )
|
||||
_SCHEDULEDISPATCHER:Stop(self.Scheduler, SchedulerID)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1021,7 +1137,7 @@ end
|
||||
|
||||
--- Set tracing for a class
|
||||
-- @param #BASE self
|
||||
-- @param #string Class
|
||||
-- @param #string Class Class name.
|
||||
function BASE:TraceClass( Class )
|
||||
_TraceClass[Class] = true
|
||||
_TraceClassMethod[Class] = {}
|
||||
@@ -1030,8 +1146,8 @@ end
|
||||
|
||||
--- Set tracing for a specific method of class
|
||||
-- @param #BASE self
|
||||
-- @param #string Class
|
||||
-- @param #string Method
|
||||
-- @param #string Class Class name.
|
||||
-- @param #string Method Method.
|
||||
function BASE:TraceClassMethod( Class, Method )
|
||||
if not _TraceClassMethod[Class] then
|
||||
_TraceClassMethod[Class] = {}
|
||||
|
||||
@@ -0,0 +1,451 @@
|
||||
--- **Core** - TACAN and other beacons.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ## Features:
|
||||
--
|
||||
-- * Provide beacon functionality to assist pilots.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Authors: Hugues "Grey_Echo" Bousquet, funkyfranky
|
||||
--
|
||||
-- @module Core.Beacon
|
||||
-- @image Core_Radio.JPG
|
||||
|
||||
--- *In order for the light to shine so brightly, the darkness must be present.* -- Francis Bacon
|
||||
--
|
||||
-- After attaching a @{#BEACON} to your @{Wrapper.Positionable#POSITIONABLE}, you need to select the right function to activate the kind of beacon you want.
|
||||
-- There are two types of BEACONs available : the AA TACAN Beacon and the general purpose Radio Beacon.
|
||||
-- Note that in both case, you can set an optional parameter : the `BeaconDuration`. This can be very usefull to simulate the battery time if your BEACON is
|
||||
-- attach to a cargo crate, for exemple.
|
||||
--
|
||||
-- ## AA TACAN Beacon usage
|
||||
--
|
||||
-- This beacon only works with airborne @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP}. Use @{#BEACON:AATACAN}() to set the beacon parameters and start the beacon.
|
||||
-- Use @#BEACON:StopAATACAN}() to stop it.
|
||||
--
|
||||
-- ## General Purpose Radio Beacon usage
|
||||
--
|
||||
-- This beacon will work with any @{Wrapper.Positionable#POSITIONABLE}, but **it won't follow the @{Wrapper.Positionable#POSITIONABLE}** ! This means that you should only use it with
|
||||
-- @{Wrapper.Positionable#POSITIONABLE} that don't move, or move very slowly. Use @{#BEACON:RadioBeacon}() to set the beacon parameters and start the beacon.
|
||||
-- Use @{#BEACON:StopRadioBeacon}() to stop it.
|
||||
--
|
||||
-- @type BEACON
|
||||
-- @field #string ClassName Name of the class "BEACON".
|
||||
-- @field Wrapper.Controllable#CONTROLLABLE Positionable The @{#CONTROLLABLE} that will receive radio capabilities.
|
||||
-- @extends Core.Base#BASE
|
||||
BEACON = {
|
||||
ClassName = "BEACON",
|
||||
Positionable = nil,
|
||||
name = nil,
|
||||
}
|
||||
|
||||
--- Beacon types supported by DCS.
|
||||
-- @type BEACON.Type
|
||||
-- @field #number NULL
|
||||
-- @field #number VOR
|
||||
-- @field #number DME
|
||||
-- @field #number VOR_DME
|
||||
-- @field #number TACAN TACtical Air Navigation system.
|
||||
-- @field #number VORTAC
|
||||
-- @field #number RSBN
|
||||
-- @field #number BROADCAST_STATION
|
||||
-- @field #number HOMER
|
||||
-- @field #number AIRPORT_HOMER
|
||||
-- @field #number AIRPORT_HOMER_WITH_MARKER
|
||||
-- @field #number ILS_FAR_HOMER
|
||||
-- @field #number ILS_NEAR_HOMER
|
||||
-- @field #number ILS_LOCALIZER
|
||||
-- @field #number ILS_GLIDESLOPE
|
||||
-- @field #number PRMG_LOCALIZER
|
||||
-- @field #number PRMG_GLIDESLOPE
|
||||
-- @field #number ICLS Same as ICLS glideslope.
|
||||
-- @field #number ICLS_LOCALIZER
|
||||
-- @field #number ICLS_GLIDESLOPE
|
||||
-- @field #number NAUTICAL_HOMER
|
||||
BEACON.Type={
|
||||
NULL = 0,
|
||||
VOR = 1,
|
||||
DME = 2,
|
||||
VOR_DME = 3,
|
||||
TACAN = 4,
|
||||
VORTAC = 5,
|
||||
RSBN = 128,
|
||||
BROADCAST_STATION = 1024,
|
||||
HOMER = 8,
|
||||
AIRPORT_HOMER = 4104,
|
||||
AIRPORT_HOMER_WITH_MARKER = 4136,
|
||||
ILS_FAR_HOMER = 16408,
|
||||
ILS_NEAR_HOMER = 16424,
|
||||
ILS_LOCALIZER = 16640,
|
||||
ILS_GLIDESLOPE = 16896,
|
||||
PRMG_LOCALIZER = 33024,
|
||||
PRMG_GLIDESLOPE = 33280,
|
||||
ICLS = 131584, --leaving this in here but it is the same as ICLS_GLIDESLOPE
|
||||
ICLS_LOCALIZER = 131328,
|
||||
ICLS_GLIDESLOPE = 131584,
|
||||
NAUTICAL_HOMER = 65536,
|
||||
}
|
||||
|
||||
--- Beacon systems supported by DCS. https://wiki.hoggitworld.com/view/DCS_command_activateBeacon
|
||||
-- @type BEACON.System
|
||||
-- @field #number PAR_10 ?
|
||||
-- @field #number RSBN_5 Russian VOR/DME system.
|
||||
-- @field #number TACAN TACtical Air Navigation system on ground.
|
||||
-- @field #number TACAN_TANKER_X TACtical Air Navigation system for tankers on X band.
|
||||
-- @field #number TACAN_TANKER_Y TACtical Air Navigation system for tankers on Y band.
|
||||
-- @field #number VOR Very High Frequency Omni-Directional Range
|
||||
-- @field #number ILS_LOCALIZER ILS localizer
|
||||
-- @field #number ILS_GLIDESLOPE ILS glideslope.
|
||||
-- @field #number PRGM_LOCALIZER PRGM localizer.
|
||||
-- @field #number PRGM_GLIDESLOPE PRGM glideslope.
|
||||
-- @field #number BROADCAST_STATION Broadcast station.
|
||||
-- @field #number VORTAC Radio-based navigational aid for aircraft pilots consisting of a co-located VHF omnidirectional range (VOR) beacon and a tactical air navigation system (TACAN) beacon.
|
||||
-- @field #number TACAN_AA_MODE_X TACtical Air Navigation for aircraft on X band.
|
||||
-- @field #number TACAN_AA_MODE_Y TACtical Air Navigation for aircraft on Y band.
|
||||
-- @field #number VORDME Radio beacon that combines a VHF omnidirectional range (VOR) with a distance measuring equipment (DME).
|
||||
-- @field #number ICLS_LOCALIZER Carrier landing system.
|
||||
-- @field #number ICLS_GLIDESLOPE Carrier landing system.
|
||||
BEACON.System={
|
||||
PAR_10 = 1,
|
||||
RSBN_5 = 2,
|
||||
TACAN = 3,
|
||||
TACAN_TANKER_X = 4,
|
||||
TACAN_TANKER_Y = 5,
|
||||
VOR = 6,
|
||||
ILS_LOCALIZER = 7,
|
||||
ILS_GLIDESLOPE = 8,
|
||||
PRMG_LOCALIZER = 9,
|
||||
PRMG_GLIDESLOPE = 10,
|
||||
BROADCAST_STATION = 11,
|
||||
VORTAC = 12,
|
||||
TACAN_AA_MODE_X = 13,
|
||||
TACAN_AA_MODE_Y = 14,
|
||||
VORDME = 15,
|
||||
ICLS_LOCALIZER = 16,
|
||||
ICLS_GLIDESLOPE = 17,
|
||||
}
|
||||
|
||||
--- Create a new BEACON Object. This doesn't activate the beacon, though, use @{#BEACON.ActivateTACAN} etc.
|
||||
-- If you want to create a BEACON, you probably should use @{Wrapper.Positionable#POSITIONABLE.GetBeacon}() instead.
|
||||
-- @param #BEACON self
|
||||
-- @param Wrapper.Positionable#POSITIONABLE Positionable The @{Positionable} that will receive radio capabilities.
|
||||
-- @return #BEACON Beacon object or #nil if the positionable is invalid.
|
||||
function BEACON:New(Positionable)
|
||||
|
||||
-- Inherit BASE.
|
||||
local self=BASE:Inherit(self, BASE:New()) --#BEACON
|
||||
|
||||
-- Debug.
|
||||
self:F(Positionable)
|
||||
|
||||
-- Set positionable.
|
||||
if Positionable:GetPointVec2() then -- It's stupid, but the only way I found to make sure positionable is valid
|
||||
self.Positionable = Positionable
|
||||
self.name=Positionable:GetName()
|
||||
self:I(string.format("New BEACON %s", tostring(self.name)))
|
||||
return self
|
||||
end
|
||||
|
||||
self:E({"The passed positionable is invalid, no BEACON created", Positionable})
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
--- Activates a TACAN BEACON.
|
||||
-- @param #BEACON self
|
||||
-- @param #number Channel TACAN channel, i.e. the "10" part in "10Y".
|
||||
-- @param #string Mode TACAN mode, i.e. the "Y" part in "10Y".
|
||||
-- @param #string Message The Message that is going to be coded in Morse and broadcasted by the beacon.
|
||||
-- @param #boolean Bearing If true, beacon provides bearing information. If false (or nil), only distance information is available.
|
||||
-- @param #number Duration How long will the beacon last in seconds. Omit for forever.
|
||||
-- @return #BEACON self
|
||||
-- @usage
|
||||
-- -- Let's create a TACAN Beacon for a tanker
|
||||
-- local myUnit = UNIT:FindByName("MyUnit")
|
||||
-- local myBeacon = myUnit:GetBeacon() -- Creates the beacon
|
||||
--
|
||||
-- myBeacon:ActivateTACAN(20, "Y", "TEXACO", true) -- Activate the beacon
|
||||
function BEACON:ActivateTACAN(Channel, Mode, Message, Bearing, Duration)
|
||||
self:T({channel=Channel, mode=Mode, callsign=Message, bearing=Bearing, duration=Duration})
|
||||
|
||||
Mode=Mode or "Y"
|
||||
|
||||
-- Get frequency.
|
||||
local Frequency=UTILS.TACANToFrequency(Channel, Mode)
|
||||
|
||||
-- Check.
|
||||
if not Frequency then
|
||||
self:E({"The passed TACAN channel is invalid, the BEACON is not emitting"})
|
||||
return self
|
||||
end
|
||||
|
||||
-- Beacon type.
|
||||
local Type=BEACON.Type.TACAN
|
||||
|
||||
-- Beacon system.
|
||||
local System=BEACON.System.TACAN
|
||||
|
||||
-- Check if unit is an aircraft and set system accordingly.
|
||||
local AA=self.Positionable:IsAir()
|
||||
|
||||
|
||||
if AA then
|
||||
System=5 --NOTE: 5 is how you cat the correct tanker behaviour! --BEACON.System.TACAN_TANKER
|
||||
-- Check if "Y" mode is selected for aircraft.
|
||||
if Mode=="X" then
|
||||
--self:E({"WARNING: The POSITIONABLE you want to attach the AA Tacan Beacon is an aircraft: Mode should Y!", self.Positionable})
|
||||
System=BEACON.System.TACAN_TANKER_X
|
||||
else
|
||||
System=BEACON.System.TACAN_TANKER_Y
|
||||
end
|
||||
end
|
||||
|
||||
-- Attached unit.
|
||||
local UnitID=self.Positionable:GetID()
|
||||
|
||||
-- Debug.
|
||||
self:I({string.format("BEACON Activating TACAN %s: Channel=%d%s, Morse=%s, Bearing=%s, Duration=%s!", tostring(self.name), Channel, Mode, Message, tostring(Bearing), tostring(Duration))})
|
||||
|
||||
-- Start beacon.
|
||||
self.Positionable:CommandActivateBeacon(Type, System, Frequency, UnitID, Channel, Mode, AA, Message, Bearing)
|
||||
|
||||
-- Stop sheduler.
|
||||
if Duration then
|
||||
self.Positionable:DeactivateBeacon(Duration)
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Activates an ICLS BEACON. The unit the BEACON is attached to should be an aircraft carrier supporting this system.
|
||||
-- @param #BEACON self
|
||||
-- @param #number Channel ICLS channel.
|
||||
-- @param #string Callsign The Message that is going to be coded in Morse and broadcasted by the beacon.
|
||||
-- @param #number Duration How long will the beacon last in seconds. Omit for forever.
|
||||
-- @return #BEACON self
|
||||
function BEACON:ActivateICLS(Channel, Callsign, Duration)
|
||||
self:F({Channel=Channel, Callsign=Callsign, Duration=Duration})
|
||||
|
||||
-- Attached unit.
|
||||
local UnitID=self.Positionable:GetID()
|
||||
|
||||
-- Debug
|
||||
self:T2({"ICLS BEACON started!"})
|
||||
|
||||
-- Start beacon.
|
||||
self.Positionable:CommandActivateICLS(Channel, UnitID, Callsign)
|
||||
|
||||
-- Stop sheduler
|
||||
if Duration then -- Schedule the stop of the BEACON if asked by the MD
|
||||
self.Positionable:DeactivateBeacon(Duration)
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Activates a TACAN BEACON on an Aircraft.
|
||||
-- @param #BEACON self
|
||||
-- @param #number TACANChannel (the "10" part in "10Y"). Note that AA TACAN are only available on Y Channels
|
||||
-- @param #string Message The Message that is going to be coded in Morse and broadcasted by the beacon
|
||||
-- @param #boolean Bearing Can the BEACON be homed on ?
|
||||
-- @param #number BeaconDuration How long will the beacon last in seconds. Omit for forever.
|
||||
-- @return #BEACON self
|
||||
-- @usage
|
||||
-- -- Let's create a TACAN Beacon for a tanker
|
||||
-- local myUnit = UNIT:FindByName("MyUnit")
|
||||
-- local myBeacon = myUnit:GetBeacon() -- Creates the beacon
|
||||
--
|
||||
-- myBeacon:AATACAN(20, "TEXACO", true) -- Activate the beacon
|
||||
function BEACON:AATACAN(TACANChannel, Message, Bearing, BeaconDuration)
|
||||
self:F({TACANChannel, Message, Bearing, BeaconDuration})
|
||||
|
||||
local IsValid = true
|
||||
|
||||
if not self.Positionable:IsAir() then
|
||||
self:E({"The POSITIONABLE you want to attach the AA Tacan Beacon is not an aircraft ! The BEACON is not emitting", self.Positionable})
|
||||
IsValid = false
|
||||
end
|
||||
|
||||
local Frequency = self:_TACANToFrequency(TACANChannel, "Y")
|
||||
if not Frequency then
|
||||
self:E({"The passed TACAN channel is invalid, the BEACON is not emitting"})
|
||||
IsValid = false
|
||||
end
|
||||
|
||||
-- I'm using the beacon type 4 (BEACON_TYPE_TACAN). For System, I'm using 5 (TACAN_TANKER_MODE_Y) if the bearing shows its bearing or 14 (TACAN_AA_MODE_Y) if it does not
|
||||
local System
|
||||
if Bearing then
|
||||
System = BEACON.System.TACAN_TANKER_Y
|
||||
else
|
||||
System = BEACON.System.TACAN_AA_MODE_Y
|
||||
end
|
||||
|
||||
if IsValid then -- Starts the BEACON
|
||||
self:T2({"AA TACAN BEACON started !"})
|
||||
self.Positionable:SetCommand({
|
||||
id = "ActivateBeacon",
|
||||
params = {
|
||||
type = BEACON.Type.TACAN,
|
||||
system = System,
|
||||
callsign = Message,
|
||||
AA = true,
|
||||
frequency = Frequency,
|
||||
bearing = Bearing,
|
||||
modeChannel = "Y",
|
||||
}
|
||||
})
|
||||
|
||||
if BeaconDuration then -- Schedule the stop of the BEACON if asked by the MD
|
||||
SCHEDULER:New(nil,
|
||||
function()
|
||||
self:StopAATACAN()
|
||||
end, {}, BeaconDuration)
|
||||
end
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Stops the AA TACAN BEACON
|
||||
-- @param #BEACON self
|
||||
-- @return #BEACON self
|
||||
function BEACON:StopAATACAN()
|
||||
self:F()
|
||||
if not self.Positionable then
|
||||
self:E({"Start the beacon first before stoping it !"})
|
||||
else
|
||||
self.Positionable:SetCommand({
|
||||
id = 'DeactivateBeacon',
|
||||
params = {
|
||||
}
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- Activates a general pupose Radio Beacon
|
||||
-- This uses the very generic singleton function "trigger.action.radioTransmission()" provided by DCS to broadcast a sound file on a specific frequency.
|
||||
-- Although any frequency could be used, only 2 DCS Modules can home on radio beacons at the time of writing : the Huey and the Mi-8.
|
||||
-- They can home in on these specific frequencies :
|
||||
-- * **Mi8**
|
||||
-- * R-828 -> 20-60MHz
|
||||
-- * ARKUD -> 100-150MHz (canal 1 : 114166, canal 2 : 114333, canal 3 : 114583, canal 4 : 121500, canal 5 : 123100, canal 6 : 124100) AM
|
||||
-- * ARK9 -> 150-1300KHz
|
||||
-- * **Huey**
|
||||
-- * AN/ARC-131 -> 30-76 Mhz FM
|
||||
-- @param #BEACON self
|
||||
-- @param #string FileName The name of the audio file
|
||||
-- @param #number Frequency in MHz
|
||||
-- @param #number Modulation either radio.modulation.AM or radio.modulation.FM
|
||||
-- @param #number Power in W
|
||||
-- @param #number BeaconDuration How long will the beacon last in seconds. Omit for forever.
|
||||
-- @return #BEACON self
|
||||
-- @usage
|
||||
-- -- Let's create a beacon for a unit in distress.
|
||||
-- -- Frequency will be 40MHz FM (home-able by a Huey's AN/ARC-131)
|
||||
-- -- The beacon they use is battery-powered, and only lasts for 5 min
|
||||
-- local UnitInDistress = UNIT:FindByName("Unit1")
|
||||
-- local UnitBeacon = UnitInDistress:GetBeacon()
|
||||
--
|
||||
-- -- Set the beacon and start it
|
||||
-- UnitBeacon:RadioBeacon("MySoundFileSOS.ogg", 40, radio.modulation.FM, 20, 5*60)
|
||||
function BEACON:RadioBeacon(FileName, Frequency, Modulation, Power, BeaconDuration)
|
||||
self:F({FileName, Frequency, Modulation, Power, BeaconDuration})
|
||||
local IsValid = false
|
||||
|
||||
-- Check the filename
|
||||
if type(FileName) == "string" then
|
||||
if FileName:find(".ogg") or FileName:find(".wav") then
|
||||
if not FileName:find("l10n/DEFAULT/") then
|
||||
FileName = "l10n/DEFAULT/" .. FileName
|
||||
end
|
||||
IsValid = true
|
||||
end
|
||||
end
|
||||
if not IsValid then
|
||||
self:E({"File name invalid. Maybe something wrong with the extension ? ", FileName})
|
||||
end
|
||||
|
||||
-- Check the Frequency
|
||||
if type(Frequency) ~= "number" and IsValid then
|
||||
self:E({"Frequency invalid. ", Frequency})
|
||||
IsValid = false
|
||||
end
|
||||
Frequency = Frequency * 1000000 -- Conversion to Hz
|
||||
|
||||
-- Check the modulation
|
||||
if Modulation ~= radio.modulation.AM and Modulation ~= radio.modulation.FM and IsValid then --TODO Maybe make this future proof if ED decides to add an other modulation ?
|
||||
self:E({"Modulation is invalid. Use DCS's enum radio.modulation.", Modulation})
|
||||
IsValid = false
|
||||
end
|
||||
|
||||
-- Check the Power
|
||||
if type(Power) ~= "number" and IsValid then
|
||||
self:E({"Power is invalid. ", Power})
|
||||
IsValid = false
|
||||
end
|
||||
Power = math.floor(math.abs(Power)) --TODO Find what is the maximum power allowed by DCS and limit power to that
|
||||
|
||||
if IsValid then
|
||||
self:T2({"Activating Beacon on ", Frequency, Modulation})
|
||||
-- Note that this is looped. I have to give this transmission a unique name, I use the class ID
|
||||
trigger.action.radioTransmission(FileName, self.Positionable:GetPositionVec3(), Modulation, true, Frequency, Power, tostring(self.ID))
|
||||
|
||||
if BeaconDuration then -- Schedule the stop of the BEACON if asked by the MD
|
||||
SCHEDULER:New( nil,
|
||||
function()
|
||||
self:StopRadioBeacon()
|
||||
end, {}, BeaconDuration)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Stops the AA TACAN BEACON
|
||||
-- @param #BEACON self
|
||||
-- @return #BEACON self
|
||||
function BEACON:StopRadioBeacon()
|
||||
self:F()
|
||||
-- The unique name of the transmission is the class ID
|
||||
trigger.action.stopRadioTransmission(tostring(self.ID))
|
||||
return self
|
||||
end
|
||||
|
||||
--- Converts a TACAN Channel/Mode couple into a frequency in Hz
|
||||
-- @param #BEACON self
|
||||
-- @param #number TACANChannel
|
||||
-- @param #string TACANMode
|
||||
-- @return #number Frequecy
|
||||
-- @return #nil if parameters are invalid
|
||||
function BEACON:_TACANToFrequency(TACANChannel, TACANMode)
|
||||
self:F3({TACANChannel, TACANMode})
|
||||
|
||||
if type(TACANChannel) ~= "number" then
|
||||
if TACANMode ~= "X" and TACANMode ~= "Y" then
|
||||
return nil -- error in arguments
|
||||
end
|
||||
end
|
||||
|
||||
-- This code is largely based on ED's code, in DCS World\Scripts\World\Radio\BeaconTypes.lua, line 137.
|
||||
-- I have no idea what it does but it seems to work
|
||||
local A = 1151 -- 'X', channel >= 64
|
||||
local B = 64 -- channel >= 64
|
||||
|
||||
if TACANChannel < 64 then
|
||||
B = 1
|
||||
end
|
||||
|
||||
if TACANMode == 'Y' then
|
||||
A = 1025
|
||||
if TACANChannel < 64 then
|
||||
A = 1088
|
||||
end
|
||||
else -- 'X'
|
||||
if TACANChannel < 64 then
|
||||
A = 962
|
||||
end
|
||||
end
|
||||
|
||||
return (A + TACANChannel - B) * 1000000
|
||||
end
|
||||
@@ -24,7 +24,7 @@
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
-- ### Contributions:
|
||||
-- ### Contributions: **funkyfranky**
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
@@ -33,6 +33,9 @@
|
||||
|
||||
|
||||
--- @type DATABASE
|
||||
-- @field #string ClassName Name of the class.
|
||||
-- @field #table Templates Templates: Units, Groups, Statics, ClientsByName, ClientsByID.
|
||||
-- @field #table CLIENTS Clients.
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
--- Contains collections of wrapper objects defined within MOOSE that reflect objects within the simulator.
|
||||
@@ -121,13 +124,12 @@ function DATABASE:New()
|
||||
self:HandleEvent( EVENTS.Dead, self._EventOnDeadOrCrash )
|
||||
self:HandleEvent( EVENTS.Crash, self._EventOnDeadOrCrash )
|
||||
self:HandleEvent( EVENTS.RemoveUnit, self._EventOnDeadOrCrash )
|
||||
--self:HandleEvent( EVENTS.UnitLost, self._EventOnDeadOrCrash ) -- DCS 2.7.1 for Aerial units no dead event ATM
|
||||
self:HandleEvent( EVENTS.Hit, self.AccountHits )
|
||||
self:HandleEvent( EVENTS.NewCargo )
|
||||
self:HandleEvent( EVENTS.DeleteCargo )
|
||||
self:HandleEvent( EVENTS.NewZone )
|
||||
self:HandleEvent( EVENTS.DeleteZone )
|
||||
|
||||
-- Follow alive players and clients
|
||||
--self:HandleEvent( EVENTS.PlayerEnterUnit, self._EventOnPlayerEnterUnit ) -- This is not working anymore!, handling this through the birth event.
|
||||
self:HandleEvent( EVENTS.PlayerLeaveUnit, self._EventOnPlayerLeaveUnit )
|
||||
|
||||
@@ -136,41 +138,10 @@ function DATABASE:New()
|
||||
self:_RegisterClients()
|
||||
self:_RegisterStatics()
|
||||
--self:_RegisterPlayers()
|
||||
self:_RegisterAirbases()
|
||||
--self:_RegisterAirbases()
|
||||
|
||||
self.UNITS_Position = 0
|
||||
|
||||
--- @param #DATABASE self
|
||||
local function CheckPlayers( self )
|
||||
|
||||
local CoalitionsData = { AlivePlayersRed = coalition.getPlayers( coalition.side.RED ), AlivePlayersBlue = coalition.getPlayers( coalition.side.BLUE ), AlivePlayersNeutral = coalition.getPlayers( coalition.side.NEUTRAL )}
|
||||
for CoalitionId, CoalitionData in pairs( CoalitionsData ) do
|
||||
--self:E( { "CoalitionData:", CoalitionData } )
|
||||
for UnitId, UnitData in pairs( CoalitionData ) do
|
||||
if UnitData and UnitData:isExist() then
|
||||
|
||||
local UnitName = UnitData:getName()
|
||||
local PlayerName = UnitData:getPlayerName()
|
||||
local PlayerUnit = UNIT:Find( UnitData )
|
||||
--self:T( { "UnitData:", UnitData, UnitName, PlayerName, PlayerUnit } )
|
||||
|
||||
if PlayerName and PlayerName ~= "" then
|
||||
if self.PLAYERS[PlayerName] == nil or self.PLAYERS[PlayerName] ~= UnitName then
|
||||
--self:E( { "Add player for unit:", UnitName, PlayerName } )
|
||||
self:AddPlayer( UnitName, PlayerName )
|
||||
--_EVENTDISPATCHER:CreateEventPlayerEnterUnit( PlayerUnit )
|
||||
local Settings = SETTINGS:Set( PlayerName )
|
||||
Settings:SetPlayerMenu( PlayerUnit )
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--self:E( "Scheduling" )
|
||||
--PlayerCheckSchedule = SCHEDULER:New( nil, CheckPlayers, { self }, 1, 1 )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -187,14 +158,16 @@ end
|
||||
|
||||
--- Adds a Unit based on the Unit Name in the DATABASE.
|
||||
-- @param #DATABASE self
|
||||
-- @param #string DCSUnitName Unit name.
|
||||
-- @return Wrapper.Unit#UNIT The added unit.
|
||||
function DATABASE:AddUnit( DCSUnitName )
|
||||
|
||||
if not self.UNITS[DCSUnitName] then
|
||||
if not self.UNITS[DCSUnitName] then
|
||||
-- Debug info.
|
||||
self:T( { "Add UNIT:", DCSUnitName } )
|
||||
local UnitRegister = UNIT:Register( DCSUnitName )
|
||||
self.UNITS[DCSUnitName] = UNIT:Register( DCSUnitName )
|
||||
|
||||
table.insert( self.UNITS_Index, DCSUnitName )
|
||||
-- Register unit
|
||||
self.UNITS[DCSUnitName]=UNIT:Register(DCSUnitName)
|
||||
end
|
||||
|
||||
return self.UNITS[DCSUnitName]
|
||||
@@ -204,12 +177,13 @@ end
|
||||
--- Deletes a Unit from the DATABASE based on the Unit Name.
|
||||
-- @param #DATABASE self
|
||||
function DATABASE:DeleteUnit( DCSUnitName )
|
||||
|
||||
self.UNITS[DCSUnitName] = nil
|
||||
end
|
||||
|
||||
--- Adds a Static based on the Static Name in the DATABASE.
|
||||
-- @param #DATABASE self
|
||||
-- @param #string DCSStaticName Name of the static.
|
||||
-- @return Wrapper.Static#STATIC The static object.
|
||||
function DATABASE:AddStatic( DCSStaticName )
|
||||
|
||||
if not self.STATICS[DCSStaticName] then
|
||||
@@ -224,8 +198,7 @@ end
|
||||
--- Deletes a Static from the DATABASE based on the Static Name.
|
||||
-- @param #DATABASE self
|
||||
function DATABASE:DeleteStatic( DCSStaticName )
|
||||
|
||||
--self.STATICS[DCSStaticName] = nil
|
||||
self.STATICS[DCSStaticName] = nil
|
||||
end
|
||||
|
||||
--- Finds a STATIC based on the StaticName.
|
||||
@@ -255,7 +228,15 @@ end
|
||||
function DATABASE:AddAirbase( AirbaseName )
|
||||
|
||||
if not self.AIRBASES[AirbaseName] then
|
||||
self.AIRBASES[AirbaseName] = AIRBASE:Register( AirbaseName )
|
||||
|
||||
-- Register airbase.
|
||||
local airbase = AIRBASE:Register( AirbaseName ) --Wrapper.Airbase#AIRBASE
|
||||
|
||||
-- Add to airbase.
|
||||
self.AIRBASES[AirbaseName]=airbase
|
||||
|
||||
-- Debug info
|
||||
self:I(string.format("Adding airbase %s (ID=%d): %s with %d parking spots", tostring(airbase.AirbaseName), airbase.AirbaseID, airbase.AirbaseCategoryName, tonumber(#airbase.parking)))
|
||||
end
|
||||
|
||||
return self.AIRBASES[AirbaseName]
|
||||
@@ -319,24 +300,81 @@ do -- Zones
|
||||
-- @return #DATABASE self
|
||||
function DATABASE:_RegisterZones()
|
||||
|
||||
for ZoneID, ZoneData in pairs( env.mission.triggers.zones ) do
|
||||
for ZoneID, ZoneData in pairs(env.mission.triggers.zones) do
|
||||
local ZoneName = ZoneData.name
|
||||
|
||||
self:I( { "Register ZONE:", Name = ZoneName } )
|
||||
local Zone = ZONE:New( ZoneName )
|
||||
self.ZONENAMES[ZoneName] = ZoneName
|
||||
self:AddZone( ZoneName, Zone )
|
||||
-- Color
|
||||
local color=ZoneData.color or {1, 0, 0, 0.15}
|
||||
|
||||
-- Create new Zone
|
||||
local Zone=nil --Core.Zone#ZONE_BASE
|
||||
|
||||
if ZoneData.type==0 then
|
||||
|
||||
---
|
||||
-- Circular zone
|
||||
---
|
||||
|
||||
self:I(string.format("Register ZONE: %s (Circular)", ZoneName))
|
||||
|
||||
Zone=ZONE:New(ZoneName)
|
||||
|
||||
else
|
||||
|
||||
---
|
||||
-- Quad-point zone
|
||||
---
|
||||
|
||||
self:I(string.format("Register ZONE: %s (Polygon, Quad)", ZoneName))
|
||||
|
||||
Zone=ZONE_POLYGON_BASE:New(ZoneName, ZoneData.verticies)
|
||||
|
||||
--for i,vec2 in pairs(ZoneData.verticies) do
|
||||
-- local coord=COORDINATE:NewFromVec2(vec2)
|
||||
-- coord:MarkToAll(string.format("%s Point %d", ZoneName, i))
|
||||
--end
|
||||
|
||||
end
|
||||
|
||||
if Zone then
|
||||
|
||||
-- Store color of zone.
|
||||
Zone.Color=color
|
||||
|
||||
-- Store zone ID.
|
||||
Zone.ZoneID=ZoneData.zoneId
|
||||
|
||||
-- Store in DB.
|
||||
self.ZONENAMES[ZoneName] = ZoneName
|
||||
|
||||
-- Add zone.
|
||||
self:AddZone(ZoneName, Zone)
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-- Polygon zones defined by late activated groups.
|
||||
for ZoneGroupName, ZoneGroup in pairs( self.GROUPS ) do
|
||||
if ZoneGroupName:match("#ZONE_POLYGON") then
|
||||
|
||||
local ZoneName1 = ZoneGroupName:match("(.*)#ZONE_POLYGON")
|
||||
local ZoneName2 = ZoneGroupName:match(".*#ZONE_POLYGON(.*)")
|
||||
local ZoneName = ZoneName1 .. ( ZoneName2 or "" )
|
||||
|
||||
self:I( { "Register ZONE_POLYGON:", Name = ZoneName } )
|
||||
-- Debug output
|
||||
self:I(string.format("Register ZONE: %s (Polygon)", ZoneName))
|
||||
|
||||
-- Create a new polygon zone.
|
||||
local Zone_Polygon = ZONE_POLYGON:New( ZoneName, ZoneGroup )
|
||||
|
||||
-- Set color.
|
||||
Zone_Polygon:SetColor({1, 0, 0}, 0.15)
|
||||
|
||||
-- Store name in DB.
|
||||
self.ZONENAMES[ZoneName] = ZoneName
|
||||
|
||||
-- Add zone to DB.
|
||||
self:AddZone( ZoneName, Zone_Polygon )
|
||||
end
|
||||
end
|
||||
@@ -432,7 +470,6 @@ do -- cargo
|
||||
local Groups = UTILS.DeepCopy( self.GROUPS ) -- This is a very important statement. CARGO_GROUP:New creates a new _DATABASE.GROUP entry, which will confuse the loop. I searched 4 hours on this to find the bug!
|
||||
|
||||
for CargoGroupName, CargoGroup in pairs( Groups ) do
|
||||
self:I( { Cargo = CargoGroupName } )
|
||||
if self:IsCargo( CargoGroupName ) then
|
||||
local CargoInfo = CargoGroupName:match("#CARGO(.*)")
|
||||
local CargoParam = CargoInfo and CargoInfo:match( "%((.*)%)")
|
||||
@@ -489,6 +526,8 @@ end
|
||||
|
||||
--- Adds a CLIENT based on the ClientName in the DATABASE.
|
||||
-- @param #DATABASE self
|
||||
-- @param #string ClientName Name of the Client unit.
|
||||
-- @return Wrapper.Client#CLIENT The client object.
|
||||
function DATABASE:AddClient( ClientName )
|
||||
|
||||
if not self.CLIENTS[ClientName] then
|
||||
@@ -626,6 +665,9 @@ function DATABASE:Spawn( SpawnTemplate )
|
||||
end
|
||||
|
||||
--- Set a status to a Group within the Database, this to check crossing events for example.
|
||||
-- @param #DATABASE self
|
||||
-- @param #string GroupName Group name.
|
||||
-- @param #string Status Status.
|
||||
function DATABASE:SetStatusGroup( GroupName, Status )
|
||||
self:F2( Status )
|
||||
|
||||
@@ -633,8 +675,11 @@ function DATABASE:SetStatusGroup( GroupName, Status )
|
||||
end
|
||||
|
||||
--- Get a status to a Group within the Database, this to check crossing events for example.
|
||||
-- @param #DATABASE self
|
||||
-- @param #string GroupName Group name.
|
||||
-- @return #string Status or an empty string "".
|
||||
function DATABASE:GetStatusGroup( GroupName )
|
||||
self:F2( Status )
|
||||
self:F2( GroupName )
|
||||
|
||||
if self.Templates.Groups[GroupName] then
|
||||
return self.Templates.Groups[GroupName].Status
|
||||
@@ -648,7 +693,8 @@ end
|
||||
-- @param #table GroupTemplate
|
||||
-- @param DCS#coalition.side CoalitionSide The coalition.side of the object.
|
||||
-- @param DCS#Object.Category CategoryID The Object.category of the object.
|
||||
-- @param DCS#country.id CountryID the country.id of the object
|
||||
-- @param DCS#country.id CountryID the country ID of the object.
|
||||
-- @param #string GroupName (Optional) The name of the group. Default is `GroupTemplate.name`.
|
||||
-- @return #DATABASE self
|
||||
function DATABASE:_RegisterGroupTemplate( GroupTemplate, CoalitionSide, CategoryID, CountryID, GroupName )
|
||||
|
||||
@@ -704,6 +750,7 @@ function DATABASE:_RegisterGroupTemplate( GroupTemplate, CoalitionSide, Category
|
||||
UnitNames[#UnitNames+1] = self.Templates.Units[UnitTemplate.name].UnitName
|
||||
end
|
||||
|
||||
-- Debug info.
|
||||
self:T( { Group = self.Templates.Groups[GroupTemplateName].GroupName,
|
||||
Coalition = self.Templates.Groups[GroupTemplateName].CoalitionID,
|
||||
Category = self.Templates.Groups[GroupTemplateName].CategoryID,
|
||||
@@ -713,6 +760,10 @@ function DATABASE:_RegisterGroupTemplate( GroupTemplate, CoalitionSide, Category
|
||||
)
|
||||
end
|
||||
|
||||
--- Get group template.
|
||||
-- @param #DATABASE self
|
||||
-- @param #string GroupName Group name.
|
||||
-- @return #table Group template table.
|
||||
function DATABASE:GetGroupTemplate( GroupName )
|
||||
local GroupTemplate = self.Templates.Groups[GroupName].Template
|
||||
GroupTemplate.SpawnCoalitionID = self.Templates.Groups[GroupName].CoalitionID
|
||||
@@ -723,13 +774,18 @@ end
|
||||
|
||||
--- Private method that registers new Static Templates within the DATABASE Object.
|
||||
-- @param #DATABASE self
|
||||
-- @param #table StaticTemplate
|
||||
-- @param #table StaticTemplate Template table.
|
||||
-- @param #number CoalitionID Coalition ID.
|
||||
-- @param #number CategoryID Category ID.
|
||||
-- @param #number CountryID Country ID.
|
||||
-- @return #DATABASE self
|
||||
function DATABASE:_RegisterStaticTemplate( StaticTemplate, CoalitionID, CategoryID, CountryID )
|
||||
|
||||
local StaticTemplate = UTILS.DeepCopy( StaticTemplate )
|
||||
|
||||
local StaticTemplateName = env.getValueDictByKey(StaticTemplate.name)
|
||||
local StaticTemplateGroupName = env.getValueDictByKey(StaticTemplate.name)
|
||||
|
||||
local StaticTemplateName=StaticTemplate.units[1].name
|
||||
|
||||
self.Templates.Statics[StaticTemplateName] = self.Templates.Statics[StaticTemplateName] or {}
|
||||
|
||||
@@ -737,14 +793,15 @@ function DATABASE:_RegisterStaticTemplate( StaticTemplate, CoalitionID, Category
|
||||
StaticTemplate.CoalitionID = CoalitionID
|
||||
StaticTemplate.CountryID = CountryID
|
||||
|
||||
self.Templates.Statics[StaticTemplateName].StaticName = StaticTemplateName
|
||||
self.Templates.Statics[StaticTemplateName].StaticName = StaticTemplateGroupName
|
||||
self.Templates.Statics[StaticTemplateName].GroupTemplate = StaticTemplate
|
||||
self.Templates.Statics[StaticTemplateName].UnitTemplate = StaticTemplate.units[1]
|
||||
self.Templates.Statics[StaticTemplateName].CategoryID = CategoryID
|
||||
self.Templates.Statics[StaticTemplateName].CoalitionID = CoalitionID
|
||||
self.Templates.Statics[StaticTemplateName].CountryID = CountryID
|
||||
|
||||
self:I( { Static = self.Templates.Statics[StaticTemplateName].StaticName,
|
||||
-- Debug info.
|
||||
self:T( { Static = self.Templates.Statics[StaticTemplateName].StaticName,
|
||||
Coalition = self.Templates.Statics[StaticTemplateName].CoalitionID,
|
||||
Category = self.Templates.Statics[StaticTemplateName].CategoryID,
|
||||
Country = self.Templates.Statics[StaticTemplateName].CountryID
|
||||
@@ -753,48 +810,115 @@ function DATABASE:_RegisterStaticTemplate( StaticTemplate, CoalitionID, Category
|
||||
|
||||
self:AddStatic( StaticTemplateName )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- @param #DATABASE self
|
||||
--- Get static group template.
|
||||
-- @param #DATABASE self
|
||||
-- @param #string StaticName Name of the static.
|
||||
-- @return #table Static template table.
|
||||
function DATABASE:GetStaticGroupTemplate( StaticName )
|
||||
local StaticTemplate = self.Templates.Statics[StaticName].GroupTemplate
|
||||
return StaticTemplate, self.Templates.Statics[StaticName].CoalitionID, self.Templates.Statics[StaticName].CategoryID, self.Templates.Statics[StaticName].CountryID
|
||||
if self.Templates.Statics[StaticName] then
|
||||
local StaticTemplate = self.Templates.Statics[StaticName].GroupTemplate
|
||||
return StaticTemplate, self.Templates.Statics[StaticName].CoalitionID, self.Templates.Statics[StaticName].CategoryID, self.Templates.Statics[StaticName].CountryID
|
||||
else
|
||||
self:E("ERROR: Static group template does NOT exist for static "..tostring(StaticName))
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
--- @param #DATABASE self
|
||||
--- Get static unit template.
|
||||
-- @param #DATABASE self
|
||||
-- @param #string StaticName Name of the static.
|
||||
-- @return #table Static template table.
|
||||
function DATABASE:GetStaticUnitTemplate( StaticName )
|
||||
local UnitTemplate = self.Templates.Statics[StaticName].UnitTemplate
|
||||
return UnitTemplate, self.Templates.Statics[StaticName].CoalitionID, self.Templates.Statics[StaticName].CategoryID, self.Templates.Statics[StaticName].CountryID
|
||||
if self.Templates.Statics[StaticName] then
|
||||
local UnitTemplate = self.Templates.Statics[StaticName].UnitTemplate
|
||||
return UnitTemplate, self.Templates.Statics[StaticName].CoalitionID, self.Templates.Statics[StaticName].CategoryID, self.Templates.Statics[StaticName].CountryID
|
||||
else
|
||||
self:E("ERROR: Static unit template does NOT exist for static "..tostring(StaticName))
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- Get group name from unit name.
|
||||
-- @param #DATABASE self
|
||||
-- @param #string UnitName Name of the unit.
|
||||
-- @return #string Group name.
|
||||
function DATABASE:GetGroupNameFromUnitName( UnitName )
|
||||
return self.Templates.Units[UnitName].GroupName
|
||||
if self.Templates.Units[UnitName] then
|
||||
return self.Templates.Units[UnitName].GroupName
|
||||
else
|
||||
self:E("ERROR: Unit template does not exist for unit "..tostring(UnitName))
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
--- Get group template from unit name.
|
||||
-- @param #DATABASE self
|
||||
-- @param #string UnitName Name of the unit.
|
||||
-- @return #table Group template.
|
||||
function DATABASE:GetGroupTemplateFromUnitName( UnitName )
|
||||
return self.Templates.Units[UnitName].GroupTemplate
|
||||
if self.Templates.Units[UnitName] then
|
||||
return self.Templates.Units[UnitName].GroupTemplate
|
||||
else
|
||||
self:E("ERROR: Unit template does not exist for unit "..tostring(UnitName))
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
--- Get group template from unit name.
|
||||
-- @param #DATABASE self
|
||||
-- @param #string UnitName Name of the unit.
|
||||
-- @return #table Group template.
|
||||
function DATABASE:GetUnitTemplateFromUnitName( UnitName )
|
||||
if self.Templates.Units[UnitName] then
|
||||
return self.Templates.Units[UnitName]
|
||||
else
|
||||
self:E("ERROR: Unit template does not exist for unit "..tostring(UnitName))
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- Get coalition ID from client name.
|
||||
-- @param #DATABASE self
|
||||
-- @param #string ClientName Name of the Client.
|
||||
-- @return #number Coalition ID.
|
||||
function DATABASE:GetCoalitionFromClientTemplate( ClientName )
|
||||
return self.Templates.ClientsByName[ClientName].CoalitionID
|
||||
end
|
||||
|
||||
--- Get category ID from client name.
|
||||
-- @param #DATABASE self
|
||||
-- @param #string ClientName Name of the Client.
|
||||
-- @return #number Category ID.
|
||||
function DATABASE:GetCategoryFromClientTemplate( ClientName )
|
||||
return self.Templates.ClientsByName[ClientName].CategoryID
|
||||
end
|
||||
|
||||
--- Get country ID from client name.
|
||||
-- @param #DATABASE self
|
||||
-- @param #string ClientName Name of the Client.
|
||||
-- @return #number Country ID.
|
||||
function DATABASE:GetCountryFromClientTemplate( ClientName )
|
||||
return self.Templates.ClientsByName[ClientName].CountryID
|
||||
end
|
||||
|
||||
--- Airbase
|
||||
|
||||
--- Get coalition ID from airbase name.
|
||||
-- @param #DATABASE self
|
||||
-- @param #string AirbaseName Name of the airbase.
|
||||
-- @return #number Coalition ID.
|
||||
function DATABASE:GetCoalitionFromAirbase( AirbaseName )
|
||||
return self.AIRBASES[AirbaseName]:GetCoalition()
|
||||
end
|
||||
|
||||
--- Get category from airbase name.
|
||||
-- @param #DATABASE self
|
||||
-- @param #string AirbaseName Name of the airbase.
|
||||
-- @return #number Category.
|
||||
function DATABASE:GetCategoryFromAirbase( AirbaseName )
|
||||
return self.AIRBASES[AirbaseName]:GetCategory()
|
||||
end
|
||||
@@ -831,33 +955,38 @@ end
|
||||
function DATABASE:_RegisterGroupsAndUnits()
|
||||
|
||||
local CoalitionsData = { GroupsRed = coalition.getGroups( coalition.side.RED ), GroupsBlue = coalition.getGroups( coalition.side.BLUE ), GroupsNeutral = coalition.getGroups( coalition.side.NEUTRAL ) }
|
||||
|
||||
for CoalitionId, CoalitionData in pairs( CoalitionsData ) do
|
||||
|
||||
for DCSGroupId, DCSGroup in pairs( CoalitionData ) do
|
||||
|
||||
if DCSGroup:isExist() then
|
||||
|
||||
-- Group name.
|
||||
local DCSGroupName = DCSGroup:getName()
|
||||
|
||||
self:I( { "Register Group:", DCSGroupName } )
|
||||
-- Add group.
|
||||
self:I(string.format("Register Group: %s", tostring(DCSGroupName)))
|
||||
self:AddGroup( DCSGroupName )
|
||||
|
||||
-- Loop over units in group.
|
||||
for DCSUnitId, DCSUnit in pairs( DCSGroup:getUnits() ) do
|
||||
|
||||
-- Get unit name.
|
||||
local DCSUnitName = DCSUnit:getName()
|
||||
self:I( { "Register Unit:", DCSUnitName } )
|
||||
|
||||
-- Add unit.
|
||||
self:I(string.format("Register Unit: %s", tostring(DCSUnitName)))
|
||||
self:AddUnit( DCSUnitName )
|
||||
|
||||
end
|
||||
else
|
||||
self:E( { "Group does not exist: ", DCSGroup } )
|
||||
self:E({"Group does not exist: ", DCSGroup})
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
self:T("Groups:")
|
||||
for GroupName, Group in pairs( self.GROUPS ) do
|
||||
self:T( { "Group:", GroupName } )
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -867,7 +996,7 @@ end
|
||||
function DATABASE:_RegisterClients()
|
||||
|
||||
for ClientName, ClientTemplate in pairs( self.Templates.ClientsByName ) do
|
||||
self:T( { "Register Client:", ClientName } )
|
||||
self:I(string.format("Register Client: %s", tostring(ClientName)))
|
||||
self:AddClient( ClientName )
|
||||
end
|
||||
|
||||
@@ -877,15 +1006,15 @@ end
|
||||
--- @param #DATABASE self
|
||||
function DATABASE:_RegisterStatics()
|
||||
|
||||
local CoalitionsData = { GroupsRed = coalition.getStaticObjects( coalition.side.RED ), GroupsBlue = coalition.getStaticObjects( coalition.side.BLUE ) }
|
||||
self:I( { Statics = CoalitionsData } )
|
||||
local CoalitionsData={GroupsRed=coalition.getStaticObjects(coalition.side.RED), GroupsBlue=coalition.getStaticObjects(coalition.side.BLUE), GroupsNeutral=coalition.getStaticObjects(coalition.side.NEUTRAL)}
|
||||
|
||||
for CoalitionId, CoalitionData in pairs( CoalitionsData ) do
|
||||
for DCSStaticId, DCSStatic in pairs( CoalitionData ) do
|
||||
|
||||
if DCSStatic:isExist() then
|
||||
local DCSStaticName = DCSStatic:getName()
|
||||
|
||||
self:T( { "Register Static:", DCSStaticName } )
|
||||
self:I(string.format("Register Static: %s", tostring(DCSStaticName)))
|
||||
self:AddStatic( DCSStaticName )
|
||||
else
|
||||
self:E( { "Static does not exist: ", DCSStatic } )
|
||||
@@ -902,7 +1031,7 @@ end
|
||||
function DATABASE:_RegisterAirbases()
|
||||
|
||||
for DCSAirbaseId, DCSAirbase in pairs(world.getAirbases()) do
|
||||
|
||||
|
||||
-- Get the airbase name.
|
||||
local DCSAirbaseName = DCSAirbase:getName()
|
||||
|
||||
@@ -912,9 +1041,24 @@ function DATABASE:_RegisterAirbases()
|
||||
-- Add and register airbase.
|
||||
local airbase=self:AddAirbase( DCSAirbaseName )
|
||||
|
||||
-- Unique ID.
|
||||
local airbaseUID=airbase:GetID(true)
|
||||
|
||||
-- Debug output.
|
||||
self:I(string.format("Register Airbase: %s, getID=%d, GetID=%d (unique=%d)", DCSAirbaseName, DCSAirbase:getID(), airbase:GetID(), airbase:GetID(true)))
|
||||
|
||||
local text=string.format("Register %s: %s (ID=%d UID=%d), parking=%d [", AIRBASE.CategoryName[airbase.category], tostring(DCSAirbaseName), airbaseID, airbaseUID, airbase.NparkingTotal)
|
||||
for _,terminalType in pairs(AIRBASE.TerminalType) do
|
||||
if airbase.NparkingTerminal and airbase.NparkingTerminal[terminalType] then
|
||||
text=text..string.format("%d=%d ", terminalType, airbase.NparkingTerminal[terminalType])
|
||||
end
|
||||
end
|
||||
text=text.."]"
|
||||
self:I(text)
|
||||
|
||||
-- Check for DCS bug IDs.
|
||||
if airbaseID~=airbase:GetID() then
|
||||
--self:E("WARNING: :getID does NOT match :GetID!")
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
return self
|
||||
@@ -930,36 +1074,74 @@ function DATABASE:_EventOnBirth( Event )
|
||||
self:F( { Event } )
|
||||
|
||||
if Event.IniDCSUnit then
|
||||
|
||||
if Event.IniObjectCategory == 3 then
|
||||
|
||||
self:AddStatic( Event.IniDCSUnitName )
|
||||
|
||||
else
|
||||
|
||||
if Event.IniObjectCategory == 1 then
|
||||
|
||||
self:AddUnit( Event.IniDCSUnitName )
|
||||
self:AddGroup( Event.IniDCSGroupName )
|
||||
|
||||
-- Add airbase if it was spawned later in the mission.
|
||||
local DCSAirbase = Airbase.getByName(Event.IniDCSUnitName)
|
||||
if DCSAirbase then
|
||||
self:I(string.format("Adding airbase %s", tostring(Event.IniDCSUnitName)))
|
||||
self:AddAirbase(Event.IniDCSUnitName)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
if Event.IniObjectCategory == 1 then
|
||||
|
||||
Event.IniUnit = self:FindUnit( Event.IniDCSUnitName )
|
||||
Event.IniGroup = self:FindGroup( Event.IniDCSGroupName )
|
||||
|
||||
-- Client
|
||||
local client=self.CLIENTS[Event.IniDCSUnitName] --Wrapper.Client#CLIENT
|
||||
|
||||
if client then
|
||||
-- TODO: create event ClientAlive
|
||||
end
|
||||
|
||||
-- Get player name.
|
||||
local PlayerName = Event.IniUnit:GetPlayerName()
|
||||
|
||||
if PlayerName then
|
||||
self:I( { "Player Joined:", PlayerName } )
|
||||
self:AddClient( Event.IniDCSUnitName )
|
||||
|
||||
-- Debug info.
|
||||
self:I(string.format("Player '%s' joint unit '%s' of group '%s'", tostring(PlayerName), tostring(Event.IniDCSUnitName), tostring(Event.IniDCSGroupName)))
|
||||
|
||||
-- Add client in case it does not exist already.
|
||||
if not client then
|
||||
client=self:AddClient(Event.IniDCSUnitName)
|
||||
end
|
||||
|
||||
-- Add player.
|
||||
client:AddPlayer(PlayerName)
|
||||
|
||||
-- Add player.
|
||||
if not self.PLAYERS[PlayerName] then
|
||||
self:AddPlayer( Event.IniUnitName, PlayerName )
|
||||
end
|
||||
|
||||
-- Player settings.
|
||||
local Settings = SETTINGS:Set( PlayerName )
|
||||
Settings:SetPlayerMenu( Event.IniUnit )
|
||||
--MENU_INDEX:Refresh( Event.IniGroup )
|
||||
Settings:SetPlayerMenu(Event.IniUnit)
|
||||
|
||||
-- Create an event.
|
||||
self:CreateEventPlayerEnterAircraft(Event.IniUnit)
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
@@ -967,22 +1149,69 @@ end
|
||||
-- @param #DATABASE self
|
||||
-- @param Core.Event#EVENTDATA Event
|
||||
function DATABASE:_EventOnDeadOrCrash( Event )
|
||||
self:F2( { Event } )
|
||||
|
||||
if Event.IniDCSUnit then
|
||||
|
||||
local name=Event.IniDCSUnitName
|
||||
|
||||
if Event.IniObjectCategory == 3 then
|
||||
|
||||
---
|
||||
-- STATICS
|
||||
---
|
||||
|
||||
if self.STATICS[Event.IniDCSUnitName] then
|
||||
self:DeleteStatic( Event.IniDCSUnitName )
|
||||
end
|
||||
else
|
||||
if Event.IniObjectCategory == 1 then
|
||||
if self.UNITS[Event.IniDCSUnitName] then
|
||||
self:DeleteUnit( Event.IniDCSUnitName )
|
||||
|
||||
---
|
||||
-- Maybe a UNIT?
|
||||
---
|
||||
|
||||
-- Delete unit.
|
||||
if self.UNITS[Event.IniDCSUnitName] then
|
||||
self:T("STATIC Event for UNIT "..tostring(Event.IniDCSUnitName))
|
||||
local DCSUnit = _DATABASE:FindUnit( Event.IniDCSUnitName )
|
||||
self:T({DCSUnit})
|
||||
if DCSUnit then
|
||||
--self:I("Creating DEAD Event for UNIT "..tostring(Event.IniDCSUnitName))
|
||||
--DCSUnit:Destroy(true)
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
else
|
||||
|
||||
if Event.IniObjectCategory == 1 then
|
||||
|
||||
---
|
||||
-- UNITS
|
||||
---
|
||||
|
||||
-- Delete unit.
|
||||
if self.UNITS[Event.IniDCSUnitName] then
|
||||
self:DeleteUnit(Event.IniDCSUnitName)
|
||||
end
|
||||
|
||||
-- Remove client players.
|
||||
local client=self.CLIENTS[name] --Wrapper.Client#CLIENT
|
||||
|
||||
if client then
|
||||
client:RemovePlayers()
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
-- Add airbase if it was spawned later in the mission.
|
||||
local airbase=self.AIRBASES[Event.IniDCSUnitName] --Wrapper.Airbase#AIRBASE
|
||||
if airbase and (airbase:IsHelipad() or airbase:IsShip()) then
|
||||
self:DeleteAirbase(Event.IniDCSUnitName)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-- Account destroys.
|
||||
self:AccountDestroys( Event )
|
||||
end
|
||||
|
||||
@@ -995,15 +1224,31 @@ function DATABASE:_EventOnPlayerEnterUnit( Event )
|
||||
|
||||
if Event.IniDCSUnit then
|
||||
if Event.IniObjectCategory == 1 then
|
||||
|
||||
-- Add unit.
|
||||
self:AddUnit( Event.IniDCSUnitName )
|
||||
|
||||
-- Ini unit.
|
||||
Event.IniUnit = self:FindUnit( Event.IniDCSUnitName )
|
||||
|
||||
-- Add group.
|
||||
self:AddGroup( Event.IniDCSGroupName )
|
||||
|
||||
-- Get player unit.
|
||||
local PlayerName = Event.IniDCSUnit:getPlayerName()
|
||||
if not self.PLAYERS[PlayerName] then
|
||||
self:AddPlayer( Event.IniDCSUnitName, PlayerName )
|
||||
|
||||
if PlayerName then
|
||||
|
||||
if not self.PLAYERS[PlayerName] then
|
||||
self:AddPlayer( Event.IniDCSUnitName, PlayerName )
|
||||
end
|
||||
|
||||
local Settings = SETTINGS:Set( PlayerName )
|
||||
Settings:SetPlayerMenu( Event.IniUnit )
|
||||
|
||||
else
|
||||
self:E("ERROR: getPlayerName() returned nil for event PlayerEnterUnit")
|
||||
end
|
||||
local Settings = SETTINGS:Set( PlayerName )
|
||||
Settings:SetPlayerMenu( Event.IniUnit )
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1016,13 +1261,30 @@ function DATABASE:_EventOnPlayerLeaveUnit( Event )
|
||||
self:F2( { Event } )
|
||||
|
||||
if Event.IniUnit then
|
||||
|
||||
if Event.IniObjectCategory == 1 then
|
||||
|
||||
-- Try to get the player name. This can be buggy for multicrew aircraft!
|
||||
local PlayerName = Event.IniUnit:GetPlayerName()
|
||||
if PlayerName and self.PLAYERS[PlayerName] then
|
||||
self:I( { "Player Left:", PlayerName } )
|
||||
|
||||
if PlayerName then --and self.PLAYERS[PlayerName] then
|
||||
|
||||
-- Debug info.
|
||||
self:I(string.format("Player '%s' left unit %s", tostring(PlayerName), tostring(Event.IniUnitName)))
|
||||
|
||||
-- Remove player menu.
|
||||
local Settings = SETTINGS:Set( PlayerName )
|
||||
Settings:RemovePlayerMenu( Event.IniUnit )
|
||||
self:DeletePlayer( Event.IniUnit, PlayerName )
|
||||
Settings:RemovePlayerMenu(Event.IniUnit)
|
||||
|
||||
-- Delete player.
|
||||
self:DeletePlayer(Event.IniUnit, PlayerName)
|
||||
|
||||
-- Client stuff.
|
||||
local client=self.CLIENTS[Event.IniDCSUnitName] --Wrapper.Client#CLIENT
|
||||
if client then
|
||||
client:RemovePlayer(PlayerName)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1249,19 +1511,19 @@ function DATABASE:SetPlayerSettings( PlayerName, Settings )
|
||||
self.PLAYERSETTINGS[PlayerName] = Settings
|
||||
end
|
||||
|
||||
--- Add a flight group to the data base.
|
||||
--- Add an OPS group (FLIGHTGROUP, ARMYGROUP, NAVYGROUP) to the data base.
|
||||
-- @param #DATABASE self
|
||||
-- @param Ops.FlightGroup#FLIGHTGROUP flightgroup
|
||||
function DATABASE:AddFlightGroup(flightgroup)
|
||||
self:I({NewFlightGroup=flightgroup.groupname})
|
||||
self.FLIGHTGROUPS[flightgroup.groupname]=flightgroup
|
||||
-- @param Ops.OpsGroup#OPSGROUP opsgroup The OPS group added to the DB.
|
||||
function DATABASE:AddOpsGroup(opsgroup)
|
||||
--env.info("Adding OPSGROUP "..tostring(opsgroup.groupname))
|
||||
self.FLIGHTGROUPS[opsgroup.groupname]=opsgroup
|
||||
end
|
||||
|
||||
--- Get a flight group from the data base.
|
||||
--- Get an OPS group (FLIGHTGROUP, ARMYGROUP, NAVYGROUP) from the data base.
|
||||
-- @param #DATABASE self
|
||||
-- @param #string groupname Group name of the flight group. Can also be passed as GROUP object.
|
||||
-- @return Ops.FlightGroup#FLIGHTGROUP Flight group object.
|
||||
function DATABASE:GetFlightGroup(groupname)
|
||||
-- @param #string groupname Group name of the group. Can also be passed as GROUP object.
|
||||
-- @return Ops.OpsGroup#OPSGROUP OPS group object.
|
||||
function DATABASE:GetOpsGroup(groupname)
|
||||
|
||||
-- Get group and group name.
|
||||
if type(groupname)=="string" then
|
||||
@@ -1269,6 +1531,23 @@ function DATABASE:GetFlightGroup(groupname)
|
||||
groupname=groupname:GetName()
|
||||
end
|
||||
|
||||
--env.info("Getting OPSGROUP "..tostring(groupname))
|
||||
return self.FLIGHTGROUPS[groupname]
|
||||
end
|
||||
|
||||
--- Find an OPSGROUP (FLIGHTGROUP, ARMYGROUP, NAVYGROUP) in the data base.
|
||||
-- @param #DATABASE self
|
||||
-- @param #string groupname Group name of the group. Can also be passed as GROUP object.
|
||||
-- @return Ops.OpsGroup#OPSGROUP OPS group object.
|
||||
function DATABASE:FindOpsGroup(groupname)
|
||||
|
||||
-- Get group and group name.
|
||||
if type(groupname)=="string" then
|
||||
else
|
||||
groupname=groupname:GetName()
|
||||
end
|
||||
|
||||
--env.info("Getting OPSGROUP "..tostring(groupname))
|
||||
return self.FLIGHTGROUPS[groupname]
|
||||
end
|
||||
|
||||
@@ -1355,19 +1634,13 @@ function DATABASE:_RegisterTemplates()
|
||||
for group_num, Template in pairs(obj_type_data.group) do
|
||||
|
||||
if obj_type_name ~= "static" and Template and Template.units and type(Template.units) == 'table' then --making sure again- this is a valid group
|
||||
self:_RegisterGroupTemplate(
|
||||
Template,
|
||||
CoalitionSide,
|
||||
_DATABASECategory[string.lower(CategoryName)],
|
||||
CountryID
|
||||
)
|
||||
|
||||
self:_RegisterGroupTemplate(Template, CoalitionSide, _DATABASECategory[string.lower(CategoryName)], CountryID)
|
||||
|
||||
else
|
||||
self:_RegisterStaticTemplate(
|
||||
Template,
|
||||
CoalitionSide,
|
||||
_DATABASECategory[string.lower(CategoryName)],
|
||||
CountryID
|
||||
)
|
||||
|
||||
self:_RegisterStaticTemplate(Template, CoalitionSide, _DATABASECategory[string.lower(CategoryName)], CountryID)
|
||||
|
||||
end --if GroupTemplate and GroupTemplate.units then
|
||||
end --for group_num, GroupTemplate in pairs(obj_type_data.group) do
|
||||
end --if ((type(obj_type_data) == 'table') and obj_type_data.group and (type(obj_type_data.group) == 'table') and (#obj_type_data.group > 0)) then
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -410,7 +410,7 @@ do -- FSM
|
||||
Transition.To = To
|
||||
|
||||
-- Debug message.
|
||||
self:T2( Transition )
|
||||
--self:T3( Transition )
|
||||
|
||||
self._Transitions[Transition] = Transition
|
||||
self:_eventmap( self.Events, Transition )
|
||||
@@ -432,7 +432,7 @@ do -- FSM
|
||||
-- @param #table ReturnEvents A table indicating for which returned events of the SubFSM which Event must be triggered in the FSM.
|
||||
-- @return Core.Fsm#FSM_PROCESS The SubFSM.
|
||||
function FSM:AddProcess( From, Event, Process, ReturnEvents )
|
||||
self:T( { From, Event } )
|
||||
--self:T3( { From, Event } )
|
||||
|
||||
local Sub = {}
|
||||
Sub.From = From
|
||||
@@ -533,14 +533,14 @@ do -- FSM
|
||||
Process._Scores[State].ScoreText = ScoreText
|
||||
Process._Scores[State].Score = Score
|
||||
|
||||
self:T( Process._Scores )
|
||||
--self:T3( Process._Scores )
|
||||
|
||||
return Process
|
||||
end
|
||||
|
||||
--- Returns a table with the scores defined.
|
||||
-- @param #FSM self
|
||||
-- @param #table Scores.
|
||||
-- @return #table Scores.
|
||||
function FSM:GetScores()
|
||||
return self._Scores or {}
|
||||
end
|
||||
@@ -576,7 +576,7 @@ do -- FSM
|
||||
self[__Event] = self[__Event] or self:_delayed_transition(Event)
|
||||
|
||||
-- Debug message.
|
||||
self:T2( "Added methods: " .. Event .. ", " .. __Event )
|
||||
--self:T3( "Added methods: " .. Event .. ", " .. __Event )
|
||||
|
||||
Events[Event] = self.Events[Event] or { map = {} }
|
||||
self:_add_to_map( Events[Event].map, EventStructure )
|
||||
@@ -791,7 +791,7 @@ do -- FSM
|
||||
return function( self, DelaySeconds, ... )
|
||||
|
||||
-- Debug.
|
||||
self:T2( "Delayed Event: " .. EventName )
|
||||
self:T3( "Delayed Event: " .. EventName )
|
||||
|
||||
local CallID = 0
|
||||
if DelaySeconds ~= nil then
|
||||
@@ -809,23 +809,23 @@ do -- FSM
|
||||
self._EventSchedules[EventName] = CallID
|
||||
|
||||
-- Debug output.
|
||||
self:T2(string.format("NEGATIVE Event %s delayed by %.1f sec SCHEDULED with CallID=%s", EventName, DelaySeconds, tostring(CallID)))
|
||||
self:T2(string.format("NEGATIVE Event %s delayed by %.3f sec SCHEDULED with CallID=%s", EventName, DelaySeconds, tostring(CallID)))
|
||||
else
|
||||
self:T2(string.format("NEGATIVE Event %s delayed by %.1f sec CANCELLED as we already have such an event in the queue.", EventName, DelaySeconds))
|
||||
self:T2(string.format("NEGATIVE Event %s delayed by %.3f sec CANCELLED as we already have such an event in the queue.", EventName, DelaySeconds))
|
||||
-- reschedule
|
||||
end
|
||||
else
|
||||
|
||||
CallID = self.CallScheduler:Schedule( self, self._handler, { EventName, ... }, DelaySeconds or 1, nil, nil, nil, 4, true )
|
||||
|
||||
self:T2(string.format("Event %s delayed by %.1f sec SCHEDULED with CallID=%s", EventName, DelaySeconds, tostring(CallID)))
|
||||
self:T2(string.format("Event %s delayed by %.3f sec SCHEDULED with CallID=%s", EventName, DelaySeconds, tostring(CallID)))
|
||||
end
|
||||
else
|
||||
error( "FSM: An asynchronous event trigger requires a DelaySeconds parameter!!! This can be positive or negative! Sorry, but will not process this." )
|
||||
end
|
||||
|
||||
-- Debug.
|
||||
self:T2( { CallID = CallID } )
|
||||
--self:T3( { CallID = CallID } )
|
||||
end
|
||||
|
||||
end
|
||||
@@ -846,7 +846,7 @@ do -- FSM
|
||||
function FSM:_gosub( ParentFrom, ParentEvent )
|
||||
local fsmtable = {}
|
||||
if self.subs[ParentFrom] and self.subs[ParentFrom][ParentEvent] then
|
||||
self:T( { ParentFrom, ParentEvent, self.subs[ParentFrom], self.subs[ParentFrom][ParentEvent] } )
|
||||
--self:T3( { ParentFrom, ParentEvent, self.subs[ParentFrom], self.subs[ParentFrom][ParentEvent] } )
|
||||
return self.subs[ParentFrom][ParentEvent]
|
||||
else
|
||||
return {}
|
||||
@@ -893,7 +893,7 @@ do -- FSM
|
||||
end
|
||||
end
|
||||
|
||||
self:T3( { Map, Event } )
|
||||
--self:T3( { Map, Event } )
|
||||
end
|
||||
|
||||
--- Get current state.
|
||||
@@ -913,7 +913,7 @@ do -- FSM
|
||||
--- Check if FSM is in state.
|
||||
-- @param #FSM self
|
||||
-- @param #string State State name.
|
||||
-- @param #boolean If true, FSM is in this state.
|
||||
-- @return #boolean If true, FSM is in this state.
|
||||
function FSM:Is( State )
|
||||
return self.current == State
|
||||
end
|
||||
@@ -921,7 +921,7 @@ do -- FSM
|
||||
--- Check if FSM is in state.
|
||||
-- @param #FSM self
|
||||
-- @param #string State State name.
|
||||
-- @param #boolean If true, FSM is in this state.
|
||||
-- @return #boolean If true, FSM is in this state.
|
||||
function FSM:is(state)
|
||||
return self.current == state
|
||||
end
|
||||
@@ -1150,7 +1150,7 @@ do -- FSM_PROCESS
|
||||
-- @param #FSM_PROCESS self
|
||||
-- @return #FSM_PROCESS
|
||||
function FSM_PROCESS:Copy( Controllable, Task )
|
||||
self:T( { self:GetClassNameAndID() } )
|
||||
--self:T3( { self:GetClassNameAndID() } )
|
||||
|
||||
|
||||
local NewFsm = self:New( Controllable, Task ) -- Core.Fsm#FSM_PROCESS
|
||||
@@ -1176,13 +1176,13 @@ do -- FSM_PROCESS
|
||||
|
||||
-- Copy End States
|
||||
for EndStateID, EndState in pairs( self:GetEndStates() ) do
|
||||
self:T( EndState )
|
||||
--self:T3( EndState )
|
||||
NewFsm:AddEndState( EndState )
|
||||
end
|
||||
|
||||
-- Copy the score tables
|
||||
for ScoreID, Score in pairs( self:GetScores() ) do
|
||||
self:T( Score )
|
||||
--self:T3( Score )
|
||||
NewFsm:AddScore( ScoreID, Score.ScoreText, Score.Score )
|
||||
end
|
||||
|
||||
@@ -1431,7 +1431,7 @@ do -- FSM_SET
|
||||
-- @param #FSM_SET self
|
||||
-- @return Core.Set#SET_BASE
|
||||
function FSM_SET:Get()
|
||||
return self.Controllable
|
||||
return self.Set
|
||||
end
|
||||
|
||||
function FSM_SET:_call_handler( step, trigger, params, EventName )
|
||||
|
||||
@@ -0,0 +1,251 @@
|
||||
--- **Core** - MarkerOps_Base.
|
||||
--
|
||||
-- **Main Features:**
|
||||
--
|
||||
-- * Create an easy way to tap into markers added to the F10 map by users.
|
||||
-- * Recognize own tag and list of keywords.
|
||||
-- * Matched keywords are handed down to functions.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **Applevangelist**
|
||||
--
|
||||
-- Date: 5 May 2021
|
||||
--
|
||||
-- ===
|
||||
---
|
||||
-- @module Core.MarkerOps_Base
|
||||
-- @image MOOSE_Core.JPG
|
||||
|
||||
--------------------------------------------------------------------------
|
||||
-- MARKEROPS_BASE Class Definition.
|
||||
--------------------------------------------------------------------------
|
||||
|
||||
--- MARKEROPS_BASE class.
|
||||
-- @type MARKEROPS_BASE
|
||||
-- @field #string ClassName Name of the class.
|
||||
-- @field #string Tag Tag to identify commands.
|
||||
-- @field #table Keywords Table of keywords to recognize.
|
||||
-- @field #string version Version of #MARKEROPS_BASE.
|
||||
-- @extends Core.Fsm#FSM
|
||||
|
||||
--- *Fiat lux.* -- Latin proverb.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- # The MARKEROPS_BASE Concept
|
||||
--
|
||||
-- This class enable scripting text-based actions from markers.
|
||||
--
|
||||
-- @field #MARKEROPS_BASE
|
||||
MARKEROPS_BASE = {
|
||||
ClassName = "MARKEROPS",
|
||||
Tag = "mytag",
|
||||
Keywords = {},
|
||||
version = "0.0.1",
|
||||
debug = false,
|
||||
}
|
||||
|
||||
--- Function to instantiate a new #MARKEROPS_BASE object.
|
||||
-- @param #MARKEROPS_BASE self
|
||||
-- @param #string Tagname Name to identify us from the event text.
|
||||
-- @param #table Keywords Table of keywords recognized from the event text.
|
||||
-- @return #MARKEROPS_BASE self
|
||||
function MARKEROPS_BASE:New(Tagname,Keywords)
|
||||
-- Inherit FSM
|
||||
local self=BASE:Inherit(self, FSM:New()) -- #MARKEROPS_BASE
|
||||
|
||||
-- Set some string id for output to DCS.log file.
|
||||
self.lid=string.format("MARKEROPS_BASE %s | ", tostring(self.version))
|
||||
|
||||
self.Tag = Tagname or "mytag"-- #string
|
||||
self.Keywords = Keywords or {} -- #table - might want to use lua regex here, too
|
||||
self.debug = false
|
||||
|
||||
-----------------------
|
||||
--- FSM Transitions ---
|
||||
-----------------------
|
||||
|
||||
-- Start State.
|
||||
self:SetStartState("Stopped")
|
||||
|
||||
-- Add FSM transitions.
|
||||
-- From State --> Event --> To State
|
||||
self:AddTransition("Stopped", "Start", "Running") -- Start the FSM.
|
||||
self:AddTransition("*", "MarkAdded", "*") -- Start the FSM.
|
||||
self:AddTransition("*", "MarkChanged", "*") -- Start the FSM.
|
||||
self:AddTransition("*", "MarkDeleted", "*") -- Start the FSM.
|
||||
self:AddTransition("Running", "Stop", "Stopped") -- Stop the FSM.
|
||||
|
||||
self:HandleEvent(EVENTS.MarkAdded, self.OnEventMark)
|
||||
self:HandleEvent(EVENTS.MarkChange, self.OnEventMark)
|
||||
self:HandleEvent(EVENTS.MarkRemoved, self.OnEventMark)
|
||||
|
||||
-- start
|
||||
self:I(self.lid..string.format("started for %s",self.Tag))
|
||||
self:__Start(1)
|
||||
return self
|
||||
|
||||
-------------------
|
||||
-- PSEUDO Functions
|
||||
-------------------
|
||||
|
||||
--- On after "MarkAdded" event. Triggered when a Marker is added to the F10 map.
|
||||
-- @function [parent=#MARKEROPS_BASE] OnAfterMarkAdded
|
||||
-- @param #MARKEROPS_BASE self
|
||||
-- @param #string From The From state
|
||||
-- @param #string Event The Event called
|
||||
-- @param #string To The To state
|
||||
-- @param #string Text The text on the marker
|
||||
-- @param #table Keywords Table of matching keywords found in the Event text
|
||||
-- @param Core.Point#COORDINATE Coord Coordinate of the marker.
|
||||
|
||||
--- On after "MarkChanged" event. Triggered when a Marker is changed on the F10 map.
|
||||
-- @function [parent=#MARKEROPS_BASE] OnAfterMarkChanged
|
||||
-- @param #MARKEROPS_BASE self
|
||||
-- @param #string From The From state
|
||||
-- @param #string Event The Event called
|
||||
-- @param #string To The To state
|
||||
-- @param #string Text The text on the marker
|
||||
-- @param #table Keywords Table of matching keywords found in the Event text
|
||||
-- @param Core.Point#COORDINATE Coord Coordinate of the marker.
|
||||
|
||||
--- On after "MarkDeleted" event. Triggered when a Marker is deleted from the F10 map.
|
||||
-- @function [parent=#MARKEROPS_BASE] OnAfterMarkDeleted
|
||||
-- @param #MARKEROPS_BASE self
|
||||
-- @param #string From The From state
|
||||
-- @param #string Event The Event called
|
||||
-- @param #string To The To state
|
||||
|
||||
--- "Stop" trigger. Used to stop the function an unhandle events
|
||||
-- @function [parent=#MARKEROPS_BASE] Stop
|
||||
|
||||
end
|
||||
|
||||
--- (internal) Handle events.
|
||||
-- @param #MARKEROPS self
|
||||
-- @param Core.Event#EVENTDATA Event
|
||||
function MARKEROPS_BASE:OnEventMark(Event)
|
||||
self:T({Event})
|
||||
if Event == nil or Event.idx == nil then
|
||||
self:E("Skipping onEvent. Event or Event.idx unknown.")
|
||||
return true
|
||||
end
|
||||
--position
|
||||
local vec3={y=Event.pos.y, x=Event.pos.x, z=Event.pos.z}
|
||||
local coord=COORDINATE:NewFromVec3(vec3)
|
||||
if self.debug then
|
||||
local coordtext = coord:ToStringLLDDM()
|
||||
local text = tostring(Event.text)
|
||||
local m = MESSAGE:New(string.format("Mark added at %s with text: %s",coordtext,text),10,"Info",false):ToAll()
|
||||
end
|
||||
-- decision
|
||||
if Event.id==world.event.S_EVENT_MARK_ADDED then
|
||||
self:T({event="S_EVENT_MARK_ADDED", carrier=self.groupname, vec3=Event.pos})
|
||||
-- Handle event
|
||||
local Eventtext = tostring(Event.text)
|
||||
if Eventtext~=nil then
|
||||
if self:_MatchTag(Eventtext) then
|
||||
local matchtable = self:_MatchKeywords(Eventtext)
|
||||
self:MarkAdded(Eventtext,matchtable,coord)
|
||||
end
|
||||
end
|
||||
elseif Event.id==world.event.S_EVENT_MARK_CHANGE then
|
||||
self:T({event="S_EVENT_MARK_CHANGE", carrier=self.groupname, vec3=Event.pos})
|
||||
-- Handle event.
|
||||
local Eventtext = tostring(Event.text)
|
||||
if Eventtext~=nil then
|
||||
if self:_MatchTag(Eventtext) then
|
||||
local matchtable = self:_MatchKeywords(Eventtext)
|
||||
self:MarkChanged(Eventtext,matchtable,coord)
|
||||
end
|
||||
end
|
||||
elseif Event.id==world.event.S_EVENT_MARK_REMOVED then
|
||||
self:T({event="S_EVENT_MARK_REMOVED", carrier=self.groupname, vec3=Event.pos})
|
||||
-- Hande event.
|
||||
local Eventtext = tostring(Event.text)
|
||||
if Eventtext~=nil then
|
||||
if self:_MatchTag(Eventtext) then
|
||||
self:MarkDeleted()
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- (internal) Match tag.
|
||||
-- @param #MARKEROPS self
|
||||
-- @param #string Eventtext Text added to the marker.
|
||||
-- @return #boolean
|
||||
function MARKEROPS_BASE:_MatchTag(Eventtext)
|
||||
local matches = false
|
||||
local type = string.lower(self.Tag) -- #string
|
||||
if string.find(string.lower(Eventtext),type) then
|
||||
matches = true --event text contains tag
|
||||
end
|
||||
return matches
|
||||
end
|
||||
|
||||
--- (internal) Match keywords table.
|
||||
-- @param #MARKEROPS self
|
||||
-- @param #string Eventtext Text added to the marker.
|
||||
-- @return #table
|
||||
function MARKEROPS_BASE:_MatchKeywords(Eventtext)
|
||||
local matchtable = {}
|
||||
local keytable = self.Keywords
|
||||
for _index,_word in pairs (keytable) do
|
||||
if string.find(string.lower(Eventtext),string.lower(_word))then
|
||||
table.insert(matchtable,_word)
|
||||
end
|
||||
end
|
||||
return matchtable
|
||||
end
|
||||
|
||||
--- On before "MarkAdded" event. Triggered when a Marker is added to the F10 map.
|
||||
-- @param #MARKEROPS_BASE self
|
||||
-- @param #string From The From state
|
||||
-- @param #string Event The Event called
|
||||
-- @param #string To The To state
|
||||
-- @param #string Text The text on the marker
|
||||
-- @param #table Keywords Table of matching keywords found in the Event text
|
||||
-- @param Core.Point#COORDINATE Coord Coordinate of the marker.
|
||||
function MARKEROPS_BASE:onbeforeMarkAdded(From,Event,To,Text,Keywords,Coord)
|
||||
self:T({self.lid,From,Event,To,Text,Keywords,Coord:ToStringLLDDM()})
|
||||
end
|
||||
|
||||
--- On before "MarkChanged" event. Triggered when a Marker is changed on the F10 map.
|
||||
-- @param #MARKEROPS_BASE self
|
||||
-- @param #string From The From state
|
||||
-- @param #string Event The Event called
|
||||
-- @param #string To The To state
|
||||
-- @param #string Text The text on the marker
|
||||
-- @param #table Keywords Table of matching keywords found in the Event text
|
||||
-- @param Core.Point#COORDINATE Coord Coordinate of the marker.
|
||||
function MARKEROPS_BASE:onbeforeMarkChanged(From,Event,To,Text,Keywords,Coord)
|
||||
self:T({self.lid,From,Event,To,Text,Keywords,Coord:ToStringLLDDM()})
|
||||
end
|
||||
|
||||
--- On before "MarkDeleted" event. Triggered when a Marker is removed from the F10 map.
|
||||
-- @param #MARKEROPS_BASE self
|
||||
-- @param #string From The From state
|
||||
-- @param #string Event The Event called
|
||||
-- @param #string To The To state
|
||||
function MARKEROPS_BASE:onbeforeMarkDeleted(From,Event,To)
|
||||
self:T({self.lid,From,Event,To})
|
||||
end
|
||||
|
||||
--- On enter "Stopped" event. Unsubscribe events.
|
||||
-- @param #MARKEROPS_BASE self
|
||||
-- @param #string From The From state
|
||||
-- @param #string Event The Event called
|
||||
-- @param #string To The To state
|
||||
function MARKEROPS_BASE:onenterStopped(From,Event,To)
|
||||
self:T({self.lid,From,Event,To})
|
||||
-- unsubscribe from events
|
||||
self:UnHandleEvent(EVENTS.MarkAdded)
|
||||
self:UnHandleEvent(EVENTS.MarkChange)
|
||||
self:UnHandleEvent(EVENTS.MarkRemoved)
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------
|
||||
-- MARKEROPS_BASE Class Definition End.
|
||||
--------------------------------------------------------------------------
|
||||
@@ -14,7 +14,7 @@
|
||||
-- * Only create or delete menus when required, and keep existing menus persistent.
|
||||
-- * Update menu structures.
|
||||
-- * Refresh menu structures intelligently, based on a time stamp of updates.
|
||||
-- - Delete obscolete menus.
|
||||
-- - Delete obsolete menus.
|
||||
-- - Create new one where required.
|
||||
-- - Don't touch the existing ones.
|
||||
-- * Provide a variable amount of parameters to menus.
|
||||
@@ -23,7 +23,7 @@
|
||||
-- * Provide a great tool to manage menus in your code.
|
||||
--
|
||||
-- DCS Menus can be managed using the MENU classes.
|
||||
-- The advantage of using MENU classes is that it hides the complexity of dealing with menu management in more advanced scanerios where you need to
|
||||
-- The advantage of using MENU classes is that it hides the complexity of dealing with menu management in more advanced scenarios where you need to
|
||||
-- set menus and later remove them, and later set them again. You'll find while using use normal DCS scripting functions, that setting and removing
|
||||
-- menus is not a easy feat if you have complex menu hierarchies defined.
|
||||
-- Using the MOOSE menu classes, the removal and refreshing of menus are nicely being handled within these classes, and becomes much more easy.
|
||||
@@ -53,7 +53,6 @@
|
||||
-- @module Core.Menu
|
||||
-- @image Core_Menu.JPG
|
||||
|
||||
|
||||
MENU_INDEX = {}
|
||||
MENU_INDEX.MenuMission = {}
|
||||
MENU_INDEX.MenuMission.Menus = {}
|
||||
@@ -64,10 +63,7 @@ MENU_INDEX.Coalition[coalition.side.RED] = {}
|
||||
MENU_INDEX.Coalition[coalition.side.RED].Menus = {}
|
||||
MENU_INDEX.Group = {}
|
||||
|
||||
|
||||
|
||||
function MENU_INDEX:ParentPath( ParentMenu, MenuText )
|
||||
|
||||
local Path = ParentMenu and "@" .. table.concat( ParentMenu.MenuPath or {}, "@" ) or ""
|
||||
if ParentMenu then
|
||||
if ParentMenu:IsInstanceOf( "MENU_GROUP" ) or ParentMenu:IsInstanceOf( "MENU_GROUP_COMMAND" ) then
|
||||
@@ -95,20 +91,16 @@ function MENU_INDEX:ParentPath( ParentMenu, MenuText )
|
||||
|
||||
Path = Path .. "@" .. MenuText
|
||||
return Path
|
||||
|
||||
end
|
||||
|
||||
|
||||
function MENU_INDEX:PrepareMission()
|
||||
self.MenuMission.Menus = self.MenuMission.Menus or {}
|
||||
end
|
||||
|
||||
|
||||
function MENU_INDEX:PrepareCoalition( CoalitionSide )
|
||||
self.Coalition[CoalitionSide] = self.Coalition[CoalitionSide] or {}
|
||||
self.Coalition[CoalitionSide].Menus = self.Coalition[CoalitionSide].Menus or {}
|
||||
end
|
||||
|
||||
---
|
||||
-- @param Wrapper.Group#GROUP Group
|
||||
function MENU_INDEX:PrepareGroup( Group )
|
||||
@@ -119,42 +111,26 @@ function MENU_INDEX:PrepareGroup( Group )
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
function MENU_INDEX:HasMissionMenu( Path )
|
||||
|
||||
return self.MenuMission.Menus[Path]
|
||||
end
|
||||
|
||||
function MENU_INDEX:SetMissionMenu( Path, Menu )
|
||||
|
||||
self.MenuMission.Menus[Path] = Menu
|
||||
end
|
||||
|
||||
function MENU_INDEX:ClearMissionMenu( Path )
|
||||
|
||||
self.MenuMission.Menus[Path] = nil
|
||||
end
|
||||
|
||||
|
||||
|
||||
function MENU_INDEX:HasCoalitionMenu( Coalition, Path )
|
||||
|
||||
return self.Coalition[Coalition].Menus[Path]
|
||||
end
|
||||
|
||||
function MENU_INDEX:SetCoalitionMenu( Coalition, Path, Menu )
|
||||
|
||||
self.Coalition[Coalition].Menus[Path] = Menu
|
||||
end
|
||||
|
||||
function MENU_INDEX:ClearCoalitionMenu( Coalition, Path )
|
||||
|
||||
self.Coalition[Coalition].Menus[Path] = nil
|
||||
end
|
||||
|
||||
|
||||
|
||||
function MENU_INDEX:HasGroupMenu( Group, Path )
|
||||
if Group and Group:IsAlive() then
|
||||
local MenuGroupName = Group:GetName()
|
||||
@@ -162,53 +138,36 @@ function MENU_INDEX:HasGroupMenu( Group, Path )
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
function MENU_INDEX:SetGroupMenu( Group, Path, Menu )
|
||||
|
||||
local MenuGroupName = Group:GetName()
|
||||
Group:F({MenuGroupName=MenuGroupName,Path=Path})
|
||||
self.Group[MenuGroupName].Menus[Path] = Menu
|
||||
end
|
||||
|
||||
function MENU_INDEX:ClearGroupMenu( Group, Path )
|
||||
|
||||
local MenuGroupName = Group:GetName()
|
||||
self.Group[MenuGroupName].Menus[Path] = nil
|
||||
end
|
||||
|
||||
function MENU_INDEX:Refresh( Group )
|
||||
|
||||
for MenuID, Menu in pairs( self.MenuMission.Menus ) do
|
||||
Menu:Refresh()
|
||||
end
|
||||
|
||||
for MenuID, Menu in pairs( self.Coalition[coalition.side.BLUE].Menus ) do
|
||||
Menu:Refresh()
|
||||
end
|
||||
|
||||
for MenuID, Menu in pairs( self.Coalition[coalition.side.RED].Menus ) do
|
||||
Menu:Refresh()
|
||||
end
|
||||
|
||||
local GroupName = Group:GetName()
|
||||
for MenuID, Menu in pairs( self.Group[GroupName].Menus ) do
|
||||
Menu:Refresh()
|
||||
end
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
do -- MENU_BASE
|
||||
|
||||
--- @type MENU_BASE
|
||||
-- @extends Base#BASE
|
||||
|
||||
-- @extends Core.Base#BASE
|
||||
--- Defines the main MENU class where other MENU classes are derived from.
|
||||
-- This is an abstract class, so don't use it.
|
||||
-- @field #MENU_BASE
|
||||
@@ -216,10 +175,10 @@ do -- MENU_BASE
|
||||
ClassName = "MENU_BASE",
|
||||
MenuPath = nil,
|
||||
MenuText = "",
|
||||
MenuParentPath = nil
|
||||
MenuParentPath = nil,
|
||||
}
|
||||
|
||||
--- Consructor
|
||||
--- Constructor
|
||||
-- @param #MENU_BASE
|
||||
-- @return #MENU_BASE
|
||||
function MENU_BASE:New( MenuText, ParentMenu )
|
||||
@@ -228,27 +187,25 @@ do -- MENU_BASE
|
||||
if ParentMenu ~= nil then
|
||||
MenuParentPath = ParentMenu.MenuPath
|
||||
end
|
||||
|
||||
local self = BASE:Inherit( self, BASE:New() )
|
||||
local self = BASE:Inherit( self, BASE:New() )
|
||||
|
||||
self.MenuPath = nil
|
||||
self.MenuText = MenuText
|
||||
self.ParentMenu = ParentMenu
|
||||
self.MenuParentPath = MenuParentPath
|
||||
self.Path = ( self.ParentMenu and "@" .. table.concat( self.MenuParentPath or {}, "@" ) or "" ) .. "@" .. self.MenuText
|
||||
self.MenuPath = nil
|
||||
self.MenuText = MenuText
|
||||
self.ParentMenu = ParentMenu
|
||||
self.MenuParentPath = MenuParentPath
|
||||
self.Path = ( self.ParentMenu and "@" .. table.concat( self.MenuParentPath or {}, "@" ) or "" ) .. "@" .. self.MenuText
|
||||
self.Menus = {}
|
||||
self.MenuCount = 0
|
||||
self.MenuStamp = timer.getTime()
|
||||
self.MenuRemoveParent = false
|
||||
|
||||
|
||||
if self.ParentMenu then
|
||||
self.ParentMenu.Menus = self.ParentMenu.Menus or {}
|
||||
self.ParentMenu.Menus[MenuText] = self
|
||||
end
|
||||
|
||||
return self
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function MENU_BASE:SetParentMenu( MenuText, Menu )
|
||||
if self.ParentMenu then
|
||||
self.ParentMenu.Menus = self.ParentMenu.Menus or {}
|
||||
@@ -256,7 +213,6 @@ do -- MENU_BASE
|
||||
self.ParentMenu.MenuCount = self.ParentMenu.MenuCount + 1
|
||||
end
|
||||
end
|
||||
|
||||
function MENU_BASE:ClearParentMenu( MenuText )
|
||||
if self.ParentMenu and self.ParentMenu.Menus[MenuText] then
|
||||
self.ParentMenu.Menus[MenuText] = nil
|
||||
@@ -266,7 +222,6 @@ do -- MENU_BASE
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Sets a @{Menu} to remove automatically the parent menu when the menu removed is the last child menu of that parent @{Menu}.
|
||||
-- @param #MENU_BASE self
|
||||
-- @param #boolean RemoveParent If true, the parent menu is automatically removed when this menu is the last child menu of that parent @{Menu}.
|
||||
@@ -276,7 +231,6 @@ do -- MENU_BASE
|
||||
self.MenuRemoveParent = RemoveParent
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Gets a @{Menu} from a parent @{Menu}
|
||||
-- @param #MENU_BASE self
|
||||
@@ -285,7 +239,6 @@ do -- MENU_BASE
|
||||
function MENU_BASE:GetMenu( MenuText )
|
||||
return self.Menus[MenuText]
|
||||
end
|
||||
|
||||
--- Sets a menu stamp for later prevention of menu removal.
|
||||
-- @param #MENU_BASE self
|
||||
-- @param MenuStamp
|
||||
@@ -323,9 +276,7 @@ do -- MENU_BASE
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
do -- MENU_COMMAND_BASE
|
||||
|
||||
--- @type MENU_COMMAND_BASE
|
||||
-- @field #function MenuCallHandler
|
||||
-- @extends Core.Menu#MENU_BASE
|
||||
@@ -346,8 +297,7 @@ do -- MENU_COMMAND_BASE
|
||||
-- @return #MENU_COMMAND_BASE
|
||||
function MENU_COMMAND_BASE:New( MenuText, ParentMenu, CommandMenuFunction, CommandMenuArguments )
|
||||
|
||||
local self = BASE:Inherit( self, MENU_BASE:New( MenuText, ParentMenu ) ) -- #MENU_COMMAND_BASE
|
||||
|
||||
local self = BASE:Inherit( self, MENU_BASE:New( MenuText, ParentMenu ) ) -- #MENU_COMMAND_BASE
|
||||
-- When a menu function goes into error, DCS displays an obscure menu message.
|
||||
-- This error handler catches the menu error and displays the full call stack.
|
||||
local ErrorHandler = function( errmsg )
|
||||
@@ -367,7 +317,7 @@ do -- MENU_COMMAND_BASE
|
||||
local Status, Result = xpcall( MenuFunction, ErrorHandler )
|
||||
end
|
||||
|
||||
return self
|
||||
return self
|
||||
end
|
||||
|
||||
--- This sets the new command function of a menu,
|
||||
@@ -380,7 +330,6 @@ do -- MENU_COMMAND_BASE
|
||||
self.CommandMenuFunction = CommandMenuFunction
|
||||
return self
|
||||
end
|
||||
|
||||
--- This sets the new command arguments of a menu,
|
||||
-- so that if a menu is regenerated, or if command arguments change,
|
||||
-- that the arguments set for the menu are loosely coupled with the menu itself!!!
|
||||
@@ -391,35 +340,30 @@ do -- MENU_COMMAND_BASE
|
||||
self.CommandMenuArguments = CommandMenuArguments
|
||||
return self
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
do -- MENU_MISSION
|
||||
|
||||
--- @type MENU_MISSION
|
||||
-- @extends Core.Menu#MENU_BASE
|
||||
|
||||
--- Manages the main menus for a complete mission.
|
||||
--
|
||||
-- You can add menus with the @{#MENU_MISSION.New} method, which constructs a MENU_MISSION object and returns you the object reference.
|
||||
-- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_MISSION.Remove}.
|
||||
-- @field #MENU_MISSION
|
||||
MENU_MISSION = {
|
||||
ClassName = "MENU_MISSION"
|
||||
ClassName = "MENU_MISSION",
|
||||
}
|
||||
|
||||
--- MENU_MISSION constructor. Creates a new MENU_MISSION object and creates the menu for a complete mission file.
|
||||
-- @param #MENU_MISSION self
|
||||
-- @param #string MenuText The text for the menu.
|
||||
-- @param #table ParentMenu The parent menu. This parameter can be ignored if you want the menu to be located at the perent menu of DCS world (under F10 other).
|
||||
-- @param #table ParentMenu The parent menu. This parameter can be ignored if you want the menu to be located at the parent menu of DCS world (under F10 other).
|
||||
-- @return #MENU_MISSION
|
||||
function MENU_MISSION:New( MenuText, ParentMenu )
|
||||
|
||||
MENU_INDEX:PrepareMission()
|
||||
local Path = MENU_INDEX:ParentPath( ParentMenu, MenuText )
|
||||
local MissionMenu = MENU_INDEX:HasMissionMenu( Path )
|
||||
|
||||
if MissionMenu then
|
||||
return MissionMenu
|
||||
else
|
||||
@@ -432,17 +376,15 @@ do -- MENU_MISSION
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Refreshes a radio item for a mission
|
||||
-- @param #MENU_MISSION self
|
||||
-- @return #MENU_MISSION
|
||||
function MENU_MISSION:Refresh()
|
||||
|
||||
do
|
||||
missionCommands.removeItem( self.MenuPath )
|
||||
self.MenuPath = missionCommands.addSubMenu( self.MenuText, self.MenuParentPath )
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Removes the sub menus recursively of this MENU_MISSION. Note that the main menu is kept!
|
||||
@@ -466,7 +408,6 @@ do -- MENU_MISSION
|
||||
MENU_INDEX:PrepareMission()
|
||||
local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText )
|
||||
local MissionMenu = MENU_INDEX:HasMissionMenu( Path )
|
||||
|
||||
if MissionMenu == self then
|
||||
self:RemoveSubMenus()
|
||||
if not MenuStamp or self.MenuStamp ~= MenuStamp then
|
||||
@@ -487,10 +428,7 @@ do -- MENU_MISSION
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
|
||||
end
|
||||
|
||||
do -- MENU_MISSION_COMMAND
|
||||
|
||||
--- @type MENU_MISSION_COMMAND
|
||||
@@ -503,7 +441,7 @@ do -- MENU_MISSION_COMMAND
|
||||
--
|
||||
-- @field #MENU_MISSION_COMMAND
|
||||
MENU_MISSION_COMMAND = {
|
||||
ClassName = "MENU_MISSION_COMMAND"
|
||||
ClassName = "MENU_MISSION_COMMAND",
|
||||
}
|
||||
|
||||
--- MENU_MISSION constructor. Creates a new radio command item for a complete mission file, which can invoke a function with parameters.
|
||||
@@ -518,7 +456,6 @@ do -- MENU_MISSION_COMMAND
|
||||
MENU_INDEX:PrepareMission()
|
||||
local Path = MENU_INDEX:ParentPath( ParentMenu, MenuText )
|
||||
local MissionMenu = MENU_INDEX:HasMissionMenu( Path )
|
||||
|
||||
if MissionMenu then
|
||||
MissionMenu:SetCommandMenuFunction( CommandMenuFunction )
|
||||
MissionMenu:SetCommandMenuArguments( arg )
|
||||
@@ -532,17 +469,15 @@ do -- MENU_MISSION_COMMAND
|
||||
return self
|
||||
end
|
||||
end
|
||||
|
||||
--- Refreshes a radio item for a mission
|
||||
-- @param #MENU_MISSION_COMMAND self
|
||||
-- @return #MENU_MISSION_COMMAND
|
||||
function MENU_MISSION_COMMAND:Refresh()
|
||||
|
||||
do
|
||||
missionCommands.removeItem( self.MenuPath )
|
||||
missionCommands.addCommand( self.MenuText, self.MenuParentPath, self.MenuCallHandler )
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Removes a radio command item for a coalition
|
||||
@@ -553,7 +488,6 @@ do -- MENU_MISSION_COMMAND
|
||||
MENU_INDEX:PrepareMission()
|
||||
local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText )
|
||||
local MissionMenu = MENU_INDEX:HasMissionMenu( Path )
|
||||
|
||||
if MissionMenu == self then
|
||||
if not MenuStamp or self.MenuStamp ~= MenuStamp then
|
||||
if ( not MenuTag ) or ( MenuTag and self.MenuTag and MenuTag == self.MenuTag ) then
|
||||
@@ -572,13 +506,8 @@ do -- MENU_MISSION_COMMAND
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
|
||||
do -- MENU_COALITION
|
||||
|
||||
--- @type MENU_COALITION
|
||||
-- @extends Core.Menu#MENU_BASE
|
||||
|
||||
@@ -634,18 +563,15 @@ do -- MENU_COALITION
|
||||
-- @param #MENU_COALITION self
|
||||
-- @param DCS#coalition.side Coalition The coalition owning the menu.
|
||||
-- @param #string MenuText The text for the menu.
|
||||
-- @param #table ParentMenu The parent menu. This parameter can be ignored if you want the menu to be located at the perent menu of DCS world (under F10 other).
|
||||
-- @param #table ParentMenu The parent menu. This parameter can be ignored if you want the menu to be located at the parent menu of DCS world (under F10 other).
|
||||
-- @return #MENU_COALITION self
|
||||
function MENU_COALITION:New( Coalition, MenuText, ParentMenu )
|
||||
|
||||
MENU_INDEX:PrepareCoalition( Coalition )
|
||||
local Path = MENU_INDEX:ParentPath( ParentMenu, MenuText )
|
||||
local CoalitionMenu = MENU_INDEX:HasCoalitionMenu( Coalition, Path )
|
||||
|
||||
if CoalitionMenu then
|
||||
return CoalitionMenu
|
||||
else
|
||||
|
||||
local self = BASE:Inherit( self, MENU_BASE:New( MenuText, ParentMenu ) )
|
||||
MENU_INDEX:SetCoalitionMenu( Coalition, Path, self )
|
||||
|
||||
@@ -656,17 +582,15 @@ do -- MENU_COALITION
|
||||
return self
|
||||
end
|
||||
end
|
||||
|
||||
--- Refreshes a radio item for a coalition
|
||||
-- @param #MENU_COALITION self
|
||||
-- @return #MENU_COALITION
|
||||
function MENU_COALITION:Refresh()
|
||||
|
||||
do
|
||||
missionCommands.removeItemForCoalition( self.Coalition, self.MenuPath )
|
||||
missionCommands.addSubMenuForCoalition( self.Coalition, self.MenuText, self.MenuParentPath )
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Removes the sub menus recursively of this MENU_COALITION. Note that the main menu is kept!
|
||||
@@ -689,7 +613,6 @@ do -- MENU_COALITION
|
||||
MENU_INDEX:PrepareCoalition( self.Coalition )
|
||||
local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText )
|
||||
local CoalitionMenu = MENU_INDEX:HasCoalitionMenu( self.Coalition, Path )
|
||||
|
||||
if CoalitionMenu == self then
|
||||
self:RemoveSubMenus()
|
||||
if not MenuStamp or self.MenuStamp ~= MenuStamp then
|
||||
@@ -709,11 +632,7 @@ do -- MENU_COALITION
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
|
||||
do -- MENU_COALITION_COMMAND
|
||||
|
||||
--- @type MENU_COALITION_COMMAND
|
||||
@@ -742,7 +661,6 @@ do -- MENU_COALITION_COMMAND
|
||||
MENU_INDEX:PrepareCoalition( Coalition )
|
||||
local Path = MENU_INDEX:ParentPath( ParentMenu, MenuText )
|
||||
local CoalitionMenu = MENU_INDEX:HasCoalitionMenu( Coalition, Path )
|
||||
|
||||
if CoalitionMenu then
|
||||
CoalitionMenu:SetCommandMenuFunction( CommandMenuFunction )
|
||||
CoalitionMenu:SetCommandMenuArguments( arg )
|
||||
@@ -757,20 +675,17 @@ do -- MENU_COALITION_COMMAND
|
||||
self:SetParentMenu( self.MenuText, self )
|
||||
return self
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- Refreshes a radio item for a coalition
|
||||
-- @param #MENU_COALITION_COMMAND self
|
||||
-- @return #MENU_COALITION_COMMAND
|
||||
function MENU_COALITION_COMMAND:Refresh()
|
||||
|
||||
do
|
||||
missionCommands.removeItemForCoalition( self.Coalition, self.MenuPath )
|
||||
missionCommands.addCommandForCoalition( self.Coalition, self.MenuText, self.MenuParentPath, self.MenuCallHandler )
|
||||
end
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Removes a radio command item for a coalition
|
||||
@@ -781,7 +696,6 @@ do -- MENU_COALITION_COMMAND
|
||||
MENU_INDEX:PrepareCoalition( self.Coalition )
|
||||
local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText )
|
||||
local CoalitionMenu = MENU_INDEX:HasCoalitionMenu( self.Coalition, Path )
|
||||
|
||||
if CoalitionMenu == self then
|
||||
if not MenuStamp or self.MenuStamp ~= MenuStamp then
|
||||
if ( not MenuTag ) or ( MenuTag and self.MenuTag and MenuTag == self.MenuTag ) then
|
||||
@@ -800,20 +714,16 @@ do -- MENU_COALITION_COMMAND
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- MENU_GROUP
|
||||
|
||||
do
|
||||
-- This local variable is used to cache the menus registered under groups.
|
||||
-- Menus don't dissapear when groups for players are destroyed and restarted.
|
||||
-- Menus don't disappear when groups for players are destroyed and restarted.
|
||||
-- So every menu for a client created must be tracked so that program logic accidentally does not create.
|
||||
-- the same menus twice during initialization logic.
|
||||
-- These menu classes are handling this logic with this variable.
|
||||
local _MENUGROUPS = {}
|
||||
|
||||
--- @type MENU_GROUP
|
||||
-- @extends Core.Menu#MENU_BASE
|
||||
|
||||
@@ -889,16 +799,13 @@ do
|
||||
MENU_INDEX:PrepareGroup( Group )
|
||||
local Path = MENU_INDEX:ParentPath( ParentMenu, MenuText )
|
||||
local GroupMenu = MENU_INDEX:HasGroupMenu( Group, Path )
|
||||
|
||||
if GroupMenu then
|
||||
return GroupMenu
|
||||
else
|
||||
self = BASE:Inherit( self, MENU_BASE:New( MenuText, ParentMenu ) )
|
||||
MENU_INDEX:SetGroupMenu( Group, Path, self )
|
||||
|
||||
self.Group = Group
|
||||
self.GroupID = Group:GetID()
|
||||
|
||||
self.MenuPath = missionCommands.addSubMenuForGroup( self.GroupID, MenuText, self.MenuParentPath )
|
||||
|
||||
self:SetParentMenu( self.MenuText, self )
|
||||
@@ -906,12 +813,10 @@ do
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Refreshes a new radio item for a group and submenus
|
||||
-- @param #MENU_GROUP self
|
||||
-- @return #MENU_GROUP
|
||||
function MENU_GROUP:Refresh()
|
||||
|
||||
do
|
||||
missionCommands.removeItemForGroup( self.GroupID, self.MenuPath )
|
||||
missionCommands.addSubMenuForGroup( self.GroupID, self.MenuText, self.MenuParentPath )
|
||||
@@ -920,7 +825,8 @@ do
|
||||
Menu:Refresh()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Removes the sub menus recursively of this MENU_GROUP.
|
||||
@@ -929,7 +835,6 @@ do
|
||||
-- @param MenuTag A Tag or Key to filter the menus to be refreshed with the Tag set.
|
||||
-- @return #MENU_GROUP self
|
||||
function MENU_GROUP:RemoveSubMenus( MenuStamp, MenuTag )
|
||||
|
||||
for MenuText, Menu in pairs( self.Menus or {} ) do
|
||||
Menu:Remove( MenuStamp, MenuTag )
|
||||
end
|
||||
@@ -938,18 +843,15 @@ do
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- Removes the main menu and sub menus recursively of this MENU_GROUP.
|
||||
-- @param #MENU_GROUP self
|
||||
-- @param MenuStamp
|
||||
-- @param MenuTag A Tag or Key to filter the menus to be refreshed with the Tag set.
|
||||
-- @return #nil
|
||||
function MENU_GROUP:Remove( MenuStamp, MenuTag )
|
||||
|
||||
MENU_INDEX:PrepareGroup( self.Group )
|
||||
local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText )
|
||||
local GroupMenu = MENU_INDEX:HasGroupMenu( self.Group, Path )
|
||||
|
||||
if GroupMenu == self then
|
||||
self:RemoveSubMenus( MenuStamp, MenuTag )
|
||||
if not MenuStamp or self.MenuStamp ~= MenuStamp then
|
||||
@@ -993,18 +895,15 @@ do
|
||||
-- @param CommandMenuArgument An argument for the function.
|
||||
-- @return #MENU_GROUP_COMMAND
|
||||
function MENU_GROUP_COMMAND:New( Group, MenuText, ParentMenu, CommandMenuFunction, ... )
|
||||
|
||||
MENU_INDEX:PrepareGroup( Group )
|
||||
local Path = MENU_INDEX:ParentPath( ParentMenu, MenuText )
|
||||
local GroupMenu = MENU_INDEX:HasGroupMenu( Group, Path )
|
||||
|
||||
if GroupMenu then
|
||||
GroupMenu:SetCommandMenuFunction( CommandMenuFunction )
|
||||
GroupMenu:SetCommandMenuArguments( arg )
|
||||
return GroupMenu
|
||||
else
|
||||
self = BASE:Inherit( self, MENU_COMMAND_BASE:New( MenuText, ParentMenu, CommandMenuFunction, arg ) )
|
||||
|
||||
MENU_INDEX:SetGroupMenu( Group, Path, self )
|
||||
|
||||
self.Group = Group
|
||||
@@ -1015,19 +914,17 @@ do
|
||||
self:SetParentMenu( self.MenuText, self )
|
||||
return self
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Refreshes a radio item for a group
|
||||
-- @param #MENU_GROUP_COMMAND self
|
||||
-- @return #MENU_GROUP_COMMAND
|
||||
function MENU_GROUP_COMMAND:Refresh()
|
||||
|
||||
do
|
||||
missionCommands.removeItemForGroup( self.GroupID, self.MenuPath )
|
||||
missionCommands.addCommandForGroup( self.GroupID, self.MenuText, self.MenuParentPath, self.MenuCallHandler )
|
||||
end
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Removes a menu structure for a group.
|
||||
@@ -1036,11 +933,9 @@ do
|
||||
-- @param MenuTag A Tag or Key to filter the menus to be refreshed with the Tag set.
|
||||
-- @return #nil
|
||||
function MENU_GROUP_COMMAND:Remove( MenuStamp, MenuTag )
|
||||
|
||||
MENU_INDEX:PrepareGroup( self.Group )
|
||||
local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText )
|
||||
local GroupMenu = MENU_INDEX:HasGroupMenu( self.Group, Path )
|
||||
|
||||
if GroupMenu == self then
|
||||
if not MenuStamp or self.MenuStamp ~= MenuStamp then
|
||||
if ( not MenuTag ) or ( MenuTag and self.MenuTag and MenuTag == self.MenuTag ) then
|
||||
@@ -1059,13 +954,9 @@ do
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- MENU_GROUP_DELAYED
|
||||
|
||||
do
|
||||
|
||||
--- @type MENU_GROUP_DELAYED
|
||||
-- @extends Core.Menu#MENU_BASE
|
||||
|
||||
@@ -1093,16 +984,13 @@ do
|
||||
MENU_INDEX:PrepareGroup( Group )
|
||||
local Path = MENU_INDEX:ParentPath( ParentMenu, MenuText )
|
||||
local GroupMenu = MENU_INDEX:HasGroupMenu( Group, Path )
|
||||
|
||||
if GroupMenu then
|
||||
return GroupMenu
|
||||
else
|
||||
self = BASE:Inherit( self, MENU_BASE:New( MenuText, ParentMenu ) )
|
||||
MENU_INDEX:SetGroupMenu( Group, Path, self )
|
||||
|
||||
self.Group = Group
|
||||
self.GroupID = Group:GetID()
|
||||
|
||||
if self.MenuParentPath then
|
||||
self.MenuPath = UTILS.DeepCopy( self.MenuParentPath )
|
||||
else
|
||||
@@ -1116,12 +1004,10 @@ do
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- Refreshes a new radio item for a group and submenus
|
||||
-- @param #MENU_GROUP_DELAYED self
|
||||
-- @return #MENU_GROUP_DELAYED
|
||||
function MENU_GROUP_DELAYED:Set()
|
||||
|
||||
do
|
||||
if not self.MenuSet then
|
||||
missionCommands.addSubMenuForGroup( self.GroupID, self.MenuText, self.MenuParentPath )
|
||||
@@ -1132,15 +1018,12 @@ do
|
||||
Menu:Set()
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- Refreshes a new radio item for a group and submenus
|
||||
-- @param #MENU_GROUP_DELAYED self
|
||||
-- @return #MENU_GROUP_DELAYED
|
||||
function MENU_GROUP_DELAYED:Refresh()
|
||||
|
||||
do
|
||||
missionCommands.removeItemForGroup( self.GroupID, self.MenuPath )
|
||||
missionCommands.addSubMenuForGroup( self.GroupID, self.MenuText, self.MenuParentPath )
|
||||
@@ -1149,7 +1032,8 @@ do
|
||||
Menu:Refresh()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Removes the sub menus recursively of this MENU_GROUP_DELAYED.
|
||||
@@ -1158,7 +1042,6 @@ do
|
||||
-- @param MenuTag A Tag or Key to filter the menus to be refreshed with the Tag set.
|
||||
-- @return #MENU_GROUP_DELAYED self
|
||||
function MENU_GROUP_DELAYED:RemoveSubMenus( MenuStamp, MenuTag )
|
||||
|
||||
for MenuText, Menu in pairs( self.Menus or {} ) do
|
||||
Menu:Remove( MenuStamp, MenuTag )
|
||||
end
|
||||
@@ -1167,18 +1050,15 @@ do
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- Removes the main menu and sub menus recursively of this MENU_GROUP.
|
||||
-- @param #MENU_GROUP_DELAYED self
|
||||
-- @param MenuStamp
|
||||
-- @param MenuTag A Tag or Key to filter the menus to be refreshed with the Tag set.
|
||||
-- @return #nil
|
||||
function MENU_GROUP_DELAYED:Remove( MenuStamp, MenuTag )
|
||||
|
||||
MENU_INDEX:PrepareGroup( self.Group )
|
||||
local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText )
|
||||
local GroupMenu = MENU_INDEX:HasGroupMenu( self.Group, Path )
|
||||
|
||||
if GroupMenu == self then
|
||||
self:RemoveSubMenus( MenuStamp, MenuTag )
|
||||
if not MenuStamp or self.MenuStamp ~= MenuStamp then
|
||||
@@ -1223,18 +1103,15 @@ do
|
||||
-- @param CommandMenuArgument An argument for the function.
|
||||
-- @return #MENU_GROUP_COMMAND_DELAYED
|
||||
function MENU_GROUP_COMMAND_DELAYED:New( Group, MenuText, ParentMenu, CommandMenuFunction, ... )
|
||||
|
||||
MENU_INDEX:PrepareGroup( Group )
|
||||
local Path = MENU_INDEX:ParentPath( ParentMenu, MenuText )
|
||||
local GroupMenu = MENU_INDEX:HasGroupMenu( Group, Path )
|
||||
|
||||
if GroupMenu then
|
||||
GroupMenu:SetCommandMenuFunction( CommandMenuFunction )
|
||||
GroupMenu:SetCommandMenuArguments( arg )
|
||||
return GroupMenu
|
||||
else
|
||||
self = BASE:Inherit( self, MENU_COMMAND_BASE:New( MenuText, ParentMenu, CommandMenuFunction, arg ) )
|
||||
|
||||
MENU_INDEX:SetGroupMenu( Group, Path, self )
|
||||
|
||||
self.Group = Group
|
||||
@@ -1250,33 +1127,29 @@ do
|
||||
self:SetParentMenu( self.MenuText, self )
|
||||
return self
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Refreshes a radio item for a group
|
||||
-- @param #MENU_GROUP_COMMAND_DELAYED self
|
||||
-- @return #MENU_GROUP_COMMAND_DELAYED
|
||||
function MENU_GROUP_COMMAND_DELAYED:Set()
|
||||
|
||||
do
|
||||
if not self.MenuSet then
|
||||
self.MenuPath = missionCommands.addCommandForGroup( self.GroupID, self.MenuText, self.MenuParentPath, self.MenuCallHandler )
|
||||
self.MenuSet = true
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Refreshes a radio item for a group
|
||||
-- @param #MENU_GROUP_COMMAND_DELAYED self
|
||||
-- @return #MENU_GROUP_COMMAND_DELAYED
|
||||
function MENU_GROUP_COMMAND_DELAYED:Refresh()
|
||||
|
||||
do
|
||||
missionCommands.removeItemForGroup( self.GroupID, self.MenuPath )
|
||||
missionCommands.addCommandForGroup( self.GroupID, self.MenuText, self.MenuParentPath, self.MenuCallHandler )
|
||||
end
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Removes a menu structure for a group.
|
||||
@@ -1285,11 +1158,9 @@ do
|
||||
-- @param MenuTag A Tag or Key to filter the menus to be refreshed with the Tag set.
|
||||
-- @return #nil
|
||||
function MENU_GROUP_COMMAND_DELAYED:Remove( MenuStamp, MenuTag )
|
||||
|
||||
MENU_INDEX:PrepareGroup( self.Group )
|
||||
local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText )
|
||||
local GroupMenu = MENU_INDEX:HasGroupMenu( self.Group, Path )
|
||||
|
||||
if GroupMenu == self then
|
||||
if not MenuStamp or self.MenuStamp ~= MenuStamp then
|
||||
if ( not MenuTag ) or ( MenuTag and self.MenuTag and MenuTag == self.MenuTag ) then
|
||||
@@ -1308,6 +1179,4 @@ do
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
-- * Send message to all players.
|
||||
-- * Send messages to a coalition.
|
||||
-- * Send messages to a specific group.
|
||||
-- * Send messages to a specific unit or client.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
@@ -35,6 +36,7 @@
|
||||
--
|
||||
-- * To a @{Client} using @{#MESSAGE.ToClient}().
|
||||
-- * To a @{Wrapper.Group} using @{#MESSAGE.ToGroup}()
|
||||
-- * To a @{Wrapper.Unit} using @{#MESSAGE.ToUnit}()
|
||||
-- * To a coalition using @{#MESSAGE.ToCoalition}().
|
||||
-- * To the red coalition using @{#MESSAGE.ToRed}().
|
||||
-- * To the blue coalition using @{#MESSAGE.ToBlue}().
|
||||
@@ -199,11 +201,14 @@ function MESSAGE:ToClient( Client, Settings )
|
||||
self.MessageDuration = Settings:GetMessageTime( self.MessageType )
|
||||
self.MessageCategory = "" -- self.MessageType .. ": "
|
||||
end
|
||||
|
||||
|
||||
local Unit = Client:GetClient()
|
||||
|
||||
if self.MessageDuration ~= 0 then
|
||||
local ClientGroupID = Client:GetClientGroupID()
|
||||
self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration )
|
||||
trigger.action.outTextForGroup( ClientGroupID, self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration , self.ClearScreen)
|
||||
--trigger.action.outTextForGroup( ClientGroupID, self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration , self.ClearScreen)
|
||||
trigger.action.outTextForUnit( Unit:GetID(), self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration , self.ClearScreen)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -233,6 +238,31 @@ function MESSAGE:ToGroup( Group, Settings )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sends a MESSAGE to a Unit.
|
||||
-- @param #MESSAGE self
|
||||
-- @param Wrapper.Unit#UNIT Unit to which the message is displayed.
|
||||
-- @return #MESSAGE Message object.
|
||||
function MESSAGE:ToUnit( Unit, Settings )
|
||||
self:F( Unit.IdentifiableName )
|
||||
|
||||
if Unit then
|
||||
|
||||
if self.MessageType then
|
||||
local Settings = Settings or ( Unit and _DATABASE:GetPlayerSettings( Unit:GetPlayerName() ) ) or _SETTINGS -- Core.Settings#SETTINGS
|
||||
self.MessageDuration = Settings:GetMessageTime( self.MessageType )
|
||||
self.MessageCategory = "" -- self.MessageType .. ": "
|
||||
end
|
||||
|
||||
if self.MessageDuration ~= 0 then
|
||||
self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration )
|
||||
trigger.action.outTextForUnit( Unit:GetID(), self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration, self.ClearScreen )
|
||||
end
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sends a MESSAGE to the Blue coalition.
|
||||
-- @param #MESSAGE self
|
||||
-- @return #MESSAGE
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,832 +0,0 @@
|
||||
--- **Core** - Is responsible for everything that is related to radio transmission and you can hear in DCS, be it TACAN beacons, Radio transmissions.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ## Features:
|
||||
--
|
||||
-- * Provide radio functionality to broadcast radio transmissions.
|
||||
-- * Provide beacon functionality to assist pilots.
|
||||
--
|
||||
-- The Radio contains 2 classes : RADIO and BEACON
|
||||
--
|
||||
-- What are radio communications in DCS?
|
||||
--
|
||||
-- * Radio transmissions consist of **sound files** that are broadcasted on a specific **frequency** (e.g. 115MHz) and **modulation** (e.g. AM),
|
||||
-- * They can be **subtitled** for a specific **duration**, the **power** in Watts of the transmiter's antenna can be set, and the transmission can be **looped**.
|
||||
--
|
||||
-- How to supply DCS my own Sound Files?
|
||||
--
|
||||
-- * Your sound files need to be encoded in **.ogg** or .wav,
|
||||
-- * Your sound files should be **as tiny as possible**. It is suggested you encode in .ogg with low bitrate and sampling settings,
|
||||
-- * They need to be added in .\l10n\DEFAULT\ in you .miz file (wich can be decompressed like a .zip file),
|
||||
-- * For simplicity sake, you can **let DCS' Mission Editor add the file** itself, by creating a new Trigger with the action "Sound to Country", and choosing your sound file and a country you don't use in your mission.
|
||||
--
|
||||
-- Due to weird DCS quirks, **radio communications behave differently** if sent by a @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP} or by any other @{Wrapper.Positionable#POSITIONABLE}
|
||||
--
|
||||
-- * If the transmitter is a @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP}, DCS will set the power of the transmission automatically,
|
||||
-- * If the transmitter is any other @{Wrapper.Positionable#POSITIONABLE}, the transmisison can't be subtitled or looped.
|
||||
--
|
||||
-- Note that obviously, the **frequency** and the **modulation** of the transmission are important only if the players are piloting an **Advanced System Modelling** enabled aircraft,
|
||||
-- like the A10C or the Mirage 2000C. They will **hear the transmission** if they are tuned on the **right frequency and modulation** (and if they are close enough - more on that below).
|
||||
-- If an FC3 aircraft is used, it will **hear every communication, whatever the frequency and the modulation** is set to. The same is true for TACAN beacons. If your aircraft isn't compatible,
|
||||
-- you won't hear/be able to use the TACAN beacon informations.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Authors: Hugues "Grey_Echo" Bousquet, funkyfranky
|
||||
--
|
||||
-- @module Core.Radio
|
||||
-- @image Core_Radio.JPG
|
||||
|
||||
|
||||
--- Models the radio capability.
|
||||
--
|
||||
-- ## RADIO usage
|
||||
--
|
||||
-- There are 3 steps to a successful radio transmission.
|
||||
--
|
||||
-- * First, you need to **"add a @{#RADIO} object** to your @{Wrapper.Positionable#POSITIONABLE}. This is done using the @{Wrapper.Positionable#POSITIONABLE.GetRadio}() function,
|
||||
-- * Then, you will **set the relevant parameters** to the transmission (see below),
|
||||
-- * When done, you can actually **broadcast the transmission** (i.e. play the sound) with the @{RADIO.Broadcast}() function.
|
||||
--
|
||||
-- Methods to set relevant parameters for both a @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP} or any other @{Wrapper.Positionable#POSITIONABLE}
|
||||
--
|
||||
-- * @{#RADIO.SetFileName}() : Sets the file name of your sound file (e.g. "Noise.ogg"),
|
||||
-- * @{#RADIO.SetFrequency}() : Sets the frequency of your transmission.
|
||||
-- * @{#RADIO.SetModulation}() : Sets the modulation of your transmission.
|
||||
-- * @{#RADIO.SetLoop}() : Choose if you want the transmission to be looped. If you need your transmission to be looped, you might need a @{#BEACON} instead...
|
||||
--
|
||||
-- Additional Methods to set relevant parameters if the transmitter is a @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP}
|
||||
--
|
||||
-- * @{#RADIO.SetSubtitle}() : Set both the subtitle and its duration,
|
||||
-- * @{#RADIO.NewUnitTransmission}() : Shortcut to set all the relevant parameters in one method call
|
||||
--
|
||||
-- Additional Methods to set relevant parameters if the transmitter is any other @{Wrapper.Positionable#POSITIONABLE}
|
||||
--
|
||||
-- * @{#RADIO.SetPower}() : Sets the power of the antenna in Watts
|
||||
-- * @{#RADIO.NewGenericTransmission}() : Shortcut to set all the relevant parameters in one method call
|
||||
--
|
||||
-- What is this power thing?
|
||||
--
|
||||
-- * If your transmission is sent by a @{Wrapper.Positionable#POSITIONABLE} other than a @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP}, you can set the power of the antenna,
|
||||
-- * Otherwise, DCS sets it automatically, depending on what's available on your Unit,
|
||||
-- * If the player gets **too far** from the transmitter, or if the antenna is **too weak**, the transmission will **fade** and **become noisyer**,
|
||||
-- * This an automated DCS calculation you have no say on,
|
||||
-- * For reference, a standard VOR station has a 100 W antenna, a standard AA TACAN has a 120 W antenna, and civilian ATC's antenna usually range between 300 and 500 W,
|
||||
-- * Note that if the transmission has a subtitle, it will be readable, regardless of the quality of the transmission.
|
||||
--
|
||||
-- @type RADIO
|
||||
-- @field Wrapper.Controllable#CONTROLLABLE Positionable The @{#CONTROLLABLE} that will transmit the radio calls.
|
||||
-- @field #string FileName Name of the sound file played.
|
||||
-- @field #number Frequency Frequency of the transmission in Hz.
|
||||
-- @field #number Modulation Modulation of the transmission (either radio.modulation.AM or radio.modulation.FM).
|
||||
-- @field #string Subtitle Subtitle of the transmission.
|
||||
-- @field #number SubtitleDuration Duration of the Subtitle in seconds.
|
||||
-- @field #number Power Power of the antenna is Watts.
|
||||
-- @field #boolean Loop Transmission is repeated (default true).
|
||||
-- @field #string alias Name of the radio transmitter.
|
||||
-- @extends Core.Base#BASE
|
||||
RADIO = {
|
||||
ClassName = "RADIO",
|
||||
FileName = "",
|
||||
Frequency = 0,
|
||||
Modulation = radio.modulation.AM,
|
||||
Subtitle = "",
|
||||
SubtitleDuration = 0,
|
||||
Power = 100,
|
||||
Loop = false,
|
||||
alias=nil,
|
||||
}
|
||||
|
||||
--- Create a new RADIO Object. This doesn't broadcast a transmission, though, use @{#RADIO.Broadcast} to actually broadcast.
|
||||
-- If you want to create a RADIO, you probably should use @{Wrapper.Positionable#POSITIONABLE.GetRadio}() instead.
|
||||
-- @param #RADIO self
|
||||
-- @param Wrapper.Positionable#POSITIONABLE Positionable The @{Positionable} that will receive radio capabilities.
|
||||
-- @return #RADIO The RADIO object or #nil if Positionable is invalid.
|
||||
function RADIO:New(Positionable)
|
||||
|
||||
-- Inherit base
|
||||
local self = BASE:Inherit( self, BASE:New() ) -- Core.Radio#RADIO
|
||||
self:F(Positionable)
|
||||
|
||||
if Positionable:GetPointVec2() then -- It's stupid, but the only way I found to make sure positionable is valid
|
||||
self.Positionable = Positionable
|
||||
return self
|
||||
end
|
||||
|
||||
self:E({error="The passed positionable is invalid, no RADIO created!", positionable=Positionable})
|
||||
return nil
|
||||
end
|
||||
|
||||
--- Set alias of the transmitter.
|
||||
-- @param #RADIO self
|
||||
-- @param #string alias Name of the radio transmitter.
|
||||
-- @return #RADIO self
|
||||
function RADIO:SetAlias(alias)
|
||||
self.alias=tostring(alias)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get alias of the transmitter.
|
||||
-- @param #RADIO self
|
||||
-- @return #string Name of the transmitter.
|
||||
function RADIO:GetAlias()
|
||||
return tostring(self.alias)
|
||||
end
|
||||
|
||||
--- Set the file name for the radio transmission.
|
||||
-- @param #RADIO self
|
||||
-- @param #string FileName File name of the sound file (i.e. "Noise.ogg")
|
||||
-- @return #RADIO self
|
||||
function RADIO:SetFileName(FileName)
|
||||
self:F2(FileName)
|
||||
|
||||
if type(FileName) == "string" then
|
||||
|
||||
if FileName:find(".ogg") or FileName:find(".wav") then
|
||||
if not FileName:find("l10n/DEFAULT/") then
|
||||
FileName = "l10n/DEFAULT/" .. FileName
|
||||
end
|
||||
|
||||
self.FileName = FileName
|
||||
return self
|
||||
end
|
||||
end
|
||||
|
||||
self:E({"File name invalid. Maybe something wrong with the extension?", FileName})
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set the frequency for the radio transmission.
|
||||
-- If the transmitting positionable is a unit or group, this also set the command "SetFrequency" with the defined frequency and modulation.
|
||||
-- @param #RADIO self
|
||||
-- @param #number Frequency Frequency in MHz. Ranges allowed for radio transmissions in DCS : 30-87.995 / 108-173.995 / 225-399.975MHz.
|
||||
-- @return #RADIO self
|
||||
function RADIO:SetFrequency(Frequency)
|
||||
self:F2(Frequency)
|
||||
|
||||
if type(Frequency) == "number" then
|
||||
|
||||
-- If frequency is in range
|
||||
if (Frequency >= 30 and Frequency <= 87.995) or (Frequency >= 108 and Frequency <= 173.995) or (Frequency >= 225 and Frequency <= 399.975) then
|
||||
|
||||
-- Convert frequency from MHz to Hz
|
||||
self.Frequency = Frequency * 1000000
|
||||
|
||||
-- If the RADIO is attached to a UNIT or a GROUP, we need to send the DCS Command "SetFrequency" to change the UNIT or GROUP frequency
|
||||
if self.Positionable.ClassName == "UNIT" or self.Positionable.ClassName == "GROUP" then
|
||||
|
||||
local commandSetFrequency={
|
||||
id = "SetFrequency",
|
||||
params = {
|
||||
frequency = self.Frequency,
|
||||
modulation = self.Modulation,
|
||||
}
|
||||
}
|
||||
|
||||
self:T2(commandSetFrequency)
|
||||
self.Positionable:SetCommand(commandSetFrequency)
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
end
|
||||
|
||||
self:E({"Frequency is outside of DCS Frequency ranges (30-80, 108-152, 225-400). Frequency unchanged.", Frequency})
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set AM or FM modulation of the radio transmitter.
|
||||
-- @param #RADIO self
|
||||
-- @param #number Modulation Modulation is either radio.modulation.AM or radio.modulation.FM.
|
||||
-- @return #RADIO self
|
||||
function RADIO:SetModulation(Modulation)
|
||||
self:F2(Modulation)
|
||||
if type(Modulation) == "number" then
|
||||
if Modulation == radio.modulation.AM or Modulation == radio.modulation.FM then --TODO Maybe make this future proof if ED decides to add an other modulation ?
|
||||
self.Modulation = Modulation
|
||||
return self
|
||||
end
|
||||
end
|
||||
self:E({"Modulation is invalid. Use DCS's enum radio.modulation. Modulation unchanged.", self.Modulation})
|
||||
return self
|
||||
end
|
||||
|
||||
--- Check validity of the power passed and sets RADIO.Power
|
||||
-- @param #RADIO self
|
||||
-- @param #number Power Power in W.
|
||||
-- @return #RADIO self
|
||||
function RADIO:SetPower(Power)
|
||||
self:F2(Power)
|
||||
|
||||
if type(Power) == "number" then
|
||||
self.Power = math.floor(math.abs(Power)) --TODO Find what is the maximum power allowed by DCS and limit power to that
|
||||
else
|
||||
self:E({"Power is invalid. Power unchanged.", self.Power})
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set message looping on or off.
|
||||
-- @param #RADIO self
|
||||
-- @param #boolean Loop If true, message is repeated indefinitely.
|
||||
-- @return #RADIO self
|
||||
function RADIO:SetLoop(Loop)
|
||||
self:F2(Loop)
|
||||
if type(Loop) == "boolean" then
|
||||
self.Loop = Loop
|
||||
return self
|
||||
end
|
||||
self:E({"Loop is invalid. Loop unchanged.", self.Loop})
|
||||
return self
|
||||
end
|
||||
|
||||
--- Check validity of the subtitle and the subtitleDuration passed and sets RADIO.subtitle and RADIO.subtitleDuration
|
||||
-- Both parameters are mandatory, since it wouldn't make much sense to change the Subtitle and not its duration
|
||||
-- @param #RADIO self
|
||||
-- @param #string Subtitle
|
||||
-- @param #number SubtitleDuration in s
|
||||
-- @return #RADIO self
|
||||
-- @usage
|
||||
-- -- create the broadcaster and attaches it a RADIO
|
||||
-- local MyUnit = UNIT:FindByName("MyUnit")
|
||||
-- local MyUnitRadio = MyUnit:GetRadio()
|
||||
--
|
||||
-- -- add a subtitle for the next transmission, which will be up for 10s
|
||||
-- MyUnitRadio:SetSubtitle("My Subtitle, 10)
|
||||
function RADIO:SetSubtitle(Subtitle, SubtitleDuration)
|
||||
self:F2({Subtitle, SubtitleDuration})
|
||||
if type(Subtitle) == "string" then
|
||||
self.Subtitle = Subtitle
|
||||
else
|
||||
self.Subtitle = ""
|
||||
self:E({"Subtitle is invalid. Subtitle reset.", self.Subtitle})
|
||||
end
|
||||
if type(SubtitleDuration) == "number" then
|
||||
self.SubtitleDuration = SubtitleDuration
|
||||
else
|
||||
self.SubtitleDuration = 0
|
||||
self:E({"SubtitleDuration is invalid. SubtitleDuration reset.", self.SubtitleDuration})
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Create a new transmission, that is to say, populate the RADIO with relevant data
|
||||
-- In this function the data is especially relevant if the broadcaster is anything but a UNIT or a GROUP,
|
||||
-- but it will work with a UNIT or a GROUP anyway.
|
||||
-- Only the #RADIO and the Filename are mandatory
|
||||
-- @param #RADIO self
|
||||
-- @param #string FileName Name of the sound file that will be transmitted.
|
||||
-- @param #number Frequency Frequency in MHz.
|
||||
-- @param #number Modulation Modulation of frequency, which is either radio.modulation.AM or radio.modulation.FM.
|
||||
-- @param #number Power Power in W.
|
||||
-- @return #RADIO self
|
||||
function RADIO:NewGenericTransmission(FileName, Frequency, Modulation, Power, Loop)
|
||||
self:F({FileName, Frequency, Modulation, Power})
|
||||
|
||||
self:SetFileName(FileName)
|
||||
if Frequency then self:SetFrequency(Frequency) end
|
||||
if Modulation then self:SetModulation(Modulation) end
|
||||
if Power then self:SetPower(Power) end
|
||||
if Loop then self:SetLoop(Loop) end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Create a new transmission, that is to say, populate the RADIO with relevant data
|
||||
-- In this function the data is especially relevant if the broadcaster is a UNIT or a GROUP,
|
||||
-- but it will work for any @{Wrapper.Positionable#POSITIONABLE}.
|
||||
-- Only the RADIO and the Filename are mandatory.
|
||||
-- @param #RADIO self
|
||||
-- @param #string FileName Name of sound file.
|
||||
-- @param #string Subtitle Subtitle to be displayed with sound file.
|
||||
-- @param #number SubtitleDuration Duration of subtitle display in seconds.
|
||||
-- @param #number Frequency Frequency in MHz.
|
||||
-- @param #number Modulation Modulation which can be either radio.modulation.AM or radio.modulation.FM
|
||||
-- @param #boolean Loop If true, loop message.
|
||||
-- @return #RADIO self
|
||||
function RADIO:NewUnitTransmission(FileName, Subtitle, SubtitleDuration, Frequency, Modulation, Loop)
|
||||
self:F({FileName, Subtitle, SubtitleDuration, Frequency, Modulation, Loop})
|
||||
|
||||
-- Set file name.
|
||||
self:SetFileName(FileName)
|
||||
|
||||
-- Set modulation AM/FM.
|
||||
if Modulation then
|
||||
self:SetModulation(Modulation)
|
||||
end
|
||||
|
||||
-- Set frequency.
|
||||
if Frequency then
|
||||
self:SetFrequency(Frequency)
|
||||
end
|
||||
|
||||
-- Set subtitle.
|
||||
if Subtitle then
|
||||
self:SetSubtitle(Subtitle, SubtitleDuration or 0)
|
||||
end
|
||||
|
||||
-- Set Looping.
|
||||
if Loop then
|
||||
self:SetLoop(Loop)
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Broadcast the transmission.
|
||||
-- * The Radio has to be populated with the new transmission before broadcasting.
|
||||
-- * Please use RADIO setters or either @{#RADIO.NewGenericTransmission} or @{#RADIO.NewUnitTransmission}
|
||||
-- * This class is in fact pretty smart, it determines the right DCS function to use depending on the type of POSITIONABLE
|
||||
-- * If the POSITIONABLE is not a UNIT or a GROUP, we use the generic (but limited) trigger.action.radioTransmission()
|
||||
-- * If the POSITIONABLE is a UNIT or a GROUP, we use the "TransmitMessage" Command
|
||||
-- * If your POSITIONABLE is a UNIT or a GROUP, the Power is ignored.
|
||||
-- * If your POSITIONABLE is not a UNIT or a GROUP, the Subtitle, SubtitleDuration are ignored
|
||||
-- @param #RADIO self
|
||||
-- @param #boolean viatrigger Use trigger.action.radioTransmission() in any case, i.e. also for UNITS and GROUPS.
|
||||
-- @return #RADIO self
|
||||
function RADIO:Broadcast(viatrigger)
|
||||
self:F({viatrigger=viatrigger})
|
||||
|
||||
-- If the POSITIONABLE is actually a UNIT or a GROUP, use the more complicated DCS command system.
|
||||
if (self.Positionable.ClassName=="UNIT" or self.Positionable.ClassName=="GROUP") and (not viatrigger) then
|
||||
self:T("Broadcasting from a UNIT or a GROUP")
|
||||
|
||||
local commandTransmitMessage={
|
||||
id = "TransmitMessage",
|
||||
params = {
|
||||
file = self.FileName,
|
||||
duration = self.SubtitleDuration,
|
||||
subtitle = self.Subtitle,
|
||||
loop = self.Loop,
|
||||
}}
|
||||
|
||||
self:T3(commandTransmitMessage)
|
||||
self.Positionable:SetCommand(commandTransmitMessage)
|
||||
else
|
||||
-- If the POSITIONABLE is anything else, we revert to the general singleton function
|
||||
-- I need to give it a unique name, so that the transmission can be stopped later. I use the class ID
|
||||
self:T("Broadcasting from a POSITIONABLE")
|
||||
trigger.action.radioTransmission(self.FileName, self.Positionable:GetPositionVec3(), self.Modulation, self.Loop, self.Frequency, self.Power, tostring(self.ID))
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
|
||||
--- Stops a transmission
|
||||
-- This function is especially usefull to stop the broadcast of looped transmissions
|
||||
-- @param #RADIO self
|
||||
-- @return #RADIO self
|
||||
function RADIO:StopBroadcast()
|
||||
self:F()
|
||||
-- If the POSITIONABLE is a UNIT or a GROUP, stop the transmission with the DCS "StopTransmission" command
|
||||
if self.Positionable.ClassName == "UNIT" or self.Positionable.ClassName == "GROUP" then
|
||||
|
||||
local commandStopTransmission={id="StopTransmission", params={}}
|
||||
|
||||
self.Positionable:SetCommand(commandStopTransmission)
|
||||
else
|
||||
-- Else, we use the appropriate singleton funciton
|
||||
trigger.action.stopRadioTransmission(tostring(self.ID))
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- After attaching a @{#BEACON} to your @{Wrapper.Positionable#POSITIONABLE}, you need to select the right function to activate the kind of beacon you want.
|
||||
-- There are two types of BEACONs available : the AA TACAN Beacon and the general purpose Radio Beacon.
|
||||
-- Note that in both case, you can set an optional parameter : the `BeaconDuration`. This can be very usefull to simulate the battery time if your BEACON is
|
||||
-- attach to a cargo crate, for exemple.
|
||||
--
|
||||
-- ## AA TACAN Beacon usage
|
||||
--
|
||||
-- This beacon only works with airborne @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP}. Use @{#BEACON:AATACAN}() to set the beacon parameters and start the beacon.
|
||||
-- Use @#BEACON:StopAATACAN}() to stop it.
|
||||
--
|
||||
-- ## General Purpose Radio Beacon usage
|
||||
--
|
||||
-- This beacon will work with any @{Wrapper.Positionable#POSITIONABLE}, but **it won't follow the @{Wrapper.Positionable#POSITIONABLE}** ! This means that you should only use it with
|
||||
-- @{Wrapper.Positionable#POSITIONABLE} that don't move, or move very slowly. Use @{#BEACON:RadioBeacon}() to set the beacon parameters and start the beacon.
|
||||
-- Use @{#BEACON:StopRadioBeacon}() to stop it.
|
||||
--
|
||||
-- @type BEACON
|
||||
-- @field #string ClassName Name of the class "BEACON".
|
||||
-- @field Wrapper.Controllable#CONTROLLABLE Positionable The @{#CONTROLLABLE} that will receive radio capabilities.
|
||||
-- @extends Core.Base#BASE
|
||||
BEACON = {
|
||||
ClassName = "BEACON",
|
||||
Positionable = nil,
|
||||
name=nil,
|
||||
}
|
||||
|
||||
--- Beacon types supported by DCS.
|
||||
-- @type BEACON.Type
|
||||
-- @field #number NULL
|
||||
-- @field #number VOR
|
||||
-- @field #number DME
|
||||
-- @field #number VOR_DME
|
||||
-- @field #number TACAN TACtical Air Navigation system.
|
||||
-- @field #number VORTAC
|
||||
-- @field #number RSBN
|
||||
-- @field #number BROADCAST_STATION
|
||||
-- @field #number HOMER
|
||||
-- @field #number AIRPORT_HOMER
|
||||
-- @field #number AIRPORT_HOMER_WITH_MARKER
|
||||
-- @field #number ILS_FAR_HOMER
|
||||
-- @field #number ILS_NEAR_HOMER
|
||||
-- @field #number ILS_LOCALIZER
|
||||
-- @field #number ILS_GLIDESLOPE
|
||||
-- @field #number PRMG_LOCALIZER
|
||||
-- @field #number PRMG_GLIDESLOPE
|
||||
-- @field #number ICLS Same as ICLS glideslope.
|
||||
-- @field #number ICLS_LOCALIZER
|
||||
-- @field #number ICLS_GLIDESLOPE
|
||||
-- @field #number NAUTICAL_HOMER
|
||||
BEACON.Type={
|
||||
NULL = 0,
|
||||
VOR = 1,
|
||||
DME = 2,
|
||||
VOR_DME = 3,
|
||||
TACAN = 4,
|
||||
VORTAC = 5,
|
||||
RSBN = 128,
|
||||
BROADCAST_STATION = 1024,
|
||||
HOMER = 8,
|
||||
AIRPORT_HOMER = 4104,
|
||||
AIRPORT_HOMER_WITH_MARKER = 4136,
|
||||
ILS_FAR_HOMER = 16408,
|
||||
ILS_NEAR_HOMER = 16424,
|
||||
ILS_LOCALIZER = 16640,
|
||||
ILS_GLIDESLOPE = 16896,
|
||||
PRMG_LOCALIZER = 33024,
|
||||
PRMG_GLIDESLOPE = 33280,
|
||||
ICLS = 131584, --leaving this in here but it is the same as ICLS_GLIDESLOPE
|
||||
ICLS_LOCALIZER = 131328,
|
||||
ICLS_GLIDESLOPE = 131584,
|
||||
NAUTICAL_HOMER = 65536,
|
||||
|
||||
}
|
||||
|
||||
--- Beacon systems supported by DCS. https://wiki.hoggitworld.com/view/DCS_command_activateBeacon
|
||||
-- @type BEACON.System
|
||||
-- @field #number PAR_10 ?
|
||||
-- @field #number RSBN_5 Russian VOR/DME system.
|
||||
-- @field #number TACAN TACtical Air Navigation system on ground.
|
||||
-- @field #number TACAN_TANKER_X TACtical Air Navigation system for tankers on X band.
|
||||
-- @field #number TACAN_TANKER_Y TACtical Air Navigation system for tankers on Y band.
|
||||
-- @field #number VOR Very High Frequency Omni-Directional Range
|
||||
-- @field #number ILS_LOCALIZER ILS localizer
|
||||
-- @field #number ILS_GLIDESLOPE ILS glideslope.
|
||||
-- @field #number PRGM_LOCALIZER PRGM localizer.
|
||||
-- @field #number PRGM_GLIDESLOPE PRGM glideslope.
|
||||
-- @field #number BROADCAST_STATION Broadcast station.
|
||||
-- @field #number VORTAC Radio-based navigational aid for aircraft pilots consisting of a co-located VHF omnidirectional range (VOR) beacon and a tactical air navigation system (TACAN) beacon.
|
||||
-- @field #number TACAN_AA_MODE_X TACtical Air Navigation for aircraft on X band.
|
||||
-- @field #number TACAN_AA_MODE_Y TACtical Air Navigation for aircraft on Y band.
|
||||
-- @field #number VORDME Radio beacon that combines a VHF omnidirectional range (VOR) with a distance measuring equipment (DME).
|
||||
-- @field #number ICLS_LOCALIZER Carrier landing system.
|
||||
-- @field #number ICLS_GLIDESLOPE Carrier landing system.
|
||||
BEACON.System={
|
||||
PAR_10 = 1,
|
||||
RSBN_5 = 2,
|
||||
TACAN = 3,
|
||||
TACAN_TANKER_X = 4,
|
||||
TACAN_TANKER_Y = 5,
|
||||
VOR = 6,
|
||||
ILS_LOCALIZER = 7,
|
||||
ILS_GLIDESLOPE = 8,
|
||||
PRMG_LOCALIZER = 9,
|
||||
PRMG_GLIDESLOPE = 10,
|
||||
BROADCAST_STATION = 11,
|
||||
VORTAC = 12,
|
||||
TACAN_AA_MODE_X = 13,
|
||||
TACAN_AA_MODE_Y = 14,
|
||||
VORDME = 15,
|
||||
ICLS_LOCALIZER = 16,
|
||||
ICLS_GLIDESLOPE = 17,
|
||||
}
|
||||
|
||||
--- Create a new BEACON Object. This doesn't activate the beacon, though, use @{#BEACON.ActivateTACAN} etc.
|
||||
-- If you want to create a BEACON, you probably should use @{Wrapper.Positionable#POSITIONABLE.GetBeacon}() instead.
|
||||
-- @param #BEACON self
|
||||
-- @param Wrapper.Positionable#POSITIONABLE Positionable The @{Positionable} that will receive radio capabilities.
|
||||
-- @return #BEACON Beacon object or #nil if the positionable is invalid.
|
||||
function BEACON:New(Positionable)
|
||||
|
||||
-- Inherit BASE.
|
||||
local self=BASE:Inherit(self, BASE:New()) --#BEACON
|
||||
|
||||
-- Debug.
|
||||
self:F(Positionable)
|
||||
|
||||
-- Set positionable.
|
||||
if Positionable:GetPointVec2() then -- It's stupid, but the only way I found to make sure positionable is valid
|
||||
self.Positionable = Positionable
|
||||
self.name=Positionable:GetName()
|
||||
self:I(string.format("New BEACON %s", tostring(self.name)))
|
||||
return self
|
||||
end
|
||||
|
||||
self:E({"The passed positionable is invalid, no BEACON created", Positionable})
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
--- Activates a TACAN BEACON.
|
||||
-- @param #BEACON self
|
||||
-- @param #number Channel TACAN channel, i.e. the "10" part in "10Y".
|
||||
-- @param #string Mode TACAN mode, i.e. the "Y" part in "10Y".
|
||||
-- @param #string Message The Message that is going to be coded in Morse and broadcasted by the beacon.
|
||||
-- @param #boolean Bearing If true, beacon provides bearing information. If false (or nil), only distance information is available.
|
||||
-- @param #number Duration How long will the beacon last in seconds. Omit for forever.
|
||||
-- @return #BEACON self
|
||||
-- @usage
|
||||
-- -- Let's create a TACAN Beacon for a tanker
|
||||
-- local myUnit = UNIT:FindByName("MyUnit")
|
||||
-- local myBeacon = myUnit:GetBeacon() -- Creates the beacon
|
||||
--
|
||||
-- myBeacon:ActivateTACAN(20, "Y", "TEXACO", true) -- Activate the beacon
|
||||
function BEACON:ActivateTACAN(Channel, Mode, Message, Bearing, Duration)
|
||||
self:T({channel=Channel, mode=Mode, callsign=Message, bearing=Bearing, duration=Duration})
|
||||
|
||||
-- Get frequency.
|
||||
local Frequency=UTILS.TACANToFrequency(Channel, Mode)
|
||||
|
||||
-- Check.
|
||||
if not Frequency then
|
||||
self:E({"The passed TACAN channel is invalid, the BEACON is not emitting"})
|
||||
return self
|
||||
end
|
||||
|
||||
-- Beacon type.
|
||||
local Type=BEACON.Type.TACAN
|
||||
|
||||
-- Beacon system.
|
||||
local System=BEACON.System.TACAN
|
||||
|
||||
-- Check if unit is an aircraft and set system accordingly.
|
||||
local AA=self.Positionable:IsAir()
|
||||
if AA then
|
||||
System=5 --NOTE: 5 is how you cat the correct tanker behaviour! --BEACON.System.TACAN_TANKER
|
||||
-- Check if "Y" mode is selected for aircraft.
|
||||
if Mode~="Y" then
|
||||
self:E({"WARNING: The POSITIONABLE you want to attach the AA Tacan Beacon is an aircraft: Mode should Y !The BEACON is not emitting.", self.Positionable})
|
||||
end
|
||||
end
|
||||
|
||||
-- Attached unit.
|
||||
local UnitID=self.Positionable:GetID()
|
||||
|
||||
-- Debug.
|
||||
self:I({string.format("BEACON Activating TACAN %s: Channel=%d%s, Morse=%s, Bearing=%s, Duration=%s!", tostring(self.name), Channel, Mode, Message, tostring(Bearing), tostring(Duration))})
|
||||
|
||||
-- Start beacon.
|
||||
self.Positionable:CommandActivateBeacon(Type, System, Frequency, UnitID, Channel, Mode, AA, Message, Bearing)
|
||||
|
||||
-- Stop sheduler.
|
||||
if Duration then
|
||||
self.Positionable:DeactivateBeacon(Duration)
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Activates an ICLS BEACON. The unit the BEACON is attached to should be an aircraft carrier supporting this system.
|
||||
-- @param #BEACON self
|
||||
-- @param #number Channel ICLS channel.
|
||||
-- @param #string Callsign The Message that is going to be coded in Morse and broadcasted by the beacon.
|
||||
-- @param #number Duration How long will the beacon last in seconds. Omit for forever.
|
||||
-- @return #BEACON self
|
||||
function BEACON:ActivateICLS(Channel, Callsign, Duration)
|
||||
self:F({Channel=Channel, Callsign=Callsign, Duration=Duration})
|
||||
|
||||
-- Attached unit.
|
||||
local UnitID=self.Positionable:GetID()
|
||||
|
||||
-- Debug
|
||||
self:T2({"ICLS BEACON started!"})
|
||||
|
||||
-- Start beacon.
|
||||
self.Positionable:CommandActivateICLS(Channel, UnitID, Callsign)
|
||||
|
||||
-- Stop sheduler
|
||||
if Duration then -- Schedule the stop of the BEACON if asked by the MD
|
||||
self.Positionable:DeactivateBeacon(Duration)
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
--- Activates a TACAN BEACON on an Aircraft.
|
||||
-- @param #BEACON self
|
||||
-- @param #number TACANChannel (the "10" part in "10Y"). Note that AA TACAN are only available on Y Channels
|
||||
-- @param #string Message The Message that is going to be coded in Morse and broadcasted by the beacon
|
||||
-- @param #boolean Bearing Can the BEACON be homed on ?
|
||||
-- @param #number BeaconDuration How long will the beacon last in seconds. Omit for forever.
|
||||
-- @return #BEACON self
|
||||
-- @usage
|
||||
-- -- Let's create a TACAN Beacon for a tanker
|
||||
-- local myUnit = UNIT:FindByName("MyUnit")
|
||||
-- local myBeacon = myUnit:GetBeacon() -- Creates the beacon
|
||||
--
|
||||
-- myBeacon:AATACAN(20, "TEXACO", true) -- Activate the beacon
|
||||
function BEACON:AATACAN(TACANChannel, Message, Bearing, BeaconDuration)
|
||||
self:F({TACANChannel, Message, Bearing, BeaconDuration})
|
||||
|
||||
local IsValid = true
|
||||
|
||||
if not self.Positionable:IsAir() then
|
||||
self:E({"The POSITIONABLE you want to attach the AA Tacan Beacon is not an aircraft ! The BEACON is not emitting", self.Positionable})
|
||||
IsValid = false
|
||||
end
|
||||
|
||||
local Frequency = self:_TACANToFrequency(TACANChannel, "Y")
|
||||
if not Frequency then
|
||||
self:E({"The passed TACAN channel is invalid, the BEACON is not emitting"})
|
||||
IsValid = false
|
||||
end
|
||||
|
||||
-- I'm using the beacon type 4 (BEACON_TYPE_TACAN). For System, I'm using 5 (TACAN_TANKER_MODE_Y) if the bearing shows its bearing
|
||||
-- or 14 (TACAN_AA_MODE_Y) if it does not
|
||||
local System
|
||||
if Bearing then
|
||||
System = 5
|
||||
else
|
||||
System = 14
|
||||
end
|
||||
|
||||
if IsValid then -- Starts the BEACON
|
||||
self:T2({"AA TACAN BEACON started !"})
|
||||
self.Positionable:SetCommand({
|
||||
id = "ActivateBeacon",
|
||||
params = {
|
||||
type = 4,
|
||||
system = System,
|
||||
callsign = Message,
|
||||
frequency = Frequency,
|
||||
}
|
||||
})
|
||||
|
||||
if BeaconDuration then -- Schedule the stop of the BEACON if asked by the MD
|
||||
SCHEDULER:New(nil,
|
||||
function()
|
||||
self:StopAATACAN()
|
||||
end, {}, BeaconDuration)
|
||||
end
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Stops the AA TACAN BEACON
|
||||
-- @param #BEACON self
|
||||
-- @return #BEACON self
|
||||
function BEACON:StopAATACAN()
|
||||
self:F()
|
||||
if not self.Positionable then
|
||||
self:E({"Start the beacon first before stoping it !"})
|
||||
else
|
||||
self.Positionable:SetCommand({
|
||||
id = 'DeactivateBeacon',
|
||||
params = {
|
||||
}
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- Activates a general pupose Radio Beacon
|
||||
-- This uses the very generic singleton function "trigger.action.radioTransmission()" provided by DCS to broadcast a sound file on a specific frequency.
|
||||
-- Although any frequency could be used, only 2 DCS Modules can home on radio beacons at the time of writing : the Huey and the Mi-8.
|
||||
-- They can home in on these specific frequencies :
|
||||
-- * **Mi8**
|
||||
-- * R-828 -> 20-60MHz
|
||||
-- * ARKUD -> 100-150MHz (canal 1 : 114166, canal 2 : 114333, canal 3 : 114583, canal 4 : 121500, canal 5 : 123100, canal 6 : 124100) AM
|
||||
-- * ARK9 -> 150-1300KHz
|
||||
-- * **Huey**
|
||||
-- * AN/ARC-131 -> 30-76 Mhz FM
|
||||
-- @param #BEACON self
|
||||
-- @param #string FileName The name of the audio file
|
||||
-- @param #number Frequency in MHz
|
||||
-- @param #number Modulation either radio.modulation.AM or radio.modulation.FM
|
||||
-- @param #number Power in W
|
||||
-- @param #number BeaconDuration How long will the beacon last in seconds. Omit for forever.
|
||||
-- @return #BEACON self
|
||||
-- @usage
|
||||
-- -- Let's create a beacon for a unit in distress.
|
||||
-- -- Frequency will be 40MHz FM (home-able by a Huey's AN/ARC-131)
|
||||
-- -- The beacon they use is battery-powered, and only lasts for 5 min
|
||||
-- local UnitInDistress = UNIT:FindByName("Unit1")
|
||||
-- local UnitBeacon = UnitInDistress:GetBeacon()
|
||||
--
|
||||
-- -- Set the beacon and start it
|
||||
-- UnitBeacon:RadioBeacon("MySoundFileSOS.ogg", 40, radio.modulation.FM, 20, 5*60)
|
||||
function BEACON:RadioBeacon(FileName, Frequency, Modulation, Power, BeaconDuration)
|
||||
self:F({FileName, Frequency, Modulation, Power, BeaconDuration})
|
||||
local IsValid = false
|
||||
|
||||
-- Check the filename
|
||||
if type(FileName) == "string" then
|
||||
if FileName:find(".ogg") or FileName:find(".wav") then
|
||||
if not FileName:find("l10n/DEFAULT/") then
|
||||
FileName = "l10n/DEFAULT/" .. FileName
|
||||
end
|
||||
IsValid = true
|
||||
end
|
||||
end
|
||||
if not IsValid then
|
||||
self:E({"File name invalid. Maybe something wrong with the extension ? ", FileName})
|
||||
end
|
||||
|
||||
-- Check the Frequency
|
||||
if type(Frequency) ~= "number" and IsValid then
|
||||
self:E({"Frequency invalid. ", Frequency})
|
||||
IsValid = false
|
||||
end
|
||||
Frequency = Frequency * 1000000 -- Conversion to Hz
|
||||
|
||||
-- Check the modulation
|
||||
if Modulation ~= radio.modulation.AM and Modulation ~= radio.modulation.FM and IsValid then --TODO Maybe make this future proof if ED decides to add an other modulation ?
|
||||
self:E({"Modulation is invalid. Use DCS's enum radio.modulation.", Modulation})
|
||||
IsValid = false
|
||||
end
|
||||
|
||||
-- Check the Power
|
||||
if type(Power) ~= "number" and IsValid then
|
||||
self:E({"Power is invalid. ", Power})
|
||||
IsValid = false
|
||||
end
|
||||
Power = math.floor(math.abs(Power)) --TODO Find what is the maximum power allowed by DCS and limit power to that
|
||||
|
||||
if IsValid then
|
||||
self:T2({"Activating Beacon on ", Frequency, Modulation})
|
||||
-- Note that this is looped. I have to give this transmission a unique name, I use the class ID
|
||||
trigger.action.radioTransmission(FileName, self.Positionable:GetPositionVec3(), Modulation, true, Frequency, Power, tostring(self.ID))
|
||||
|
||||
if BeaconDuration then -- Schedule the stop of the BEACON if asked by the MD
|
||||
SCHEDULER:New( nil,
|
||||
function()
|
||||
self:StopRadioBeacon()
|
||||
end, {}, BeaconDuration)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Stops the AA TACAN BEACON
|
||||
-- @param #BEACON self
|
||||
-- @return #BEACON self
|
||||
function BEACON:StopRadioBeacon()
|
||||
self:F()
|
||||
-- The unique name of the transmission is the class ID
|
||||
trigger.action.stopRadioTransmission(tostring(self.ID))
|
||||
return self
|
||||
end
|
||||
|
||||
--- Converts a TACAN Channel/Mode couple into a frequency in Hz
|
||||
-- @param #BEACON self
|
||||
-- @param #number TACANChannel
|
||||
-- @param #string TACANMode
|
||||
-- @return #number Frequecy
|
||||
-- @return #nil if parameters are invalid
|
||||
function BEACON:_TACANToFrequency(TACANChannel, TACANMode)
|
||||
self:F3({TACANChannel, TACANMode})
|
||||
|
||||
if type(TACANChannel) ~= "number" then
|
||||
if TACANMode ~= "X" and TACANMode ~= "Y" then
|
||||
return nil -- error in arguments
|
||||
end
|
||||
end
|
||||
|
||||
-- This code is largely based on ED's code, in DCS World\Scripts\World\Radio\BeaconTypes.lua, line 137.
|
||||
-- I have no idea what it does but it seems to work
|
||||
local A = 1151 -- 'X', channel >= 64
|
||||
local B = 64 -- channel >= 64
|
||||
|
||||
if TACANChannel < 64 then
|
||||
B = 1
|
||||
end
|
||||
|
||||
if TACANMode == 'Y' then
|
||||
A = 1025
|
||||
if TACANChannel < 64 then
|
||||
A = 1088
|
||||
end
|
||||
else -- 'X'
|
||||
if TACANChannel < 64 then
|
||||
A = 962
|
||||
end
|
||||
end
|
||||
|
||||
return (A + TACANChannel - B) * 1000000
|
||||
end
|
||||
|
||||
|
||||
@@ -109,9 +109,11 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr
|
||||
self.ObjectSchedulers = self.ObjectSchedulers or setmetatable( {}, { __mode = "v" } )
|
||||
|
||||
if Scheduler.MasterObject then
|
||||
--env.info("FF Object Scheduler")
|
||||
self.ObjectSchedulers[CallID] = Scheduler
|
||||
self:F3( { CallID = CallID, ObjectScheduler = tostring(self.ObjectSchedulers[CallID]), MasterObject = tostring(Scheduler.MasterObject) } )
|
||||
else
|
||||
--env.info("FF Persistent Scheduler")
|
||||
self.PersistentSchedulers[CallID] = Scheduler
|
||||
self:F3( { CallID = CallID, PersistentScheduler = self.PersistentSchedulers[CallID] } )
|
||||
end
|
||||
@@ -122,7 +124,7 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr
|
||||
self.Schedule[Scheduler][CallID].Function = ScheduleFunction
|
||||
self.Schedule[Scheduler][CallID].Arguments = ScheduleArguments
|
||||
self.Schedule[Scheduler][CallID].StartTime = timer.getTime() + ( Start or 0 )
|
||||
self.Schedule[Scheduler][CallID].Start = Start + 0.1
|
||||
self.Schedule[Scheduler][CallID].Start = Start + 0.001
|
||||
self.Schedule[Scheduler][CallID].Repeat = Repeat or 0
|
||||
self.Schedule[Scheduler][CallID].Randomize = Randomize or 0
|
||||
self.Schedule[Scheduler][CallID].Stop = Stop
|
||||
@@ -219,6 +221,7 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr
|
||||
if ShowTrace then
|
||||
SchedulerObject:T( Prefix .. Name .. ":" .. Line .. " (" .. Source .. ")" )
|
||||
end
|
||||
-- The master object is passed as first parameter. A few :Schedule() calls in MOOSE expect this currently. But in principle it should be removed.
|
||||
return ScheduleFunction( SchedulerObject, unpack( ScheduleArguments ) )
|
||||
end
|
||||
Status, Result = xpcall( Timer, ErrorHandler )
|
||||
@@ -317,7 +320,7 @@ end
|
||||
--- Stop dispatcher.
|
||||
-- @param #SCHEDULEDISPATCHER self
|
||||
-- @param Core.Scheduler#SCHEDULER Scheduler Scheduler object.
|
||||
-- @param #table CallID Call ID.
|
||||
-- @param #string CallID (Optional) Scheduler Call ID. If nil, all pending schedules are stopped recursively.
|
||||
function SCHEDULEDISPATCHER:Stop( Scheduler, CallID )
|
||||
self:F2( { Stop = CallID, Scheduler = Scheduler } )
|
||||
|
||||
|
||||
@@ -238,7 +238,7 @@ end
|
||||
-- @param #number Stop Time interval in seconds after which the scheduler will be stoppe.
|
||||
-- @param #number TraceLevel Trace level [0,3]. Default 3.
|
||||
-- @param Core.Fsm#FSM Fsm Finite state model.
|
||||
-- @return #table The ScheduleID of the planned schedule.
|
||||
-- @return #string The Schedule ID of the planned schedule.
|
||||
function SCHEDULER:Schedule( MasterObject, SchedulerFunction, SchedulerArguments, Start, Repeat, RandomizeFactor, Stop, TraceLevel, Fsm )
|
||||
self:F2( { Start, Repeat, RandomizeFactor, Stop } )
|
||||
self:T3( { SchedulerArguments } )
|
||||
@@ -273,7 +273,7 @@ end
|
||||
|
||||
--- (Re-)Starts the schedules or a specific schedule if a valid ScheduleID is provided.
|
||||
-- @param #SCHEDULER self
|
||||
-- @param #string ScheduleID (Optional) The ScheduleID of the planned (repeating) schedule.
|
||||
-- @param #string ScheduleID (Optional) The Schedule ID of the planned (repeating) schedule.
|
||||
function SCHEDULER:Start( ScheduleID )
|
||||
self:F3( { ScheduleID } )
|
||||
self:T(string.format("Starting scheduler ID=%s", tostring(ScheduleID)))
|
||||
|
||||
+1014
-131
File diff suppressed because it is too large
Load Diff
@@ -236,6 +236,7 @@ do -- SETTINGS
|
||||
|
||||
--- SETTINGS constructor.
|
||||
-- @param #SETTINGS self
|
||||
-- @param #string PlayerName (Optional) Set settings for this player.
|
||||
-- @return #SETTINGS
|
||||
function SETTINGS:Set( PlayerName )
|
||||
|
||||
@@ -252,6 +253,7 @@ do -- SETTINGS
|
||||
self:SetMessageTime( MESSAGE.Type.Overview, 60 )
|
||||
self:SetMessageTime( MESSAGE.Type.Update, 15 )
|
||||
self:SetEraModern()
|
||||
self:SetLocale("en")
|
||||
return self
|
||||
else
|
||||
local Settings = _DATABASE:GetPlayerSettings( PlayerName )
|
||||
@@ -283,7 +285,21 @@ do -- SETTINGS
|
||||
function SETTINGS:SetMetric()
|
||||
self.Metric = true
|
||||
end
|
||||
|
||||
|
||||
--- Sets the SETTINGS default text locale.
|
||||
-- @param #SETTINGS self
|
||||
-- @param #string Locale
|
||||
function SETTINGS:SetLocale(Locale)
|
||||
self.Locale = Locale or "en"
|
||||
end
|
||||
|
||||
--- Gets the SETTINGS text locale.
|
||||
-- @param #SETTINGS self
|
||||
-- @return #string
|
||||
function SETTINGS:GetLocale()
|
||||
return self.Locale or _SETTINGS:GetLocale()
|
||||
end
|
||||
|
||||
--- Gets if the SETTINGS is metric.
|
||||
-- @param #SETTINGS self
|
||||
-- @return #boolean true if metric.
|
||||
|
||||
@@ -1718,27 +1718,29 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT
|
||||
self:T(string.format("Group %s is spawned on farp/ship/runway %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName()))
|
||||
nfree=SpawnAirbase:GetFreeParkingSpotsNumber(termtype, true)
|
||||
spots=SpawnAirbase:GetFreeParkingSpotsTable(termtype, true)
|
||||
--[[
|
||||
elseif Parkingdata~=nil then
|
||||
-- Parking data explicitly set by user as input parameter.
|
||||
nfree=#Parkingdata
|
||||
spots=Parkingdata
|
||||
]]
|
||||
else
|
||||
if ishelo then
|
||||
if termtype==nil then
|
||||
-- Helo is spawned. Try exclusive helo spots first.
|
||||
self:T(string.format("Helo group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.HelicopterOnly))
|
||||
spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup, AIRBASE.TerminalType.HelicopterOnly, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits)
|
||||
spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup, AIRBASE.TerminalType.HelicopterOnly, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata)
|
||||
nfree=#spots
|
||||
if nfree<nunits then
|
||||
-- Not enough helo ports. Let's try also other terminal types.
|
||||
self:T(string.format("Helo group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.HelicopterUsable))
|
||||
spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup, AIRBASE.TerminalType.HelicopterUsable, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits)
|
||||
spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup, AIRBASE.TerminalType.HelicopterUsable, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata)
|
||||
nfree=#spots
|
||||
end
|
||||
else
|
||||
-- No terminal type specified. We try all spots except shelters.
|
||||
self:T(string.format("Helo group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), termtype))
|
||||
spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup, termtype, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits)
|
||||
spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup, termtype, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata)
|
||||
nfree=#spots
|
||||
end
|
||||
else
|
||||
@@ -1747,29 +1749,30 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT
|
||||
if isbomber or istransport or istanker or isawacs then
|
||||
-- First we fill the potentially bigger spots.
|
||||
self:T(string.format("Transport/bomber group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.OpenBig))
|
||||
spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup, AIRBASE.TerminalType.OpenBig, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits)
|
||||
spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup, AIRBASE.TerminalType.OpenBig, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata)
|
||||
nfree=#spots
|
||||
if nfree<nunits then
|
||||
-- Now we try the smaller ones.
|
||||
self:T(string.format("Transport/bomber group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.OpenMedOrBig))
|
||||
spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup, AIRBASE.TerminalType.OpenMedOrBig, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits)
|
||||
spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup, AIRBASE.TerminalType.OpenMedOrBig, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata)
|
||||
nfree=#spots
|
||||
end
|
||||
else
|
||||
self:T(string.format("Fighter group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.FighterAircraft))
|
||||
spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup, AIRBASE.TerminalType.FighterAircraft, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits)
|
||||
spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup, AIRBASE.TerminalType.FighterAircraft, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata)
|
||||
nfree=#spots
|
||||
end
|
||||
else
|
||||
-- Terminal type explicitly given.
|
||||
self:T(string.format("Plane group %s is at %s using terminal type %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), tostring(termtype)))
|
||||
spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup, termtype, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits)
|
||||
spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup, termtype, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata)
|
||||
nfree=#spots
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Get parking data.
|
||||
-- Debug: Get parking data.
|
||||
--[[
|
||||
local parkingdata=SpawnAirbase:GetParkingSpotsTable(termtype)
|
||||
self:T(string.format("Parking at %s, terminal type %s:", SpawnAirbase:GetName(), tostring(termtype)))
|
||||
for _,_spot in pairs(parkingdata) do
|
||||
@@ -1777,6 +1780,7 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT
|
||||
SpawnAirbase:GetName(), _spot.TerminalID, _spot.TerminalType,tostring(_spot.Free),tostring(_spot.TOAC),_spot.TerminalID0,_spot.DistToRwy))
|
||||
end
|
||||
self:T(string.format("%s at %s: free parking spots = %d - number of units = %d", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), nfree, nunits))
|
||||
]]
|
||||
|
||||
-- Set this to true if not enough spots are available for emergency air start.
|
||||
local _notenough=false
|
||||
@@ -2144,27 +2148,29 @@ function SPAWN:ParkAircraft( SpawnAirbase, TerminalType, Parkingdata, SpawnIndex
|
||||
self:T(string.format("Group %s is spawned on farp/ship/runway %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName()))
|
||||
nfree=SpawnAirbase:GetFreeParkingSpotsNumber(termtype, true)
|
||||
spots=SpawnAirbase:GetFreeParkingSpotsTable(termtype, true)
|
||||
--[[
|
||||
elseif Parkingdata~=nil then
|
||||
-- Parking data explicitly set by user as input parameter.
|
||||
nfree=#Parkingdata
|
||||
spots=Parkingdata
|
||||
]]
|
||||
else
|
||||
if ishelo then
|
||||
if termtype==nil then
|
||||
-- Helo is spawned. Try exclusive helo spots first.
|
||||
self:T(string.format("Helo group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.HelicopterOnly))
|
||||
spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup, AIRBASE.TerminalType.HelicopterOnly, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits)
|
||||
spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup, AIRBASE.TerminalType.HelicopterOnly, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata)
|
||||
nfree=#spots
|
||||
if nfree<nunits then
|
||||
-- Not enough helo ports. Let's try also other terminal types.
|
||||
self:T(string.format("Helo group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.HelicopterUsable))
|
||||
spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup, AIRBASE.TerminalType.HelicopterUsable, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits)
|
||||
spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup, AIRBASE.TerminalType.HelicopterUsable, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata)
|
||||
nfree=#spots
|
||||
end
|
||||
else
|
||||
-- No terminal type specified. We try all spots except shelters.
|
||||
self:T(string.format("Helo group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), termtype))
|
||||
spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup, termtype, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits)
|
||||
spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup, termtype, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata)
|
||||
nfree=#spots
|
||||
end
|
||||
else
|
||||
@@ -2176,29 +2182,30 @@ function SPAWN:ParkAircraft( SpawnAirbase, TerminalType, Parkingdata, SpawnIndex
|
||||
if isbomber or istransport then
|
||||
-- First we fill the potentially bigger spots.
|
||||
self:T(string.format("Transport/bomber group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.OpenBig))
|
||||
spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup, AIRBASE.TerminalType.OpenBig, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits)
|
||||
spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup, AIRBASE.TerminalType.OpenBig, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata)
|
||||
nfree=#spots
|
||||
if nfree<nunits then
|
||||
-- Now we try the smaller ones.
|
||||
self:T(string.format("Transport/bomber group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.OpenMedOrBig))
|
||||
spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup, AIRBASE.TerminalType.OpenMedOrBig, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits)
|
||||
spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup, AIRBASE.TerminalType.OpenMedOrBig, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata)
|
||||
nfree=#spots
|
||||
end
|
||||
else
|
||||
self:T(string.format("Fighter group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.FighterAircraft))
|
||||
spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup, AIRBASE.TerminalType.FighterAircraft, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits)
|
||||
spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup, AIRBASE.TerminalType.FighterAircraft, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata)
|
||||
nfree=#spots
|
||||
end
|
||||
else
|
||||
-- Terminal type explicitly given.
|
||||
self:T(string.format("Plane group %s is at %s using terminal type %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), tostring(termtype)))
|
||||
spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup, termtype, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits)
|
||||
spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup, termtype, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata)
|
||||
nfree=#spots
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Get parking data.
|
||||
-- Debug: Get parking data.
|
||||
--[[
|
||||
local parkingdata=SpawnAirbase:GetParkingSpotsTable(termtype)
|
||||
self:T2(string.format("Parking at %s, terminal type %s:", SpawnAirbase:GetName(), tostring(termtype)))
|
||||
for _,_spot in pairs(parkingdata) do
|
||||
@@ -2206,6 +2213,7 @@ function SPAWN:ParkAircraft( SpawnAirbase, TerminalType, Parkingdata, SpawnIndex
|
||||
SpawnAirbase:GetName(), _spot.TerminalID, _spot.TerminalType,tostring(_spot.Free),tostring(_spot.TOAC),_spot.TerminalID0,_spot.DistToRwy))
|
||||
end
|
||||
self:T(string.format("%s at %s: free parking spots = %d - number of units = %d", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), nfree, nunits))
|
||||
]]
|
||||
|
||||
-- Set this to true if not enough spots are available for emergency air start.
|
||||
local _notenough=false
|
||||
@@ -2415,8 +2423,7 @@ end
|
||||
-- @param #SPAWN self
|
||||
-- @param DCS#Vec3 Vec3 The Vec3 coordinates where to spawn the group.
|
||||
-- @param #number SpawnIndex (optional) The index which group to spawn within the given zone.
|
||||
-- @return Wrapper.Group#GROUP that was spawned.
|
||||
-- @return #nil Nothing was spawned.
|
||||
-- @return Wrapper.Group#GROUP that was spawned or #nil if nothing was spawned.
|
||||
function SPAWN:SpawnFromVec3( Vec3, SpawnIndex )
|
||||
self:F( { self.SpawnTemplatePrefix, Vec3, SpawnIndex } )
|
||||
|
||||
@@ -2485,8 +2492,7 @@ end
|
||||
-- @param #SPAWN self
|
||||
-- @param Core.Point#Coordinate Coordinate The Coordinate coordinates where to spawn the group.
|
||||
-- @param #number SpawnIndex (optional) The index which group to spawn within the given zone.
|
||||
-- @return Wrapper.Group#GROUP that was spawned.
|
||||
-- @return #nil Nothing was spawned.
|
||||
-- @return Wrapper.Group#GROUP that was spawned or #nil if nothing was spawned.
|
||||
function SPAWN:SpawnFromCoordinate( Coordinate, SpawnIndex )
|
||||
self:F( { self.SpawnTemplatePrefix, SpawnIndex } )
|
||||
|
||||
@@ -2502,8 +2508,7 @@ end
|
||||
-- @param #SPAWN self
|
||||
-- @param Core.Point#POINT_VEC3 PointVec3 The PointVec3 coordinates where to spawn the group.
|
||||
-- @param #number SpawnIndex (optional) The index which group to spawn within the given zone.
|
||||
-- @return Wrapper.Group#GROUP that was spawned.
|
||||
-- @return #nil Nothing was spawned.
|
||||
-- @return Wrapper.Group#GROUP that was spawned or #nil if nothing was spawned.
|
||||
-- @usage
|
||||
--
|
||||
-- local SpawnPointVec3 = ZONE:New( ZoneName ):GetPointVec3( 2000 ) -- Get the center of the ZONE object at 2000 meters from the ground.
|
||||
@@ -2527,8 +2532,7 @@ end
|
||||
-- @param #number MinHeight (optional) The minimum height to spawn an airborne group into the zone.
|
||||
-- @param #number MaxHeight (optional) The maximum height to spawn an airborne group into the zone.
|
||||
-- @param #number SpawnIndex (optional) The index which group to spawn within the given zone.
|
||||
-- @return Wrapper.Group#GROUP that was spawned.
|
||||
-- @return #nil Nothing was spawned.
|
||||
-- @return Wrapper.Group#GROUP that was spawned or #nil if nothing was spawned.
|
||||
-- @usage
|
||||
--
|
||||
-- local SpawnVec2 = ZONE:New( ZoneName ):GetVec2()
|
||||
@@ -2561,8 +2565,7 @@ end
|
||||
-- @param #number MinHeight (optional) The minimum height to spawn an airborne group into the zone.
|
||||
-- @param #number MaxHeight (optional) The maximum height to spawn an airborne group into the zone.
|
||||
-- @param #number SpawnIndex (optional) The index which group to spawn within the given zone.
|
||||
-- @return Wrapper.Group#GROUP that was spawned.
|
||||
-- @return #nil Nothing was spawned.
|
||||
-- @return Wrapper.Group#GROUP that was spawned or #nil if nothing was spawned.
|
||||
-- @usage
|
||||
--
|
||||
-- local SpawnPointVec2 = ZONE:New( ZoneName ):GetPointVec2()
|
||||
@@ -2618,8 +2621,7 @@ end
|
||||
-- @param #number MinHeight (optional) The minimum height to spawn an airborne group into the zone.
|
||||
-- @param #number MaxHeight (optional) The maximum height to spawn an airborne group into the zone.
|
||||
-- @param #number SpawnIndex (optional) The index which group to spawn within the given zone.
|
||||
-- @return Wrapper.Group#GROUP that was spawned.
|
||||
-- @return #nil Nothing was spawned.
|
||||
-- @return Wrapper.Group#GROUP that was spawned or #nil if nothing was spawned.
|
||||
-- @usage
|
||||
--
|
||||
-- local SpawnStatic = STATIC:FindByName( StaticName )
|
||||
@@ -2650,8 +2652,7 @@ end
|
||||
-- @param #number MinHeight (optional) The minimum height to spawn an airborne group into the zone.
|
||||
-- @param #number MaxHeight (optional) The maximum height to spawn an airborne group into the zone.
|
||||
-- @param #number SpawnIndex (optional) The index which group to spawn within the given zone.
|
||||
-- @return Wrapper.Group#GROUP that was spawned.
|
||||
-- @return #nil when nothing was spawned.
|
||||
-- @return Wrapper.Group#GROUP that was spawned or #nil if nothing was spawned.
|
||||
-- @usage
|
||||
--
|
||||
-- local SpawnZone = ZONE:New( ZoneName )
|
||||
@@ -2846,18 +2847,37 @@ end
|
||||
-- The method will search for a #-mark, and will return the text before the #-mark.
|
||||
-- It will return nil of no prefix was found.
|
||||
-- @param #SPAWN self
|
||||
-- @param DCS#UNIT DCSUnit The @{DCSUnit} to be searched.
|
||||
-- @return #string The prefix
|
||||
-- @return #nil Nothing found
|
||||
-- @param Wrapper.Group#GROUP SpawnGroup The GROUP object.
|
||||
-- @return #string The prefix or #nil if nothing was found.
|
||||
function SPAWN:_GetPrefixFromGroup( SpawnGroup )
|
||||
self:F3( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnGroup } )
|
||||
|
||||
local GroupName = SpawnGroup:GetName()
|
||||
|
||||
if GroupName then
|
||||
local SpawnPrefix = string.match( GroupName, ".*#" )
|
||||
|
||||
local SpawnPrefix=self:_GetPrefixFromGroupName(GroupName)
|
||||
|
||||
return SpawnPrefix
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
--- Return the prefix of a spawned group.
|
||||
-- The method will search for a `#`-mark, and will return the text before the `#`-mark. It will return nil of no prefix was found.
|
||||
-- @param #SPAWN self
|
||||
-- @param #string SpawnGroupName The name of the spawned group.
|
||||
-- @return #string The prefix or #nil if nothing was found.
|
||||
function SPAWN:_GetPrefixFromGroupName(SpawnGroupName)
|
||||
|
||||
if SpawnGroupName then
|
||||
|
||||
local SpawnPrefix=string.match(SpawnGroupName, ".*#")
|
||||
|
||||
if SpawnPrefix then
|
||||
SpawnPrefix = SpawnPrefix:sub( 1, -2 )
|
||||
SpawnPrefix = SpawnPrefix:sub(1, -2)
|
||||
end
|
||||
|
||||
return SpawnPrefix
|
||||
end
|
||||
|
||||
@@ -3271,19 +3291,25 @@ end
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function SPAWN:_OnDeadOrCrash( EventData )
|
||||
self:F( self.SpawnTemplatePrefix )
|
||||
|
||||
local SpawnGroup = EventData.IniGroup
|
||||
|
||||
if SpawnGroup then
|
||||
local EventPrefix = self:_GetPrefixFromGroup( SpawnGroup )
|
||||
if EventPrefix then -- EventPrefix can be nil if no # is found, which means, no spawnable group!
|
||||
local unit=UNIT:FindByName(EventData.IniUnitName)
|
||||
|
||||
if unit then
|
||||
|
||||
local EventPrefix = self:_GetPrefixFromGroupName(unit.GroupName)
|
||||
|
||||
if EventPrefix then -- EventPrefix can be nil if no # is found, which means, no spawnable group!
|
||||
self:T( { "Dead event: " .. EventPrefix } )
|
||||
if EventPrefix == self.SpawnTemplatePrefix or ( self.SpawnAliasPrefix and EventPrefix == self.SpawnAliasPrefix ) then
|
||||
self.AliveUnits = self.AliveUnits - 1
|
||||
self:T( "Alive Units: " .. self.AliveUnits )
|
||||
end
|
||||
|
||||
if EventPrefix == self.SpawnTemplatePrefix or ( self.SpawnAliasPrefix and EventPrefix == self.SpawnAliasPrefix ) then
|
||||
|
||||
self.AliveUnits = self.AliveUnits - 1
|
||||
|
||||
self:T( "Alive Units: " .. self.AliveUnits )
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Will detect AIR Units taking off... When the event takes place, the spawned Group is registered as airborne...
|
||||
@@ -3400,8 +3426,8 @@ function SPAWN:_SpawnCleanUpScheduler()
|
||||
if Stamp.Vec2 then
|
||||
if SpawnUnit:InAir() == false and SpawnUnit:GetVelocityKMH() < 1 then
|
||||
local NewVec2 = SpawnUnit:GetVec2()
|
||||
if Stamp.Vec2.x == NewVec2.x and Stamp.Vec2.y == NewVec2.y then
|
||||
-- If the plane is not moving, and is on the ground, assign it with a timestamp...
|
||||
if (Stamp.Vec2.x == NewVec2.x and Stamp.Vec2.y == NewVec2.y) or (SpawnUnit:GetLife() <= 1) then
|
||||
-- If the plane is not moving or dead , and is on the ground, assign it with a timestamp...
|
||||
if Stamp.Time + self.SpawnCleanUpInterval < timer.getTime() then
|
||||
self:T( { "CleanUp Scheduler:", "ReSpawning:", SpawnGroup:GetName() } )
|
||||
self:ReSpawn( SpawnCursor )
|
||||
@@ -3419,7 +3445,7 @@ function SPAWN:_SpawnCleanUpScheduler()
|
||||
else
|
||||
if SpawnUnit:InAir() == false then
|
||||
Stamp.Vec2 = SpawnUnit:GetVec2()
|
||||
if SpawnUnit:GetVelocityKMH() < 1 then
|
||||
if (SpawnUnit:GetVelocityKMH() < 1) then
|
||||
Stamp.Time = timer.getTime()
|
||||
end
|
||||
else
|
||||
|
||||
@@ -46,14 +46,14 @@
|
||||
-- @field #number InitOffsetAngle Link offset angle in degrees.
|
||||
-- @field #number InitStaticHeading Heading of the static.
|
||||
-- @field #string InitStaticLivery Livery for aircraft.
|
||||
-- @field #string InitStaticShape Shape of teh static.
|
||||
-- @field #string InitStaticShape Shape of the static.
|
||||
-- @field #string InitStaticType Type of the static.
|
||||
-- @field #string InitStaticCategory Categrory of the static.
|
||||
-- @field #string InitStaticName Name of the static.
|
||||
-- @field Core.Point#COORDINATE InitStaticCoordinate Coordinate where to spawn the static.
|
||||
-- @field #boolean InitDead Set static to be dead if true.
|
||||
-- @field #boolean InitCargo If true, static can act as cargo.
|
||||
-- @field #number InitCargoMass Mass of cargo in kg.
|
||||
-- @field #boolean InitStaticDead Set static to be dead if true.
|
||||
-- @field #boolean InitStaticCargo If true, static can act as cargo.
|
||||
-- @field #number InitStaticCargoMass Mass of cargo in kg.
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
|
||||
@@ -138,24 +138,24 @@ SPAWNSTATIC = {
|
||||
-- @return #SPAWNSTATIC self
|
||||
function SPAWNSTATIC:NewFromStatic(SpawnTemplateName, SpawnCountryID)
|
||||
|
||||
local self = BASE:Inherit( self, BASE:New() ) -- #SPAWNSTATIC
|
||||
local self = BASE:Inherit( self, BASE:New() ) -- #SPAWNSTATIC
|
||||
|
||||
local TemplateStatic, CoalitionID, CategoryID, CountryID = _DATABASE:GetStaticGroupTemplate(SpawnTemplateName)
|
||||
|
||||
if TemplateStatic then
|
||||
self.SpawnTemplatePrefix = SpawnTemplateName
|
||||
self.TemplateStaticUnit = UTILS.DeepCopy(TemplateStatic.units[1])
|
||||
self.CountryID = SpawnCountryID or CountryID
|
||||
self.CategoryID = CategoryID
|
||||
self.CoalitionID = CoalitionID
|
||||
self.SpawnIndex = 0
|
||||
else
|
||||
error( "SPAWNSTATIC:New: There is no static declared in the mission editor with SpawnTemplatePrefix = '" .. tostring(SpawnTemplateName) .. "'" )
|
||||
end
|
||||
local TemplateStatic, CoalitionID, CategoryID, CountryID = _DATABASE:GetStaticGroupTemplate(SpawnTemplateName)
|
||||
|
||||
if TemplateStatic then
|
||||
self.SpawnTemplatePrefix = SpawnTemplateName
|
||||
self.TemplateStaticUnit = UTILS.DeepCopy(TemplateStatic.units[1])
|
||||
self.CountryID = SpawnCountryID or CountryID
|
||||
self.CategoryID = CategoryID
|
||||
self.CoalitionID = CoalitionID
|
||||
self.SpawnIndex = 0
|
||||
else
|
||||
error( "SPAWNSTATIC:New: There is no static declared in the mission editor with SpawnTemplatePrefix = '" .. tostring(SpawnTemplateName) .. "'" )
|
||||
end
|
||||
|
||||
self:SetEventPriority( 5 )
|
||||
|
||||
return self
|
||||
return self
|
||||
end
|
||||
|
||||
--- Creates the main object to spawn a @{Static} given a template table.
|
||||
@@ -241,12 +241,26 @@ function SPAWNSTATIC:InitShape(StaticShape)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Initialize parameters for spawning FARPs.
|
||||
-- @param #SPAWNSTATIC self
|
||||
-- @param #number CallsignID Callsign ID. Default 1 (="London").
|
||||
-- @param #number Frequency Frequency in MHz. Default 127.5 MHz.
|
||||
-- @param #number Modulation Modulation 0=AM, 1=FM.
|
||||
-- @return #SPAWNSTATIC self
|
||||
function SPAWNSTATIC:InitFARP(CallsignID, Frequency, Modulation)
|
||||
self.InitFarp=true
|
||||
self.InitFarpCallsignID=CallsignID or 1
|
||||
self.InitFarpFreq=Frequency or 127.5
|
||||
self.InitFarpModu=Modulation or 0
|
||||
return self
|
||||
end
|
||||
|
||||
--- Initialize cargo mass.
|
||||
-- @param #SPAWNSTATIC self
|
||||
-- @param #number Mass Mass of the cargo in kg.
|
||||
-- @return #SPAWNSTATIC self
|
||||
function SPAWNSTATIC:InitCargoMass(Mass)
|
||||
self.InitCargoMass=Mass
|
||||
self.InitStaticCargoMass=Mass
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -255,7 +269,16 @@ end
|
||||
-- @param #boolean IsCargo If true, this static can act as cargo.
|
||||
-- @return #SPAWNSTATIC self
|
||||
function SPAWNSTATIC:InitCargo(IsCargo)
|
||||
self.InitCargo=IsCargo
|
||||
self.InitStaticCargo=IsCargo
|
||||
return self
|
||||
end
|
||||
|
||||
--- Initialize as dead.
|
||||
-- @param #SPAWNSTATIC self
|
||||
-- @param #boolean IsCargo If true, this static is dead.
|
||||
-- @return #SPAWNSTATIC self
|
||||
function SPAWNSTATIC:InitDead(IsDead)
|
||||
self.InitStaticDead=IsDead
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -403,12 +426,16 @@ function SPAWNSTATIC:_SpawnStatic(Template, CountryID)
|
||||
Template.livery_id=self.InitStaticLivery
|
||||
end
|
||||
|
||||
if self.InitDead~=nil then
|
||||
Template.dead=self.InitDead
|
||||
if self.InitStaticDead~=nil then
|
||||
Template.dead=self.InitStaticDead
|
||||
end
|
||||
|
||||
if self.InitCargo~=nil then
|
||||
Template.isCargo=self.InitCargo
|
||||
if self.InitStaticCargo~=nil then
|
||||
Template.canCargo=self.InitStaticCargo
|
||||
end
|
||||
|
||||
if self.InitStaticCargoMass~=nil then
|
||||
Template.mass=self.InitStaticCargoMass
|
||||
end
|
||||
|
||||
if self.InitLinkUnit then
|
||||
@@ -420,22 +447,51 @@ function SPAWNSTATIC:_SpawnStatic(Template, CountryID)
|
||||
Template.offsets.angle=self.InitOffsetAngle and math.rad(self.InitOffsetAngle) or 0
|
||||
end
|
||||
|
||||
if self.InitFarp then
|
||||
Template.heliport_callsign_id = self.InitFarpCallsignID
|
||||
Template.heliport_frequency = self.InitFarpFreq
|
||||
Template.heliport_modulation = self.InitFarpModu
|
||||
Template.unitId=nil
|
||||
end
|
||||
|
||||
-- Increase spawn index counter.
|
||||
self.SpawnIndex = self.SpawnIndex + 1
|
||||
|
||||
-- Name of the spawned static.
|
||||
Template.name = self.InitStaticName or string.format("%s#%05d", self.SpawnTemplatePrefix, self.SpawnIndex)
|
||||
|
||||
-- Register the new static.
|
||||
--_DATABASE:_RegisterStaticTemplate(Template, self.CoalitionID, self.CategoryID, CountryID)
|
||||
_DATABASE:AddStatic(Template.name)
|
||||
-- Add and register the new static.
|
||||
local mystatic=_DATABASE:AddStatic(Template.name)
|
||||
|
||||
-- Debug output.
|
||||
self:T(Template)
|
||||
|
||||
-- Add static to the game.
|
||||
local Static=coalition.addStaticObject(CountryID, Template)
|
||||
|
||||
local Static=nil
|
||||
|
||||
if self.InitFarp then
|
||||
|
||||
return _DATABASE:FindStatic(Static:getName())
|
||||
local TemplateGroup={}
|
||||
TemplateGroup.units={}
|
||||
TemplateGroup.units[1]=Template
|
||||
|
||||
TemplateGroup.visible=true
|
||||
TemplateGroup.hidden=false
|
||||
TemplateGroup.x=Template.x
|
||||
TemplateGroup.y=Template.y
|
||||
TemplateGroup.name=Template.name
|
||||
|
||||
self:T("Spawning FARP")
|
||||
self:T({Template=Template})
|
||||
self:T({TemplateGroup=TemplateGroup})
|
||||
|
||||
-- ED's dirty way to spawn FARPS.
|
||||
Static=coalition.addGroup(CountryID, -1, TemplateGroup)
|
||||
else
|
||||
self:T("Spawning Static")
|
||||
self:T2({Template=Template})
|
||||
Static=coalition.addStaticObject(CountryID, Template)
|
||||
end
|
||||
|
||||
return mystatic
|
||||
end
|
||||
|
||||
@@ -0,0 +1,202 @@
|
||||
--- **Core** - TEXTANDSOUND (MOOSE gettext) system
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ## Main Features:
|
||||
--
|
||||
-- * A GetText for Moose
|
||||
-- * Build a set of localized text entries, alongside their sounds and subtitles
|
||||
-- * Aimed at class developers to offer localizable language support
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ## Example Missions:
|
||||
--
|
||||
-- Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/).
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **applevangelist**
|
||||
-- ## Date: April 2022
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module Core.TextAndSound
|
||||
-- @image MOOSE.JPG
|
||||
|
||||
--- Text and Sound class.
|
||||
-- @type TEXTANDSOUND
|
||||
-- @field #string ClassName Name of this class.
|
||||
-- @field #string version Versioning.
|
||||
-- @field #string lid LID for log entries.
|
||||
-- @field #string locale Default locale of this object.
|
||||
-- @field #table entries Table of entries.
|
||||
-- @field #string textclass Name of the class the texts belong to.
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
---
|
||||
--
|
||||
-- @field #TEXTANDSOUND
|
||||
TEXTANDSOUND = {
|
||||
ClassName = "TEXTANDSOUND",
|
||||
version = "0.0.1",
|
||||
lid = "",
|
||||
locale = "en",
|
||||
entries = {},
|
||||
textclass = "",
|
||||
}
|
||||
|
||||
--- Text and Sound entry.
|
||||
-- @type TEXTANDSOUND.Entry
|
||||
-- @field #string Classname Name of the class this entry is for.
|
||||
-- @field #string Locale Locale of this entry, defaults to "en".
|
||||
-- @field #table Data The list of entries.
|
||||
|
||||
--- Text and Sound data
|
||||
-- @type TEXTANDSOUND.Data
|
||||
-- @field #string ID ID of this entry for retrieval.
|
||||
-- @field #string Text Text of this entry.
|
||||
-- @field #string Soundfile (optional) Soundfile File name of the corresponding sound file.
|
||||
-- @field #number Soundlength (optional) Length of the sound file in seconds.
|
||||
-- @field #string Subtitle (optional) Subtitle for the sound file.
|
||||
|
||||
--- Instantiate a new object
|
||||
-- @param #TEXTANDSOUND self
|
||||
-- @param #string ClassName Name of the class this instance is providing texts for.
|
||||
-- @param #string Defaultlocale (Optional) Default locale of this instance, defaults to "en".
|
||||
-- @return #TEXTANDSOUND self
|
||||
function TEXTANDSOUND:New(ClassName,Defaultlocale)
|
||||
-- Inherit everything from BASE class.
|
||||
local self=BASE:Inherit(self, BASE:New())
|
||||
-- Set some string id for output to DCS.log file.
|
||||
self.lid=string.format("%s (%s) | ", self.ClassName, self.version)
|
||||
self.locale = Defaultlocale or (_SETTINGS:GetLocale() or "en")
|
||||
self.textclass = ClassName or "none"
|
||||
self.entries = {}
|
||||
local initentry = {} -- #TEXTANDSOUND.Entry
|
||||
initentry.Classname = ClassName
|
||||
initentry.Data = {}
|
||||
initentry.Locale = self.locale
|
||||
self.entries[self.locale] = initentry
|
||||
self:I(self.lid .. "Instantiated.")
|
||||
self:T({self.entries[self.locale]})
|
||||
return self
|
||||
end
|
||||
|
||||
--- Add an entry
|
||||
-- @param #TEXTANDSOUND self
|
||||
-- @param #string Locale Locale to set for this entry, e.g. "de".
|
||||
-- @param #string ID Unique(!) ID of this entry under this locale (i.e. use the same ID to get localized text for the entry in another language).
|
||||
-- @param #string Text Text for this entry.
|
||||
-- @param #string Soundfile (Optional) Sound file name for this entry.
|
||||
-- @param #number Soundlength (Optional) Length of the sound file in seconds.
|
||||
-- @param #string Subtitle (Optional) Subtitle to be used alongside the sound file.
|
||||
-- @return #TEXTANDSOUND self
|
||||
function TEXTANDSOUND:AddEntry(Locale,ID,Text,Soundfile,Soundlength,Subtitle)
|
||||
self:T(self.lid .. "AddEntry")
|
||||
local locale = Locale or self.locale
|
||||
local dataentry = {} -- #TEXTANDSOUND.Data
|
||||
dataentry.ID = ID or "1"
|
||||
dataentry.Text = Text or "none"
|
||||
dataentry.Soundfile = Soundfile
|
||||
dataentry.Soundlength = Soundlength or 0
|
||||
dataentry.Subtitle = Subtitle
|
||||
if not self.entries[locale] then
|
||||
local initentry = {} -- #TEXTANDSOUND.Entry
|
||||
initentry.Classname = self.textclass -- class name entry
|
||||
initentry.Data = {} -- data array
|
||||
initentry.Locale = locale -- default locale
|
||||
self.entries[locale] = initentry
|
||||
end
|
||||
self.entries[locale].Data[ID] = dataentry
|
||||
self:T({self.entries[locale].Data})
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get an entry
|
||||
-- @param #TEXTANDSOUND self
|
||||
-- @param #string ID The unique ID of the data to be retrieved.
|
||||
-- @param #string Locale (Optional) The locale of the text to be retrieved - defauls to default locale set with `New()`.
|
||||
-- @return #string Text Text or nil if not found and no fallback.
|
||||
-- @return #string Soundfile Filename or nil if not found and no fallback.
|
||||
-- @return #string Soundlength Length of the sound or 0 if not found and no fallback.
|
||||
-- @return #string Subtitle Text for subtitle or nil if not found and no fallback.
|
||||
function TEXTANDSOUND:GetEntry(ID,Locale)
|
||||
self:T(self.lid .. "GetEntry")
|
||||
local locale = Locale or self.locale
|
||||
if not self.entries[locale] then
|
||||
-- fall back to default "en"
|
||||
locale = self.locale
|
||||
end
|
||||
local Text,Soundfile,Soundlength,Subtitle = nil, nil, 0, nil
|
||||
if self.entries[locale] then
|
||||
if self.entries[locale].Data then
|
||||
local data = self.entries[locale].Data[ID] -- #TEXTANDSOUND.Data
|
||||
if data then
|
||||
Text = data.Text
|
||||
Soundfile = data.Soundfile
|
||||
Soundlength = data.Soundlength
|
||||
Subtitle = data.Subtitle
|
||||
elseif self.entries[self.locale].Data[ID] then
|
||||
-- no matching entry, try default
|
||||
local data = self.entries[self.locale].Data[ID]
|
||||
Text = data.Text
|
||||
Soundfile = data.Soundfile
|
||||
Soundlength = data.Soundlength
|
||||
Subtitle = data.Subtitle
|
||||
end
|
||||
end
|
||||
else
|
||||
return nil, nil, 0, nil
|
||||
end
|
||||
return Text,Soundfile,Soundlength,Subtitle
|
||||
end
|
||||
|
||||
--- Get the default locale of this object
|
||||
-- @param #TEXTANDSOUND self
|
||||
-- @return #string locale
|
||||
function TEXTANDSOUND:GetDefaultLocale()
|
||||
self:T(self.lid .. "GetDefaultLocale")
|
||||
return self.locale
|
||||
end
|
||||
|
||||
--- Set default locale of this object
|
||||
-- @param #TEXTANDSOUND self
|
||||
-- @param #string locale
|
||||
-- @return #TEXTANDSOUND self
|
||||
function TEXTANDSOUND:SetDefaultLocale(locale)
|
||||
self:T(self.lid .. "SetDefaultLocale")
|
||||
self.locale = locale or "en"
|
||||
return self
|
||||
end
|
||||
|
||||
--- Check if a locale exists
|
||||
-- @param #TEXTANDSOUND self
|
||||
-- @return #boolean outcome
|
||||
function TEXTANDSOUND:HasLocale(Locale)
|
||||
self:T(self.lid .. "HasLocale")
|
||||
return self.entries[Locale] and true or false
|
||||
end
|
||||
|
||||
--- Flush all entries to the log
|
||||
-- @param #TEXTANDSOUND self
|
||||
-- @return #TEXTANDSOUND self
|
||||
function TEXTANDSOUND:FlushToLog()
|
||||
self:I(self.lid .. "Flushing entries:")
|
||||
local text = string.format("Textclass: %s | Default Locale: %s",self.textclass, self.locale)
|
||||
for _,_entry in pairs(self.entries) do
|
||||
local entry = _entry -- #TEXTANDSOUND.Entry
|
||||
local text = string.format("Textclassname: %s | Locale: %s",entry.Classname, entry.Locale)
|
||||
self:I(text)
|
||||
for _ID,_data in pairs(entry.Data) do
|
||||
local data = _data -- #TEXTANDSOUND.Data
|
||||
local text = string.format("ID: %s\nText: %s\nSoundfile: %s With length: %d\nSubtitle: %s",tostring(_ID), data.Text or "none",data.Soundfile or "none",data.Soundlength or 0,data.Subtitle or "none")
|
||||
self:I(text)
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
----------------------------------------------------------------
|
||||
-- End TextAndSound
|
||||
----------------------------------------------------------------
|
||||
@@ -11,7 +11,7 @@
|
||||
--
|
||||
-- ### Author: **funkyfranky**
|
||||
-- @module Core.Timer
|
||||
-- @image CORE_Timer.png
|
||||
-- @image Core_Scheduler.JPG
|
||||
|
||||
|
||||
--- TIMER class.
|
||||
@@ -19,6 +19,7 @@
|
||||
-- @field #string ClassName Name of the class.
|
||||
-- @field #string lid Class id string for output to DCS log file.
|
||||
-- @field #number tid Timer ID returned by the DCS API function.
|
||||
-- @field #number uid Unique ID of the timer.
|
||||
-- @field #function func Timer function.
|
||||
-- @field #table para Parameters passed to the timer function.
|
||||
-- @field #number Tstart Relative start time in seconds.
|
||||
@@ -26,17 +27,16 @@
|
||||
-- @field #number dT Time interval between function calls in seconds.
|
||||
-- @field #number ncalls Counter of function calls.
|
||||
-- @field #number ncallsMax Max number of function calls. If reached, timer is stopped.
|
||||
-- @field #boolean isrunning If `true`, timer is running. Else it was not started yet or was stopped.
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
--- *Better three hours too soon than a minute too late.* – William Shakespeare
|
||||
--- *Better three hours too soon than a minute too late.* - William Shakespeare
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- # The TIMER Concept
|
||||
--
|
||||
-- The TIMER class is the little sister of the SCHEDULER class. It does the same thing but is a bit easier to use and has less overhead. It should be sufficient in many cases.
|
||||
-- The TIMER class is the little sister of the @{Core.Scheduler#SCHEDULER} class. It does the same thing but is a bit easier to use and has less overhead. It should be sufficient in many cases.
|
||||
--
|
||||
-- It provides an easy interface to the DCS [timer.scheduleFunction](https://wiki.hoggitworld.com/view/DCS_func_scheduleFunction).
|
||||
--
|
||||
@@ -62,20 +62,20 @@
|
||||
--
|
||||
-- Note that
|
||||
--
|
||||
-- * if *Tstart* is not specified (*nil*), the first function call happens immediately.
|
||||
-- * if *Tstart* is not specified (*nil*), the first function call happens immediately, i.e. after one millisecond.
|
||||
-- * if *dT* is not specified (*nil*), the function is called only once.
|
||||
-- * if *Duration* is not specified (*nil*), the timer runs forever or until stopped manually or until the max function calls are reached (see below).
|
||||
--
|
||||
-- For example,
|
||||
--
|
||||
-- mytimer:Start(3) -- Will call the function once after 3 seconds.
|
||||
-- mytimer:Start(nil, 0.5) -- Will call right now and then every 0.5 sec until all eternaty.
|
||||
-- mytimer:Start(nil, 0.5) -- Will call right now and then every 0.5 sec until all eternity.
|
||||
-- mytimer:Start(nil, 2.0, 20) -- Will call right now and then every 2.0 sec for 20 sec.
|
||||
-- mytimer:Start(1.0, nil, 10) -- Does not make sense as the function is only called once anyway.
|
||||
--
|
||||
-- ## Stopping the Timer
|
||||
--
|
||||
-- The timer can be stopped manually by the @{#TIMER.Start}(*Delay*) function
|
||||
-- The timer can be stopped manually by the @{#TIMER.Stop}(*Delay*) function
|
||||
--
|
||||
-- mytimer:Stop()
|
||||
--
|
||||
@@ -105,19 +105,17 @@ TIMER = {
|
||||
--- Timer ID.
|
||||
_TIMERID=0
|
||||
|
||||
--- Timer data base.
|
||||
--_TIMERDB={}
|
||||
|
||||
--- TIMER class version.
|
||||
-- @field #string version
|
||||
TIMER.version="0.1.0"
|
||||
TIMER.version="0.1.2"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- TODO list
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
-- TODO: A lot.
|
||||
-- TODO: Write docs.
|
||||
-- TODO: Randomization.
|
||||
-- TODO: Pause/unpause.
|
||||
-- DONE: Write docs.
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Constructor
|
||||
@@ -142,6 +140,9 @@ function TIMER:New(Function, ...)
|
||||
-- Number of function calls.
|
||||
self.ncalls=0
|
||||
|
||||
-- Not running yet.
|
||||
self.isrunning=false
|
||||
|
||||
-- Increase counter
|
||||
_TIMERID=_TIMERID+1
|
||||
|
||||
@@ -151,9 +152,6 @@ function TIMER:New(Function, ...)
|
||||
-- Log id.
|
||||
self.lid=string.format("TIMER UID=%d | ", self.uid)
|
||||
|
||||
-- Add to DB.
|
||||
--_TIMERDB[self.uid]=self
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -185,6 +183,9 @@ function TIMER:Start(Tstart, dT, Duration)
|
||||
-- Set log id.
|
||||
self.lid=string.format("TIMER UID=%d/%d | ", self.uid, self.tid)
|
||||
|
||||
-- Is now running.
|
||||
self.isrunning=true
|
||||
|
||||
-- Debug info.
|
||||
self:T(self.lid..string.format("Starting Timer in %.3f sec, dT=%s, Tstop=%s", self.Tstart-Tnow, tostring(self.dT), tostring(self.Tstop)))
|
||||
|
||||
@@ -209,9 +210,9 @@ function TIMER:Stop(Delay)
|
||||
self:T(self.lid..string.format("Stopping timer by removing timer function after %d calls!", self.ncalls))
|
||||
timer.removeFunction(self.tid)
|
||||
|
||||
-- Remove DB entry.
|
||||
--_TIMERDB[self.uid]=nil
|
||||
|
||||
-- Not running any more.
|
||||
self.isrunning=false
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
@@ -228,6 +229,22 @@ function TIMER:SetMaxFunctionCalls(Nmax)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set time interval. Can also be set when the timer is already running and is applied after the next function call.
|
||||
-- @param #TIMER self
|
||||
-- @param #number dT Time interval in seconds.
|
||||
-- @return #TIMER self
|
||||
function TIMER:SetTimeInterval(dT)
|
||||
self.dT=dT
|
||||
return self
|
||||
end
|
||||
|
||||
--- Check if the timer has been started and was not stopped.
|
||||
-- @param #TIMER self
|
||||
-- @return #boolean If `true`, the timer is running.
|
||||
function TIMER:IsRunning()
|
||||
return self.isrunning
|
||||
end
|
||||
|
||||
--- Call timer function.
|
||||
-- @param #TIMER self
|
||||
-- @param #number time DCS model time in seconds.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -295,6 +295,17 @@ do -- country
|
||||
-- @field QATAR
|
||||
-- @field OMAN
|
||||
-- @field UNITED_ARAB_EMIRATES
|
||||
-- @field SOUTH_AFRICA
|
||||
-- @field CUBA
|
||||
-- @field PORTUGAL
|
||||
-- @field GDR
|
||||
-- @field LEBANON
|
||||
-- @field CJTF_BLUE
|
||||
-- @field CJTF_RED
|
||||
-- @field UN_PEACEKEEPERS
|
||||
-- @field Argentinia
|
||||
-- @field Cyprus
|
||||
-- @field Slovenia
|
||||
|
||||
country = {} --#country
|
||||
|
||||
@@ -460,6 +471,22 @@ do -- Types
|
||||
--@field #boolean lateActivated
|
||||
--@field #boolean uncontrolled
|
||||
|
||||
--- DCS template data structure.
|
||||
-- @type Template
|
||||
-- @field #boolean uncontrolled Aircraft is uncontrolled.
|
||||
-- @field #boolean lateActivation Group is late activated.
|
||||
-- @field #number x 2D Position on x-axis in meters.
|
||||
-- @field #number y 2D Position on y-axis in meters.
|
||||
-- @field #table units Unit list.
|
||||
--
|
||||
|
||||
--- Unit data structure.
|
||||
--@type Template.Unit
|
||||
--@field #string name Name of the unit.
|
||||
--@field #number x
|
||||
--@field #number y
|
||||
--@field #number alt
|
||||
|
||||
end --
|
||||
|
||||
|
||||
@@ -476,8 +503,9 @@ do -- Object
|
||||
-- @field UNIT
|
||||
-- @field WEAPON
|
||||
-- @field STATIC
|
||||
-- @field SCENERY
|
||||
-- @field BASE
|
||||
-- @field SCENERY
|
||||
-- @field CARGO
|
||||
|
||||
--- @type Object.Desc
|
||||
-- @extends #Desc
|
||||
@@ -487,6 +515,10 @@ do -- Object
|
||||
--- @function [parent=#Object] isExist
|
||||
-- @param #Object self
|
||||
-- @return #boolean
|
||||
|
||||
--- @function [parent=#Object] isActive
|
||||
-- @param #Object self
|
||||
-- @return #boolean
|
||||
|
||||
--- @function [parent=#Object] destroy
|
||||
-- @param #Object self
|
||||
@@ -736,7 +768,66 @@ do -- Airbase
|
||||
|
||||
end -- Airbase
|
||||
|
||||
do -- Spot
|
||||
|
||||
--- [DCS Class Spot](https://wiki.hoggitworld.com/view/DCS_Class_Spot)
|
||||
-- Represents a spot from laser or IR-pointer.
|
||||
-- @type Spot
|
||||
-- @field #Spot.Category Category enum that stores spot categories.
|
||||
|
||||
--- Enum that stores spot categories.
|
||||
-- @type Spot.Category
|
||||
-- @field #string INFRA_RED
|
||||
-- @field #string LASER
|
||||
|
||||
|
||||
--- Creates a laser ray emanating from the given object to a point in 3d space.
|
||||
-- @function [parent=#Spot] createLaser
|
||||
-- @param DCS#Object Source The source object of the laser.
|
||||
-- @param DCS#Vec3 LocalRef An optional 3D offset for the source.
|
||||
-- @param DCS#Vec3 Vec3 Target coordinate where the ray is pointing at.
|
||||
-- @param #number LaserCode Any 4 digit number between 1111 and 1788.
|
||||
-- @return #Spot
|
||||
|
||||
--- Creates an infrared ray emanating from the given object to a point in 3d space. Can be seen with night vision goggles.
|
||||
-- @function [parent=#Spot] createInfraRed
|
||||
-- @param DCS#Object Source Source position of the IR ray.
|
||||
-- @param DCS#Vec3 LocalRef An optional 3D offset for the source.
|
||||
-- @param DCS#Vec3 Vec3 Target coordinate where the ray is pointing at.
|
||||
-- @return #Spot
|
||||
|
||||
--- Returns a vec3 table of the x, y, and z coordinates for the position of the given object in 3D space. Coordinates are dependent on the position of the maps origin.
|
||||
-- @function [parent=#Spot] getPoint
|
||||
-- @param #Spot self
|
||||
-- @return DCS#Vec3 Point in 3D, where the beam is pointing at.
|
||||
|
||||
--- Sets the destination point from which the source of the spot is drawn toward.
|
||||
-- @function [parent=#Spot] setPoint
|
||||
-- @param #Spot self
|
||||
-- @param DCS#Vec3 Vec3 Point in 3D, where the beam is pointing at.
|
||||
|
||||
--- Returns the number that is used to define the laser code for which laser designation can track.
|
||||
-- @function [parent=#Spot] getCode
|
||||
-- @param #Spot self
|
||||
-- @return #number Code The laser code used.
|
||||
|
||||
--- Sets the number that is used to define the laser code for which laser designation can track.
|
||||
-- @function [parent=#Spot] setCode
|
||||
-- @param #Spot self
|
||||
-- @param #number Code The laser code. Default value is 1688.
|
||||
|
||||
--- Destroys the spot.
|
||||
-- @function [parent=#Spot] destroy
|
||||
-- @param #Spot self
|
||||
|
||||
--- Gets the category of the spot (laser or IR).
|
||||
-- @function [parent=#Spot] getCategory
|
||||
-- @param #Spot self
|
||||
-- @return #string Category.
|
||||
|
||||
Spot = {} --#Spot
|
||||
|
||||
end -- Spot
|
||||
|
||||
do -- Controller
|
||||
--- Controller is an object that performs A.I.-routines. Other words controller is an instance of A.I.. Controller stores current main task, active enroute tasks and behavior options. Controller performs commands. Please, read DCS A-10C GUI Manual EN.pdf chapter "Task Planning for Unit Groups", page 91 to understand A.I. system of DCS:A-10C.
|
||||
@@ -1060,6 +1151,11 @@ do -- Unit
|
||||
-- @function [parent=#Unit] getAmmo
|
||||
-- @param #Unit self
|
||||
-- @return #Unit.Ammo
|
||||
|
||||
--- Returns the number of infantry that can be embark onto the aircraft. Only returns a value if run on airplanes or helicopters. Returns nil if run on ground or ship units.
|
||||
-- @function [parent=#Unit] getDescentCapacity
|
||||
-- @param #Unit self
|
||||
-- @return #number Number of soldiers that embark.
|
||||
|
||||
--- Returns the unit sensors.
|
||||
-- @function [parent=#Unit] getSensors
|
||||
@@ -1093,6 +1189,10 @@ do -- Unit
|
||||
-- @param #Unit self
|
||||
-- @return #Unit.Desc
|
||||
|
||||
--- GROUND - Switch on/off radar emissions
|
||||
-- @function [parent=#Unit] enableEmission
|
||||
-- @param #Unit self
|
||||
-- @param #boolean switch
|
||||
|
||||
Unit = {} --#Unit
|
||||
|
||||
@@ -1163,7 +1263,7 @@ do -- Group
|
||||
-- @param #Group self
|
||||
-- @return #number
|
||||
|
||||
--- Returns initial size of the group. If some of the units will be destroyed, initial size of the group will not be changed. Initial size limits the unitNumber parameter for Group.getUnit() function.
|
||||
--- Returns initial size of the group. If some of the units will be destroyed, initial size of the group will not be changed; Initial size limits the unitNumber parameter for Group.getUnit() function.
|
||||
-- @function [parent=#Group] getInitialSize
|
||||
-- @param #Group self
|
||||
-- @return #number
|
||||
@@ -1178,6 +1278,11 @@ do -- Group
|
||||
-- @param #Group self
|
||||
-- @return #Controller
|
||||
|
||||
--- GROUND - Switch on/off radar emissions
|
||||
-- @function [parent=#Group] enableEmission
|
||||
-- @param #Group self
|
||||
-- @param #boolean switch
|
||||
|
||||
Group = {} --#Group
|
||||
|
||||
end -- Group
|
||||
@@ -1393,4 +1498,4 @@ do -- AI
|
||||
|
||||
AI = {} --#AI
|
||||
|
||||
end -- AI
|
||||
end -- AI
|
||||
|
||||
@@ -0,0 +1,980 @@
|
||||
--- **Functional** - AI CSAR system
|
||||
--
|
||||
-- ## Main Features:
|
||||
--
|
||||
-- * Send out helicopters to downed pilots
|
||||
-- * Rescues players and AI alike
|
||||
-- * Coalition specific
|
||||
-- * Starting from a FARP or Airbase
|
||||
-- * Dedicated MASH zone
|
||||
-- * Some FSM functions to include in your mission scripts
|
||||
-- * Limit number of available helos
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ## Example Missions:
|
||||
--
|
||||
-- Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/CSR-001%20-%20Basics).
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **applevangelist**
|
||||
-- Last Update April 2022
|
||||
--
|
||||
-- ===
|
||||
-- @module Functional.AICSAR
|
||||
-- @image MOOSE.JPG
|
||||
|
||||
|
||||
|
||||
--- AI CSAR class.
|
||||
-- @type AICSAR
|
||||
-- @field #string ClassName Name of this class.
|
||||
-- @field #string version Versioning.
|
||||
-- @field #string lid LID for log entries.
|
||||
-- @field #number coalition Colition side.
|
||||
-- @field #string template Template for pilot.
|
||||
-- @field #string helotemplate Template for CSAR helo.
|
||||
-- @field #string alias Alias Name.
|
||||
-- @field Wrapper.Airbase#AIRBASE farp FARP object from where to start.
|
||||
-- @field Core.Zone#ZONE farpzone MASH zone to drop rescued pilots.
|
||||
-- @field #number maxdistance Max distance to go for a rescue.
|
||||
-- @field #table pilotqueue Queue of pilots to rescue.
|
||||
-- @field #number pilotindex Table index to bind pilot to helo.
|
||||
-- @field #table helos Table of Ops.FlightGroup#FLIGHTGROUP objects
|
||||
-- @field #boolean verbose Switch more output.
|
||||
-- @field #number rescuezoneradius Radius around downed pilot for the helo to land in.
|
||||
-- @field #table rescued Track number of rescued pilot.
|
||||
-- @field #boolean autoonoff Only send a helo when no human heli pilots are available.
|
||||
-- @field Core.Set#SET_CLIENT playerset Track if alive heli pilots are available.
|
||||
-- @field #boolean limithelos limit available number of helos going on mission (defaults to true)
|
||||
-- @field #number helonumber number of helos available (default: 3)
|
||||
-- @extends Core.Fsm#FSM
|
||||
|
||||
|
||||
--- *I once donated a pint of my finest red corpuscles to the great American Red Cross and the doctor opined my blood was very helpful; contained so much alcohol they could use it to sterilize their instruments.*
|
||||
-- W.C.Fields
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- # AICSAR Concept
|
||||
--
|
||||
-- For an AI or human pilot landing with a parachute, a rescue mission will be spawned. The helicopter will fly to the pilot, pick him or her up,
|
||||
-- and fly back to a designated MASH (medical) zone, drop the pilot and then return to base.
|
||||
-- Operational maxdistance can be set as well as the landing radius around the downed pilot.
|
||||
-- Keep in mind that AI helicopters cannot hover-load at the time of writing, so rescue operations over water or in the mountains might not
|
||||
-- work.
|
||||
-- Optionally, if you have a CSAR operation with human pilots in your mission, you can set AICSAR to ignore missions when human helicopter
|
||||
-- pilots are around.
|
||||
--
|
||||
-- ## Setup
|
||||
--
|
||||
-- Setup is a one-liner:
|
||||
--
|
||||
-- -- @param #string Alias Name of this instance.
|
||||
-- -- @param #number Coalition Coalition as in coalition.side.BLUE, can also be passed as "blue", "red" or "neutral"
|
||||
-- -- @param #string Pilottemplate Pilot template name.
|
||||
-- -- @param #string Helotemplate Helicopter template name.
|
||||
-- -- @param Wrapper.Airbase#AIRBASE FARP FARP object or Airbase from where to start.
|
||||
-- -- @param Core.Zone#ZONE MASHZone Zone where to drop pilots after rescue.
|
||||
-- local my_aicsar=AICSAR:New("Luftrettung",coalition.side.BLUE,"Downed Pilot","Rescue Helo",AIRBASE:FindByName("Test FARP"),ZONE:New("MASH"))
|
||||
--
|
||||
-- ## Options are
|
||||
--
|
||||
-- my_aicsar.maxdistance -- maximum operational distance in meters. Defaults to 50NM or 92.6km
|
||||
-- my_aicsar.rescuezoneradius -- landing zone around downed pilot. Defaults to 200m
|
||||
-- my_aicsar.autoonoff -- stop operations when human helicopter pilots are around. Defaults to true.
|
||||
-- my_aicsar.verbose -- text messages to own coalition about ongoing operations. Defaults to true.
|
||||
-- my_aicsarlimithelos -- limit available number of helos going on mission (defaults to true)
|
||||
-- my_aicsar.helonumber -- number of helos available (default: 3)
|
||||
--
|
||||
-- ## Radio options
|
||||
--
|
||||
-- Radio messages, soundfile names and (for SRS) lengths are defined in three enumerators, so you can customize, localize messages and soundfiles to your liking:
|
||||
--
|
||||
-- Defaults are:
|
||||
--
|
||||
-- AICSAR.Messages = {
|
||||
-- EN = {
|
||||
-- INITIALOK = "Roger, Pilot, we hear you. Stay where you are, a helo is on the way!",
|
||||
-- INITIALNOTOK = "Sorry, Pilot. You're behind maximum operational distance! Good Luck!",
|
||||
-- PILOTDOWN = "Pilot down at ", -- note that this will be appended with the position
|
||||
-- PILOTKIA = "Pilot KIA!",
|
||||
-- HELODOWN = "CSAR Helo Down!",
|
||||
-- PILOTRESCUED = "Pilot rescued!",
|
||||
-- PILOTINHELO = "Pilot picked up!",
|
||||
-- },
|
||||
-- }
|
||||
--
|
||||
-- Correspondingly, sound file names are defined as these defaults:
|
||||
--
|
||||
-- AICSAR.RadioMessages = {
|
||||
-- EN = {
|
||||
-- INITIALOK = "initialok.ogg",
|
||||
-- INITIALNOTOK = "initialnotok.ogg",
|
||||
-- PILOTDOWN = "pilotdown.ogg",
|
||||
-- PILOTKIA = "pilotkia.ogg",
|
||||
-- HELODOWN = "helodown.ogg",
|
||||
-- PILOTRESCUED = "pilotrescued.ogg",
|
||||
-- PILOTINHELO = "pilotinhelo.ogg",
|
||||
-- },
|
||||
-- }
|
||||
--
|
||||
-- and these default transmission lengths in seconds:
|
||||
--
|
||||
-- AICSAR.RadioLength = {
|
||||
-- EN = {
|
||||
-- INITIALOK = 4.1,
|
||||
-- INITIALNOTOK = 4.6,
|
||||
-- PILOTDOWN = 2.6,
|
||||
-- PILOTKIA = 1.1,
|
||||
-- HELODOWN = 2.1,
|
||||
-- PILOTRESCUED = 3.5,
|
||||
-- PILOTINHELO = 2.6,
|
||||
-- },
|
||||
-- }
|
||||
--
|
||||
-- The easiest way to add a soundfile to your mission is to use the "Sound to..." trigger in the mission editor. This will effectively
|
||||
-- save your sound file inside of the .miz mission file.
|
||||
--
|
||||
-- To customize or localize your texts and sounds, you can take e.g. the following approach to add a German language version:
|
||||
--
|
||||
-- -- parameters are: locale, ID, text, soundfilename, duration
|
||||
-- my_aicsar.gettext:AddEntry("de","INITIALOK","Copy, Pilot, wir hören Sie. Bleiben Sie, wo Sie sind, ein Hubschrauber sammelt Sie auf!","okneu.ogg",5.0)
|
||||
-- my_aicsar.locale = "de" -- plays and shows the defined German language texts and sound. Fallback is "en", if something is undefined.
|
||||
--
|
||||
-- Switch on radio transmissions via **either** SRS **or** "normal" DCS radio e.g. like so:
|
||||
--
|
||||
-- my_aicsar:SetSRSRadio(true,"C:\\Program Files\\DCS-SimpleRadio-Standalone",270,radio.modulation.AM,5002)
|
||||
--
|
||||
-- or
|
||||
--
|
||||
-- my_aicsar:SetDCSRadio(true,300,radio.modulation.AM,GROUP:FindByName("FARP-Radio"))
|
||||
--
|
||||
-- See the function documentation for parameter details.
|
||||
--
|
||||
-- ===
|
||||
---
|
||||
--
|
||||
-- @field #AICSAR
|
||||
AICSAR = {
|
||||
ClassName = "AICSAR",
|
||||
version = "0.0.8",
|
||||
lid = "",
|
||||
coalition = coalition.side.BLUE,
|
||||
template = "",
|
||||
helotemplate = "",
|
||||
alias = "",
|
||||
farp = nil,
|
||||
farpzone = nil,
|
||||
maxdistance = UTILS.NMToMeters(50),
|
||||
pilotqueue = {},
|
||||
pilotindex = 0,
|
||||
helos = {},
|
||||
verbose = true,
|
||||
rescuezoneradius = 200,
|
||||
rescued = {},
|
||||
autoonoff = true,
|
||||
playerset = nil,
|
||||
Messages = {},
|
||||
SRS = nil,
|
||||
SRSRadio = false,
|
||||
SRSFrequency = 243,
|
||||
SRSPath = "\\",
|
||||
SRSModulation = radio.modulation.AM,
|
||||
SRSSoundPath = nil, -- defaults to "l10n/DEFAULT/", i.e. add messages via "Sount to..." in the ME
|
||||
SRSPort = 5002,
|
||||
DCSRadio = false,
|
||||
DCSFrequency = 243,
|
||||
DCSModulation = radio.modulation.AM,
|
||||
DCSRadioGroup = nil,
|
||||
limithelos = true,
|
||||
helonumber = 3,
|
||||
gettext = nil,
|
||||
locale ="en", -- default text language
|
||||
}
|
||||
|
||||
-- TODO Messages
|
||||
--- Messages enum
|
||||
-- @field Messages
|
||||
AICSAR.Messages = {
|
||||
EN = {
|
||||
INITIALOK = "Roger, Pilot, we hear you. Stay where you are, a helo is on the way!",
|
||||
INITIALNOTOK = "Sorry, Pilot. You're behind maximum operational distance! Good Luck!",
|
||||
PILOTDOWN = "Pilot down at ",
|
||||
PILOTKIA = "Pilot KIA!",
|
||||
HELODOWN = "CSAR Helo Down!",
|
||||
PILOTRESCUED = "Pilot rescued!",
|
||||
PILOTINHELO = "Pilot picked up!",
|
||||
},
|
||||
DE = {
|
||||
INITIALOK = "Copy, Pilot, wir hören Sie. Bleiben Sie, wo Sie sind!\nEin Hubschrauber sammelt Sie auf!",
|
||||
INITIALNOTOK = "Verstehe, Pilot. Sie sind zu weit weg von uns.\nViel Glück!",
|
||||
PILOTDOWN = "Pilot abgestürzt: ",
|
||||
PILOTKIA = "Pilot gefallen!",
|
||||
HELODOWN = "CSAR Hubschrauber verloren!",
|
||||
PILOTRESCUED = "Pilot gerettet!",
|
||||
PILOTINHELO = "Pilot an Bord geholt!",
|
||||
},
|
||||
}
|
||||
|
||||
-- TODO Radio Messages
|
||||
--- Radio Messages enum for ogg files
|
||||
-- @field RadioMessages
|
||||
AICSAR.RadioMessages = {
|
||||
EN = {
|
||||
INITIALOK = "initialok.ogg", -- 4.1 secs
|
||||
INITIALNOTOK = "initialnotok.ogg", -- 4.6 secs
|
||||
PILOTDOWN = "pilotdown.ogg", -- 2.6 secs
|
||||
PILOTKIA = "pilotkia.ogg", -- 1.1 sec
|
||||
HELODOWN = "helodown.ogg", -- 2.1 secs
|
||||
PILOTRESCUED = "pilotrescued.ogg", -- 3.5 secs
|
||||
PILOTINHELO = "pilotinhelo.ogg", -- 2.6 secs
|
||||
},
|
||||
}
|
||||
|
||||
-- TODO Radio Messages
|
||||
--- Radio Messages enum for ogg files length in secs
|
||||
-- @field RadioLength
|
||||
AICSAR.RadioLength = {
|
||||
EN = {
|
||||
INITIALOK = 4.1,
|
||||
INITIALNOTOK = 4.6,
|
||||
PILOTDOWN = 2.6,
|
||||
PILOTKIA = 1.1,
|
||||
HELODOWN = 2.1,
|
||||
PILOTRESCUED = 3.5,
|
||||
PILOTINHELO = 2.6,
|
||||
},
|
||||
}
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Constructor
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Function to create a new AICSAR object
|
||||
-- @param #AICSAR self
|
||||
-- @param #string Alias Name of this instance.
|
||||
-- @param #number Coalition Coalition as in coalition.side.BLUE, can also be passed as "blue", "red" or "neutral"
|
||||
-- @param #string Pilottemplate Pilot template name.
|
||||
-- @param #string Helotemplate Helicopter template name.
|
||||
-- @param Wrapper.Airbase#AIRBASE FARP FARP object or Airbase from where to start.
|
||||
-- @param Core.Zone#ZONE MASHZone Zone where to drop pilots after rescue.
|
||||
-- @return #AICSAR self
|
||||
function AICSAR:New(Alias,Coalition,Pilottemplate,Helotemplate,FARP,MASHZone)
|
||||
-- Inherit everything from FSM class.
|
||||
local self=BASE:Inherit(self, FSM:New())
|
||||
|
||||
--set Coalition
|
||||
if Coalition and type(Coalition)=="string" then
|
||||
if Coalition=="blue" then
|
||||
self.coalition=coalition.side.BLUE
|
||||
self.coalitiontxt = Coalition
|
||||
elseif Coalition=="red" then
|
||||
self.coalition=coalition.side.RED
|
||||
self.coalitiontxt = Coalition
|
||||
elseif Coalition=="neutral" then
|
||||
self.coalition=coalition.side.NEUTRAL
|
||||
self.coalitiontxt = Coalition
|
||||
else
|
||||
self:E("ERROR: Unknown coalition in AICSAR!")
|
||||
end
|
||||
else
|
||||
self.coalition = Coalition
|
||||
self.coalitiontxt = string.lower(UTILS.GetCoalitionName(self.coalition))
|
||||
end
|
||||
|
||||
-- Set alias.
|
||||
if Alias then
|
||||
self.alias=tostring(Alias)
|
||||
else
|
||||
self.alias="Red Cross"
|
||||
if self.coalition then
|
||||
if self.coalition==coalition.side.RED then
|
||||
self.alias="IFRC"
|
||||
elseif self.coalition==coalition.side.BLUE then
|
||||
self.alias="CSAR"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
self.template = Pilottemplate
|
||||
self.helotemplate = Helotemplate
|
||||
self.farp = FARP
|
||||
self.farpzone = MASHZone
|
||||
self.playerset = SET_CLIENT:New():FilterActive(true):FilterCategories("helicopter"):FilterStart()
|
||||
|
||||
-- Radio
|
||||
self.SRS = nil
|
||||
self.SRSRadio = false
|
||||
self.SRSFrequency = 243
|
||||
self.SRSPath = "\\"
|
||||
self.SRSModulation = radio.modulation.AM
|
||||
self.SRSSoundPath = nil -- defaults to "l10n/DEFAULT/", i.e. add messages via "Sound to..." in the ME
|
||||
self.SRSPort = 5002
|
||||
|
||||
-- DCS Radio - add messages via "Sound to..." in the ME
|
||||
self.DCSRadio = false
|
||||
self.DCSFrequency = 243
|
||||
self.DCSModulation = radio.modulation.AM
|
||||
self.DCSRadioGroup = nil
|
||||
self.DCSRadioQueue = nil
|
||||
|
||||
self.MGRS_Accuracy = 2
|
||||
|
||||
-- limit number of available helos at the same time
|
||||
self.limithelos = true
|
||||
self.helonumber = 3
|
||||
|
||||
-- localization
|
||||
self:InitLocalization()
|
||||
|
||||
-- Set some string id for output to DCS.log file.
|
||||
self.lid=string.format("%s (%s) | ", self.alias, self.coalition and UTILS.GetCoalitionName(self.coalition) or "unknown")
|
||||
|
||||
-- Start State.
|
||||
self:SetStartState("Stopped")
|
||||
|
||||
-- Add FSM transitions.
|
||||
-- From State --> Event --> To State
|
||||
self:AddTransition("Stopped", "Start", "Running") -- Start FSM.
|
||||
self:AddTransition("*", "Status", "*") -- CSAR status update.
|
||||
self:AddTransition("*", "PilotDown", "*") -- Pilot down
|
||||
self:AddTransition("*", "PilotPickedUp", "*") -- Pilot in helo
|
||||
self:AddTransition("*", "PilotRescued", "*") -- Pilot Rescued
|
||||
self:AddTransition("*", "PilotKIA", "*") -- Pilot dead
|
||||
self:AddTransition("*", "HeloDown", "*") -- Helo dead
|
||||
self:AddTransition("*", "Stop", "Stopped") -- Stop FSM.
|
||||
|
||||
self:HandleEvent(EVENTS.LandingAfterEjection)
|
||||
|
||||
self:__Start(math.random(2,5))
|
||||
|
||||
local text = string.format("%sAICSAR Version %s Starting",self.lid,self.version)
|
||||
|
||||
self:I(text)
|
||||
|
||||
------------------------
|
||||
--- Pseudo Functions ---
|
||||
------------------------
|
||||
|
||||
--- Triggers the FSM event "Status".
|
||||
-- @function [parent=#AICSAR] Status
|
||||
-- @param #AICSAR self
|
||||
|
||||
--- Triggers the FSM event "Status" after a delay.
|
||||
-- @function [parent=#AICSAR] __Status
|
||||
-- @param #AICSAR self
|
||||
-- @param #number delay Delay in seconds.
|
||||
|
||||
--- Triggers the FSM event "Stop".
|
||||
-- @function [parent=#AICSAR] Stop
|
||||
-- @param #AICSAR self
|
||||
|
||||
--- Triggers the FSM event "Stop" after a delay.
|
||||
-- @function [parent=#AICSAR] __Stop
|
||||
-- @param #AICSAR self
|
||||
-- @param #number delay Delay in seconds.
|
||||
|
||||
--- On after "PilotDown" event.
|
||||
-- @function [parent=#AICSAR] OnAfterPilotDown
|
||||
-- @param #AICSAR self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param Core.Point#COORDINATE Coordinate Location of the pilot.
|
||||
-- @param #boolean InReach True if in maxdistance else false.
|
||||
|
||||
--- On after "PilotPickedUp" event.
|
||||
-- @function [parent=#AICSAR] OnAfterPilotPickedUp
|
||||
-- @param #AICSAR self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param Ops.FlightGroup#FLIGHTGROUP Helo
|
||||
-- @param #table CargoTable of Ops.OpsGroup#OPSGROUP Cargo objects
|
||||
-- @param #number Index
|
||||
|
||||
--- On after "PilotRescued" event.
|
||||
-- @function [parent=#AICSAR] OnAfterPilotRescued
|
||||
-- @param #AICSAR self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
|
||||
--- On after "PilotKIA" event.
|
||||
-- @function [parent=#AICSAR] OnAfterPilotKIA
|
||||
-- @param #AICSAR self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
|
||||
--- On after "HeloDown" event.
|
||||
-- @function [parent=#AICSAR] OnAfterHeloDown
|
||||
-- @param #AICSAR self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param Ops.FlightGroup#FLIGHTGROUP Helo
|
||||
-- @param #number Index
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- [Internal] Create the Moose TextAndSoundEntries
|
||||
-- @param #AICSAR self
|
||||
-- @return #AICSAR self
|
||||
function AICSAR:InitLocalization()
|
||||
self:T(self.lid .. "InitLocalization")
|
||||
-- English standard localization
|
||||
self.gettext=TEXTANDSOUND:New(self.ClassName, "en")
|
||||
self.gettext:AddEntry("en","INITIALOK",AICSAR.Messages.EN.INITIALOK,AICSAR.RadioMessages.EN.INITIALOK,AICSAR.RadioLength.INITIALOK)
|
||||
self.gettext:AddEntry("en","INITIALNOTOK",AICSAR.Messages.EN.INITIALNOTOK,AICSAR.RadioMessages.EN.INITIALNOTOK,AICSAR.RadioLength.EN.INITIALNOTOK)
|
||||
self.gettext:AddEntry("en","HELODOWN",AICSAR.Messages.EN.HELODOWN,AICSAR.RadioMessages.EN.HELODOWN,AICSAR.RadioLength.EN.HELODOWN)
|
||||
self.gettext:AddEntry("en","PILOTDOWN",AICSAR.Messages.EN.PILOTDOWN,AICSAR.RadioMessages.EN.PILOTDOWN,AICSAR.RadioLength.EN.PILOTDOWN)
|
||||
self.gettext:AddEntry("en","PILOTINHELO",AICSAR.Messages.EN.PILOTINHELO,AICSAR.RadioMessages.EN.PILOTINHELO,AICSAR.RadioLength.EN.PILOTINHELO)
|
||||
self.gettext:AddEntry("en","PILOTKIA",AICSAR.Messages.EN.PILOTKIA,AICSAR.RadioMessages.EN.PILOTKIA,AICSAR.RadioLength.EN.PILOTKIA)
|
||||
self.gettext:AddEntry("en","PILOTRESCUED",AICSAR.Messages.EN.PILOTRESCUED,AICSAR.RadioMessages.EN.PILOTRESCUED,AICSAR.RadioLength.EN.PILOTRESCUED)
|
||||
-- German localization - we keep the sound files English
|
||||
self.gettext:AddEntry("de","INITIALOK",AICSAR.Messages.DE.INITIALOK,AICSAR.RadioMessages.EN.INITIALOK,AICSAR.RadioLength.INITIALOK)
|
||||
self.gettext:AddEntry("de","INITIALNOTOK",AICSAR.Messages.DE.INITIALNOTOK,AICSAR.RadioMessages.EN.INITIALNOTOK,AICSAR.RadioLength.EN.INITIALNOTOK)
|
||||
self.gettext:AddEntry("de","HELODOWN",AICSAR.Messages.DE.HELODOWN,AICSAR.RadioMessages.EN.HELODOWN,AICSAR.RadioLength.EN.HELODOWN)
|
||||
self.gettext:AddEntry("de","PILOTDOWN",AICSAR.Messages.DE.PILOTDOWN,AICSAR.RadioMessages.EN.PILOTDOWN,AICSAR.RadioLength.EN.PILOTDOWN)
|
||||
self.gettext:AddEntry("de","PILOTINHELO",AICSAR.Messages.DE.PILOTINHELO,AICSAR.RadioMessages.EN.PILOTINHELO,AICSAR.RadioLength.EN.PILOTINHELO)
|
||||
self.gettext:AddEntry("de","PILOTKIA",AICSAR.Messages.DE.PILOTKIA,AICSAR.RadioMessages.EN.PILOTKIA,AICSAR.RadioLength.EN.PILOTKIA)
|
||||
self.gettext:AddEntry("de","PILOTRESCUED",AICSAR.Messages.DE.PILOTRESCUED,AICSAR.RadioMessages.EN.PILOTRESCUED,AICSAR.RadioLength.EN.PILOTRESCUED)
|
||||
self.locale = "en"
|
||||
return self
|
||||
end
|
||||
|
||||
--- [User] Switch sound output on and use SRS
|
||||
-- @param #AICSAR self
|
||||
-- @param #boolean OnOff Switch on (true) or off (false).
|
||||
-- @param #string Path Path to your SRS Server Component, e.g. "E:\\\\Program Files\\\\DCS-SimpleRadio-Standalone"
|
||||
-- @param #number Frequency Defaults to 243 (guard)
|
||||
-- @param #number Modulation Radio modulation. Defaults to radio.modulation.AM
|
||||
-- @param #string SoundPath Where to find the audio files. Defaults to nil, i.e. add messages via "Sound to..." in the Mission Editor.
|
||||
-- @param #number Port Port of the SRS, defaults to 5002.
|
||||
-- @return #AICSAR self
|
||||
function AICSAR:SetSRSRadio(OnOff,Path,Frequency,Modulation,SoundPath,Port)
|
||||
self:T(self.lid .. "SetSRSRadio")
|
||||
self:T(self.lid .. "SetSRSRadio to "..tostring(OnOff))
|
||||
self.SRSRadio = OnOff and true
|
||||
self.SRSFrequency = Frequency or 243
|
||||
self.SRSPath = Path or "c:\\"
|
||||
self.SRSModulation = Modulation or radio.modulation.AM
|
||||
local soundpath = os.getenv('TMP') .. "\\DCS\\Mission\\l10n\\DEFAULT" -- defaults to "l10n/DEFAULT/", i.e. add messages by "Sound to..." in the ME
|
||||
self.SRSSoundPath = SoundPath or soundpath
|
||||
self.SRSPort = Port or 5002
|
||||
if OnOff then
|
||||
self.SRS = MSRS:New(Path,Frequency,Modulation)
|
||||
self.SRS:SetPort(self.SRSPort)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- [User] Switch sound output on and use normale (DCS) radio
|
||||
-- @param #AICSAR self
|
||||
-- @param #boolean OnOff Switch on (true) or off (false).
|
||||
-- @param #number Frequency Defaults to 243 (guard).
|
||||
-- @param #number Modulation Radio modulation. Defaults to radio.modulation.AM.
|
||||
-- @param Wrapper.Group#GROUP Group The group to use as sending station.
|
||||
-- @return #AICSAR self
|
||||
function AICSAR:SetDCSRadio(OnOff,Frequency,Modulation,Group)
|
||||
self:T(self.lid .. "SetDCSRadio")
|
||||
self:T(self.lid .. "SetDCSRadio to "..tostring(OnOff))
|
||||
self.DCSRadio = OnOff and true
|
||||
self.DCSFrequency = Frequency or 243
|
||||
self.DCSModulation = Modulation or radio.modulation.AM
|
||||
self.DCSRadioGroup = Group
|
||||
if self.DCSRadio then
|
||||
self.DCSRadioQueue = RADIOQUEUE:New(Frequency,Modulation,"AI-CSAR")
|
||||
self.DCSRadioQueue:Start(5,5)
|
||||
self.DCSRadioQueue:SetRadioPower(1000)
|
||||
self.DCSRadioQueue:SetSenderCoordinate(Group:GetCoordinate())
|
||||
else
|
||||
if self.DCSRadioQueue then
|
||||
self.DCSRadioQueue:Stop()
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- [Internal] Sound output via non-SRS Radio. Add message files (.ogg) via "Sound to..." in the ME.
|
||||
-- @param #AICSAR self
|
||||
-- @param #string Soundfile Name of the soundfile
|
||||
-- @param #number Duration Duration of the sound
|
||||
-- @param #string Subtitle Text to display
|
||||
-- @return #AICSAR self
|
||||
function AICSAR:DCSRadioBroadcast(Soundfile,Duration,Subtitle)
|
||||
self:T(self.lid .. "DCSRadioBroadcast")
|
||||
local radioqueue = self.DCSRadioQueue -- Sound.RadioQueue#RADIOQUEUE
|
||||
radioqueue:NewTransmission(Soundfile,Duration,nil,2,nil,Subtitle,10)
|
||||
return self
|
||||
end
|
||||
|
||||
--- [Internal] Catch the landing after ejection and spawn a pilot in situ.
|
||||
-- @param #AICSAR self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
-- @return #AICSAR self
|
||||
function AICSAR:OnEventLandingAfterEjection(EventData)
|
||||
self:T(self.lid .. "OnEventLandingAfterEjection ID=" .. EventData.id)
|
||||
|
||||
-- autorescue on off?
|
||||
if self.autoonoff then
|
||||
if self.playerset:CountAlive() > 0 then
|
||||
return self
|
||||
end
|
||||
end
|
||||
|
||||
local _event = EventData -- Core.Event#EVENTDATA
|
||||
-- get position and spawn in a template pilot
|
||||
local _LandingPos = COORDINATE:NewFromVec3(_event.initiator:getPosition().p)
|
||||
local _country = _event.initiator:getCountry()
|
||||
local _coalition = coalition.getCountryCoalition( _country )
|
||||
|
||||
-- DONE: add distance check
|
||||
local distancetofarp = _LandingPos:Get2DDistance(self.farp:GetCoordinate())
|
||||
|
||||
-- Mayday Message
|
||||
local Text,Soundfile,Soundlength,Subtitle = self.gettext:GetEntry("PILOTDOWN",self.locale)
|
||||
local text = ""
|
||||
if _coalition == self.coalition then
|
||||
if self.verbose then
|
||||
local setting = {}
|
||||
setting.MGRS_Accuracy = self.MGRS_Accuracy
|
||||
local location = _LandingPos:ToStringMGRS(setting)
|
||||
text = Text .. location .. "!"
|
||||
MESSAGE:New(text,15,"AICSAR"):ToCoalition(self.coalition)
|
||||
end
|
||||
if self.SRSRadio then
|
||||
local sound = SOUNDFILE:New(Soundfile,self.SRSSoundPath,Soundlength)
|
||||
sound:SetPlayWithSRS(true)
|
||||
self.SRS:PlaySoundFile(sound,2)
|
||||
elseif self.DCSRadio then
|
||||
self:DCSRadioBroadcast(Soundfile,Soundlength,text)
|
||||
end
|
||||
end
|
||||
|
||||
-- further processing
|
||||
if _coalition == self.coalition and distancetofarp <= self.maxdistance then
|
||||
-- in reach
|
||||
self:T(self.lid .. "Spawning new Pilot")
|
||||
self.pilotindex = self.pilotindex + 1
|
||||
local newpilot = SPAWN:NewWithAlias(self.template,string.format("%s-AICSAR-%d",self.template, self.pilotindex))
|
||||
newpilot:InitDelayOff()
|
||||
newpilot:OnSpawnGroup(
|
||||
function (grp)
|
||||
self.pilotqueue[self.pilotindex] = grp
|
||||
end
|
||||
)
|
||||
newpilot:SpawnFromCoordinate(_LandingPos)
|
||||
|
||||
Unit.destroy(_event.initiator) -- shagrat remove static Pilot model
|
||||
self:__PilotDown(2,_LandingPos,true)
|
||||
elseif _coalition == self.coalition and distancetofarp > self.maxdistance then
|
||||
-- out of reach, apologies, too far off
|
||||
self:T(self.lid .. "Pilot out of reach")
|
||||
self:__PilotDown(2,_LandingPos,false)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- [Internal] Get FlightGroup
|
||||
-- @param #AICSAR self
|
||||
-- @return Ops.FlightGroup#FLIGHTGROUP The FlightGroup
|
||||
function AICSAR:_GetFlight()
|
||||
self:T(self.lid .. "_GetFlight")
|
||||
-- Helo Carrier.
|
||||
local newhelo = SPAWN:NewWithAlias(self.helotemplate,self.helotemplate..math.random(1,10000))
|
||||
:InitDelayOff()
|
||||
:InitUnControlled(true)
|
||||
:Spawn()
|
||||
|
||||
local nhelo=FLIGHTGROUP:New(newhelo)
|
||||
nhelo:SetHomebase(self.farp)
|
||||
nhelo:Activate()
|
||||
return nhelo
|
||||
end
|
||||
|
||||
--- [Internal] Create a new rescue mission
|
||||
-- @param #AICSAR self
|
||||
-- @param Wrapper.Group#GROUP Pilot The pilot to be rescued.
|
||||
-- @param #number Index Index number of this pilot
|
||||
-- @return #AICSAR self
|
||||
function AICSAR:_InitMission(Pilot,Index)
|
||||
self:T(self.lid .. "_InitMission")
|
||||
|
||||
local pickupzone = ZONE_GROUP:New(Pilot:GetName(),Pilot,self.rescuezoneradius)
|
||||
--local pilotset = SET_GROUP:New()
|
||||
--pilotset:AddGroup(Pilot)
|
||||
|
||||
-- Cargo transport assignment.
|
||||
local opstransport=OPSTRANSPORT:New(Pilot, pickupzone, self.farpzone)
|
||||
|
||||
local helo = self:_GetFlight()
|
||||
-- inject reservation
|
||||
helo.AICSARReserved = true
|
||||
|
||||
-- Cargo transport assignment to first Huey group.
|
||||
helo:AddOpsTransport(opstransport)
|
||||
|
||||
-- callback functions
|
||||
local function AICPickedUp(Helo,Cargo,Index)
|
||||
self:__PilotPickedUp(2,Helo,Cargo,Index)
|
||||
end
|
||||
|
||||
local function AICHeloDead(Helo,Index)
|
||||
self:__HeloDown(2,Helo,Index)
|
||||
end
|
||||
|
||||
function helo:OnAfterLoadingDone(From,Event,To)
|
||||
AICPickedUp(helo,helo:GetCargoGroups(),Index)
|
||||
end
|
||||
|
||||
function helo:OnAfterDead(From,Event,To)
|
||||
AICHeloDead(helo,Index)
|
||||
end
|
||||
|
||||
self.helos[Index] = helo
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- [Internal] Check if pilot arrived in rescue zone (MASH)
|
||||
-- @param #AICSAR self
|
||||
-- @param Wrapper.Group#GROUP Pilot The pilot to be rescued.
|
||||
-- @return #boolean outcome
|
||||
function AICSAR:_CheckInMashZone(Pilot)
|
||||
self:T(self.lid .. "_CheckQueue")
|
||||
if Pilot:IsInZone(self.farpzone) then
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
--- [Internal] Check helo queue
|
||||
-- @param #AICSAR self
|
||||
-- @return #AICSAR self
|
||||
function AICSAR:_CheckHelos()
|
||||
self:T(self.lid .. "_CheckHelos")
|
||||
for _index,_helo in pairs(self.helos) do
|
||||
local helo = _helo -- Ops.FlightGroup#FLIGHTGROUP
|
||||
if helo and helo.ClassName == "FLIGHTGROUP" then
|
||||
local state = helo:GetState()
|
||||
local name = helo:GetName()
|
||||
self:T("Helo group "..name.." in state "..state)
|
||||
if state == "Arrived" then
|
||||
helo:__Stop(5)
|
||||
self.helos[_index] = nil
|
||||
end
|
||||
else
|
||||
self.helos[_index] = nil
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- [Internal] Count helos queue
|
||||
-- @param #AICSAR self
|
||||
-- @return #number Number of helos on mission
|
||||
function AICSAR:_CountHelos()
|
||||
self:T(self.lid .. "_CountHelos")
|
||||
local count = 0
|
||||
for _index,_helo in pairs(self.helos) do
|
||||
count = count + 1
|
||||
end
|
||||
return count
|
||||
end
|
||||
|
||||
--- [Internal] Check pilot queue for next mission
|
||||
-- @param #AICSAR self
|
||||
-- @return #AICSAR self
|
||||
function AICSAR:_CheckQueue()
|
||||
self:T(self.lid .. "_CheckQueue")
|
||||
for _index, _pilot in pairs(self.pilotqueue) do
|
||||
local classname = _pilot.ClassName and _pilot.ClassName or "NONE"
|
||||
local name = _pilot.GroupName and _pilot.GroupName or "NONE"
|
||||
local helocount = self:_CountHelos()
|
||||
--self:T("Looking at " .. classname .. " " .. name)
|
||||
-- find one w/o mission
|
||||
if _pilot and _pilot.ClassName and _pilot.ClassName == "GROUP" then
|
||||
local flightgroup = self.helos[_index] -- Ops.FlightGroup#FLIGHTGROUP
|
||||
-- rescued?
|
||||
if self:_CheckInMashZone(_pilot) then
|
||||
self:T("Pilot" .. _pilot.GroupName .. " rescued!")
|
||||
_pilot:Destroy(false)
|
||||
self.pilotqueue[_index] = nil
|
||||
self.rescued[_index] = true
|
||||
self:__PilotRescued(2)
|
||||
if flightgroup then
|
||||
flightgroup.AICSARReserved = false
|
||||
end
|
||||
end -- end rescued
|
||||
-- has no mission assigned?
|
||||
if not _pilot.AICSAR then
|
||||
-- helo available?
|
||||
if self.limithelos and helocount >= self.helonumber then
|
||||
-- none free
|
||||
break
|
||||
end -- end limit
|
||||
_pilot.AICSAR = {}
|
||||
_pilot.AICSAR.Status = "Initiated"
|
||||
_pilot.AICSAR.Boarded = false
|
||||
self:_InitMission(_pilot,_index)
|
||||
break
|
||||
else
|
||||
-- update status from OPSGROUP
|
||||
if flightgroup then
|
||||
local state = flightgroup:GetState()
|
||||
_pilot.AICSAR.Status = state
|
||||
end
|
||||
--self:T("Flight for " .. _pilot.GroupName .. " in state " .. state)
|
||||
end -- end has mission
|
||||
end -- end if pilot
|
||||
end -- end loop
|
||||
return self
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- FSM Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- [Internal] onafterStart
|
||||
-- @param #AICSAR self
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @return #AICSAR self
|
||||
function AICSAR:onafterStart(From, Event, To)
|
||||
self:T({From, Event, To})
|
||||
self:__Status(3)
|
||||
return self
|
||||
end
|
||||
|
||||
--- [Internal] onafterStatus
|
||||
-- @param #AICSAR self
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @return #AICSAR self
|
||||
function AICSAR:onafterStatus(From, Event, To)
|
||||
self:T({From, Event, To})
|
||||
self:_CheckQueue()
|
||||
self:_CheckHelos()
|
||||
self:__Status(30)
|
||||
return self
|
||||
end
|
||||
|
||||
--- [Internal] onafterStop
|
||||
-- @param #AICSAR self
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @return #AICSAR self
|
||||
function AICSAR:onafterStop(From, Event, To)
|
||||
self:T({From, Event, To})
|
||||
self:UnHandleEvent(EVENTS.LandingAfterEjection)
|
||||
if self.DCSRadioQueue then
|
||||
self.DCSRadioQueue:Stop()
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- [Internal] onafterPilotDown
|
||||
-- @param #AICSAR self
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param Core.Point#COORDINATE Coordinate Location of the pilot.
|
||||
-- @param #boolean InReach True if in maxdistance else false.
|
||||
-- @return #AICSAR self
|
||||
function AICSAR:onafterPilotDown(From, Event, To, Coordinate, InReach)
|
||||
self:T({From, Event, To})
|
||||
local CoordinateText = Coordinate:ToStringMGRS()
|
||||
local inreach = tostring(InReach)
|
||||
--local text = string.format("Pilot down at %s. In reach = %s",CoordinateText,inreach)
|
||||
if InReach then
|
||||
local text,Soundfile,Soundlength,Subtitle = self.gettext:GetEntry("INITIALOK",self.locale)
|
||||
--local text = AICSAR.Messages.EN.INITIALOK
|
||||
self:T(text)
|
||||
if self.verbose then
|
||||
MESSAGE:New(text,15,"AICSAR"):ToCoalition(self.coalition)
|
||||
end
|
||||
if self.SRSRadio then
|
||||
local sound = SOUNDFILE:New(Soundfile,self.SRSSoundPath,Soundlength)
|
||||
sound:SetPlayWithSRS(true)
|
||||
self.SRS:PlaySoundFile(sound,2)
|
||||
elseif self.DCSRadio then
|
||||
self:DCSRadioBroadcast(Soundfile,Soundlength,text)
|
||||
end
|
||||
else
|
||||
local text,Soundfile,Soundlength,Subtitle = self.gettext:GetEntry("INITIALNOTOK",self.locale)
|
||||
--local text = AICSAR.Messages.EN.INITIALNOTOK
|
||||
self:T(text)
|
||||
if self.verbose then
|
||||
MESSAGE:New(text,15,"AICSAR"):ToCoalition(self.coalition)
|
||||
end
|
||||
if self.SRSRadio then
|
||||
local sound = SOUNDFILE:New(Soundfile,self.SRSSoundPath,Soundlength)
|
||||
sound:SetPlayWithSRS(true)
|
||||
self.SRS:PlaySoundFile(sound,2)
|
||||
elseif self.DCSRadio then
|
||||
self:DCSRadioBroadcast(Soundfile,Soundlength,text)
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- [Internal] onafterPilotKIA
|
||||
-- @param #AICSAR self
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @return #AICSAR self
|
||||
function AICSAR:onafterPilotKIA(From, Event, To)
|
||||
self:T({From, Event, To})
|
||||
local text,Soundfile,Soundlength,Subtitle = self.gettext:GetEntry("PILOTKIA",self.locale)
|
||||
if self.verbose then
|
||||
MESSAGE:New(text,15,"AICSAR"):ToCoalition(self.coalition)
|
||||
end
|
||||
if self.SRSRadio then
|
||||
local sound = SOUNDFILE:New(Soundfile,self.SRSSoundPath,Soundlength)
|
||||
sound:SetPlayWithSRS(true)
|
||||
self.SRS:PlaySoundFile(sound,2)
|
||||
elseif self.DCSRadio then
|
||||
self:DCSRadioBroadcast(Soundfile,Soundlength,text)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- [Internal] onafterHeloDown
|
||||
-- @param #AICSAR self
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param Ops.FlightGroup#FLIGHTGROUP Helo
|
||||
-- @param #number Index
|
||||
-- @return #AICSAR self
|
||||
function AICSAR:onafterHeloDown(From, Event, To, Helo, Index)
|
||||
self:T({From, Event, To})
|
||||
local text,Soundfile,Soundlength,Subtitle = self.gettext:GetEntry("HELODOWN",self.locale)
|
||||
if self.verbose then
|
||||
MESSAGE:New(text,15,"AICSAR"):ToCoalition(self.coalition)
|
||||
end
|
||||
if self.SRSRadio then
|
||||
local sound = SOUNDFILE:New(Soundfile,self.SRSSoundPath,Soundlength)
|
||||
sound:SetPlayWithSRS(true)
|
||||
self.SRS:PlaySoundFile(sound,2)
|
||||
elseif self.DCSRadio then
|
||||
self:DCSRadioBroadcast(Soundfile,Soundlength,text)
|
||||
end
|
||||
local findex = 0
|
||||
local fhname = Helo:GetName()
|
||||
-- find index of Helo
|
||||
if Index and Index > 0 then
|
||||
findex=Index
|
||||
else
|
||||
for _index, _helo in pairs(self.helos) do
|
||||
local helo = _helo -- Ops.FlightGroup#FLIGHTGROUP
|
||||
local hname = helo:GetName()
|
||||
if fhname == hname then
|
||||
findex = _index
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
-- find pilot
|
||||
if findex > 0 and not self.rescued[findex] then
|
||||
local pilot = self.pilotqueue[findex]
|
||||
self.helos[findex] = nil
|
||||
if pilot.AICSAR.Boarded then
|
||||
self:T("Helo Down: Found DEAD Pilot ID " .. findex .. " with name " .. pilot:GetName())
|
||||
-- pilot also dead
|
||||
self:__PilotKIA(2)
|
||||
self.pilotqueue[findex] = nil
|
||||
else
|
||||
-- initiate new mission
|
||||
self:T("Helo Down: Found ALIVE Pilot ID " .. findex .. " with name " .. pilot:GetName())
|
||||
self:_InitMission(pilot,findex)
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- [Internal] onafterPilotRescued
|
||||
-- @param #AICSAR self
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @return #AICSAR self
|
||||
function AICSAR:onafterPilotRescued(From, Event, To)
|
||||
self:T({From, Event, To})
|
||||
local text,Soundfile,Soundlength,Subtitle = self.gettext:GetEntry("PILOTRESCUED",self.locale)
|
||||
if self.verbose then
|
||||
MESSAGE:New(text,15,"AICSAR"):ToCoalition(self.coalition)
|
||||
end
|
||||
if self.SRSRadio then
|
||||
local sound = SOUNDFILE:New(Soundfile,self.SRSSoundPath,Soundlength)
|
||||
sound:SetPlayWithSRS(true)
|
||||
self.SRS:PlaySoundFile(sound,2)
|
||||
elseif self.DCSRadio then
|
||||
self:DCSRadioBroadcast(Soundfile,Soundlength,text)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- [Internal] onafterPilotPickedUp
|
||||
-- @param #AICSAR self
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param Ops.FlightGroup#FLIGHTGROUP Helo
|
||||
-- @param #table CargoTable of Ops.OpsGroup#OPSGROUP Cargo objects
|
||||
-- @param #number Index
|
||||
-- @return #AICSAR self
|
||||
function AICSAR:onafterPilotPickedUp(From, Event, To, Helo, CargoTable, Index)
|
||||
self:T({From, Event, To})
|
||||
local text,Soundfile,Soundlength,Subtitle = self.gettext:GetEntry("PILOTINHELO",self.locale)
|
||||
if self.verbose then
|
||||
MESSAGE:New(text,15,"AICSAR"):ToCoalition(self.coalition)
|
||||
end
|
||||
if self.SRSRadio then
|
||||
local sound = SOUNDFILE:New(Soundfile,self.SRSSoundPath,Soundlength)
|
||||
sound:SetPlayWithSRS(true)
|
||||
self.SRS:PlaySoundFile(sound,2)
|
||||
elseif self.DCSRadio then
|
||||
self:DCSRadioBroadcast(Soundfile,Soundlength,text)
|
||||
end
|
||||
local findex = 0
|
||||
local fhname = Helo:GetName()
|
||||
if Index and Index > 0 then
|
||||
findex = Index
|
||||
else
|
||||
-- find index of Helo
|
||||
for _index, _helo in pairs(self.helos) do
|
||||
local helo = _helo -- Ops.FlightGroup#FLIGHTGROUP
|
||||
local hname = helo:GetName()
|
||||
if fhname == hname then
|
||||
findex = _index
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
-- find pilot
|
||||
if findex > 0 then
|
||||
local pilot = self.pilotqueue[findex]
|
||||
self:T("Boarded: Found Pilot ID " .. findex .. " with name " .. pilot:GetName())
|
||||
pilot.AICSAR.Boarded = true -- mark as boarded
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- END AICSAR
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
@@ -59,7 +59,13 @@ function ATC_GROUND:New( Airbases, AirbaseList )
|
||||
|
||||
|
||||
for AirbaseID, Airbase in pairs( self.Airbases ) do
|
||||
Airbase.ZoneBoundary = _DATABASE:FindAirbase( AirbaseID ):GetZone()
|
||||
-- Specified ZoneBoundary is used if setted or Airbase radius by default
|
||||
if Airbase.ZoneBoundary then
|
||||
Airbase.ZoneBoundary = ZONE_POLYGON_BASE:New( "Boundary " .. AirbaseID, Airbase.ZoneBoundary )
|
||||
else
|
||||
Airbase.ZoneBoundary = _DATABASE:FindAirbase( AirbaseID ):GetZone()
|
||||
end
|
||||
|
||||
Airbase.ZoneRunways = {}
|
||||
for PointsRunwayID, PointsRunway in pairs( Airbase.PointsRunways ) do
|
||||
Airbase.ZoneRunways[PointsRunwayID] = ZONE_POLYGON_BASE:New( "Runway " .. PointsRunwayID, PointsRunway )
|
||||
@@ -1053,7 +1059,7 @@ end
|
||||
-- * `AIRBASE.Nevada.Lincoln_County`
|
||||
-- * `AIRBASE.Nevada.McCarran_International_Airport`
|
||||
-- * `AIRBASE.Nevada.Mesquite`
|
||||
-- * `AIRBASE.Nevada.Mina_Airport_3Q0`
|
||||
-- * `AIRBASE.Nevada.Mina_Airport`
|
||||
-- * `AIRBASE.Nevada.Nellis_AFB`
|
||||
-- * `AIRBASE.Nevada.North_Las_Vegas`
|
||||
-- * `AIRBASE.Nevada.Pahute_Mesa_Airstrip`
|
||||
@@ -1288,7 +1294,7 @@ ATC_GROUND_NEVADA = {
|
||||
},
|
||||
},
|
||||
},
|
||||
[AIRBASE.Nevada.Mina_Airport_3Q0] = {
|
||||
[AIRBASE.Nevada.Mina_Airport] = {
|
||||
PointsRunways = {
|
||||
[1] = {
|
||||
[1] = {["y"]=-290054.57371429,["x"]=-160930.02228572,},
|
||||
@@ -3263,5 +3269,310 @@ function ATC_GROUND_PERSIANGULF:Start( RepeatScanSeconds )
|
||||
end
|
||||
|
||||
|
||||
--- @type ATC_GROUND_MARIANAISLANDS
|
||||
-- @extends #ATC_GROUND
|
||||
|
||||
|
||||
|
||||
--- # ATC\_GROUND\_MARIANA, extends @{#ATC_GROUND}
|
||||
--
|
||||
-- The ATC\_GROUND\_MARIANA class monitors the speed of the airplanes at the airbase during taxi.
|
||||
-- The pilots may not drive faster than the maximum speed for the airbase, or they will be despawned.
|
||||
--
|
||||
-- ---
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- ---
|
||||
--
|
||||
-- The default maximum speed for the airbases at Persian Gulf is **50 km/h**. Warnings are given if this speed limit is trespassed.
|
||||
-- Players will be immediately kicked when driving faster than **150 km/h** on the taxi way.
|
||||
--
|
||||
-- The ATC\_GROUND\_MARIANA class monitors the speed of the airplanes at the airbase during taxi.
|
||||
-- The pilots may not drive faster than the maximum speed for the airbase, or they will be despawned.
|
||||
--
|
||||
-- The pilot will receive 3 times a warning during speeding. After the 3rd warning, if the pilot is still driving
|
||||
-- faster than the maximum allowed speed, the pilot will be kicked.
|
||||
--
|
||||
-- Different airbases have different maximum speeds, according safety regulations.
|
||||
--
|
||||
-- # Airbases monitored
|
||||
--
|
||||
-- The following airbases are monitored at the Mariana Island region.
|
||||
-- Use the @{Wrapper.Airbase#AIRBASE.MarianaIslands} enumeration to select the airbases to be monitored.
|
||||
--
|
||||
-- * AIRBASE.MarianaIslands.Rota_Intl
|
||||
-- * AIRBASE.MarianaIslands.Andersen_AFB
|
||||
-- * AIRBASE.MarianaIslands.Antonio_B_Won_Pat_Intl
|
||||
-- * AIRBASE.MarianaIslands.Saipan_Intl
|
||||
-- * AIRBASE.MarianaIslands.Tinian_Intl
|
||||
-- * AIRBASE.MarianaIslands.Olf_Orote
|
||||
--
|
||||
-- # Installation
|
||||
--
|
||||
-- ## In Single Player Missions
|
||||
--
|
||||
-- ATC\_GROUND is fully functional in single player.
|
||||
--
|
||||
-- ## In Multi Player Missions
|
||||
--
|
||||
-- ATC\_GROUND is functional in multi player, however ...
|
||||
--
|
||||
-- Due to a bug in DCS since release 1.5, the despawning of clients are not anymore working in multi player.
|
||||
-- To **work around this problem**, a much better solution has been made, using the **slot blocker** script designed
|
||||
-- by Ciribob.
|
||||
--
|
||||
-- With the help of __Ciribob__, this script has been extended to also kick client players while in flight.
|
||||
-- ATC\_GROUND is communicating with this modified script to kick players!
|
||||
--
|
||||
-- Install the file **SimpleSlotBlockGameGUI.lua** on the server, following the installation instructions described by Ciribob.
|
||||
--
|
||||
-- [Simple Slot Blocker from Ciribob & FlightControl](https://github.com/ciribob/DCS-SimpleSlotBlock)
|
||||
--
|
||||
-- # Script it!
|
||||
--
|
||||
-- ## 1. ATC_GROUND_MARIANAISLANDS Constructor
|
||||
--
|
||||
-- Creates a new ATC_GROUND_MARIANAISLANDS object that will monitor pilots taxiing behaviour.
|
||||
--
|
||||
-- -- This creates a new ATC_GROUND_MARIANAISLANDS object.
|
||||
--
|
||||
-- -- Monitor for these clients the airbases.
|
||||
-- AirbasePoliceCaucasus = ATC_GROUND_MARIANAISLANDS:New()
|
||||
--
|
||||
-- ATC_Ground = ATC_GROUND_MARIANAISLANDS:New(
|
||||
-- { AIRBASE.MarianaIslands.Andersen_AFB,
|
||||
-- AIRBASE.MarianaIslands.Saipan_Intl
|
||||
-- }
|
||||
-- )
|
||||
--
|
||||
--
|
||||
-- ## 2. Set various options
|
||||
--
|
||||
-- There are various methods that you can use to tweak the behaviour of the ATC\_GROUND classes.
|
||||
--
|
||||
-- ### 2.1 Speed limit at an airbase.
|
||||
--
|
||||
-- * @{#ATC_GROUND.SetKickSpeed}(): Set the speed limit allowed at an airbase in meters per second.
|
||||
-- * @{#ATC_GROUND.SetKickSpeedKmph}(): Set the speed limit allowed at an airbase in kilometers per hour.
|
||||
-- * @{#ATC_GROUND.SetKickSpeedMiph}(): Set the speed limit allowed at an airbase in miles per hour.
|
||||
--
|
||||
-- ### 2.2 Prevent Takeoff at an airbase. Players will be kicked immediately.
|
||||
--
|
||||
-- * @{#ATC_GROUND.SetMaximumKickSpeed}(): Set the maximum speed allowed at an airbase in meters per second.
|
||||
-- * @{#ATC_GROUND.SetMaximumKickSpeedKmph}(): Set the maximum speed allowed at an airbase in kilometers per hour.
|
||||
-- * @{#ATC_GROUND.SetMaximumKickSpeedMiph}(): Set the maximum speed allowed at an airbase in miles per hour.
|
||||
--
|
||||
---- @field #ATC_GROUND_MARIANAISLANDS
|
||||
ATC_GROUND_MARIANAISLANDS = {
|
||||
ClassName = "ATC_GROUND_MARIANAISLANDS",
|
||||
Airbases = {
|
||||
|
||||
[AIRBASE.MarianaIslands.Andersen_AFB] = {
|
||||
ZoneBoundary = {
|
||||
[1]={["y"]=16534.138036037,["x"]=11357.42159178,},
|
||||
[2]={["y"]=16193.406442738,["x"]=12080.012957533,},
|
||||
[3]={["y"]=13846.966851869,["x"]=12017.348398727,},
|
||||
[4]={["y"]=13085.815989171,["x"]=11686.317876875,},
|
||||
[5]={["y"]=13157.991797443,["x"]=11307.826209991,},
|
||||
[6]={["y"]=12055.725179065,["x"]=10795.955695916,},
|
||||
[7]={["y"]=12762.455491112,["x"]=8890.9830441032,},
|
||||
[8]={["y"]=15955.829493693,["x"]=10333.527220132,},
|
||||
[9]={["y"]=16537.500532414,["x"]=11302.009499603,},
|
||||
},
|
||||
PointsRunways = {
|
||||
[1]={
|
||||
[1]={["y"]=12586.683049611,["x"]=10224.374497932,},
|
||||
[2]={["y"]=16191.720475696,["x"]=11791.299100017,},
|
||||
[3]={["y"]=16126.93956642,["x"]=11938.855615591,},
|
||||
[4]={["y"]=12520.758127164,["x"]=10385.177131701,},
|
||||
[5]={["y"]=12584.654720512,["x"]=10227.416991581,},
|
||||
},
|
||||
[2]={
|
||||
[1]={["y"]=12663.030391743,["x"]=9661.9623015306,},
|
||||
[2]={["y"]=16478.347303358,["x"]=11328.665745976,},
|
||||
[3]={["y"]=16405.4731048,["x"]=11479.11570429,},
|
||||
[4]={["y"]=12597.277684174,["x"]=9817.9733769647,},
|
||||
[5]={["y"]=12661.894752524,["x"]=9674.4462086962,},
|
||||
},
|
||||
},
|
||||
},
|
||||
[AIRBASE.MarianaIslands.Antonio_B_Won_Pat_Intl] = {
|
||||
ZoneBoundary = {
|
||||
[1]={["y"]=2288.5182403943,["x"]=1469.0170841716,},
|
||||
[2]={["y"]=1126.2025877996,["x"]=1174.37135631,},
|
||||
[3]={["y"]=-2015.6461924287,["x"]=-484.62000718931,},
|
||||
[4]={["y"]=-2102.1292389114,["x"]=-988.03393750566,},
|
||||
[5]={["y"]=476.03853524366,["x"]=-1220.1783269883,},
|
||||
[6]={["y"]=2059.2220058047,["x"]=78.889693514402,},
|
||||
[7]={["y"]=1898.1396965104,["x"]=705.67531284795,},
|
||||
[8]={["y"]=2760.1768681934,["x"]=1026.0681119777,},
|
||||
[9]={["y"]=2317.2278959994,["x"]=1460.8143254273,},
|
||||
},
|
||||
PointsRunways = {
|
||||
[1]={
|
||||
[1]={["y"]=-1872.6620108821,["x"]=-924.3572605835,},
|
||||
[2]={["y"]=1763.4754603305,["x"]=735.35988877983,},
|
||||
[3]={["y"]=1700.6941677961,["x"]=866.32615476157,},
|
||||
[4]={["y"]=-1934.0078007732,["x"]=-779.8149298453,},
|
||||
[5]={["y"]=-1875.0113982627,["x"]=-914.95971106094,},
|
||||
},
|
||||
[2]={
|
||||
[1]={["y"]=-1512.9403660377,["x"]=-1005.5903386188,},
|
||||
[2]={["y"]=1577.9055714735,["x"]=413.22750176368,},
|
||||
[3]={["y"]=1523.1182807849,["x"]=543.89726442232,},
|
||||
[4]={["y"]=-1572.5102998047,["x"]=-867.04004322806,},
|
||||
[5]={["y"]=-1514.2790162347,["x"]=-1003.5823633233,},
|
||||
},
|
||||
},
|
||||
},
|
||||
[AIRBASE.MarianaIslands.Rota_Intl] = {
|
||||
ZoneBoundary = {
|
||||
[1]={["y"]=47237.615412849,["x"]=76048.890408862,},
|
||||
[2]={["y"]=49938.030053628,["x"]=75921.721582932,},
|
||||
[3]={["y"]=49931.24873272,["x"]=75735.184004851,},
|
||||
[4]={["y"]=49295.999227075,["x"]=75754.716414519,},
|
||||
[5]={["y"]=49286.963307515,["x"]=75510.037806569,},
|
||||
[6]={["y"]=48774.280745707,["x"]=75513.331990155,},
|
||||
[7]={["y"]=48785.021396773,["x"]=75795.691662161,},
|
||||
[8]={["y"]=47232.749278491,["x"]=75839.239059146,},
|
||||
[9]={["y"]=47236.687866223,["x"]=76042.706764692,},
|
||||
},
|
||||
PointsRunways = {
|
||||
[1]={
|
||||
[1]={["y"]=49741.295228062,["x"]=75901.50955922,},
|
||||
[2]={["y"]=49739.033213305,["x"]=75768.333440425,},
|
||||
[3]={["y"]=47448.460520408,["x"]=75857.400271466,},
|
||||
[4]={["y"]=47452.270177742,["x"]=75999.965448133,},
|
||||
[5]={["y"]=49738.502011054,["x"]=75905.338915708,},
|
||||
},
|
||||
},
|
||||
},
|
||||
[AIRBASE.MarianaIslands.Saipan_Intl] = {
|
||||
ZoneBoundary = {
|
||||
[1]={["y"]=100489.08491445,["x"]=179799.05158855,},
|
||||
[2]={["y"]=100869.73415313,["x"]=179948.98719903,},
|
||||
[3]={["y"]=101364.78967515,["x"]=180831.98517043,},
|
||||
[4]={["y"]=101563.85713359,["x"]=180885.21496237,},
|
||||
[5]={["y"]=101733.92591034,["x"]=180457.73296886,},
|
||||
[6]={["y"]=103340.30228775,["x"]=180990.08362622,},
|
||||
[7]={["y"]=103459.55080438,["x"]=180453.77747027,},
|
||||
[8]={["y"]=100406.63048095,["x"]=179266.60983762,},
|
||||
[9]={["y"]=100225.55027532,["x"]=179423.9380961,},
|
||||
[10]={["y"]=100477.48558937,["x"]=179791.9827288,},
|
||||
},
|
||||
PointsRunways = {
|
||||
[1]={
|
||||
[1]={["y"]=103170.38882002,["x"]=180654.56630524,},
|
||||
[2]={["y"]=103235.37868835,["x"]=180497.25368418,},
|
||||
[3]={["y"]=100564.72969504,["x"]=179435.41443498,},
|
||||
[4]={["y"]=100509.30718722,["x"]=179584.65394733,},
|
||||
[5]={["y"]=103163.53918905,["x"]=180651.82645285,},
|
||||
},
|
||||
[2]={
|
||||
[1]={["y"]=103048.83223261,["x"]=180819.94107128,},
|
||||
[2]={["y"]=103087.60579257,["x"]=180720.06315265,},
|
||||
[3]={["y"]=101037.52694966,["x"]=179899.50061624,},
|
||||
[4]={["y"]=100994.61708907,["x"]=180009.33151758,},
|
||||
[5]={["y"]=103043.26643227,["x"]=180820.40488798,},
|
||||
},
|
||||
},
|
||||
},
|
||||
[AIRBASE.MarianaIslands.Tinian_Intl] = {
|
||||
ZoneBoundary = {
|
||||
[1]={["y"]=88393.477575413,["x"]=166704.16076438,},
|
||||
[2]={["y"]=91581.732441809,["x"]=167402.54409276,},
|
||||
[3]={["y"]=91533.451647402,["x"]=166826.23670062,},
|
||||
[4]={["y"]=90827.604136952,["x"]=166699.75590414,},
|
||||
[5]={["y"]=90894.853975623,["x"]=166375.37836304,},
|
||||
[6]={["y"]=89995.027922869,["x"]=166224.92495935,},
|
||||
[7]={["y"]=88937.62899352,["x"]=166244.48573911,},
|
||||
[8]={["y"]=88408.916178231,["x"]=166480.39896864,},
|
||||
[9]={["y"]=88387.745481732,["x"]=166685.82715656,},
|
||||
},
|
||||
PointsRunways = {
|
||||
[1]={
|
||||
[1]={["y"]=91329.480937912,["x"]=167204.44064529,},
|
||||
[2]={["y"]=91363.95475433,["x"]=167038.15603429,},
|
||||
[3]={["y"]=88585.849307337,["x"]=166520.3807647,},
|
||||
[4]={["y"]=88554.422227212,["x"]=166686.49505251,},
|
||||
[5]={["y"]=91318.8152578,["x"]=167203.31794212,},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
},
|
||||
}
|
||||
|
||||
--- Creates a new ATC_GROUND_MARIANAISLANDS object.
|
||||
-- @param #ATC_GROUND_MARIANAISLANDS self
|
||||
-- @param AirbaseNames A list {} of airbase names (Use AIRBASE.MarianaIslands enumerator).
|
||||
-- @return #ATC_GROUND_MARIANAISLANDS self
|
||||
function ATC_GROUND_MARIANAISLANDS:New( AirbaseNames )
|
||||
|
||||
-- Inherits from BASE
|
||||
local self = BASE:Inherit( self, ATC_GROUND:New( self.Airbases, AirbaseNames ) )
|
||||
|
||||
self:SetKickSpeedKmph( 50 )
|
||||
self:SetMaximumKickSpeedKmph( 150 )
|
||||
|
||||
-- -- Andersen
|
||||
-- local AndersenBoundary = GROUP:FindByName( "Andersen Boundary" )
|
||||
-- self.Airbases[AIRBASE.MarianaIslands.Andersen_AFB].ZoneBoundary = ZONE_POLYGON:New( "Andersen Boundary", AndersenBoundary ):SmokeZone(SMOKECOLOR.White):Flush()
|
||||
--
|
||||
-- local AndersenRunway1 = GROUP:FindByName( "Andersen Runway 1" )
|
||||
-- self.Airbases[AIRBASE.MarianaIslands.Andersen_AFB].ZoneRunways[1] = ZONE_POLYGON:New( "Andersen Runway 1", AndersenRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush()
|
||||
--
|
||||
-- local AndersenRunway2 = GROUP:FindByName( "Andersen Runway 2" )
|
||||
-- self.Airbases[AIRBASE.MarianaIslands.Andersen_AFB].ZoneRunways[2] = ZONE_POLYGON:New( "Andersen Runway 2", AndersenRunway2 ):SmokeZone(SMOKECOLOR.Red):Flush()
|
||||
--
|
||||
--
|
||||
-- -- Antonio_B_Won_Pat_International_Airport
|
||||
-- local AntonioBoundary = GROUP:FindByName( "Antonio Boundary" )
|
||||
-- self.Airbases[AIRBASE.MarianaIslands.Antonio_B_Won_Pat_Intl].ZoneBoundary = ZONE_POLYGON:New( "Antonio Boundary", AntonioBoundary ):SmokeZone(SMOKECOLOR.White):Flush()
|
||||
--
|
||||
-- local AntonioRunway1 = GROUP:FindByName( "Antonio Runway 1" )
|
||||
-- self.Airbases[AIRBASE.MarianaIslands.Antonio_B_Won_Pat_Intl].ZoneRunways[1] = ZONE_POLYGON:New( "Antonio Runway 1", AntonioRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush()
|
||||
--
|
||||
-- local AntonioRunway2 = GROUP:FindByName( "Antonio Runway 2" )
|
||||
-- self.Airbases[AIRBASE.MarianaIslands.Antonio_B_Won_Pat_Intl].ZoneRunways[2] = ZONE_POLYGON:New( "Antonio Runway 2", AntonioRunway2 ):SmokeZone(SMOKECOLOR.Red):Flush()
|
||||
--
|
||||
--
|
||||
-- -- Rota_International_Airport
|
||||
-- local RotaBoundary = GROUP:FindByName( "Rota Boundary" )
|
||||
-- self.Airbases[AIRBASE.MarianaIslands.Rota_Intl].ZoneBoundary = ZONE_POLYGON:New( "Rota Boundary", RotaBoundary ):SmokeZone(SMOKECOLOR.White):Flush()
|
||||
--
|
||||
-- local RotaRunway1 = GROUP:FindByName( "Rota Runway 1" )
|
||||
-- self.Airbases[AIRBASE.MarianaIslands.Rota_Intl].ZoneRunways[1] = ZONE_POLYGON:New( "Rota Runway 1", RotaRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush()
|
||||
--
|
||||
--
|
||||
-- -- Saipan_International_Airport
|
||||
-- local SaipanBoundary = GROUP:FindByName( "Saipan Boundary" )
|
||||
-- self.Airbases[AIRBASE.MarianaIslands.Saipan_Intl].ZoneBoundary = ZONE_POLYGON:New( "Saipan Boundary", SaipanBoundary ):SmokeZone(SMOKECOLOR.White):Flush()
|
||||
--
|
||||
-- local SaipanRunway1 = GROUP:FindByName( "Saipan Runway 1" )
|
||||
-- self.Airbases[AIRBASE.MarianaIslands.Saipan_Intl].ZoneRunways[1] = ZONE_POLYGON:New( "Saipan Runway 1", SaipanRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush()
|
||||
--
|
||||
-- local SaipanRunway2 = GROUP:FindByName( "Saipan Runway 2" )
|
||||
-- self.Airbases[AIRBASE.MarianaIslands.Saipan_Intl].ZoneRunways[2] = ZONE_POLYGON:New( "Saipan Runway 2", SaipanRunway2 ):SmokeZone(SMOKECOLOR.Red):Flush()
|
||||
--
|
||||
--
|
||||
-- -- Tinian_International_Airport
|
||||
-- local TinianBoundary = GROUP:FindByName( "Tinian Boundary" )
|
||||
-- self.Airbases[AIRBASE.MarianaIslands.Tinian_Intl].ZoneBoundary = ZONE_POLYGON:New( "Tinian Boundary", TinianBoundary ):SmokeZone(SMOKECOLOR.White):Flush()
|
||||
--
|
||||
-- local TinianRunway1 = GROUP:FindByName( "Tinian Runway 1" )
|
||||
-- self.Airbases[AIRBASE.MarianaIslands.Tinian_Intl].ZoneRunways[1] = ZONE_POLYGON:New( "Tinian Runway 1", TinianRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush()
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Start SCHEDULER for ATC_GROUND_MARIANAISLANDS object.
|
||||
-- @param #ATC_GROUND_MARIANAISLANDS self
|
||||
-- @param RepeatScanSeconds Time in second for defining occurency of alerts.
|
||||
-- @return nothing
|
||||
function ATC_GROUND_MARIANAISLANDS:Start( RepeatScanSeconds )
|
||||
RepeatScanSeconds = RepeatScanSeconds or 0.05
|
||||
self.AirbaseMonitor = SCHEDULER:New( self, self._AirbaseMonitor, { self }, 0, 2, RepeatScanSeconds )
|
||||
end
|
||||
|
||||
@@ -693,7 +693,7 @@ ARTY.db={
|
||||
|
||||
--- Arty script version.
|
||||
-- @field #string version
|
||||
ARTY.version="1.1.8"
|
||||
ARTY.version="1.2.0"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
@@ -1146,12 +1146,11 @@ end
|
||||
-- @param alias (Optional) Alias name the group will be calling itself when sending messages. Default is the group name.
|
||||
-- @return #ARTY ARTY object or nil if group does not exist or is not a ground or naval group.
|
||||
function ARTY:NewFromCargoGroup(cargogroup, alias)
|
||||
BASE:F2({cargogroup=cargogroup, alias=alias})
|
||||
|
||||
if cargogroup then
|
||||
BASE:T(self.lid..string.format("ARTY script version %s. Added CARGO group %s.", ARTY.version, cargogroup:GetName()))
|
||||
BASE:T(string.format("ARTY script version %s. Added CARGO group %s.", ARTY.version, cargogroup:GetName()))
|
||||
else
|
||||
BASE:E(self.lid.."ERROR: Requested ARTY CARGO GROUP does not exist! (Has to be a MOOSE CARGO(!) group.)")
|
||||
BASE:E("ERROR: Requested ARTY CARGO GROUP does not exist! (Has to be a MOOSE CARGO(!) group.)")
|
||||
return nil
|
||||
end
|
||||
|
||||
@@ -2766,38 +2765,32 @@ function ARTY:onafterStatus(Controllable, From, Event, To)
|
||||
self:_EventFromTo("onafterStatus", Event, From, To)
|
||||
|
||||
-- Get ammo.
|
||||
local ntot, nshells, nrockets, nmissiles=self:GetAmmo()
|
||||
local nammo, nshells, nrockets, nmissiles=self:GetAmmo()
|
||||
|
||||
-- We have a cargo group ==> check if group was loaded into a carrier.
|
||||
if self.iscargo and self.cargogroup then
|
||||
if self.cargogroup:IsLoaded() and not self:is("InTransit") then
|
||||
-- Group is now InTransit state. Current target is canceled.
|
||||
self:T(self.lid..string.format("Group %s has been loaded into a carrier and is now transported.", self.alias))
|
||||
self:Loaded()
|
||||
elseif self.cargogroup:IsUnLoaded() then
|
||||
-- Group has been unloaded and is combat ready again.
|
||||
self:T(self.lid..string.format("Group %s has been unloaded from the carrier.", self.alias))
|
||||
self:UnLoaded()
|
||||
end
|
||||
end
|
||||
|
||||
-- FSM state.
|
||||
local fsmstate=self:GetState()
|
||||
self:T(self.lid..string.format("Status %s, Ammo total=%d: shells=%d [smoke=%d, illu=%d, nukes=%d*%.3f kT], rockets=%d, missiles=%d", fsmstate, ntot, nshells, self.Nsmoke, self.Nillu, self.Nukes, self.nukewarhead/1000000, nrockets, nmissiles))
|
||||
self:T(self.lid..string.format("Status %s, Ammo total=%d: shells=%d [smoke=%d, illu=%d, nukes=%d*%.3f kT], rockets=%d, missiles=%d", fsmstate, nammo, nshells, self.Nsmoke, self.Nillu, self.Nukes, self.nukewarhead/1000000, nrockets, nmissiles))
|
||||
|
||||
if self.Controllable and self.Controllable:IsAlive() then
|
||||
|
||||
-- We have a cargo group ==> check if group was loaded into a carrier.
|
||||
if self.cargogroup then
|
||||
if self.cargogroup:IsLoaded() and not self:is("InTransit") then
|
||||
-- Group is now InTransit state. Current target is canceled.
|
||||
self:T(self.lid..string.format("Group %s has been loaded into a carrier and is now transported.", self.alias))
|
||||
self:Loaded()
|
||||
elseif self.cargogroup:IsUnLoaded() then
|
||||
-- Group has been unloaded and is combat ready again.
|
||||
self:T(self.lid..string.format("Group %s has been unloaded from the carrier.", self.alias))
|
||||
self:UnLoaded()
|
||||
end
|
||||
end
|
||||
|
||||
-- Debug current status info.
|
||||
if self.Debug then
|
||||
self:_StatusReport()
|
||||
end
|
||||
|
||||
-- Group is being transported as cargo ==> skip everything and check again in 5 seconds.
|
||||
if self:is("InTransit") then
|
||||
self:__Status(-5)
|
||||
return
|
||||
end
|
||||
|
||||
-- Group on the move.
|
||||
if self:is("Moving") then
|
||||
self:T2(self.lid..string.format("%s: Moving", Controllable:GetName()))
|
||||
@@ -2884,7 +2877,7 @@ function ARTY:onafterStatus(Controllable, From, Event, To)
|
||||
end
|
||||
|
||||
-- Get ammo.
|
||||
local nammo, nshells, nrockets, nmissiles=self:GetAmmo()
|
||||
--local nammo, nshells, nrockets, nmissiles=self:GetAmmo()
|
||||
|
||||
-- Check if we have a target in the queue for which weapons are still available.
|
||||
local gotsome=false
|
||||
@@ -2914,9 +2907,23 @@ function ARTY:onafterStatus(Controllable, From, Event, To)
|
||||
-- Call status again in ~10 sec.
|
||||
self:__Status(self.StatusInterval)
|
||||
|
||||
elseif self.iscargo then
|
||||
|
||||
-- We have a cargo group ==> check if group was loaded into a carrier.
|
||||
if self.cargogroup and self.cargogroup:IsAlive() then
|
||||
|
||||
-- Group is being transported as cargo ==> skip everything and check again in 5 seconds.
|
||||
if self:is("InTransit") then
|
||||
self:__Status(-5)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
else
|
||||
self:E(self.lid..string.format("Arty group %s is not alive!", self.groupname))
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
@@ -0,0 +1,961 @@
|
||||
--- **Functional** - Autolase targets in the field.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- **AUOTLASE** - Autolase targets in the field.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ## Missions:
|
||||
--
|
||||
-- ### [Autolase](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- **Main Features:**
|
||||
--
|
||||
-- * Detect and lase contacts automatically
|
||||
-- * Targets are lased by threat priority order
|
||||
-- * Use FSM events to link functionality into your scripts
|
||||
-- * Easy setup
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--- Spot on!
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- # 1 Autolase concept
|
||||
--
|
||||
-- * Detect and lase contacts automatically
|
||||
-- * Targets are lased by threat priority order
|
||||
-- * Use FSM events to link functionality into your scripts
|
||||
-- * Set laser codes and smoke colors per Recce unit
|
||||
-- * Easy set-up
|
||||
--
|
||||
-- # 2 Basic usage
|
||||
--
|
||||
-- ## 2.2 Set up a group of Recce Units:
|
||||
--
|
||||
-- local FoxSet = SET_GROUP:New():FilterPrefixes("Recce"):FilterCoalitions("blue"):FilterStart()
|
||||
--
|
||||
-- ## 2.3 (Optional) Set up a group of pilots, this will drive who sees the F10 menu entry:
|
||||
--
|
||||
-- local Pilotset = SET_CLIENT:New():FilterCoalitions("blue"):FilterActive(true):FilterStart()
|
||||
--
|
||||
-- ## 2.4 Set up and start Autolase:
|
||||
--
|
||||
-- local autolaser = AUTOLASE:New(FoxSet,coalition.side.BLUE,"Wolfpack",Pilotset)
|
||||
--
|
||||
-- ## 2.5 Example - Using a fixed laser code and color for a specific Recce unit:
|
||||
--
|
||||
-- local recce = SPAWN:New("Reaper")
|
||||
-- :InitDelayOff()
|
||||
-- :OnSpawnGroup(
|
||||
-- function (group)
|
||||
-- local unit = group:GetUnit(1)
|
||||
-- local name = unit:GetName()
|
||||
-- autolaser:SetRecceLaserCode(name,1688)
|
||||
-- autolaser:SetRecceSmokeColor(name,SMOKECOLOR.Red)
|
||||
-- end
|
||||
-- )
|
||||
-- :InitCleanUp(60)
|
||||
-- :InitLimit(1,0)
|
||||
-- :SpawnScheduled(30,0.5)
|
||||
--
|
||||
-- ## 2.6 Example - Inform pilots about events:
|
||||
--
|
||||
-- autolaser:SetNotifyPilots(true) -- defaults to true, also shown if debug == true
|
||||
-- -- Note - message are shown to pilots in the #SET_CLIENT only if using the pilotset option, else to the coalition.
|
||||
--
|
||||
--
|
||||
-- ### Author: **applevangelist**
|
||||
-- @module Functional.Autolase
|
||||
-- @image Designation.JPG
|
||||
--
|
||||
-- Date: 24 Oct 2021
|
||||
--
|
||||
--- Class AUTOLASE
|
||||
-- @type AUTOLASE
|
||||
-- @field #string ClassName
|
||||
-- @field #string lid
|
||||
-- @field #number verbose
|
||||
-- @field #string alias
|
||||
-- @field #boolean debug
|
||||
-- @field #string version
|
||||
-- @extends Ops.Intel#INTEL
|
||||
|
||||
---
|
||||
-- @field #AUTOLASE
|
||||
AUTOLASE = {
|
||||
ClassName = "AUTOLASE",
|
||||
lid = "",
|
||||
verbose = 0,
|
||||
alias = "",
|
||||
debug = false,
|
||||
}
|
||||
|
||||
--- Laser spot info
|
||||
-- @type AUTOLASE.LaserSpot
|
||||
-- @field Core.Spot#SPOT laserspot
|
||||
-- @field Wrapper.Unit#UNIT lasedunit
|
||||
-- @field Wrapper.Unit#UNIT lasingunit
|
||||
-- @field #number lasercode
|
||||
-- @field #string location
|
||||
-- @field #number timestamp
|
||||
-- @field #string unitname
|
||||
-- @field #string reccename
|
||||
-- @field #string unittype
|
||||
|
||||
--- AUTOLASE class version.
|
||||
-- @field #string version
|
||||
AUTOLASE.version = "0.0.11"
|
||||
|
||||
-------------------------------------------------------------------
|
||||
-- Begin Functional.Autolase.lua
|
||||
-------------------------------------------------------------------
|
||||
|
||||
--- Constructor for a new Autolase instance.
|
||||
-- @param #AUTOLASE self
|
||||
-- @param Core.Set#SET_GROUP RecceSet Set of detecting and lasing units
|
||||
-- @param #number Coalition Coalition side. Can also be passed as a string "red", "blue" or "neutral".
|
||||
-- @param #string Alias (Optional) An alias how this object is called in the logs etc.
|
||||
-- @param Core.Set#SET_CLIENT PilotSet (Optional) Set of clients for precision bombing, steering menu creation. Leave nil for a coalition-wide F10 entry and display.
|
||||
-- @return #AUTOLASE self
|
||||
function AUTOLASE:New(RecceSet, Coalition, Alias, PilotSet)
|
||||
BASE:T({RecceSet, Coalition, Alias, PilotSet})
|
||||
|
||||
-- Inherit everything from BASE class.
|
||||
local self=BASE:Inherit(self, BASE:New()) -- #AUTOLASE
|
||||
|
||||
if Coalition and type(Coalition)=="string" then
|
||||
if Coalition=="blue" then
|
||||
self.coalition=coalition.side.BLUE
|
||||
elseif Coalition=="red" then
|
||||
self.coalition=coalition.side.RED
|
||||
elseif Coalition=="neutral" then
|
||||
self.coalition=coalition.side.NEUTRAL
|
||||
else
|
||||
self:E("ERROR: Unknown coalition in AUTOLASE!")
|
||||
end
|
||||
end
|
||||
|
||||
-- Set alias.
|
||||
if Alias then
|
||||
self.alias=tostring(Alias)
|
||||
else
|
||||
self.alias="Lion"
|
||||
if self.coalition then
|
||||
if self.coalition==coalition.side.RED then
|
||||
self.alias="Wolf"
|
||||
elseif self.coalition==coalition.side.BLUE then
|
||||
self.alias="Fox"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- inherit from INTEL
|
||||
local self=BASE:Inherit(self, INTEL:New(RecceSet, Coalition, Alias)) -- #AUTOLASE
|
||||
|
||||
self.RecceSet = RecceSet
|
||||
self.DetectVisual = true
|
||||
self.DetectOptical = true
|
||||
self.DetectRadar = true
|
||||
self.DetectIRST = true
|
||||
self.DetectRWR = true
|
||||
self.DetectDLINK = true
|
||||
self.LaserCodes = UTILS.GenerateLaserCodes()
|
||||
self.LaseDistance = 5000
|
||||
self.LaseDuration = 300
|
||||
self.GroupsByThreat = {}
|
||||
self.UnitsByThreat = {}
|
||||
self.RecceNames = {}
|
||||
self.RecceLaserCode = {}
|
||||
self.RecceSmokeColor = {}
|
||||
self.RecceUnitNames= {}
|
||||
self.maxlasing = 4
|
||||
self.CurrentLasing = {}
|
||||
self.lasingindex = 0
|
||||
self.deadunitnotes = {}
|
||||
self.usepilotset = false
|
||||
self.reporttimeshort = 10
|
||||
self.reporttimelong = 30
|
||||
self.smoketargets = false
|
||||
self.smokecolor = SMOKECOLOR.Red
|
||||
self.notifypilots = true
|
||||
self.targetsperrecce = {}
|
||||
self.RecceUnits = {}
|
||||
self.forcecooldown = true
|
||||
self.cooldowntime = 60
|
||||
self.useSRS = false
|
||||
self.SRSPath = ""
|
||||
self.SRSFreq = 251
|
||||
self.SRSMod = radio.modulation.AM
|
||||
|
||||
-- Set some string id for output to DCS.log file.
|
||||
self.lid=string.format("AUTOLASE %s (%s) | ", self.alias, self.coalition and UTILS.GetCoalitionName(self.coalition) or "unknown")
|
||||
|
||||
-- Add FSM transitions.
|
||||
-- From State --> Event --> To State
|
||||
self:AddTransition("*", "Monitor", "*") -- Start FSM
|
||||
self:AddTransition("*", "Lasing", "*") -- Lasing target
|
||||
self:AddTransition("*", "TargetLost", "*") -- Lost target
|
||||
self:AddTransition("*", "TargetDestroyed", "*") -- Target destroyed
|
||||
self:AddTransition("*", "RecceKIA", "*") -- Recce KIA
|
||||
self:AddTransition("*", "LaserTimeout", "*") -- Laser timed out
|
||||
self:AddTransition("*", "Cancel", "*") -- Stop Autolase
|
||||
|
||||
-- Menu Entry
|
||||
if not PilotSet then
|
||||
self.Menu = MENU_COALITION_COMMAND:New(self.coalition,"Autolase",nil,self.ShowStatus,self)
|
||||
else
|
||||
self.usepilotset = true
|
||||
self.pilotset = PilotSet
|
||||
self:HandleEvent(EVENTS.PlayerEnterAircraft)
|
||||
self:SetPilotMenu()
|
||||
end
|
||||
|
||||
self:SetClusterAnalysis(false, false)
|
||||
|
||||
self:__Start(2)
|
||||
self:__Monitor(math.random(5,10))
|
||||
|
||||
return self
|
||||
|
||||
------------------------
|
||||
--- Pseudo Functions ---
|
||||
------------------------
|
||||
|
||||
--- Triggers the FSM event "Monitor".
|
||||
-- @function [parent=#AUTOLASE] Status
|
||||
-- @param #AUTOLASE self
|
||||
|
||||
--- Triggers the FSM event "Monitor" after a delay.
|
||||
-- @function [parent=#AUTOLASE] __Status
|
||||
-- @param #AUTOLASE self
|
||||
-- @param #number delay Delay in seconds.
|
||||
|
||||
--- Triggers the FSM event "Cancel".
|
||||
-- @function [parent=#AUTOLASE] Cancel
|
||||
-- @param #AUTOLASE self
|
||||
|
||||
--- Triggers the FSM event "Cancel" after a delay.
|
||||
-- @function [parent=#AUTOLASE] __Cancel
|
||||
-- @param #AUTOLASE self
|
||||
-- @param #number delay Delay in seconds.
|
||||
|
||||
--- On After "RecceKIA" event.
|
||||
-- @function [parent=#AUTOLASE] OnAfterRecceKIA
|
||||
-- @param #AUTOLASE self
|
||||
-- @param #string From The from state
|
||||
-- @param #string Event The event
|
||||
-- @param #string To The to state
|
||||
-- @param #string RecceName The lost Recce
|
||||
|
||||
--- On After "TargetDestroyed" event.
|
||||
-- @function [parent=#AUTOLASE] OnAfterTargetDestroyed
|
||||
-- @param #AUTOLASE self
|
||||
-- @param #string From The from state
|
||||
-- @param #string Event The event
|
||||
-- @param #string To The to state
|
||||
-- @param #string UnitName The destroyed unit\'s name
|
||||
-- @param #string RecceName The Recce name lasing
|
||||
|
||||
--- On After "TargetLost" event.
|
||||
-- @function [parent=#AUTOLASE] OnAfterTargetLost
|
||||
-- @param #AUTOLASE self
|
||||
-- @param #string From The from state
|
||||
-- @param #string Event The event
|
||||
-- @param #string To The to state
|
||||
-- @param #string UnitName The lost unit\'s name
|
||||
-- @param #string RecceName The Recce name lasing
|
||||
|
||||
--- On After "LaserTimeout" event.
|
||||
-- @function [parent=#AUTOLASE] OnAfterLaserTimeout
|
||||
-- @param #AUTOLASE self
|
||||
-- @param #string From The from state
|
||||
-- @param #string Event The event
|
||||
-- @param #string To The to state
|
||||
-- @param #string UnitName The lost unit\'s name
|
||||
-- @param #string RecceName The Recce name lasing
|
||||
|
||||
--- On After "Lasing" event.
|
||||
-- @function [parent=#AUTOLASE] OnAfterLasing
|
||||
-- @param #AUTOLASE self
|
||||
-- @param #string From The from state
|
||||
-- @param #string Event The event
|
||||
-- @param #string To The to state
|
||||
-- @param Functional.Autolase#AUTOLASE.LaserSpot LaserSpot The LaserSpot data table
|
||||
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------
|
||||
-- Helper Functions
|
||||
-------------------------------------------------------------------
|
||||
|
||||
--- (Internal) Function to set pilot menu.
|
||||
-- @param #AUTOLASE self
|
||||
-- @return #AUTOLASE self
|
||||
function AUTOLASE:SetPilotMenu()
|
||||
if self.usepilotset then
|
||||
local pilottable = self.pilotset:GetSetObjects() or {}
|
||||
for _,_unit in pairs (pilottable) do
|
||||
local Unit = _unit -- Wrapper.Unit#UNIT
|
||||
if Unit and Unit:IsAlive() then
|
||||
local Group = Unit:GetGroup()
|
||||
local lasemenu = MENU_GROUP_COMMAND:New(Group,"Autolase Status",nil,self.ShowStatus,self,Group)
|
||||
lasemenu:Refresh()
|
||||
end
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- (Internal) Event function for new pilots.
|
||||
-- @param #AUTOLASE self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
-- @return #AUTOLASE self
|
||||
function AUTOLASE:OnEventPlayerEnterAircraft(EventData)
|
||||
self:SetPilotMenu()
|
||||
return self
|
||||
end
|
||||
|
||||
--- (Internal) Function to get a laser code by recce name
|
||||
-- @param #AUTOLASE self
|
||||
-- @param #string RecceName Unit(!) name of the Recce
|
||||
-- @return #AUTOLASE self
|
||||
function AUTOLASE:GetLaserCode(RecceName)
|
||||
local code = 1688
|
||||
if self.RecceLaserCode[RecceName] == nil then
|
||||
code = self.LaserCodes[math.random(#self.LaserCodes)]
|
||||
self.RecceLaserCode[RecceName] = code
|
||||
else
|
||||
code = self.RecceLaserCode[RecceName]
|
||||
end
|
||||
return code
|
||||
end
|
||||
|
||||
--- (Internal) Function to get a smoke color by recce name
|
||||
-- @param #AUTOLASE self
|
||||
-- @param #string RecceName Unit(!) name of the Recce
|
||||
-- @return #AUTOLASE self
|
||||
function AUTOLASE:GetSmokeColor(RecceName)
|
||||
local color = self.smokecolor
|
||||
if self.RecceSmokeColor[RecceName] == nil then
|
||||
self.RecceSmokeColor[RecceName] = color
|
||||
else
|
||||
color = self.RecceSmokeColor[RecceName]
|
||||
end
|
||||
return color
|
||||
end
|
||||
|
||||
--- (User) Function enable sending messages via SRS.
|
||||
-- @param #AUTOLASE self
|
||||
-- @param #boolean OnOff Switch usage on and off
|
||||
-- @param #string Path Path to SRS directory, e.g. C:\\Program Files\\DCS-SimpleRadio-Standalon
|
||||
-- @param #number Frequency Frequency to send, e.g. 243
|
||||
-- @param #number Modulation Modulation i.e. radio.modulation.AM or radio.modulation.FM
|
||||
-- @return #AUTOLASE self
|
||||
function AUTOLASE:SetUsingSRS(OnOff,Path,Frequency,Modulation)
|
||||
self.useSRS = OnOff or true
|
||||
self.SRSPath = Path or "E:\\Program Files\\DCS-SimpleRadio-Standalone"
|
||||
self.SRSFreq = Frequency or 271
|
||||
self.SRSMod = Modulation or radio.modulation.AM
|
||||
return self
|
||||
end
|
||||
|
||||
--- (User) Function set max lasing targets
|
||||
-- @param #AUTOLASE self
|
||||
-- @param #number Number Max number of targets to lase at once
|
||||
-- @return #AUTOLASE self
|
||||
function AUTOLASE:SetMaxLasingTargets(Number)
|
||||
self.maxlasing = Number or 4
|
||||
return self
|
||||
end
|
||||
|
||||
--- (Internal) Function set notify pilots on events
|
||||
-- @param #AUTOLASE self
|
||||
-- @param #boolean OnOff Switch messaging on (true) or off (false)
|
||||
-- @return #AUTOLASE self
|
||||
function AUTOLASE:SetNotifyPilots(OnOff)
|
||||
self.notifypilots = OnOff and true
|
||||
return self
|
||||
end
|
||||
|
||||
--- (User) Function to set a specific code to a Recce.
|
||||
-- @param #AUTOLASE self
|
||||
-- @param #string RecceName (Unit!) Name of the Recce
|
||||
-- @param #number Code The lase code
|
||||
-- @return #AUTOLASE self
|
||||
function AUTOLASE:SetRecceLaserCode(RecceName, Code)
|
||||
local code = Code or 1688
|
||||
self.RecceLaserCode[RecceName] = code
|
||||
return self
|
||||
end
|
||||
|
||||
--- (User) Function to set a specific smoke color for a Recce.
|
||||
-- @param #AUTOLASE self
|
||||
-- @param #string RecceName (Unit!) Name of the Recce
|
||||
-- @param #number Color The color, e.g. SMOKECOLOR.Red, SMOKECOLOR.Green etc
|
||||
-- @return #AUTOLASE self
|
||||
function AUTOLASE:SetRecceSmokeColor(RecceName, Color)
|
||||
local color = Color or self.smokecolor
|
||||
self.RecceSmokeColor[RecceName] = color
|
||||
return self
|
||||
end
|
||||
|
||||
--- (User) Function to force laser cooldown and cool down time
|
||||
-- @param #AUTOLASE self
|
||||
-- @param #boolean OnOff Switch cool down on (true) or off (false) - defaults to true
|
||||
-- @param #number Seconds Number of seconds for cooldown - dafaults to 60 seconds
|
||||
-- @return #AUTOLASE self
|
||||
function AUTOLASE:SetLaserCoolDown(OnOff, Seconds)
|
||||
self.forcecooldown = OnOff and true
|
||||
self.cooldowntime = Seconds or 60
|
||||
return self
|
||||
end
|
||||
|
||||
--- (User) Function to set message show times.
|
||||
-- @param #AUTOLASE self
|
||||
-- @param #number long Longer show time
|
||||
-- @param #number short Shorter show time
|
||||
-- @return #AUTOLASE self
|
||||
function AUTOLASE:SetReportingTimes(long, short)
|
||||
self.reporttimeshort = short or 10
|
||||
self.reporttimelong = long or 30
|
||||
return self
|
||||
end
|
||||
|
||||
--- (User) Function to set lasing distance in meters and duration in seconds
|
||||
-- @param #AUTOLASE self
|
||||
-- @param #number Distance (Max) distance for lasing in meters - default 5000 meters
|
||||
-- @param #number Duration (Max) duration for lasing in seconds - default 300 secs
|
||||
-- @return #AUTOLASE self
|
||||
function AUTOLASE:SetLasingParameters(Distance, Duration)
|
||||
self.LaseDistance = Distance or 5000
|
||||
self.LaseDuration = Duration or 300
|
||||
return self
|
||||
end
|
||||
|
||||
--- (User) Function to set smoking of targets.
|
||||
-- @param #AUTOLASE self
|
||||
-- @param #boolean OnOff Switch smoking on or off
|
||||
-- @param #number Color Smokecolor, e.g. SMOKECOLOR.Red
|
||||
-- @return #AUTOLASE self
|
||||
function AUTOLASE:SetSmokeTargets(OnOff,Color)
|
||||
self.smoketargets = OnOff
|
||||
self.smokecolor = Color or SMOKECOLOR.Red
|
||||
return self
|
||||
end
|
||||
|
||||
--- (Internal) Function to calculate line of sight.
|
||||
-- @param #AUTOLASE self
|
||||
-- @param Wrapper.Unit#UNIT Unit
|
||||
-- @return #number LOS Line of sight in meters
|
||||
function AUTOLASE:GetLosFromUnit(Unit)
|
||||
local lasedistance = self.LaseDistance
|
||||
local unitheight = Unit:GetHeight()
|
||||
local coord = Unit:GetCoordinate()
|
||||
local landheight = coord:GetLandHeight()
|
||||
local asl = unitheight - landheight
|
||||
if asl > 100 then
|
||||
local absquare = lasedistance^2+asl^2
|
||||
lasedistance = math.sqrt(absquare)
|
||||
end
|
||||
return lasedistance
|
||||
end
|
||||
|
||||
--- (Internal) Function to check on lased targets.
|
||||
-- @param #AUTOLASE self
|
||||
-- @return #AUTOLASE self
|
||||
function AUTOLASE:CleanCurrentLasing()
|
||||
local lasingtable = self.CurrentLasing
|
||||
local newtable = {}
|
||||
local newreccecount = {}
|
||||
local lasing = 0
|
||||
|
||||
for _ind,_entry in pairs(lasingtable) do
|
||||
local entry = _entry -- #AUTOLASE.LaserSpot
|
||||
if not newreccecount[entry.reccename] then
|
||||
newreccecount[entry.reccename] = 0
|
||||
end
|
||||
end
|
||||
|
||||
for _,_recce in pairs (self.RecceSet:GetSetObjects()) do
|
||||
local recce = _recce --Wrapper.Group#GROUP
|
||||
if recce and recce:IsAlive() then
|
||||
local unit = recce:GetUnit(1)
|
||||
local name = unit:GetName()
|
||||
if not self.RecceUnits[name] then
|
||||
self.RecceUnits[name] = { name=name, unit=unit, cooldown = false, timestamp = timer.getAbsTime() }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for _ind,_entry in pairs(lasingtable) do
|
||||
local entry = _entry -- #AUTOLASE.LaserSpot
|
||||
local valid = 0
|
||||
local reccedead = false
|
||||
local unitdead = false
|
||||
local lostsight = false
|
||||
local timeout = false
|
||||
local Tnow = timer.getAbsTime()
|
||||
-- check recce dead
|
||||
local recce = entry.lasingunit
|
||||
if recce and recce:IsAlive() then
|
||||
valid = valid + 1
|
||||
else
|
||||
reccedead = true
|
||||
self:__RecceKIA(2,entry.reccename)
|
||||
end
|
||||
-- check entry dead
|
||||
local unit = entry.lasedunit
|
||||
if unit and unit:IsAlive() == true then
|
||||
valid = valid + 1
|
||||
else
|
||||
unitdead = true
|
||||
if not self.deadunitnotes[entry.unitname] then
|
||||
self.deadunitnotes[entry.unitname] = true
|
||||
self:__TargetDestroyed(2,entry.unitname,entry.reccename)
|
||||
end
|
||||
end
|
||||
-- check entry out of sight
|
||||
if not reccedead and not unitdead then
|
||||
if self:CanLase(recce,unit) then
|
||||
valid = valid + 1
|
||||
else
|
||||
lostsight = true
|
||||
entry.laserspot:LaseOff()
|
||||
self:__TargetLost(2,entry.unitname,entry.reccename)
|
||||
end
|
||||
end
|
||||
-- check timed out
|
||||
local timestamp = entry.timestamp
|
||||
if Tnow - timestamp < self.LaseDuration and not lostsight then
|
||||
valid = valid + 1
|
||||
else
|
||||
timeout = true
|
||||
entry.laserspot:LaseOff()
|
||||
|
||||
self.RecceUnits[entry.reccename].cooldown = true
|
||||
self.RecceUnits[entry.reccename].timestamp = timer.getAbsTime()
|
||||
|
||||
if not lostsight then
|
||||
self:__LaserTimeout(2,entry.unitname,entry.reccename)
|
||||
end
|
||||
end
|
||||
if valid == 4 then
|
||||
self.lasingindex = self.lasingindex + 1
|
||||
newtable[self.lasingindex] = entry
|
||||
newreccecount[entry.reccename] = newreccecount[entry.reccename] + 1
|
||||
lasing = lasing + 1
|
||||
end
|
||||
end
|
||||
self.CurrentLasing = newtable
|
||||
self.targetsperrecce = newreccecount
|
||||
return lasing
|
||||
end
|
||||
|
||||
--- (Internal) Function to show status.
|
||||
-- @param #AUTOLASE self
|
||||
-- @param Wrapper.Group#GROUP Group (Optional) show to a certain group
|
||||
-- @return #AUTOLASE self
|
||||
function AUTOLASE:ShowStatus(Group)
|
||||
local report = REPORT:New("Autolase")
|
||||
local reccetable = self.RecceSet:GetSetObjects()
|
||||
for _,_recce in pairs(reccetable) do
|
||||
if _recce and _recce:IsAlive() then
|
||||
local unit = _recce:GetUnit(1)
|
||||
local name = unit:GetName()
|
||||
local code = self:GetLaserCode(name)
|
||||
report:Add(string.format("Recce %s has code %d",name,code))
|
||||
end
|
||||
end
|
||||
local lines = 0
|
||||
for _ind,_entry in pairs(self.CurrentLasing) do
|
||||
local entry = _entry -- #AUTOLASE.LaserSpot
|
||||
local reccename = entry.reccename
|
||||
local typename = entry.unittype
|
||||
local code = entry.lasercode
|
||||
local locationstring = entry.location
|
||||
local text = string.format("%s lasing %s code %d\nat %s",reccename,typename,code,locationstring)
|
||||
report:Add(text)
|
||||
lines = lines + 1
|
||||
end
|
||||
if lines == 0 then
|
||||
report:Add("No targets!")
|
||||
end
|
||||
local reporttime = self.reporttimelong
|
||||
if lines == 0 then reporttime = self.reporttimeshort end
|
||||
if Group and Group:IsAlive() then
|
||||
local m = MESSAGE:New(report:Text(),reporttime,"Info"):ToGroup(Group)
|
||||
else
|
||||
local m = MESSAGE:New(report:Text(),reporttime,"Info"):ToCoalition(self.coalition)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- (Internal) Function to show messages.
|
||||
-- @param #AUTOLASE self
|
||||
-- @param #string Message The message to be sent
|
||||
-- @param #number Duration Duration in seconds
|
||||
-- @return #AUTOLASE self
|
||||
function AUTOLASE:NotifyPilots(Message,Duration)
|
||||
if self.usepilotset then
|
||||
local pilotset = self.pilotset:GetSetObjects() --#table
|
||||
for _,_pilot in pairs(pilotset) do
|
||||
local pilot = _pilot -- Wrapper.Unit#UNIT
|
||||
if pilot and pilot:IsAlive() then
|
||||
local Group = pilot:GetGroup()
|
||||
local m = MESSAGE:New(Message,Duration,"Autolase"):ToGroup(Group)
|
||||
end
|
||||
end
|
||||
elseif not self.debug then
|
||||
local m = MESSAGE:New(Message,Duration,"Autolase"):ToCoalition(self.coalition)
|
||||
else
|
||||
local m = MESSAGE:New(Message,Duration,"Autolase"):ToAll()
|
||||
end
|
||||
if self.debug then self:I(Message) end
|
||||
return self
|
||||
end
|
||||
|
||||
--- (User) Send messages via SRS.
|
||||
-- @param #AUTOLASE self
|
||||
-- @param #string Message The (short!) message to be sent, e.g. "Lasing target!"
|
||||
-- @return #AUTOLASE self
|
||||
-- @usage Step 1 - set up the radio basics **once** with
|
||||
-- my_autolase:SetUsingSRS(true,"C:\\path\\SRS-Folder",251,radio.modulation.AM)
|
||||
-- Step 2 - send a message, e.g.
|
||||
-- function my_autolase:OnAfterLasing(From, Event, To, LaserSpot)
|
||||
-- my_autolase:NotifyPilotsWithSRS("Reaper lasing new target!")
|
||||
-- end
|
||||
function AUTOLASE:NotifyPilotsWithSRS(Message)
|
||||
if self.useSRS then
|
||||
-- Create a SOUNDTEXT object.
|
||||
if self.debug then
|
||||
BASE:TraceOn()
|
||||
BASE:TraceClass("SOUNDTEXT")
|
||||
BASE:TraceClass("MSRS")
|
||||
end
|
||||
local path = self.SRSPath or "C:\\Program Files\\DCS-SimpleRadio-Standalone"
|
||||
local freq = self.SRSFreq or 271
|
||||
local mod = self.SRSMod or radio.modulation.AM
|
||||
local text=SOUNDTEXT:New(Message)
|
||||
-- MOOSE SRS
|
||||
local msrs=MSRS:New(path, freq, mod)
|
||||
-- Text-to speech with default voice after 2 seconds.
|
||||
msrs:PlaySoundText(text, 2)
|
||||
end
|
||||
if self.debug then self:I(Message) end
|
||||
return self
|
||||
end
|
||||
|
||||
--- (Internal) Function to check if a unit is already lased.
|
||||
-- @param #AUTOLASE self
|
||||
-- @param #string unitname Name of the unit to check
|
||||
-- @return #boolean outcome True or false
|
||||
function AUTOLASE:CheckIsLased(unitname)
|
||||
local outcome = false
|
||||
for _,_laserspot in pairs(self.CurrentLasing) do
|
||||
local spot = _laserspot -- #AUTOLASE.LaserSpot
|
||||
if spot.unitname == unitname then
|
||||
outcome = true
|
||||
break
|
||||
end
|
||||
end
|
||||
return outcome
|
||||
end
|
||||
|
||||
--- (Internal) Function to check if a unit can be lased.
|
||||
-- @param #AUTOLASE self
|
||||
-- @param Wrapper.Unit#UNIT Recce The Recce #UNIT
|
||||
-- @param Wrapper.Unit#UNIT Unit The lased #UNIT
|
||||
-- @return #boolean outcome True or false
|
||||
function AUTOLASE:CanLase(Recce,Unit)
|
||||
local canlase = false
|
||||
-- cooldown?
|
||||
if Recce and Recce:IsAlive() == true then
|
||||
local name = Recce:GetName()
|
||||
local cooldown = self.RecceUnits[name].cooldown and self.forcecooldown
|
||||
if cooldown then
|
||||
local Tdiff = timer.getAbsTime() - self.RecceUnits[name].timestamp
|
||||
if Tdiff < self.cooldowntime then
|
||||
return false
|
||||
else
|
||||
self.RecceUnits[name].cooldown = false
|
||||
end
|
||||
end
|
||||
-- calculate LOS
|
||||
local reccecoord = Recce:GetCoordinate()
|
||||
local unitcoord = Unit:GetCoordinate()
|
||||
local islos = reccecoord:IsLOS(unitcoord,2.5)
|
||||
-- calculate distance
|
||||
local distance = math.floor(reccecoord:Get3DDistance(unitcoord))
|
||||
local lasedistance = self:GetLosFromUnit(Recce)
|
||||
if distance <= lasedistance and islos then
|
||||
canlase = true
|
||||
end
|
||||
end
|
||||
return canlase
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------
|
||||
-- FSM Functions
|
||||
-------------------------------------------------------------------
|
||||
|
||||
--- (Internal) FSM Function for monitoring
|
||||
-- @param #AUTOLASE self
|
||||
-- @param #string From The from state
|
||||
-- @param #string Event The event
|
||||
-- @param #string To The to state
|
||||
-- @return #AUTOLASE self
|
||||
function AUTOLASE:onbeforeMonitor(From, Event, To)
|
||||
self:T({From, Event, To})
|
||||
-- Check if group has detected any units.
|
||||
self:UpdateIntel()
|
||||
return self
|
||||
end
|
||||
|
||||
--- (Internal) FSM Function for monitoring
|
||||
-- @param #AUTOLASE self
|
||||
-- @param #string From The from state
|
||||
-- @param #string Event The event
|
||||
-- @param #string To The to state
|
||||
-- @return #AUTOLASE self
|
||||
function AUTOLASE:onafterMonitor(From, Event, To)
|
||||
self:T({From, Event, To})
|
||||
|
||||
-- Housekeeping
|
||||
local countlases = self:CleanCurrentLasing()
|
||||
|
||||
self:SetPilotMenu()
|
||||
|
||||
local detecteditems = self.Contacts or {} -- #table of Ops.Intelligence#INTEL.Contact
|
||||
local groupsbythreat = {}
|
||||
local report = REPORT:New("Detections")
|
||||
local lines = 0
|
||||
for _,_contact in pairs(detecteditems) do
|
||||
local contact = _contact -- Ops.Intelligence#INTEL.Contact
|
||||
local grp = contact.group
|
||||
local coord = contact.position
|
||||
local reccename = contact.recce or "none"
|
||||
local reccegrp = UNIT:FindByName(reccename)
|
||||
if reccegrp then
|
||||
local reccecoord = reccegrp:GetCoordinate()
|
||||
local distance = math.floor(reccecoord:Get3DDistance(coord))
|
||||
local text = string.format("%s of %s | Distance %d km | Threatlevel %d",contact.attribute, contact.groupname, math.floor(distance/1000), contact.threatlevel)
|
||||
report:Add(text)
|
||||
self:T(text)
|
||||
if self.debug then self:I(text) end
|
||||
lines = lines + 1
|
||||
-- sort out groups beyond sight
|
||||
local lasedistance = self:GetLosFromUnit(reccegrp)
|
||||
if grp:IsGround() and lasedistance >= distance then
|
||||
table.insert(groupsbythreat,{contact.group,contact.threatlevel})
|
||||
self.RecceNames[contact.groupname] = contact.recce
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
self.GroupsByThreat = groupsbythreat
|
||||
|
||||
if self.verbose > 2 and lines > 0 then
|
||||
local m=MESSAGE:New(report:Text(),self.reporttimeshort,"Autolase"):ToAll()
|
||||
end
|
||||
|
||||
table.sort(self.GroupsByThreat, function(a,b)
|
||||
local aNum = a[2] -- Coin value of a
|
||||
local bNum = b[2] -- Coin value of b
|
||||
return aNum > bNum -- Return their comparisons, < for ascending, > for descending
|
||||
end)
|
||||
|
||||
-- build table of Units
|
||||
local unitsbythreat = {}
|
||||
for _,_entry in pairs(self.GroupsByThreat) do
|
||||
local group = _entry[1] -- Wrapper.Group#GROUP
|
||||
if group and group:IsAlive() then
|
||||
local units = group:GetUnits()
|
||||
local reccename = self.RecceNames[group:GetName()]
|
||||
for _,_unit in pairs(units) do
|
||||
local unit = _unit -- Wrapper.Unit#UNIT
|
||||
if unit and unit:IsAlive() then
|
||||
local threat = unit:GetThreatLevel()
|
||||
local coord = unit:GetCoordinate()
|
||||
if threat > 0 then
|
||||
local unitname = unit:GetName()
|
||||
table.insert(unitsbythreat,{unit,threat})
|
||||
self.RecceUnitNames[unitname] = reccename
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
self.UnitsByThreat = unitsbythreat
|
||||
|
||||
table.sort(self.UnitsByThreat, function(a,b)
|
||||
local aNum = a[2] -- Coin value of a
|
||||
local bNum = b[2] -- Coin value of b
|
||||
return aNum > bNum -- Return their comparisons, < for ascending, > for descending
|
||||
end)
|
||||
|
||||
local unitreport = REPORT:New("Detected Units")
|
||||
|
||||
local lines = 0
|
||||
for _,_entry in pairs(self.UnitsByThreat) do
|
||||
local threat = _entry[2]
|
||||
local unit = _entry[1]
|
||||
local unitname = unit:GetName()
|
||||
local text = string.format("Unit %s | Threatlevel %d | Detected by %s",unitname,threat,self.RecceUnitNames[unitname])
|
||||
unitreport:Add(text)
|
||||
lines = lines + 1
|
||||
self:T(text)
|
||||
if self.debug then self:I(text) end
|
||||
end
|
||||
|
||||
if self.verbose > 2 and lines > 0 then
|
||||
local m=MESSAGE:New(unitreport:Text(),self.reporttimeshort,"Autolase"):ToAll()
|
||||
end
|
||||
|
||||
for _,_detectingunit in pairs(self.RecceUnits) do
|
||||
|
||||
local reccename = _detectingunit.name
|
||||
local recce = _detectingunit.unit
|
||||
local reccecount = self.targetsperrecce[reccename] or 0
|
||||
local targets = 0
|
||||
for _,_entry in pairs(self.UnitsByThreat) do
|
||||
local unit = _entry[1] -- Wrapper.Unit#UNIT
|
||||
local unitname = unit:GetName()
|
||||
local canlase = self:CanLase(recce,unit)
|
||||
if targets+reccecount < self.maxlasing and not self:CheckIsLased(unitname) and unit:IsAlive() and canlase then
|
||||
targets = targets + 1
|
||||
local code = self:GetLaserCode(reccename)
|
||||
local spot = SPOT:New(recce)
|
||||
spot:LaseOn(unit,code,self.LaseDuration)
|
||||
local locationstring = unit:GetCoordinate():ToStringLLDDM()
|
||||
local laserspot = { -- #AUTOLASE.LaserSpot
|
||||
laserspot = spot,
|
||||
lasedunit = unit,
|
||||
lasingunit = recce,
|
||||
lasercode = code,
|
||||
location = locationstring,
|
||||
timestamp = timer.getAbsTime(),
|
||||
unitname = unitname,
|
||||
reccename = reccename,
|
||||
unittype = unit:GetTypeName(),
|
||||
}
|
||||
if self.smoketargets then
|
||||
local coord = unit:GetCoordinate()
|
||||
local color = self:GetSmokeColor(reccename)
|
||||
coord:Smoke(color)
|
||||
end
|
||||
self.lasingindex = self.lasingindex + 1
|
||||
self.CurrentLasing[self.lasingindex] = laserspot
|
||||
self:__Lasing(2,laserspot)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
self:__Monitor(-30)
|
||||
return self
|
||||
end
|
||||
|
||||
--- (Internal) FSM Function onbeforeRecceKIA
|
||||
-- @param #AUTOLASE self
|
||||
-- @param #string From The from state
|
||||
-- @param #string Event The event
|
||||
-- @param #string To The to state
|
||||
-- @param #string RecceName The lost Recce
|
||||
-- @return #AUTOLASE self
|
||||
function AUTOLASE:onbeforeRecceKIA(From,Event,To,RecceName)
|
||||
self:T({From, Event, To, RecceName})
|
||||
if self.notifypilots or self.debug then
|
||||
local text = string.format("Recce %s KIA!",RecceName)
|
||||
self:NotifyPilots(text,self.reporttimeshort)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- (Internal) FSM Function onbeforeTargetDestroyed
|
||||
-- @param #AUTOLASE self
|
||||
-- @param #string From The from state
|
||||
-- @param #string Event The event
|
||||
-- @param #string To The to state
|
||||
-- @param #string UnitName The destroyed unit\'s name
|
||||
-- @param #string RecceName The Recce name lasing
|
||||
-- @return #AUTOLASE self
|
||||
function AUTOLASE:onbeforeTargetDestroyed(From,Event,To,UnitName,RecceName)
|
||||
self:T({From, Event, To, UnitName, RecceName})
|
||||
if self.notifypilots or self.debug then
|
||||
local text = string.format("Unit %s destroyed! Good job!",UnitName)
|
||||
self:NotifyPilots(text,self.reporttimeshort)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- (Internal) FSM Function onbeforeTargetLost
|
||||
-- @param #AUTOLASE self
|
||||
-- @param #string From The from state
|
||||
-- @param #string Event The event
|
||||
-- @param #string To The to state
|
||||
-- @param #string UnitName The lost unit\'s name
|
||||
-- @param #string RecceName The Recce name lasing
|
||||
-- @return #AUTOLASE self
|
||||
function AUTOLASE:onbeforeTargetLost(From,Event,To,UnitName,RecceName)
|
||||
self:T({From, Event, To, UnitName,RecceName})
|
||||
if self.notifypilots or self.debug then
|
||||
local text = string.format("%s lost sight of unit %s.",RecceName,UnitName)
|
||||
self:NotifyPilots(text,self.reporttimeshort)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- (Internal) FSM Function onbeforeLaserTimeout
|
||||
-- @param #AUTOLASE self
|
||||
-- @param #string From The from state
|
||||
-- @param #string Event The event
|
||||
-- @param #string To The to state
|
||||
-- @param #string UnitName The lost unit\'s name
|
||||
-- @param #string RecceName The Recce name lasing
|
||||
-- @return #AUTOLASE self
|
||||
function AUTOLASE:onbeforeLaserTimeout(From,Event,To,UnitName,RecceName)
|
||||
self:T({From, Event, To, UnitName,RecceName})
|
||||
if self.notifypilots or self.debug then
|
||||
local text = string.format("%s laser timeout on unit %s.",RecceName,UnitName)
|
||||
self:NotifyPilots(text,self.reporttimeshort)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- (Internal) FSM Function onbeforeLasing
|
||||
-- @param #AUTOLASE self
|
||||
-- @param #string From The from state
|
||||
-- @param #string Event The event
|
||||
-- @param #string To The to state
|
||||
-- @param Functional.Autolase#AUTOLASE.LaserSpot LaserSpot The LaserSpot data table
|
||||
-- @return #AUTOLASE self
|
||||
function AUTOLASE:onbeforeLasing(From,Event,To,LaserSpot)
|
||||
self:T({From, Event, To, LaserSpot.unittype})
|
||||
if self.notifypilots or self.debug then
|
||||
local laserspot = LaserSpot -- #AUTOLASE.LaserSpot
|
||||
local text = string.format("%s is lasing %s code %d\nat %s",laserspot.reccename,laserspot.unittype,laserspot.lasercode,laserspot.location)
|
||||
self:NotifyPilots(text,self.reporttimeshort+5)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- (Internal) FSM Function onbeforeCancel
|
||||
-- @param #AUTOLASE self
|
||||
-- @param #string From The from state
|
||||
-- @param #string Event The event
|
||||
-- @param #string To The to state
|
||||
-- @return #AUTOLASE self
|
||||
function AUTOLASE:onbeforeCancel(From,Event,To)
|
||||
self:UnHandleEvent(EVENTS.PlayerEnterAircraft)
|
||||
self:__Stop(2)
|
||||
return self
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------
|
||||
-- End Functional.Autolase.lua
|
||||
-------------------------------------------------------------------
|
||||
@@ -1001,7 +1001,13 @@ do -- DESIGNATE
|
||||
local ID = self.Detection:GetDetectedItemID( DetectedItem )
|
||||
local MenuText = ID --.. ", " .. Coord:ToStringA2G( AttackGroup )
|
||||
|
||||
MenuText = string.format( "(%3s) %s", Designating, MenuText )
|
||||
-- Use injected MenuName from TaskA2GDispatcher if using same Detection Object
|
||||
if DetectedItem.DesignateMenuName then
|
||||
MenuText = string.format( "(%3s) %s", Designating, DetectedItem.DesignateMenuName )
|
||||
else
|
||||
MenuText = string.format( "(%3s) %s", Designating, MenuText )
|
||||
end
|
||||
|
||||
local DetectedMenu = MENU_GROUP_DELAYED:New( AttackGroup, MenuText, MenuDesignate ):SetTime( MenuTime ):SetTag( self.DesignateName )
|
||||
|
||||
-- Build the Lasing menu.
|
||||
|
||||
@@ -599,7 +599,7 @@ do -- DETECTION_BASE
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @param Wrapper.Group#GROUP DetectionGroup The Group detecting.
|
||||
-- @param Wrapper.Group#GROUP Detection The Group detecting.
|
||||
-- @param #number DetectionTimeStamp Time stamp of detection event.
|
||||
function DETECTION_BASE:onafterDetection( From, Event, To, Detection, DetectionTimeStamp )
|
||||
|
||||
@@ -670,7 +670,7 @@ do -- DETECTION_BASE
|
||||
|
||||
local DetectedObjectVec3 = DetectedObject:getPoint()
|
||||
local DetectedObjectVec2 = { x = DetectedObjectVec3.x, y = DetectedObjectVec3.z }
|
||||
local DetectionGroupVec3 = Detection:GetVec3()
|
||||
local DetectionGroupVec3 = Detection:GetVec3() or {x=0,y=0,z=0}
|
||||
local DetectionGroupVec2 = { x = DetectionGroupVec3.x, y = DetectionGroupVec3.z }
|
||||
|
||||
local Distance = ( ( DetectedObjectVec3.x - DetectionGroupVec3.x )^2 +
|
||||
@@ -714,7 +714,7 @@ do -- DETECTION_BASE
|
||||
if self.RejectZones then
|
||||
for RejectZoneID, RejectZone in pairs( self.RejectZones ) do
|
||||
local RejectZone = RejectZone -- Core.Zone#ZONE_BASE
|
||||
if RejectZone:IsPointVec2InZone( DetectedObjectVec2 ) == true then
|
||||
if RejectZone:IsVec2InZone( DetectedObjectVec2 ) == true then
|
||||
DetectionAccepted = false
|
||||
end
|
||||
end
|
||||
@@ -759,7 +759,7 @@ do -- DETECTION_BASE
|
||||
local ZoneProbability = ZoneData[2] -- #number
|
||||
ZoneProbability = ZoneProbability * 30 / 300
|
||||
|
||||
if ZoneObject:IsPointVec2InZone( DetectedObjectVec2 ) == true then
|
||||
if ZoneObject:IsVec2InZone( DetectedObjectVec2 ) == true then
|
||||
local Probability = math.random() -- Selects a number between 0 and 1
|
||||
--self:T( { Probability, ZoneProbability } )
|
||||
if Probability > ZoneProbability then
|
||||
@@ -2485,13 +2485,13 @@ do -- DETECTION_AREAS
|
||||
-- ## 4.1) Retrieve the Detected Unit Sets and Detected Zones
|
||||
--
|
||||
-- The methods to manage the DetectedItems[].Set(s) are implemented in @{Functional.Detection#DECTECTION_BASE} and
|
||||
-- the methods to manage the DetectedItems[].Zone(s) is implemented in @{Functional.Detection#DETECTION_AREAS}.
|
||||
-- the methods to manage the DetectedItems[].Zone(s) are implemented in @{Functional.Detection#DETECTION_AREAS}.
|
||||
--
|
||||
-- Retrieve the DetectedItems[].Set with the method @{Functional.Detection#DETECTION_BASE.GetDetectedSet}(). A @{Core.Set#SET_UNIT} object will be returned.
|
||||
--
|
||||
-- Retrieve the formed @{Zone@ZONE_UNIT}s as a result of the grouping the detected units within the DetectionZoneRange, use the method @{Functional.Detection#DETECTION_BASE.GetDetectionZones}().
|
||||
-- To understand the amount of zones created, use the method @{Functional.Detection#DETECTION_BASE.GetDetectionZoneCount}().
|
||||
-- If you want to obtain a specific zone from the DetectedZones, use the method @{Functional.Detection#DETECTION_BASE.GetDetectionZone}() with a given index.
|
||||
-- Retrieve the formed @{Zone@ZONE_UNIT}s as a result of the grouping the detected units within the DetectionZoneRange, use the method @{Functional.Detection#DETECTION_AREAS.GetDetectionZones}().
|
||||
-- To understand the amount of zones created, use the method @{Functional.Detection#DETECTION_AREAS.GetDetectionZoneCount}().
|
||||
-- If you want to obtain a specific zone from the DetectedZones, use the method @{Functional.Detection#DETECTION_AREAS.GetDetectionZoneByID}() with a given index.
|
||||
--
|
||||
-- ## 4.4) Flare or Smoke detected units
|
||||
--
|
||||
@@ -2535,7 +2535,49 @@ do -- DETECTION_AREAS
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Retrieve set of detected zones.
|
||||
-- @param #DETECTION_AREAS self
|
||||
-- @return Core.Set#SET_ZONE The @{Set} of ZONE_UNIT objects detected.
|
||||
function DETECTION_AREAS:GetDetectionZones()
|
||||
local zoneset = SET_ZONE:New()
|
||||
for _ID,_Item in pairs (self.DetectedItems) do
|
||||
local item = _Item -- #DETECTION_BASE.DetectedItem
|
||||
if item.Zone then
|
||||
zoneset:AddZone(item.Zone)
|
||||
end
|
||||
end
|
||||
return zoneset
|
||||
end
|
||||
|
||||
--- Retrieve a specific zone by its ID (number)
|
||||
-- @param #DETECTION_AREAS self
|
||||
-- @param #number ID
|
||||
-- @return Core.Zone#ZONE_UNIT The zone or nil if it does not exist
|
||||
function DETECTION_AREAS:GetDetectionZoneByID(ID)
|
||||
local zone = nil
|
||||
for _ID,_Item in pairs (self.DetectedItems) do
|
||||
local item = _Item -- #DETECTION_BASE.DetectedItem
|
||||
if item.ID == ID then
|
||||
zone = item.Zone
|
||||
break
|
||||
end
|
||||
end
|
||||
return zone
|
||||
end
|
||||
|
||||
--- Retrieve number of detected zones.
|
||||
-- @param #DETECTION_AREAS self
|
||||
-- @return #number The number of zones.
|
||||
function DETECTION_AREAS:GetDetectionZoneCount()
|
||||
local zoneset = 0
|
||||
for _ID,_Item in pairs (self.DetectedItems) do
|
||||
if _Item.Zone then
|
||||
zoneset = zoneset + 1
|
||||
end
|
||||
end
|
||||
return zoneset
|
||||
end
|
||||
|
||||
--- Report summary of a detected item using a given numeric index.
|
||||
-- @param #DETECTION_AREAS self
|
||||
-- @param #DETECTION_BASE.DetectedItem DetectedItem The DetectedItem.
|
||||
|
||||
@@ -0,0 +1,730 @@
|
||||
--- **Functional** - (R2.5) - Monitor and export flight model data.
|
||||
--
|
||||
-- Record flight data and export it to a csv file.
|
||||
--
|
||||
-- ## Recorded Data:
|
||||
--
|
||||
-- * Time,
|
||||
-- * Altitude,
|
||||
-- * Temperature,
|
||||
-- * Pressure,
|
||||
-- * Velocity (total and x,y,z components),
|
||||
-- * Acceleration (total and x,y,z components),
|
||||
-- * AoA,
|
||||
-- * Pitch and pitch rate,
|
||||
-- * Roll and roll rate,
|
||||
-- * Yaw and yaw rate,
|
||||
-- * Turn rate.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **funkyfranky**
|
||||
-- @module Functional.FMData
|
||||
-- @image Functional_Rat2.png
|
||||
|
||||
|
||||
--- FMD class.
|
||||
-- @type FMD
|
||||
-- @field #string ClassName Name of the class.
|
||||
-- @field #boolean Debug Debug mode. Messages to all about status.
|
||||
-- @field #string lid Class id string for output to DCS log file.
|
||||
-- @field #table players Player table.
|
||||
-- @field #table menuadded Table of units where the F10 radio menu was added.
|
||||
-- @field #string savepath Path to save data files.
|
||||
-- @extends Core.Fsm#FSM
|
||||
|
||||
--- Be sure!
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- # The FMD Concept
|
||||
--
|
||||
-- This class can be used to record flight data such as velocity, acceleration, pitch, roll, yaw and turn rates. The output is written to a csv file for later analysis.
|
||||
--
|
||||
-- # Usage
|
||||
--
|
||||
-- The script is very easy to use. It only requires the line
|
||||
--
|
||||
-- fmd=FMD:New()
|
||||
--
|
||||
-- This will automatically start the FMD script.
|
||||
--
|
||||
-- Each player will get an entry "FMD" in the F-10 radio menu. There he can start or stop the data recording.
|
||||
--
|
||||
-- **IMPORTANT**
|
||||
-- Due to a DCS bug, it is necessary that (in single player mode), player/clients have to **hit ESC twice** before entering an aircraft client slot.
|
||||
-- Otherwise, the script will not load and now menus will be created for the player.
|
||||
--
|
||||
-- # Output
|
||||
--
|
||||
-- After the data recording is completed, the data is written to a csv file. The file name starts with **FMD** and contains the employed airframe plus a running number.
|
||||
--
|
||||
-- However, one must desanitize the **io** and **lfs** lines the DCS root directory. Otherwise, DCS will not allow data to be written to file.
|
||||
--
|
||||
-- @field #FMD
|
||||
FMD = {
|
||||
ClassName = "FMD",
|
||||
Debug = false,
|
||||
lid = nil,
|
||||
players = {},
|
||||
menuadded = {},
|
||||
savepath = nil,
|
||||
}
|
||||
|
||||
--- Player data table.
|
||||
-- @type FMD.PlayerData
|
||||
-- @field Core.Scheduler#SCHEDULER scheduler Scheduler
|
||||
-- @field Wrapper.Unit#UNIT unit Player unit.
|
||||
-- @field #string unitname Name of the unit.
|
||||
-- @field Wrapper.Client#CLIENT client Player client.
|
||||
-- @field #string actype Aircraft type.
|
||||
-- @field #string name Player name.
|
||||
-- @field #number dt Time step for data recording in seconds. Default 0.01 sec ==> 100 data points per second!
|
||||
-- @field #number rd Recording duration in seconds.
|
||||
-- @field #boolean recording If true, recording started.
|
||||
-- @field #table data Data table.
|
||||
-- @field #number SID Scheduler ID.
|
||||
|
||||
--- Data point table.
|
||||
-- @type FMD.DataPoint
|
||||
-- @field #number time Abs mission time.
|
||||
-- @field #number T Temperature in degrees Celsius.
|
||||
-- @field #number P Pressure.
|
||||
-- @field #number Alt Altitude ASL in meters.
|
||||
-- @field #number AoA Angle of Attack in degrees.
|
||||
-- @field #number Pitch Pitch angle in degrees.
|
||||
-- @field #number Roll Roll angle in degrees.
|
||||
-- @field #number Yaw Yaw angle in degrees.
|
||||
-- @field #number Climbrate Climb rate in m/s.
|
||||
-- @field #number Vtot Total velocity in m/s.
|
||||
-- @field DCS#Vec3 v Velocity vector. Components x,y,z in m/s.
|
||||
-- @field DCS#Vec3 o Orientation vector. Components x,y,z in meters.
|
||||
-- @field #number omega Angle velocity in m/s.
|
||||
-- @field #number Hdg Heading in degrees.
|
||||
-- @field #number Atot Total acceleration m/s^2.
|
||||
-- @field DCS#Vec3 a Acceleration vector.
|
||||
-- @field #number DRoll Roll speed in degrees/second.
|
||||
-- @field #number DPitch Pitch speed in degrees/second.
|
||||
-- @field #number DYaw Yaw speed in degrees/second.
|
||||
|
||||
--- Main group level radio menu: F10 Other/FMD.
|
||||
-- @field #table MenuF10
|
||||
FMD.MenuF10={}
|
||||
|
||||
--- FMD mission level F10 root menu.
|
||||
-- @field #table MenuF10Root
|
||||
FMD.MenuF10Root=nil
|
||||
|
||||
--- FMD script version.
|
||||
-- @field #string version
|
||||
FMD.version="0.1.0"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Constructor
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Create a new FMD class object.
|
||||
-- @param #FMD self
|
||||
-- @return #FMD self.
|
||||
function FMD:New()
|
||||
|
||||
-- Inherit everthing from FSM class.
|
||||
local self=BASE:Inherit(self, FSM:New()) -- #FMD
|
||||
|
||||
-- Start State.
|
||||
self:SetStartState("Stopped")
|
||||
|
||||
-- Log string.
|
||||
self.lid="FMD | "
|
||||
|
||||
-- Add FSM transitions.
|
||||
-- From State --> Event --> To State
|
||||
self:AddTransition("Stopped", "Start", "Running") -- Start FMD script.
|
||||
self:AddTransition("*", "Status", "*") -- Start FMD script.
|
||||
|
||||
-- Start FMD.
|
||||
self:Start()
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- On after Start event.
|
||||
-- @param #FMD self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
function FMD:onafterStart(From, Event, To)
|
||||
|
||||
-- Short info.
|
||||
local text=string.format("Starting Flight Model Data script version %s", FMD.version)
|
||||
self:I(self.lid..text)
|
||||
|
||||
-- Handle events.
|
||||
self:HandleEvent(EVENTS.Birth)
|
||||
end
|
||||
|
||||
--- On after Stop event.
|
||||
-- @param #FMD self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
function FMD:onafterStop(From, Event, To)
|
||||
|
||||
-- Short info.
|
||||
local text=string.format("Stopping Flight Model Data script version %s", FMD.version)
|
||||
self:I(self.lid..text)
|
||||
|
||||
-- Handle events.
|
||||
self:UnHandleEvent(EVENTS.Birth)
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Event Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- FMD event handler for event birth.
|
||||
-- @param #FMD self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function FMD:OnEventBirth(EventData)
|
||||
self:F3({eventbirth = EventData})
|
||||
|
||||
local _unitName=EventData.IniUnitName
|
||||
local _unit, _playername=self:_GetPlayerUnitAndName(_unitName)
|
||||
|
||||
self:T2(self.lid.."BIRTH: unit = "..tostring(EventData.IniUnitName))
|
||||
self:T2(self.lid.."BIRTH: group = "..tostring(EventData.IniGroupName))
|
||||
self:T2(self.lid.."BIRTH: player = "..tostring(_playername))
|
||||
|
||||
if _unit and _playername then
|
||||
|
||||
local _uid=_unit:GetID()
|
||||
local _group=_unit:GetGroup()
|
||||
local _callsign=_unit:GetCallsign()
|
||||
|
||||
-- Debug output.
|
||||
local text=string.format("Pilot %s, callsign %s entered unit %s of group %s.", _playername, _callsign, _unitName, _group:GetName())
|
||||
self:T(self.lid..text)
|
||||
MESSAGE:New(text, 5):ToAllIf(self.Debug)
|
||||
|
||||
-- Add Menu commands.
|
||||
self:_AddF10Commands(_unitName)
|
||||
|
||||
-- Player data.
|
||||
self.players[_playername]={}
|
||||
|
||||
local player=self.players[_playername] --#FMD.PlayerData
|
||||
|
||||
player.scheduler=SCHEDULER:New()
|
||||
player.unit=_unit
|
||||
player.unitname=_unitName
|
||||
player.name=_playername
|
||||
player.client=CLIENT:FindByName(_unitName, nil, true)
|
||||
player.actype=_unit:GetTypeName()
|
||||
player.dt=0.01
|
||||
player.rd=30
|
||||
player.recording=false
|
||||
player.data={}
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Data Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Get data of unit.
|
||||
-- @param #FMD self
|
||||
-- @param Wrapper.Unit#UNIT unit
|
||||
-- @return #FMD.DataPoint Datapoint.
|
||||
function FMD:_GetDataPoint(unit)
|
||||
|
||||
if unit and unit:IsAlive() then
|
||||
|
||||
-- Current coordinate.
|
||||
local coord=unit:GetCoordinate()
|
||||
|
||||
local dp={} --#FMD.DataPoint
|
||||
|
||||
dp.a={}
|
||||
dp.Alt=coord.y
|
||||
dp.AoA=unit:GetAoA()
|
||||
dp.Atot=nil
|
||||
dp.P=coord:GetPressure()
|
||||
dp.Pitch=unit:GetPitch()
|
||||
dp.Roll=unit:GetRoll()
|
||||
dp.time=timer.getAbsTime()
|
||||
dp.T=coord:GetTemperature()
|
||||
dp.v=unit:GetVelocityVec3()
|
||||
dp.Vtot=UTILS.VecNorm(dp.v)
|
||||
dp.Yaw=unit:GetYaw()
|
||||
dp.o=unit:GetOrientationX()
|
||||
dp.Hdg=unit:GetHeading()
|
||||
|
||||
return dp
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
--- Get data of unit.
|
||||
-- @param #FMD self
|
||||
-- @param #FMD.PlayerData playerData Player data.
|
||||
-- @return #FMD.DataPoint Datapoint.
|
||||
function FMD:_Derivative(playerData)
|
||||
|
||||
local function numderiv(fpm,fpp,h)
|
||||
return 0.5*(fpp-fpm)/h
|
||||
end
|
||||
|
||||
local datapoints=playerData.data
|
||||
--local dt=playerData.dt
|
||||
|
||||
for i=2,#datapoints-1 do
|
||||
|
||||
local dpm=datapoints[i-1] --#FMD.DataPoint
|
||||
local dpp=datapoints[i+1] --#FMD.DataPoint
|
||||
local dpi=datapoints[i] --#FMD.DataPoint
|
||||
|
||||
-- Time step.
|
||||
local dt=0.5*(dpp.time-dpm.time)
|
||||
|
||||
dpi.Atot=numderiv(dpm.Vtot, dpp.Vtot, dt)
|
||||
|
||||
dpi.a.x=numderiv(dpm.v.x, dpp.v.x, dt)
|
||||
dpi.a.y=numderiv(dpm.v.y, dpp.v.y, dt)
|
||||
dpi.a.z=numderiv(dpm.v.z, dpp.v.z, dt)
|
||||
|
||||
dpi.DPitch=numderiv(dpm.Pitch, dpp.Pitch, dt)
|
||||
--dpi.DRoll=numderiv(dpm.Roll, dpp.Roll, dt)
|
||||
dpi.DYaw=numderiv(dpm.Yaw, dpp.Yaw, dt)
|
||||
|
||||
-- Roll shortcuts.
|
||||
local r1=dpm.Roll
|
||||
local r2=dpp.Roll
|
||||
|
||||
-- Put roll in [0,360)
|
||||
if (r1<0) then
|
||||
r1=r1+360
|
||||
end
|
||||
if (r2<0) then
|
||||
r2=r2+360
|
||||
end
|
||||
|
||||
-- Handle case where 360 deg periodicity strikes.
|
||||
if r1<90 and r2>270 then
|
||||
r1=r1+360
|
||||
end
|
||||
if r1>270 and r2<90 then
|
||||
r2=r2+360
|
||||
end
|
||||
|
||||
--
|
||||
dpi.DRoll=numderiv(r1, r2, dt)
|
||||
|
||||
local ang=UTILS.VecAngle(dpm.o, dpp.o)
|
||||
dpi.omega=numderiv(0, ang, dt)
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Record data
|
||||
-- @param #FMD self
|
||||
-- @param #FMD.PlayerData playerData Player data table.
|
||||
function FMD:_RecordData(playerData)
|
||||
|
||||
-- Check if we are recording already.
|
||||
if not playerData.recording then
|
||||
|
||||
-- Inform player.
|
||||
local text=string.format("Data recording starts for %.1f sec.", playerData.rd)
|
||||
MESSAGE:New(text, 3, "FMD"):ToClient(playerData.client)
|
||||
|
||||
-- Activate recording switch.
|
||||
playerData.recording=true
|
||||
end
|
||||
|
||||
-- Check if unit is alive.
|
||||
if playerData.unit and playerData.unit:IsAlive() then
|
||||
|
||||
-- Get data point.
|
||||
local dp=self:_GetDataPoint(playerData.unit)
|
||||
|
||||
-- Add data point to player table.
|
||||
table.insert(playerData.data, dp)
|
||||
|
||||
else
|
||||
-- Stop recording if player unit is not alive.
|
||||
self:_StopRecording(playerData.unitname)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Save data.
|
||||
-- @param #FMD self
|
||||
-- @param #FMD.PlayerData playerData Player data table.
|
||||
function FMD:_SaveData(playerData)
|
||||
|
||||
-- Nothing to save.
|
||||
if playerData==nil or #playerData.data==0 or not io then
|
||||
return
|
||||
end
|
||||
|
||||
--- Function that saves data to file
|
||||
local function _savefile(filename, data)
|
||||
local f = assert(io.open(filename, "wb"))
|
||||
f:write(data)
|
||||
f:close()
|
||||
end
|
||||
|
||||
-- Set path or default.
|
||||
local path=self.savepath
|
||||
if lfs then
|
||||
path=path or lfs.writedir()
|
||||
end
|
||||
|
||||
-- Create unused file name.
|
||||
local filename=nil
|
||||
for i=1,9999 do
|
||||
|
||||
-- Create file name.
|
||||
filename=string.format("FMD-%s_%s-%04d.csv", playerData.name, playerData.actype, i)
|
||||
|
||||
-- Set path.
|
||||
if path~=nil then
|
||||
filename=path.."\\"..filename
|
||||
end
|
||||
|
||||
-- Check if file exists.
|
||||
local _exists=UTILS.FileExists(filename)
|
||||
if not _exists then
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- Info
|
||||
local text=string.format("Saving player %s flight data to file %s", playerData.name, filename)
|
||||
self:I(self.lid..text)
|
||||
|
||||
-- Header line
|
||||
local data="#Time,Altitude,Temperature,Pressure,Vtot,Vx,Vy,Vz,Atot,ax,ay,az,AoA,Pitch,dPitch/dt,Roll,dRoll/dt,Yaw,dYaw/dt,Turn Rate\n"
|
||||
|
||||
local g0=playerData.data[1] --#FMD.DataPoint
|
||||
local T0=g0.time
|
||||
|
||||
-- Calculate derivatives.
|
||||
self:_Derivative(playerData)
|
||||
|
||||
for i=2,#playerData.data-1 do
|
||||
|
||||
local dp=playerData.data[i] --#FMD.DataPoint
|
||||
|
||||
-- Conversion m/s == Mach.
|
||||
local ms2mach=0.00291545
|
||||
|
||||
local t=(dp.time-T0) or 0
|
||||
local a=dp.Alt or 0
|
||||
local b=dp.T or 0
|
||||
local c=dp.P or 0
|
||||
local d=dp.Vtot or 0
|
||||
local e=dp.v.x or 0
|
||||
local f=dp.v.y or 0
|
||||
local g=dp.v.z or 0
|
||||
local h=dp.Atot or 0
|
||||
local i=dp.a.x or 0
|
||||
local j=dp.a.y or 0
|
||||
local k=dp.a.z or 0
|
||||
local l=dp.AoA or 0
|
||||
local m=dp.Pitch or 0
|
||||
local n=dp.DPitch or 0
|
||||
local roll=dp.Roll or 0
|
||||
if roll<0 then
|
||||
roll=roll+360
|
||||
end
|
||||
local o=roll --dp.Roll or 0
|
||||
local p=dp.DRoll or 0
|
||||
local q=dp.Yaw or 0
|
||||
local r=dp.DYaw or 0
|
||||
local s=dp.omega or 0
|
||||
|
||||
-- Debug output.
|
||||
self:T3(t)
|
||||
self:T3(a)
|
||||
self:T3(b)
|
||||
self:T3(c)
|
||||
self:T3(d)
|
||||
self:T3(e)
|
||||
self:T3(f)
|
||||
self:T3(g)
|
||||
self:T3(h)
|
||||
self:T3(i)
|
||||
self:T3(j)
|
||||
self:T3(k)
|
||||
self:T3(l)
|
||||
self:T3(m)
|
||||
self:T3(n)
|
||||
self:T3(o)
|
||||
self:T3(p)
|
||||
self:T3(q)
|
||||
self:T3(r)
|
||||
self:T3(s)
|
||||
|
||||
-- t a b c d e f g h i j k l m n o p q r s
|
||||
data=data..string.format("%.2f,%.2f,%.2f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f\n",
|
||||
t, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s)
|
||||
|
||||
end
|
||||
|
||||
-- Save file.
|
||||
_savefile(filename, data)
|
||||
|
||||
-- Clear data.
|
||||
playerData.data={}
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- RADIO MENU Functions
|
||||
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Add menu commands for player.
|
||||
-- @param #FMD self
|
||||
-- @param #string _unitName Name of player unit.
|
||||
function FMD:_AddF10Commands(_unitName)
|
||||
self:F(_unitName)
|
||||
|
||||
-- Get player unit and name.
|
||||
local _unit, playername = self:_GetPlayerUnitAndName(_unitName)
|
||||
|
||||
-- Check for player unit.
|
||||
if _unit and playername then
|
||||
|
||||
-- Get group and ID.
|
||||
local group=_unit:GetGroup()
|
||||
local gid=group:GetID()
|
||||
|
||||
if group and gid then
|
||||
|
||||
if not self.menuadded[gid] then
|
||||
|
||||
-- Enable switch so we don't do this twice.
|
||||
self.menuadded[gid]=true
|
||||
|
||||
-- Set menu root path.
|
||||
local _rootPath=nil
|
||||
if FMD.MenuF10Root then
|
||||
------------------------
|
||||
-- MISSON LEVEL MENUE --
|
||||
------------------------
|
||||
|
||||
-- F10/FMD/...
|
||||
_rootPath=FMD.MenuF10Root
|
||||
|
||||
else
|
||||
------------------------
|
||||
-- GROUP LEVEL MENUES --
|
||||
------------------------
|
||||
|
||||
-- Main F10 menu: F10/FMD/
|
||||
if FMD.MenuF10[gid]==nil then
|
||||
FMD.MenuF10[gid]=missionCommands.addSubMenuForGroup(gid, "FMD")
|
||||
end
|
||||
|
||||
|
||||
-- F10/FMD/...
|
||||
_rootPath=FMD.MenuF10[gid]
|
||||
|
||||
end
|
||||
|
||||
--------------------------------
|
||||
-- F10/F<X> FMD/F1 Time Interval
|
||||
--------------------------------
|
||||
local _timePath=missionCommands.addSubMenuForGroup(gid, "Time Interval", _rootPath)
|
||||
-- F10/FMD/F1 Time Interval/
|
||||
missionCommands.addCommandForGroup(gid, "Delta t=0.01 s", _timePath, self._SetTimeInterval, self, _unitName, 0.01)
|
||||
missionCommands.addCommandForGroup(gid, "Delta t=0.1 s", _timePath, self._SetTimeInterval, self, _unitName, 0.1)
|
||||
missionCommands.addCommandForGroup(gid, "Delta t=1.0 s", _timePath, self._SetTimeInterval, self, _unitName, 1.0)
|
||||
missionCommands.addCommandForGroup(gid, "Delta t=10 s", _timePath, self._SetTimeInterval, self, _unitName, 10.0)
|
||||
missionCommands.addCommandForGroup(gid, "Delta t=30 s", _timePath, self._SetTimeInterval, self, _unitName, 30.0)
|
||||
missionCommands.addCommandForGroup(gid, "Delta t=60 s", _timePath, self._SetTimeInterval, self, _unitName, 60.0)
|
||||
|
||||
-------------------------------
|
||||
-- F10/F<X> FMD/F1 Rec Duration
|
||||
-------------------------------
|
||||
local _durPath=missionCommands.addSubMenuForGroup(gid, "Rec Duration", _rootPath)
|
||||
-- F10/FMD/F1 Rec Duration/
|
||||
missionCommands.addCommandForGroup(gid, "T=10 s", _durPath, self._SetRecDuration, self, _unitName, 10)
|
||||
missionCommands.addCommandForGroup(gid, "T=30 s", _durPath, self._SetRecDuration, self, _unitName, 30)
|
||||
missionCommands.addCommandForGroup(gid, "T=60 s", _durPath, self._SetRecDuration, self, _unitName, 60)
|
||||
missionCommands.addCommandForGroup(gid, "T=5 min", _durPath, self._SetRecDuration, self, _unitName, 5*60)
|
||||
missionCommands.addCommandForGroup(gid, "T=10 min", _durPath, self._SetRecDuration, self, _unitName, 10*60)
|
||||
|
||||
--------------------------------
|
||||
-- F10/F<X> FMD/
|
||||
--------------------------------
|
||||
|
||||
-- F10/FMD/Start Recording
|
||||
missionCommands.addCommandForGroup(gid, "Start Recording", _rootPath, self._StartRecording, self, _unitName) -- F1
|
||||
missionCommands.addCommandForGroup(gid, "Stop Recording", _rootPath, self._StopRecording, self, _unitName) -- F2
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- Set recording duration.
|
||||
-- @param #FMD self
|
||||
-- @param #string _unitName Name of player unit.
|
||||
-- @param #number rd Recording duration in sec.
|
||||
function FMD:_SetRecDuration(_unitName, rd)
|
||||
|
||||
-- Get player unit and name.
|
||||
local _unit, _playername = self:_GetPlayerUnitAndName(_unitName)
|
||||
|
||||
-- Check if we have a unit which is a player.
|
||||
if _unit and _playername then
|
||||
local playerData=self.players[_playername] --#FMD.PlayerData
|
||||
|
||||
if playerData then
|
||||
|
||||
-- Set dt.
|
||||
playerData.rd=rd
|
||||
|
||||
-- Inform player.
|
||||
local text=string.format("Data recording duration set to %.1f sec.", playerData.rd)
|
||||
MESSAGE:New(text, 10, "FMD"):ToClient(playerData.client)
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- Set time interval for data recording.
|
||||
-- @param #FMD self
|
||||
-- @param #string _unitName Name of player unit.
|
||||
-- @param #number dt Time interval in seconds.
|
||||
function FMD:_SetTimeInterval(_unitName, dt)
|
||||
|
||||
-- Get player unit and name.
|
||||
local _unit, _playername = self:_GetPlayerUnitAndName(_unitName)
|
||||
|
||||
-- Check if we have a unit which is a player.
|
||||
if _unit and _playername then
|
||||
local playerData=self.players[_playername] --#FMD.PlayerData
|
||||
|
||||
if playerData then
|
||||
|
||||
-- Set dt.
|
||||
playerData.dt=dt
|
||||
|
||||
-- Inform player.
|
||||
local text=string.format("Data recording time interval set to %.3f sec.", playerData.dt)
|
||||
MESSAGE:New(text, 10, "FMD"):ToClient(playerData.client)
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Start data recording.
|
||||
-- @param #FMD self
|
||||
-- @param #string _unitName Name of player unit.
|
||||
function FMD:_StartRecording(_unitName)
|
||||
|
||||
-- Get player unit and name.
|
||||
local _unit, _playername = self:_GetPlayerUnitAndName(_unitName)
|
||||
|
||||
-- Check if we have a unit which is a player.
|
||||
if _unit and _playername then
|
||||
local playerData=self.players[_playername] --#FMD.PlayerData
|
||||
|
||||
if playerData then
|
||||
|
||||
-- Delay before recording starts.
|
||||
local delay=3
|
||||
|
||||
-- Inform player.
|
||||
local text=string.format("Data recording will be started in %d seconds with %.3f sec timestep for %.1f sec.", delay, playerData.dt, playerData.rd)
|
||||
MESSAGE:New(text, 3, "FMD"):ToClient(playerData.client)
|
||||
|
||||
-- Start scheduler.
|
||||
playerData.SID=playerData.scheduler:Schedule(nil, self._RecordData, {self, playerData}, delay, playerData.dt, 0.0)
|
||||
|
||||
-- Stop scheduler once.
|
||||
playerData.scheduler:ScheduleOnce(playerData.rd+delay, self._StopRecording, self,_unitName)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Stop data recording.
|
||||
-- @param #FMD self
|
||||
-- @param #string _unitName Name of player unit.
|
||||
function FMD:_StopRecording(_unitName)
|
||||
|
||||
-- Get player unit and name.
|
||||
local _unit, _playername = self:_GetPlayerUnitAndName(_unitName)
|
||||
|
||||
-- Check if we have a unit which is a player.
|
||||
if _unit and _playername then
|
||||
local playerData=self.players[_playername] --#FMD.PlayerData
|
||||
|
||||
if playerData then
|
||||
|
||||
local ndata=#playerData.data
|
||||
|
||||
-- Inform player.
|
||||
local text=string.format("Data recording stopped. Data points recorded: %d", ndata)
|
||||
MESSAGE:New(text, 10, "FMD"):ToClient(playerData.client)
|
||||
|
||||
-- No recording switch.
|
||||
playerData.recording=false
|
||||
|
||||
-- Stop scheduler.
|
||||
playerData.scheduler:Stop(playerData.SID)
|
||||
|
||||
-- Save data.
|
||||
self:_SaveData(playerData)
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Returns the unit of a player and the player name. If the unit does not belong to a player, nil is returned.
|
||||
-- @param #FMD self
|
||||
-- @param #string _unitName Name of the player unit.
|
||||
-- @return Wrapper.Unit#UNIT Unit of player or nil.
|
||||
-- @return #string Name of the player or nil.
|
||||
function FMD:_GetPlayerUnitAndName(_unitName)
|
||||
self:F2(_unitName)
|
||||
|
||||
if _unitName ~= nil then
|
||||
|
||||
-- Get DCS unit from its name.
|
||||
local DCSunit=Unit.getByName(_unitName)
|
||||
|
||||
if DCSunit then
|
||||
|
||||
local playername=DCSunit:getPlayerName()
|
||||
local unit=UNIT:Find(DCSunit)
|
||||
|
||||
self:T2({DCSunit=DCSunit, unit=unit, playername=playername})
|
||||
if DCSunit and unit and playername then
|
||||
return unit, playername
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-- Return nil if we could not find a player.
|
||||
return nil,nil
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
-- @module Functional.FOX
|
||||
-- @image Functional_FOX.png
|
||||
|
||||
|
||||
--- FOX class.
|
||||
-- @type FOX
|
||||
-- @field #string ClassName Name of the class.
|
||||
@@ -47,8 +46,7 @@
|
||||
-- @field #number dt10 Time step [sec] for missile position updates if distance to target > 10 km and < 50 km. Default 1 sec.
|
||||
-- @field #number dt05 Time step [sec] for missile position updates if distance to target > 5 km and < 10 km. Default 0.5 sec.
|
||||
-- @field #number dt01 Time step [sec] for missile position updates if distance to target > 1 km and < 5 km. Default 0.1 sec.
|
||||
-- @field #number dt00 Time step [sec] for missile position updates if distance to target < 1 km. Default 0.01 sec.
|
||||
-- @field #boolean
|
||||
-- @field #number dt00 Time step [sec] for missile position updates if distance to target < 1 km. Default 0.01 sec.
|
||||
-- @extends Core.Fsm#FSM
|
||||
|
||||
--- Fox 3!
|
||||
@@ -1707,8 +1705,8 @@ end
|
||||
--- Returns the unit of a player and the player name. If the unit does not belong to a player, nil is returned.
|
||||
-- @param #FOX self
|
||||
-- @param DCS#Weapon weapon The weapon.
|
||||
-- @return #number Notching heading right, i.e. missile heading +90�
|
||||
-- @return #number Notching heading left, i.e. missile heading -90�.
|
||||
-- @return #number Notching heading right, i.e. missile heading +90°.
|
||||
-- @return #number Notching heading left, i.e. missile heading -90°.
|
||||
function FOX:_GetNotchingHeadings(weapon)
|
||||
|
||||
if weapon then
|
||||
@@ -1813,4 +1811,4 @@ end
|
||||
|
||||
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,28 +1,28 @@
|
||||
--- **Functional** -- Train missile defence and deflection.
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ## Features:
|
||||
--
|
||||
--
|
||||
-- * Track the missiles fired at you and other players, providing bearing and range information of the missiles towards the airplanes.
|
||||
-- * Provide alerts of missile launches, including detailed information of the units launching, including bearing, range �
|
||||
-- * Provide alerts of missile launches, including detailed information of the units launching, including bearing, range
|
||||
-- * Provide alerts when a missile would have killed your aircraft.
|
||||
-- * Provide alerts when the missile self destructs.
|
||||
-- * Enable / Disable and Configure the Missile Trainer using the various menu options.
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- ## Missions:
|
||||
--
|
||||
--
|
||||
-- [MIT - Missile Trainer](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/MIT%20-%20Missile%20Trainer)
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- Uses the MOOSE messaging system to be alerted of any missiles fired, and when a missile would hit your aircraft,
|
||||
-- the class will destroy the missile within a certain range, to avoid damage to your aircraft.
|
||||
--
|
||||
--
|
||||
-- When running a mission where the missile trainer is used, the following radio menu structure ( 'Radio Menu' -> 'Other (F10)' -> 'MissileTrainer' ) options are available for the players:
|
||||
--
|
||||
--
|
||||
-- * **Messages**: Menu to configure all messages.
|
||||
-- * **Messages On**: Show all messages.
|
||||
-- * **Messages Off**: Disable all messages.
|
||||
@@ -45,23 +45,23 @@
|
||||
-- * **Range Off**: Disable range information when a missile is fired to a target.
|
||||
-- * **Bearing On**: Shows bearing information when a missile is fired to a target.
|
||||
-- * **Bearing Off**: Disable bearing information when a missile is fired to a target.
|
||||
-- * **Distance**: Menu to configure the distance when a missile needs to be destroyed when near to a player, during tracking. This will improve/influence hit calculation accuracy, but has the risk of damaging the aircraft when the missile reaches the aircraft before the distance is measured.
|
||||
-- * **Distance**: Menu to configure the distance when a missile needs to be destroyed when near to a player, during tracking. This will improve/influence hit calculation accuracy, but has the risk of damaging the aircraft when the missile reaches the aircraft before the distance is measured.
|
||||
-- * **50 meter**: Destroys the missile when the distance to the aircraft is below or equal to 50 meter.
|
||||
-- * **100 meter**: Destroys the missile when the distance to the aircraft is below or equal to 100 meter.
|
||||
-- * **150 meter**: Destroys the missile when the distance to the aircraft is below or equal to 150 meter.
|
||||
-- * **200 meter**: Destroys the missile when the distance to the aircraft is below or equal to 200 meter.
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- ### Authors: **FlightControl**
|
||||
--
|
||||
--
|
||||
-- ### Contributions:
|
||||
--
|
||||
-- * **Stuka (Danny)**: Who you can search on the Eagle Dynamics Forums. Working together with Danny has resulted in the MISSILETRAINER class.
|
||||
-- Danny has shared his ideas and together we made a design.
|
||||
--
|
||||
-- * **Stuka (Danny)**: Who you can search on the Eagle Dynamics Forums. Working together with Danny has resulted in the MISSILETRAINER class.
|
||||
-- Danny has shared his ideas and together we made a design.
|
||||
-- Together with the **476 virtual team**, we tested the MISSILETRAINER class, and got much positive feedback!
|
||||
-- * **132nd Squadron**: Testing and optimizing the logic.
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module Functional.MissileTrainer
|
||||
@@ -76,7 +76,7 @@
|
||||
---
|
||||
--
|
||||
-- # Constructor:
|
||||
--
|
||||
--
|
||||
-- Create a new MISSILETRAINER object with the @{#MISSILETRAINER.New} method:
|
||||
--
|
||||
-- * @{#MISSILETRAINER.New}: Creates a new MISSILETRAINER object taking the maximum distance to your aircraft to evaluate when a missile needs to be destroyed.
|
||||
@@ -84,7 +84,7 @@
|
||||
-- MISSILETRAINER will collect each unit declared in the mission with a skill level "Client" and "Player", and will monitor the missiles shot at those.
|
||||
--
|
||||
-- # Initialization:
|
||||
--
|
||||
--
|
||||
-- A MISSILETRAINER object will behave differently based on the usage of initialization methods:
|
||||
--
|
||||
-- * @{#MISSILETRAINER.InitMessagesOnOff}: Sets by default the display of any message to be ON or OFF.
|
||||
@@ -97,8 +97,8 @@
|
||||
-- * @{#MISSILETRAINER.InitRangeOnOff}: Sets by default the display of range information of missiles ON of OFF.
|
||||
-- * @{#MISSILETRAINER.InitBearingOnOff}: Sets by default the display of bearing information of missiles ON of OFF.
|
||||
-- * @{#MISSILETRAINER.InitMenusOnOff}: Allows to configure the options through the radio menu.
|
||||
--
|
||||
-- @field #MISSILETRAINER
|
||||
--
|
||||
-- @field #MISSILETRAINER
|
||||
MISSILETRAINER = {
|
||||
ClassName = "MISSILETRAINER",
|
||||
TrackingMissiles = {},
|
||||
@@ -167,7 +167,7 @@ end
|
||||
-- When a missile is fired a SCHEDULER is set off that follows the missile. When near a certain a client player, the missile will be destroyed.
|
||||
-- @param #MISSILETRAINER self
|
||||
-- @param #number Distance The distance in meters when a tracked missile needs to be destroyed when close to a player.
|
||||
-- @param #string Briefing (Optional) Will show a text to the players when starting their mission. Can be used for briefing purposes.
|
||||
-- @param #string Briefing (Optional) Will show a text to the players when starting their mission. Can be used for briefing purposes.
|
||||
-- @return #MISSILETRAINER
|
||||
function MISSILETRAINER:New( Distance, Briefing )
|
||||
local self = BASE:Inherit( self, BASE:New() )
|
||||
@@ -194,8 +194,8 @@ function MISSILETRAINER:New( Distance, Briefing )
|
||||
-- self:F( "ForEach:" .. Client.UnitName )
|
||||
-- Client:Alive( self._Alive, self )
|
||||
-- end
|
||||
--
|
||||
self.DBClients:ForEachClient(
|
||||
--
|
||||
self.DBClients:ForEachClient(
|
||||
function( Client )
|
||||
self:F( "ForEach:" .. Client.UnitName )
|
||||
Client:Alive( self._Alive, self )
|
||||
@@ -207,9 +207,9 @@ function MISSILETRAINER:New( Distance, Briefing )
|
||||
-- self.DB:ForEachClient(
|
||||
-- --- @param Wrapper.Client#CLIENT Client
|
||||
-- function( Client )
|
||||
--
|
||||
--
|
||||
-- ... actions ...
|
||||
--
|
||||
--
|
||||
-- end
|
||||
-- )
|
||||
|
||||
@@ -225,7 +225,7 @@ function MISSILETRAINER:New( Distance, Briefing )
|
||||
|
||||
self.DetailsRangeOnOff = true
|
||||
self.DetailsBearingOnOff = true
|
||||
|
||||
|
||||
self.MenusOnOff = true
|
||||
|
||||
self.TrackingMissiles = {}
|
||||
@@ -293,7 +293,7 @@ end
|
||||
--- Increases, decreases the missile tracking message display frequency with the provided time interval in seconds.
|
||||
-- The default frequency is a 3 second interval, so the Tracking Frequency parameter specifies the increase or decrease from the default 3 seconds or the last frequency update.
|
||||
-- @param #MISSILETRAINER self
|
||||
-- @param #number TrackingFrequency Provide a negative or positive value in seconds to incraese or decrease the display frequency.
|
||||
-- @param #number TrackingFrequency Provide a negative or positive value in seconds to incraese or decrease the display frequency.
|
||||
-- @return #MISSILETRAINER self
|
||||
function MISSILETRAINER:InitTrackingFrequency( TrackingFrequency )
|
||||
self:F( TrackingFrequency )
|
||||
@@ -478,30 +478,30 @@ function MISSILETRAINER:OnEventShot( EVentData )
|
||||
if TrainerTargetDCSUnit then
|
||||
local TrainerTargetDCSUnitName = Unit.getName( TrainerTargetDCSUnit )
|
||||
local TrainerTargetSkill = _DATABASE.Templates.Units[TrainerTargetDCSUnitName].Template.skill
|
||||
|
||||
|
||||
self:T(TrainerTargetDCSUnitName )
|
||||
|
||||
|
||||
local Client = self.DBClients:FindClient( TrainerTargetDCSUnitName )
|
||||
if Client then
|
||||
|
||||
|
||||
local TrainerSourceUnit = UNIT:Find( TrainerSourceDCSUnit )
|
||||
local TrainerTargetUnit = UNIT:Find( TrainerTargetDCSUnit )
|
||||
|
||||
|
||||
if self.MessagesOnOff == true and self.AlertsLaunchesOnOff == true then
|
||||
|
||||
|
||||
local Message = MESSAGE:New(
|
||||
string.format( "%s launched a %s",
|
||||
TrainerSourceUnit:GetTypeName(),
|
||||
TrainerWeaponName
|
||||
) .. self:_AddRange( Client, TrainerWeapon ) .. self:_AddBearing( Client, TrainerWeapon ), 5, "Launch Alert" )
|
||||
|
||||
|
||||
if self.AlertsToAll then
|
||||
Message:ToAll()
|
||||
else
|
||||
Message:ToClient( Client )
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local ClientID = Client:GetID()
|
||||
self:T( ClientID )
|
||||
local MissileData = {}
|
||||
@@ -579,52 +579,52 @@ function MISSILETRAINER:_TrackMissiles()
|
||||
end
|
||||
|
||||
-- ALERTS PART
|
||||
|
||||
|
||||
-- Loop for all Player Clients to check the alerts and deletion of missiles.
|
||||
for ClientDataID, ClientData in pairs( self.TrackingMissiles ) do
|
||||
|
||||
local Client = ClientData.Client
|
||||
|
||||
|
||||
if Client and Client:IsAlive() then
|
||||
|
||||
for MissileDataID, MissileData in pairs( ClientData.MissileData ) do
|
||||
self:T3( MissileDataID )
|
||||
|
||||
|
||||
local TrainerSourceUnit = MissileData.TrainerSourceUnit
|
||||
local TrainerWeapon = MissileData.TrainerWeapon
|
||||
local TrainerTargetUnit = MissileData.TrainerTargetUnit
|
||||
local TrainerWeaponTypeName = MissileData.TrainerWeaponTypeName
|
||||
local TrainerWeaponLaunched = MissileData.TrainerWeaponLaunched
|
||||
|
||||
|
||||
if Client and Client:IsAlive() and TrainerSourceUnit and TrainerSourceUnit:IsAlive() and TrainerWeapon and TrainerWeapon:isExist() and TrainerTargetUnit and TrainerTargetUnit:IsAlive() then
|
||||
local PositionMissile = TrainerWeapon:getPosition().p
|
||||
local TargetVec3 = Client:GetVec3()
|
||||
|
||||
|
||||
local Distance = ( ( PositionMissile.x - TargetVec3.x )^2 +
|
||||
( PositionMissile.y - TargetVec3.y )^2 +
|
||||
( PositionMissile.z - TargetVec3.z )^2
|
||||
) ^ 0.5 / 1000
|
||||
|
||||
|
||||
if Distance <= self.Distance then
|
||||
-- Hit alert
|
||||
TrainerWeapon:destroy()
|
||||
if self.MessagesOnOff == true and self.AlertsHitsOnOff == true then
|
||||
|
||||
|
||||
self:T( "killed" )
|
||||
|
||||
|
||||
local Message = MESSAGE:New(
|
||||
string.format( "%s launched by %s killed %s",
|
||||
TrainerWeapon:getTypeName(),
|
||||
TrainerSourceUnit:GetTypeName(),
|
||||
TrainerTargetUnit:GetPlayerName()
|
||||
), 15, "Hit Alert" )
|
||||
|
||||
|
||||
if self.AlertsToAll == true then
|
||||
Message:ToAll()
|
||||
else
|
||||
Message:ToClient( Client )
|
||||
end
|
||||
|
||||
|
||||
MissileData = nil
|
||||
table.remove( ClientData.MissileData, MissileDataID )
|
||||
self:T(ClientData.MissileData)
|
||||
@@ -639,7 +639,7 @@ function MISSILETRAINER:_TrackMissiles()
|
||||
TrainerWeaponTypeName,
|
||||
TrainerSourceUnit:GetTypeName()
|
||||
), 5, "Tracking" )
|
||||
|
||||
|
||||
if self.AlertsToAll == true then
|
||||
Message:ToAll()
|
||||
else
|
||||
@@ -660,41 +660,41 @@ function MISSILETRAINER:_TrackMissiles()
|
||||
if ShowMessages == true and self.MessagesOnOff == true and self.TrackingOnOff == true then -- Only do this when tracking information needs to be displayed.
|
||||
|
||||
-- TRACKING PART
|
||||
|
||||
|
||||
-- For the current client, the missile range and bearing details are displayed To the Player Client.
|
||||
-- For the other clients, the missile range and bearing details are displayed To the other Player Clients.
|
||||
-- To achieve this, a cross loop is done for each Player Client <-> Other Player Client missile information.
|
||||
|
||||
-- To achieve this, a cross loop is done for each Player Client <-> Other Player Client missile information.
|
||||
|
||||
-- Main Player Client loop
|
||||
for ClientDataID, ClientData in pairs( self.TrackingMissiles ) do
|
||||
|
||||
|
||||
local Client = ClientData.Client
|
||||
--self:T2( { Client:GetName() } )
|
||||
|
||||
|
||||
|
||||
|
||||
ClientData.MessageToClient = ""
|
||||
ClientData.MessageToAll = ""
|
||||
|
||||
|
||||
-- Other Players Client loop
|
||||
for TrackingDataID, TrackingData in pairs( self.TrackingMissiles ) do
|
||||
|
||||
|
||||
for MissileDataID, MissileData in pairs( TrackingData.MissileData ) do
|
||||
--self:T3( MissileDataID )
|
||||
|
||||
|
||||
local TrainerSourceUnit = MissileData.TrainerSourceUnit
|
||||
local TrainerWeapon = MissileData.TrainerWeapon
|
||||
local TrainerTargetUnit = MissileData.TrainerTargetUnit
|
||||
local TrainerWeaponTypeName = MissileData.TrainerWeaponTypeName
|
||||
local TrainerWeaponLaunched = MissileData.TrainerWeaponLaunched
|
||||
|
||||
|
||||
if Client and Client:IsAlive() and TrainerSourceUnit and TrainerSourceUnit:IsAlive() and TrainerWeapon and TrainerWeapon:isExist() and TrainerTargetUnit and TrainerTargetUnit:IsAlive() then
|
||||
|
||||
|
||||
if ShowMessages == true then
|
||||
local TrackingTo
|
||||
TrackingTo = string.format( " -> %s",
|
||||
TrainerWeaponTypeName
|
||||
)
|
||||
|
||||
|
||||
if ClientDataID == TrackingDataID then
|
||||
if ClientData.MessageToClient == "" then
|
||||
ClientData.MessageToClient = "Missiles to You:\n"
|
||||
@@ -712,7 +712,7 @@ function MISSILETRAINER:_TrackMissiles()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- Once the Player Client and the Other Player Client tracking messages are prepared, show them.
|
||||
if ClientData.MessageToClient ~= "" or ClientData.MessageToAll ~= "" then
|
||||
local Message = MESSAGE:New( ClientData.MessageToClient .. ClientData.MessageToAll, 1, "Tracking" ):ToClient( Client )
|
||||
|
||||
@@ -742,7 +742,7 @@ function PSEUDOATC:ReportWeather(GID, UID, position, location)
|
||||
local T=position:GetTemperature()
|
||||
|
||||
-- Correct unit system.
|
||||
local _T=string.format('%d°F', UTILS.CelciusToFarenheit(T))
|
||||
local _T=string.format('%d°F', UTILS.CelsiusToFahrenheit(T))
|
||||
if settings:IsMetric() then
|
||||
_T=string.format('%d°C', T)
|
||||
end
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,855 @@
|
||||
--- **Ops** - (R2.5) - Random Air Traffic.
|
||||
--
|
||||
--
|
||||
--
|
||||
-- RAT2 creates random air traffic on the map.
|
||||
--
|
||||
--
|
||||
--
|
||||
-- **Main Features:**
|
||||
--
|
||||
-- * It's very random.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **funkyfranky**
|
||||
-- @module Functional.Rat2
|
||||
-- @image Functional_Rat2.png
|
||||
|
||||
|
||||
--- RAT2 class.
|
||||
-- @type RAT2
|
||||
-- @field #string ClassName Name of the class.
|
||||
-- @field #boolean Debug Debug mode. Messages to all about status.
|
||||
-- @field #string lid Class id string for output to DCS log file.
|
||||
-- @field #table Qcraft Table of rat crafts.
|
||||
-- @extends Core.Fsm#FSM
|
||||
|
||||
--- Be surprised!
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- # The RAT2 Concept
|
||||
--
|
||||
--
|
||||
--
|
||||
-- @field #RAT2
|
||||
RAT2 = {
|
||||
ClassName = "RAT2",
|
||||
Debug = false,
|
||||
lid = nil,
|
||||
Qcraft = {},
|
||||
}
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Constructor
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Create a new RAT2 class object.
|
||||
-- @param #RAT2 self
|
||||
-- @return #RAT2 self.
|
||||
function RAT2:New()
|
||||
|
||||
-- Inherit everthing from FSM class.
|
||||
local self=BASE:Inherit(self, FSM:New()) -- #RAT2
|
||||
|
||||
-- Start State.
|
||||
self:SetStartState("Stopped")
|
||||
|
||||
self.lid="RAT2 | "
|
||||
|
||||
-- Add FSM transitions.
|
||||
-- From State --> Event --> To State
|
||||
self:AddTransition("Stopped", "Load", "Stopped") -- Load player scores from file.
|
||||
self:AddTransition("Stopped", "Start", "Running") -- Start RAT2 script.
|
||||
self:AddTransition("*", "Status", "*") -- Start RAT2 script.
|
||||
self:AddTransition("*", "Spawned", "*") -- A group was spawned.
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Create a new RAT2 class object.
|
||||
-- @param #RAT2 self
|
||||
-- @param #RATAC ratcraft The aircraft to add.
|
||||
-- @return #RAT2 self.
|
||||
function RAT2:AddAircraft(ratcraft)
|
||||
|
||||
-- Add ratcraft to spawn queue.
|
||||
table.insert(self.Qcraft, ratcraft)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Status Functions
|
||||
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Start random air traffic.
|
||||
-- @param #RAT2 self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
function RAT2:onafterStart(From, Event, To)
|
||||
|
||||
self:__Status(-1)
|
||||
end
|
||||
|
||||
|
||||
--- Check spawn queue and spawn aircraft if necessary.
|
||||
-- @param #RAT2 self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
function RAT2:onafterStatus(From, Event, To)
|
||||
|
||||
self:I(string.format("RAT status %s", self:GetState()))
|
||||
|
||||
-- Check queue of aircraft to spawn.
|
||||
self:_CheckQueueSpawn()
|
||||
|
||||
self:__Status(-10)
|
||||
end
|
||||
|
||||
|
||||
--- Check spawn queue and spawn aircraft if necessary.
|
||||
-- @param #RAT2 self
|
||||
function RAT2:_CheckQueueSpawn()
|
||||
|
||||
self:I(string.format("Checking flights"))
|
||||
|
||||
-- Loop over all ratcraft.
|
||||
for i,_ratcraft in pairs(self.Qcraft) do
|
||||
local ratcraft=_ratcraft --Functional.RatCraft#RATCRAFT
|
||||
|
||||
-- Get number of alive groups.
|
||||
local Nalive=ratcraft:_GetAliveGroups()
|
||||
|
||||
for _,_flight in pairs(ratcraft.flights) do
|
||||
local flight=_flight --Ops.FlightGroup#FLIGHTGROUP
|
||||
|
||||
self:I(string.format("Checking flight group %s in status %s", flight.groupname, flight:GetState()))
|
||||
|
||||
if flight:IsInUtero() or flight:IsArrived() or flight:IsDead() then
|
||||
|
||||
self:I(string.format("Try Spawning new flight group %s in status %s", flight.groupname, flight:GetState()))
|
||||
|
||||
-- Get departure and parking.
|
||||
local departure, parking=ratcraft:GetDeparture()
|
||||
|
||||
-- Get destination depending on departure.
|
||||
local destination=ratcraft:GetDestination(departure)
|
||||
|
||||
if departure and destination and parking then
|
||||
|
||||
self:I(string.format("Spawning new flight group %s in status %s", flight.groupname, flight:GetState()))
|
||||
|
||||
-- Try to spawn a ratcraft group.
|
||||
local group=self:_SpawnRatcraft(ratcraft, flight, departure, destination, parking)
|
||||
|
||||
-- Remove queue item and break loop.
|
||||
if group then
|
||||
|
||||
-- We add a little delay
|
||||
self:__Spawned(0.1, group, flight, ratcraft)
|
||||
|
||||
break
|
||||
end -- if group
|
||||
|
||||
end -- if departure
|
||||
end -- if spawn flight
|
||||
end -- for flights
|
||||
end -- for ratcraft
|
||||
end
|
||||
|
||||
--- Check spawn queue and spawn aircraft if necessary.
|
||||
-- @param #RAT2 self
|
||||
function RAT2:_CheckArrived()
|
||||
|
||||
for _,_ratcraft in pairs(self.Qcraft) do
|
||||
local ratcraft=_ratcraft --Functional.RatCraft#RATCRAFT
|
||||
for _,_flight in pairs(ratcraft.flights) do
|
||||
local flight=_flight --Ops.FlightGroup#FLIGHTGROUP
|
||||
|
||||
if flight:IsArrived() then
|
||||
-- Destroy group and create a remove unit event.
|
||||
flight.group:Destroy(nil)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- FSM functions
|
||||
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Function called after a RAT group was spawned.
|
||||
-- @param #RAT2 self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param Wrapper.Group#GROUP group The spawned group.
|
||||
-- @param Ops.FlightGroup#FLIGHTGROUP flight The flight group.
|
||||
-- @param Functional.RatCraft#RATCRAFT ratcraft The ratcraft object.
|
||||
function RAT2:onafterSpawned(From, Event, To, group, flight, ratcraft)
|
||||
|
||||
group:SmokeGreen()
|
||||
|
||||
end
|
||||
|
||||
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Spawn functions
|
||||
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Spawn an aircraft asset (plane or helo) at the airbase associated with the warehouse.
|
||||
-- @param #RAT2 self
|
||||
-- @param Functional.RatCraft#RATCRAFT ratcraft Ratcraft to spawn.
|
||||
-- @param Ops.FlightGroup#FLIGHTGROUP flight Flight group.
|
||||
-- @param Functional.RatCraft#RATCRAFT.Departure departure Departure.
|
||||
-- @param Functional.RatCraft#RATCRAFT.Departure destination Destination.
|
||||
-- @param #table parking Parking data for this group.
|
||||
-- @return Wrapper.Group#GROUP The spawned group or nil if the group could not be spawned.
|
||||
function RAT2:_SpawnRatcraft(ratcraft, flight, departure, destination, parking)
|
||||
|
||||
-- Prepare the spawn template.
|
||||
local template=self:_SpawnAssetPrepareTemplate(ratcraft, flight.groupname)
|
||||
|
||||
-- Get flight path if the group goes to another warehouse by itself.
|
||||
template.route.points=self:_GetFlightplan(ratcraft, AIRBASE:FindByName(departure.name), AIRBASE:FindByName(destination.name))
|
||||
|
||||
local airbase=AIRBASE:FindByName(departure.name) --self:_GetAirbase(departure)
|
||||
|
||||
-- Get airbase ID and category.
|
||||
local AirbaseID = airbase:GetID()
|
||||
local AirbaseCategory = airbase:GetAirbaseCategory()
|
||||
|
||||
-- Check enough parking spots.
|
||||
if AirbaseCategory==Airbase.Category.HELIPAD or AirbaseCategory==Airbase.Category.SHIP then
|
||||
|
||||
--TODO Figure out what's necessary in this case.
|
||||
|
||||
else
|
||||
|
||||
if #parking<#template.units then
|
||||
local text=string.format("ERROR: Not enough parking! Free parking = %d < %d aircraft to be spawned.", #parking, #template.units)
|
||||
self:_DebugMessage(text)
|
||||
return nil
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-- Position the units.
|
||||
for i=1,#template.units do
|
||||
|
||||
-- Unit template.
|
||||
local unit = template.units[i]
|
||||
|
||||
if AirbaseCategory == Airbase.Category.HELIPAD or AirbaseCategory == Airbase.Category.SHIP then
|
||||
|
||||
-- Helipads we take the position of the airbase location, since the exact location of the spawn point does not make sense.
|
||||
local coord=airbase:GetCoordinate()
|
||||
|
||||
unit.x=coord.x
|
||||
unit.y=coord.z
|
||||
unit.alt=coord.y
|
||||
|
||||
unit.parking_id = nil
|
||||
unit.parking = nil
|
||||
|
||||
else
|
||||
|
||||
local coord=parking[i].Coordinate --Core.Point#COORDINATE
|
||||
local terminal=parking[i].TerminalID --#number
|
||||
|
||||
if self.Debug then
|
||||
coord:MarkToAll(string.format("Spawnplace unit %s terminal %d.", unit.name, terminal))
|
||||
end
|
||||
|
||||
unit.x=coord.x
|
||||
unit.y=coord.z
|
||||
unit.alt=coord.y
|
||||
|
||||
unit.parking_id = nil
|
||||
unit.parking = terminal
|
||||
|
||||
end
|
||||
|
||||
if destination.parking then
|
||||
local spot=destination.parking[i] --Wrapper.Airbase#AIRBASE.ParkingSpot
|
||||
unit.parking_landing=spot.TerminalID
|
||||
end
|
||||
|
||||
if ratcraft.livery==nil and #ratcraft.liveries>0 then
|
||||
ratcraft.livery=ratcraft.liveries[math.random(#ratcraft.liveries)]
|
||||
end
|
||||
|
||||
-- Set livery.
|
||||
if ratcraft.livery then
|
||||
unit.livery_id = ratcraft.livery
|
||||
end
|
||||
|
||||
-- Set skill.
|
||||
if ratcraft.skill then
|
||||
unit.skill= ratcraft.skill
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-- And template position.
|
||||
template.x = template.units[1].x
|
||||
template.y = template.units[1].y
|
||||
|
||||
-- Uncontrolled spawning.
|
||||
template.uncontrolled=ratcraft.uncontrolled
|
||||
|
||||
-- Debug info.
|
||||
self:T2({airtemplate=template})
|
||||
|
||||
-- Spawn group.
|
||||
local group=_DATABASE:Spawn(template) --Wrapper.Group#GROUP
|
||||
|
||||
return group
|
||||
end
|
||||
|
||||
|
||||
--- Prepare a spawn template for the asset. Deep copy of asset template, adjusting template and unit names, nillifying group and unit ids.
|
||||
-- @param #RAT2 self
|
||||
-- @param Functional.RatCraft#RATCRAFT ratcraft Aircraft that will be spawned.
|
||||
-- @param #string groupname Name for the group to be spawned.
|
||||
-- @return #table Prepared new spawn template.
|
||||
function RAT2:_SpawnAssetPrepareTemplate(ratcraft, groupname)
|
||||
|
||||
-- Create an own copy of the template!
|
||||
local template=UTILS.DeepCopy(ratcraft.template)
|
||||
|
||||
-- Set unique name.
|
||||
template.name=groupname
|
||||
|
||||
-- Set current(!) coalition and country.
|
||||
template.CoalitionID=ratcraft.coalition
|
||||
template.CountryID=ratcraft.country
|
||||
|
||||
-- Nillify the group ID.
|
||||
template.groupId=nil
|
||||
|
||||
-- No late activation.
|
||||
template.lateActivation=false
|
||||
|
||||
-- Set and empty route.
|
||||
template.route = {}
|
||||
template.route.routeRelativeTOT=true
|
||||
template.route.points = {}
|
||||
|
||||
-- Handle units.
|
||||
for i=1,#template.units do
|
||||
|
||||
-- Unit template.
|
||||
local unit=template.units[i]
|
||||
|
||||
-- Nillify the unit ID.
|
||||
unit.unitId=nil
|
||||
|
||||
-- Set unit name: <alias>-01, <alias>-02, ...
|
||||
unit.name=string.format("%s-%02d", template.name , i)
|
||||
|
||||
end
|
||||
|
||||
return template
|
||||
end
|
||||
|
||||
--@param #RCRAFT.Attribute _attribute Generlized attribute of unit.
|
||||
|
||||
--- Get the proper terminal type based on generalized attribute of the group.
|
||||
--@param #RAT2 self
|
||||
--@param #number _category Airbase category.
|
||||
--@return Wrapper.Airbase#AIRBASE.TerminalType Terminal type for this group.
|
||||
function RAT2:_GetTerminal(_attribute, _category)
|
||||
|
||||
-- Default terminal is "large".
|
||||
local _terminal=AIRBASE.TerminalType.OpenBig
|
||||
|
||||
if _attribute==RCRAFT.Attribute.FIGHTER then
|
||||
-- Fighter ==> small.
|
||||
_terminal=AIRBASE.TerminalType.FighterAircraft
|
||||
elseif _attribute==RCRAFT.Attribute.BOMBER or _attribute==RCRAFT.Attribute.TRANSPORTPLANE or _attribute==RCRAFT.Attribute.TANKER or _attribute==RCRAFT.Attribute.AWACS then
|
||||
-- Bigger aircraft.
|
||||
_terminal=AIRBASE.TerminalType.OpenBig
|
||||
elseif _attribute==RCRAFT.Attribute.TRANSPORTHELO or _attribute==RCRAFT.Attribute.ATTACKHELO then
|
||||
-- Helicopter.
|
||||
_terminal=AIRBASE.TerminalType.HelicopterUsable
|
||||
else
|
||||
--_terminal=AIRBASE.TerminalType.OpenMedOrBig
|
||||
end
|
||||
|
||||
-- For ships, we allow medium spots for all fixed wing aircraft. There are smaller tankers and AWACS aircraft that can use a carrier.
|
||||
if _category==Airbase.Category.SHIP then
|
||||
if not (_attribute==RCRAFT.Attribute.TRANSPORTHELO or _attribute==RCRAFT.Attribute.ATTACKHELO) then
|
||||
_terminal=AIRBASE.TerminalType.OpenMedOrBig
|
||||
end
|
||||
end
|
||||
|
||||
return _terminal
|
||||
end
|
||||
|
||||
|
||||
--- Seach unoccupied parking spots at the airbase for a list of assets. For each asset group a list of parking spots is returned.
|
||||
-- During the search also the not yet spawned asset aircraft are considered.
|
||||
-- If not enough spots for all asset units could be found, the routine returns nil!
|
||||
-- @param #RAT2 self
|
||||
-- @param Wrapper.Airbase#AIRBASE airbase The airbase where we search for parking spots.
|
||||
-- @param Functional.RatCraft#RATCRAFT ratcraft Ratcraft.
|
||||
-- @return #table Table of coordinates and terminal IDs of free parking spots. Each table entry has the elements .Coordinate and .TerminalID.
|
||||
function RAT2:_FindParking(airbase, ratcraft)
|
||||
|
||||
-- Init default
|
||||
local scanradius=50
|
||||
local scanunits=true
|
||||
local scanstatics=true
|
||||
local scanscenery=false
|
||||
local verysafe=false
|
||||
|
||||
-- Function calculating the overlap of two (square) objects.
|
||||
local function _overlap(l1,l2,dist)
|
||||
local safedist=(l1/2+l2/2)*1.05 -- 5% safety margine added to safe distance!
|
||||
local safe = (dist > safedist)
|
||||
self:T3(string.format("l1=%.1f l2=%.1f s=%.1f d=%.1f ==> safe=%s", l1,l2,safedist,dist,tostring(safe)))
|
||||
return safe
|
||||
end
|
||||
|
||||
-- Get parking spot data table. This contains all free and "non-free" spots.
|
||||
local parkingdata=airbase:GetParkingSpotsTable()
|
||||
|
||||
-- List of obstacles.
|
||||
local obstacles={}
|
||||
|
||||
-- Loop over all parking spots and get the currently present obstacles.
|
||||
-- How long does this take on very large airbases, i.e. those with hundereds of parking spots? Seems to be okay!
|
||||
for _,parkingspot in pairs(parkingdata) do
|
||||
|
||||
-- Coordinate of the parking spot.
|
||||
local _spot=parkingspot.Coordinate -- Core.Point#COORDINATE
|
||||
local _termid=parkingspot.TerminalID
|
||||
|
||||
-- Scan a radius of 100 meters around the spot.
|
||||
local _,_,_,_units,_statics,_sceneries=_spot:ScanObjects(scanradius, scanunits, scanstatics, scanscenery)
|
||||
|
||||
-- Check all units.
|
||||
for _,_unit in pairs(_units) do
|
||||
local unit=_unit --Wrapper.Unit#UNIT
|
||||
local _coord=unit:GetCoordinate()
|
||||
local _size=self:_GetObjectSize(unit:GetDCSObject())
|
||||
local _name=unit:GetName()
|
||||
table.insert(obstacles, {coord=_coord, size=_size, name=_name, type="unit"})
|
||||
end
|
||||
|
||||
-- Check all statics.
|
||||
for _,static in pairs(_statics) do
|
||||
local _vec3=static:getPoint()
|
||||
local _coord=COORDINATE:NewFromVec3(_vec3)
|
||||
local _name=static:getName()
|
||||
local _size=self:_GetObjectSize(static)
|
||||
table.insert(obstacles, {coord=_coord, size=_size, name=_name, type="static"})
|
||||
end
|
||||
|
||||
-- Check all scenery.
|
||||
for _,scenery in pairs(_sceneries) do
|
||||
local _vec3=scenery:getPoint()
|
||||
local _coord=COORDINATE:NewFromVec3(_vec3)
|
||||
local _name=scenery:getTypeName()
|
||||
local _size=self:_GetObjectSize(scenery)
|
||||
table.insert(obstacles,{coord=_coord, size=_size, name=_name, type="scenery"})
|
||||
end
|
||||
|
||||
-- TODO check clients. Clients cannot be spawned. So we can loop over them.
|
||||
|
||||
end
|
||||
|
||||
-- Parking data for all assets.
|
||||
local parking={}
|
||||
|
||||
-- Get terminal type of this asset
|
||||
local terminaltype=self:_GetTerminal(ratcraft.attribute, airbase:GetAirbaseCategory())
|
||||
|
||||
local Nunits=#ratcraft.templategroup:GetUnits()
|
||||
|
||||
-- Loop over all units - each one needs a spot.
|
||||
for i=1,Nunits do
|
||||
|
||||
-- Loop over all parking spots.
|
||||
local gotit=false
|
||||
for _,_parkingspot in pairs(parkingdata) do
|
||||
local parkingspot=_parkingspot --Wrapper.Airbase#AIRBASE.ParkingSpot
|
||||
|
||||
-- Check correct terminal type for asset. We don't want helos in shelters etc.
|
||||
if AIRBASE._CheckTerminalType(parkingspot.TerminalType, terminaltype) then
|
||||
|
||||
-- Coordinate of the parking spot.
|
||||
local _spot=parkingspot.Coordinate -- Core.Point#COORDINATE
|
||||
local _termid=parkingspot.TerminalID
|
||||
local _toac=parkingspot.TOAC
|
||||
|
||||
--env.info(string.format("FF asset=%s (id=%d): needs terminal type=%d, id=%d, #obstacles=%d", _asset.templatename, _asset.uid, terminaltype, _termid, #obstacles))
|
||||
|
||||
local free=true
|
||||
local problem=nil
|
||||
|
||||
-- Safe parking using TO_AC from DCS result.
|
||||
if self.safeparking and _toac then
|
||||
free=false
|
||||
self:T("Parking spot %d is occupied by other aircraft taking off or landing.", _termid)
|
||||
end
|
||||
|
||||
-- Loop over all obstacles.
|
||||
for _,obstacle in pairs(obstacles) do
|
||||
|
||||
-- Check if aircraft overlaps with any obstacle.
|
||||
local dist=_spot:Get2DDistance(obstacle.coord)
|
||||
local safe=_overlap(ratcraft.size, obstacle.size, dist)
|
||||
|
||||
-- Spot is blocked.
|
||||
if not safe then
|
||||
--env.info(string.format("FF asset=%s (id=%d): spot id=%d dist=%.1fm is NOT SAFE", _asset.templatename, _asset.uid, _termid, dist))
|
||||
free=false
|
||||
problem=obstacle
|
||||
problem.dist=dist
|
||||
break
|
||||
else
|
||||
--env.info(string.format("FF asset=%s (id=%d): spot id=%d dist=%.1fm is SAFE", _asset.templatename, _asset.uid, _termid, dist))
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-- Check if spot is free
|
||||
if free then
|
||||
|
||||
-- Add parkingspot for this asset unit.
|
||||
table.insert(parking, parkingspot)
|
||||
|
||||
self:T(self.lid..string.format("Parking spot #%d is free for ratcraft unit id=%d!", _termid, i))
|
||||
|
||||
-- Add the unit as obstacle so that this spot will not be available for the next unit.
|
||||
table.insert(obstacles, {coord=_spot, size=ratcraft.size, name=ratcraft.templatename, type="ratcraft"})
|
||||
|
||||
-- Break loop over parking spots.
|
||||
gotit=true
|
||||
break
|
||||
|
||||
else
|
||||
|
||||
-- Debug output for occupied spots.
|
||||
self:T(self.lid..string.format("Parking spot #%d is occupied or not big enough!", _termid))
|
||||
if self.Debug then
|
||||
local coord=problem.coord --Core.Point#COORDINATE
|
||||
local text=string.format("Obstacle blocking spot #%d is %s type %s with size=%.1f m and distance=%.1f m.", _termid, problem.name, problem.type, problem.size, problem.dist)
|
||||
coord:MarkToAll(string.format(text))
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end -- check terminal type
|
||||
end -- loop over parking spots
|
||||
|
||||
-- No parking spot for at least one unit :(
|
||||
if not gotit then
|
||||
self:T(self.lid..string.format("WARNING: No free parking spot for ratcraft unit i=%d", i))
|
||||
return nil
|
||||
end
|
||||
|
||||
end -- loop over units
|
||||
|
||||
return parking
|
||||
end
|
||||
|
||||
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Flightplan functions
|
||||
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Calculate the maximum height an aircraft can reach for the given parameters.
|
||||
-- @param #RAT2 self
|
||||
-- @param #number D Total distance in meters from Departure to holding point at destination.
|
||||
-- @param #number alphaC Climb angle in rad.
|
||||
-- @param #number alphaD Descent angle in rad.
|
||||
-- @param #number Hdep AGL altitude of departure point.
|
||||
-- @param #number Hdest AGL altitude of destination point.
|
||||
-- @param #number Deltahhold Relative altitude of holding point above destination.
|
||||
-- @return #number Maximum height the aircraft can reach.
|
||||
function RAT2:_GetMaxHeight(D, alphaC, alphaD, Hdep, Hdest, Deltahhold)
|
||||
|
||||
local Hhold=Hdest+Deltahhold
|
||||
local hdest=Hdest-Hdep
|
||||
local hhold=hdest+Deltahhold
|
||||
|
||||
local Dp=math.sqrt(D^2 + hhold^2)
|
||||
|
||||
local alphaS=math.atan(hdest/D) -- slope angle
|
||||
local alphaH=math.atan(hhold/D) -- angle to holding point (could be necative!)
|
||||
|
||||
local alphaCp=alphaC-alphaH -- climb angle with slope
|
||||
local alphaDp=alphaD+alphaH -- descent angle with slope
|
||||
|
||||
-- ASA triangle.
|
||||
local gammap=math.pi-alphaCp-alphaDp
|
||||
local sCp=Dp*math.sin(alphaDp)/math.sin(gammap)
|
||||
local sDp=Dp*math.sin(alphaCp)/math.sin(gammap)
|
||||
|
||||
-- Max height from departure.
|
||||
local hmax=sCp*math.sin(alphaC)
|
||||
|
||||
return hmax
|
||||
end
|
||||
|
||||
|
||||
--- Make a flight plan from a departure to a destination airport.
|
||||
-- @param #RAT2 self
|
||||
-- @param Functional.RatCraft#RATCRAFT ratcraft Ratcraft object.
|
||||
-- @param Wrapper.Airbase#AIRBASE departure Departure airbase.
|
||||
-- @param Wrapper.Airbase#AIRBASE destination Destination airbase.
|
||||
-- @return #table Table of flightplan waypoints.
|
||||
-- @return #table Table of flightplan coordinates.
|
||||
function RAT2:_GetFlightplan(ratcraft, departure, destination)
|
||||
|
||||
-- Parameters
|
||||
local Vmax=ratcraft.speedmax
|
||||
local Range=ratcraft.range
|
||||
local category=ratcraft.category
|
||||
local ceiling=ratcraft.DCSdesc.Hmax
|
||||
local Vymax=ratcraft.DCSdesc.VyMax
|
||||
|
||||
-- Max cruise speed 90% of max speed.
|
||||
local VxCruiseMax=0.90*Vmax
|
||||
|
||||
-- Min cruise speed 70% of max cruise or 600 km/h whichever is lower.
|
||||
local VxCruiseMin = math.min(VxCruiseMax*0.75, 750)
|
||||
|
||||
-- Cruise speed (randomized). Expectation value at midpoint between min and max.
|
||||
local VxCruise = UTILS.RandomGaussian((VxCruiseMax-VxCruiseMin)/2+VxCruiseMin, (VxCruiseMax-VxCruiseMax)/4, VxCruiseMin, VxCruiseMax)
|
||||
|
||||
-- Climb speed 90% ov Vmax but max 720 km/h.
|
||||
local VxClimb = math.min(Vmax*0.90, 720)
|
||||
|
||||
-- Descent speed 60% of Vmax but max 500 km/h.
|
||||
local VxDescent = math.min(Vmax*0.60, 500)
|
||||
|
||||
-- Holding speed is 90% of descent speed.
|
||||
local VxHolding = VxDescent*0.9
|
||||
|
||||
-- Final leg is 90% of holding speed.
|
||||
local VxFinal = VxHolding*0.9
|
||||
|
||||
-- Reasonably civil climb speed Vy=1500 ft/min = 7.6 m/s but max aircraft specific climb rate.
|
||||
local VyClimb=math.min(7.6, Vymax)
|
||||
|
||||
-- Climb angle in rad.
|
||||
local AlphaClimb=math.rad(4)
|
||||
|
||||
-- Descent angle in rad. Moderate 4 degrees.
|
||||
local AlphaDescent=math.rad(4)
|
||||
|
||||
-- Expected cruise level (peak of Gaussian distribution)
|
||||
local FLcruise_expect=150*RAT.unit.FL2m
|
||||
if category==Group.Category.HELICOPTER then
|
||||
FLcruise_expect=1000 -- 1000 m ASL
|
||||
end
|
||||
|
||||
-------------------------
|
||||
--- DEPARTURE AIRPORT ---
|
||||
-------------------------
|
||||
|
||||
-- Coordinates of departure point.
|
||||
local Pdeparture=departure:GetCoordinate()
|
||||
|
||||
-- Height ASL of departure point.
|
||||
local H_departure=Pdeparture.y
|
||||
|
||||
---------------------------
|
||||
--- DESTINATION AIRPORT ---
|
||||
---------------------------
|
||||
|
||||
-- Position of destination airport.
|
||||
local Pdestination=destination:GetCoordinate()
|
||||
|
||||
-- Height ASL of destination airport/zone.
|
||||
local H_destination=Pdestination.y
|
||||
|
||||
-----------------------------
|
||||
--- DESCENT/HOLDING POINT ---
|
||||
-----------------------------
|
||||
|
||||
-- Get a random point between 5 and 10 km away from the destination.
|
||||
local Rhmin=5000
|
||||
local Rhmax=10000
|
||||
|
||||
-- For helos we set a distance between 500 to 1000 m.
|
||||
if category==Group.Category.HELICOPTER then
|
||||
Rhmin=500
|
||||
Rhmax=1000
|
||||
end
|
||||
|
||||
-- Coordinates of the holding point. y is the land height at that point.
|
||||
local Pholding=Pdestination:GetRandomCoordinateInRadius(Rhmax, Rhmin)
|
||||
|
||||
-- Distance from holding point to final destination (not used).
|
||||
local d_holding=Pholding:Get2DDistance(Pdestination)
|
||||
|
||||
-- AGL height of holding point.
|
||||
local H_holding=Pholding.y
|
||||
|
||||
---------------
|
||||
--- GENERAL ---
|
||||
---------------
|
||||
|
||||
-- We go directly to the holding point not the destination airport. From there, planes are guided by DCS to final approach.
|
||||
local heading=Pdeparture:HeadingTo(Pholding)
|
||||
local d_total=Pdeparture:Get2DDistance(Pholding)
|
||||
|
||||
------------------------------
|
||||
--- Holding Point Altitude ---
|
||||
------------------------------
|
||||
|
||||
-- Holding point altitude. For planes between 1600 and 2400 m AGL. For helos 160 to 240 m AGL.
|
||||
local h_holding=1200
|
||||
if category==Group.Category.HELICOPTER then
|
||||
h_holding=150
|
||||
end
|
||||
h_holding=UTILS.Randomize(h_holding, 0.2)
|
||||
|
||||
-- Max holding altitude.
|
||||
local DeltaholdingMax=self:_GetMaxHeight(d_total, AlphaClimb, AlphaDescent, H_departure, H_holding, 0)
|
||||
|
||||
if h_holding>DeltaholdingMax then
|
||||
h_holding=math.abs(DeltaholdingMax)
|
||||
end
|
||||
|
||||
-- This is the height ASL of the holding point we want to fly to.
|
||||
local Hh_holding=H_holding+h_holding
|
||||
|
||||
---------------------------
|
||||
--- Max Flight Altitude ---
|
||||
---------------------------
|
||||
|
||||
-- Get max flight altitude relative to H_departure.
|
||||
local h_max=self:_GetMaxHeight(d_total, AlphaClimb, AlphaDescent, H_departure, H_holding, h_holding)
|
||||
|
||||
-- Max flight level ASL aircraft can reach for given angles and distance.
|
||||
local FLmax = h_max+H_departure
|
||||
|
||||
--CRUISE
|
||||
-- Min cruise alt is just above holding point at destination or departure height, whatever is larger.
|
||||
local FLmin=math.max(H_departure, Hh_holding)
|
||||
|
||||
-- Ensure that FLmax not above its service ceiling.
|
||||
FLmax=math.min(FLmax, ceiling)
|
||||
|
||||
-- If the route is very short we set FLmin a bit lower than FLmax.
|
||||
if FLmin>FLmax then
|
||||
FLmin=FLmax
|
||||
end
|
||||
|
||||
-- Expected cruise altitude - peak of gaussian distribution.
|
||||
if FLcruise_expect<FLmin then
|
||||
FLcruise_expect=FLmin
|
||||
end
|
||||
if FLcruise_expect>FLmax then
|
||||
FLcruise_expect=FLmax
|
||||
end
|
||||
|
||||
-- Set cruise altitude. Selected from Gaussian distribution but limited to FLmin and FLmax.
|
||||
local FLcruise=UTILS.RandomGaussian(FLcruise_expect, math.abs(FLmax-FLmin)/4, FLmin, FLmax)
|
||||
|
||||
-- Climb and descent heights.
|
||||
local h_climb = FLcruise - H_departure
|
||||
local h_descent = FLcruise - Hh_holding
|
||||
|
||||
-- Get distances.
|
||||
local d_climb = h_climb/math.tan(AlphaClimb)
|
||||
local d_descent = h_descent/math.tan(AlphaDescent)
|
||||
local d_cruise = d_total-d_climb-d_descent
|
||||
|
||||
-- Debug.
|
||||
local text=string.format("Flight plan:\n")
|
||||
text=text..string.format("Vx max = %.2f km/h\n", Vmax)
|
||||
text=text..string.format("Vx climb = %.2f km/h\n", VxClimb*3.6)
|
||||
text=text..string.format("Vx cruise = %.2f km/h\n", VxCruise*3.6)
|
||||
text=text..string.format("Vx descent = %.2f km/h\n", VxDescent*3.6)
|
||||
text=text..string.format("Vx holding = %.2f km/h\n", VxHolding*3.6)
|
||||
text=text..string.format("Vx final = %.2f km/h\n", VxFinal*3.6)
|
||||
text=text..string.format("Vy max = %.2f m/s\n", Vymax)
|
||||
text=text..string.format("Vy climb = %.2f m/s\n", VyClimb)
|
||||
text=text..string.format("Alpha Climb = %.2f Deg\n", math.deg(AlphaClimb))
|
||||
text=text..string.format("Alpha Descent = %.2f Deg\n", math.deg(AlphaDescent))
|
||||
text=text..string.format("Dist climb = %.3f km\n", d_climb/1000)
|
||||
text=text..string.format("Dist cruise = %.3f km\n", d_cruise/1000)
|
||||
text=text..string.format("Dist descent = %.3f km\n", d_descent/1000)
|
||||
text=text..string.format("Dist total = %.3f km\n", d_total/1000)
|
||||
text=text..string.format("h_climb = %.3f km\n", h_climb/1000)
|
||||
text=text..string.format("h_desc = %.3f km\n", h_descent/1000)
|
||||
text=text..string.format("h_holding = %.3f km\n", h_holding/1000)
|
||||
text=text..string.format("h_max = %.3f km\n", h_max/1000)
|
||||
text=text..string.format("FL min = %.3f km\n", FLmin/1000)
|
||||
text=text..string.format("FL expect = %.3f km\n", FLcruise_expect/1000)
|
||||
text=text..string.format("FL cruise * = %.3f km\n", FLcruise/1000)
|
||||
text=text..string.format("FL max = %.3f km\n", FLmax/1000)
|
||||
text=text..string.format("Ceiling = %.3f km\n", ceiling/1000)
|
||||
text=text..string.format("Max range = %.3f km\n", Range/1000)
|
||||
self:T(self.lid..text)
|
||||
|
||||
-- Ensure that cruise distance is positve. Can be slightly negative in special cases. And we don't want to turn back.
|
||||
if d_cruise<0 then
|
||||
d_cruise=100
|
||||
end
|
||||
|
||||
------------------------
|
||||
--- Create Waypoints ---
|
||||
------------------------
|
||||
|
||||
-- Waypoints and coordinates
|
||||
local wp={}
|
||||
local c={}
|
||||
|
||||
--- Departure/Take-off
|
||||
c[#c+1]=Pdeparture
|
||||
wp[#wp+1]=Pdeparture:WaypointAir("RADIO", COORDINATE.WaypointType.TakeOffParking, COORDINATE.WaypointAction.FromParkingArea, VxClimb, true, departure, nil, "Departure")
|
||||
|
||||
--- Begin of Cruise
|
||||
local Pcruise=Pdeparture:Translate(d_climb, heading)
|
||||
Pcruise.y=FLcruise
|
||||
c[#c+1]=Pcruise
|
||||
wp[#wp+1]=Pcruise:WaypointAir("BARO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, VxCruise, true, nil, nil, "Cruise")
|
||||
|
||||
--- Descent
|
||||
local Pdescent=Pcruise:Translate(d_cruise, heading)
|
||||
Pdescent.y=FLcruise
|
||||
c[#c+1]=Pdescent
|
||||
wp[#wp+1]=Pdescent:WaypointAir("BARO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, VxDescent, true, nil, nil, "Descent")
|
||||
|
||||
--- Holding point
|
||||
Pholding.y=H_holding+h_holding
|
||||
c[#c+1]=Pholding
|
||||
wp[#wp+1]=Pholding:WaypointAir("BARO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, VxHolding, true, nil, nil, "Holding")
|
||||
|
||||
--- Final destination.
|
||||
c[#c+1]=Pdestination
|
||||
wp[#wp+1]=Pdestination:WaypointAir("RADIO", COORDINATE.WaypointType.Land, COORDINATE.WaypointAction.Landing, VxFinal, true, destination, nil, "Final Destination")
|
||||
|
||||
|
||||
-- Mark points at waypoints for debugging.
|
||||
if self.Debug or true then
|
||||
for i,coord in pairs(c) do
|
||||
local coord=coord --Core.Point#COORDINATE
|
||||
local dist=0
|
||||
if i>1 then
|
||||
dist=coord:Get2DDistance(c[i-1])
|
||||
end
|
||||
coord:MarkToAll(string.format("Waypoint %i, distance = %.2f km",i, dist/1000))
|
||||
end
|
||||
end
|
||||
|
||||
return wp,c
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,596 @@
|
||||
--- **Functional** - (R2.5) - Random Air Traffic.
|
||||
--
|
||||
--
|
||||
--
|
||||
-- RATCRAFT creates random air traffic on the map.
|
||||
--
|
||||
--
|
||||
--
|
||||
-- **Main Features:**
|
||||
--
|
||||
-- * It's very random.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **funkyfranky**
|
||||
-- @module Functional.RatCraft
|
||||
-- @image Functional_Rat2.png
|
||||
|
||||
--- RATCRAFT
|
||||
--
|
||||
-- @type RATCRAFT
|
||||
-- @field #string ClassName Name of the class.
|
||||
-- @field #boolean Debugmode Debug mode. Messages to all about status.
|
||||
-- @field #string lid Class id string for output to DCS log file.
|
||||
-- @field #string templatename Name of the group template.
|
||||
-- @field #string alias Alias for spawn prefix. Default template group name.
|
||||
-- @field #table template Table of the group template.
|
||||
-- @field Wrapper.Group#GROUP templategroup Template group.
|
||||
-- @field #number coalition Coalition side.
|
||||
-- @field #number country Country number.
|
||||
-- @field #string actype Aircraft type name.
|
||||
-- @field #number category Category (plane=X, helo=Y).
|
||||
-- @field #string attribute Generalized attribute.
|
||||
-- @field #number ceiling Max altitude in meters of the aircraft group.
|
||||
-- @field #number speedmax Max speed in km/h of the aircraft group.
|
||||
-- @field #number sizex Size X-axis in meters.
|
||||
-- @field #number sizez Size Z-axis in meters.
|
||||
-- @field #number size Max size (X, Z) in meters.
|
||||
-- @field #table liveries Table of liveries.
|
||||
-- @field #string livery Current livery.
|
||||
-- @field #string skill Skill of group.
|
||||
-- @field #string onboard Onboard number. This should better be on UNIT level!
|
||||
-- @field #table departures Defined departures.
|
||||
-- @field #table departure Current departure airbase or zone.
|
||||
-- @field #table destinations Defined destinations.
|
||||
-- @field #table destination Current destination.
|
||||
-- @field #boolean commute If true, commute between to airbases/zones.
|
||||
-- @field #boolean journey If true, continue journey from the current destination.
|
||||
-- @field #string takeoff Takeoff type cold, hot, air.
|
||||
-- @field #string landing Landing type.
|
||||
-- @field #table flights Table of flight groups.
|
||||
-- @field #number Nspawn Number of max spawned aircraft groups.
|
||||
-- @field #number Nunits Number of units in group.
|
||||
-- @extends Core.Fsm#FSM
|
||||
RATCRAFT = {
|
||||
ClassName = "RATCRAFT",
|
||||
Debugmode = false,
|
||||
lid = nil,
|
||||
templatename = nil,
|
||||
alias = nil,
|
||||
template = nil,
|
||||
templategroup = nil,
|
||||
coalition = nil,
|
||||
country = nil,
|
||||
actype = nil,
|
||||
category = nil,
|
||||
attribute = nil,
|
||||
ceiling = nil,
|
||||
speedmax = nil,
|
||||
sizex = nil,
|
||||
sizez = nil,
|
||||
size = nil,
|
||||
liveries = {},
|
||||
livery = nil,
|
||||
skill = nil,
|
||||
onboard = nil,
|
||||
departures = {},
|
||||
destinations = {},
|
||||
departure = nil,
|
||||
destination = nil,
|
||||
commute = nil,
|
||||
journey = nil,
|
||||
takeoff = nil,
|
||||
landing = nil,
|
||||
flights = {},
|
||||
Nspawn = 2,
|
||||
Nunits = nil,
|
||||
}
|
||||
|
||||
--- Generalized asset attributes. Can be used to request assets with certain general characteristics. See [DCS attributes](https://wiki.hoggitworld.com/view/DCS_enum_attributes) on hoggit.
|
||||
-- @type RATCRAFT.Attribute
|
||||
-- @field #string TRANSPORTPLANE Airplane with transport capability. This can be used to transport other assets.
|
||||
-- @field #string AWACS Airborne Early Warning and Control System.
|
||||
-- @field #string FIGHTER Fighter, interceptor, ... airplane.
|
||||
-- @field #string BOMBER Aircraft which can be used for strategic bombing.
|
||||
-- @field #string TANKER Airplane which can refuel other aircraft.
|
||||
-- @field #string TRANSPORTHELO Helicopter with transport capability. This can be used to transport other assets.
|
||||
-- @field #string ATTACKHELO Attack helicopter.
|
||||
-- @field #string UAV Unpiloted Aerial Vehicle, e.g. drones.
|
||||
RATCRAFT.Attribute = {
|
||||
TRANSPORTPLANE="TransportPlane",
|
||||
AWACS="AWACS",
|
||||
FIGHTER="Fighter",
|
||||
BOMBER="Bomber",
|
||||
TANKER="Tanker",
|
||||
TRANSPORTHELO="TransportHelo",
|
||||
ATTACKHELO="AttackHelo",
|
||||
UAV="UAV",
|
||||
OTHER="Other",
|
||||
}
|
||||
|
||||
--- Departure/destination type, i.e. airbase or zone.
|
||||
-- @type RATCRAFT.DeType
|
||||
-- @field #string AIRBASE Departure/destination is an airbase.
|
||||
-- @field #string ZONE Departure/destination is a zone.
|
||||
RATCRAFT.DeType={
|
||||
AIRBASE="Airbase",
|
||||
ZONE="Zone",
|
||||
}
|
||||
|
||||
--- Departure/destination parameters.
|
||||
-- @type RATCRAFT.Departure
|
||||
-- @field #string name Name of the departure/destination airbase/zone.
|
||||
-- @field #string type Type of the departure/destination, i.e. an airbase or a zone.
|
||||
-- @field #table parking Number of parking spot IDs for the aircraft.
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- TODO list
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
-- TODO: helos
|
||||
-- TODO: despawn when arrived
|
||||
-- TODO: zones as departures
|
||||
-- TODO: zones as destinations
|
||||
-- TODO: monitor if stationary
|
||||
-- TODO: commute
|
||||
-- TODO: continue journey
|
||||
-- TODO: return zone
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Constructor
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Create a new RATCRAFT class object from a template group defined in the mission editor.
|
||||
-- @param #RATCRAFT self
|
||||
-- @param #string groupname Name of the **late activated** template group.
|
||||
-- @param #number nspawn Number of groups to spawn. Default 1.
|
||||
-- @param #string alias (Optional) Alias for the group name used as spawn name prefix. Default is the group name.
|
||||
-- @return #RATCRAFT self
|
||||
function RATCRAFT:New(groupname, nspawn, alias)
|
||||
|
||||
-- Inherit everything from FSM class.
|
||||
local self=BASE:Inherit(self, FSM:New()) --#RATCRAFT
|
||||
|
||||
-- Defaults
|
||||
self.Nspawn=nspawn or 1
|
||||
self.alias=alias or groupname
|
||||
|
||||
-- Start State.
|
||||
self:SetStartState("Stopped")
|
||||
|
||||
-- Add FSM transitions.
|
||||
-- From State --> Event --> To State
|
||||
self:AddTransition("Stopped", "Load", "Stopped") -- Load ratcraft from file.
|
||||
self:AddTransition("Stopped", "Start", "Running") -- Start RAT2 script.
|
||||
self:AddTransition("Running", "Pause", "Paused") -- Pause spawning RATCRAFT.
|
||||
self:AddTransition("Paused", "Unpause", "Running") -- UnPause spawning RATCRAFT.
|
||||
|
||||
-- Initialize aircraft parameters.
|
||||
self:_Init(groupname)
|
||||
|
||||
self:Start()
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Init aircraft parameters.
|
||||
-- @param #RATCRAFT self
|
||||
-- @param #string groupname Name of the template group.
|
||||
-- @return #RATCRAFT self
|
||||
function RATCRAFT:_Init(groupname)
|
||||
|
||||
self.templatename=groupname
|
||||
self.templategroup=GROUP:FindByName(groupname)
|
||||
self.template=self.templategroup:GetTemplate()
|
||||
|
||||
|
||||
self.speedmax=self.templategroup:GetSpeedMax()
|
||||
self.range=1000000
|
||||
self.DCSdesc=self.templategroup:GetDCSDesc(1)
|
||||
|
||||
self.coalition=self.templategroup:GetCoalition()
|
||||
self.country=self.templategroup:GetUnit(1):GetCountry()
|
||||
|
||||
self.category=self.templategroup:GetUnit(1):GetCategory()
|
||||
|
||||
|
||||
self.Nunits=#self.templategroup:GetUnits()
|
||||
|
||||
-- aircraft dimensions
|
||||
self.sizex=self.DCSdesc.box.max.x
|
||||
self.sizey=self.DCSdesc.box.max.y
|
||||
self.sizez=self.DCSdesc.box.max.z
|
||||
self.size =math.max(self.sizex, self.sizez)
|
||||
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- User Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Add adeparture airbase for the aircraft.
|
||||
-- @param #RATCRAFT self
|
||||
-- @param #string airbasename Departure airbase name.
|
||||
-- @param #table parking (Optional) Table of parking spot IDs. At least the number of units in the group must be given.
|
||||
-- @return #RATCRAFT self
|
||||
function RATCRAFT:AddDepartureAirbase(airbasename, parking)
|
||||
|
||||
local departure={} --#RATCRAFT.Departure
|
||||
|
||||
local spots=nil
|
||||
if parking then
|
||||
if type(parking)=="number" then
|
||||
parking={parking}
|
||||
end
|
||||
|
||||
-- Convert parking IDs to complete parking data.
|
||||
spots={}
|
||||
local airbase=AIRBASE:FindByName(airbasename)
|
||||
for _,TerminalID in pairs(parking) do
|
||||
local spot=airbase:GetParkingSpotData(TerminalID)
|
||||
if spot then
|
||||
self:I(string.format("Add in parking spot %d", spot.TerminalID))
|
||||
table.insert(spots, spot)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
departure.name=airbasename
|
||||
departure.type=RATCRAFT.DeType.AIRBASE
|
||||
departure.parking=spots
|
||||
table.insert(self.departures, departure)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Add destination airbase for the aircraft and optionally specific parking spot IDs to be used by this group.
|
||||
-- @param #RATCRAFT self
|
||||
-- @param #string airbasename Destination airbase name.
|
||||
-- @param #table parking (Optional) Table of parking spot IDs. At least the number of units in the group must be given.
|
||||
-- @return #RATCRAFT self
|
||||
function RATCRAFT:AddDestinationAirbase(airbasename, parking)
|
||||
|
||||
local destination={} --#RATCRAFT.Departure
|
||||
|
||||
-- Convert parking IDs to complete parking data.
|
||||
local spots=nil
|
||||
if parking then
|
||||
if type(parking)=="number" then
|
||||
parking={parking}
|
||||
end
|
||||
|
||||
spots={}
|
||||
local airbase=AIRBASE:FindByName(airbasename)
|
||||
for _,TerminalID in pairs(parking) do
|
||||
local spot=airbase:GetParkingSpotData(TerminalID)
|
||||
if spot then
|
||||
table.insert(spots, spot)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
destination.name=airbasename
|
||||
destination.type=RATCRAFT.DeType.AIRBASE
|
||||
destination.parking=spots
|
||||
table.insert(self.destinations, destination)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
|
||||
--- Add a departure for the aircraft.
|
||||
-- @param #RATCRAFT self
|
||||
-- @param Core.Zone#ZONE zone Zone.
|
||||
-- @return #RATCRAFT self
|
||||
function RATCRAFT:AddDepartureZone(zone)
|
||||
|
||||
local departure={} --#RATCRAFT.Departure
|
||||
|
||||
departure.type={}
|
||||
|
||||
table.insert(self.departures, zone)
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- FSM Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Add a departure for the aircraft.
|
||||
-- @param #RATCRAFT self
|
||||
function RATCRAFT:onafterStart()
|
||||
|
||||
-- Create flight groups.
|
||||
for i=1,self.Nspawn do
|
||||
|
||||
-- Group name.
|
||||
local groupname=string.format("%s-%02d", self.alias, i)
|
||||
|
||||
-- New flight group.
|
||||
local flightgroup=FLIGHTGROUP:New(groupname)
|
||||
|
||||
-- Add flight to tabloe.
|
||||
table.insert(self.flights, flightgroup)
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Departure Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Get departure.
|
||||
-- @param #RATCRAFT self
|
||||
-- @return #RATCRAFT.Departure
|
||||
-- @return #table Parking data table.
|
||||
function RATCRAFT:GetDeparture()
|
||||
|
||||
-- TODO: for commute/continue, set departure to current airbase/zone. Get same parking spot.
|
||||
|
||||
local departures=UTILS.DeepCopy(self.departures)
|
||||
|
||||
-- Try each departure in random order.
|
||||
for i=1,#departures do
|
||||
|
||||
-- Roll dice.
|
||||
local r=math.random(#departures)
|
||||
|
||||
-- Get random departure
|
||||
local departure=departures[r] --#RATCRAFT.Departure
|
||||
|
||||
-- Check if airbase or zone.
|
||||
if departure.type==RATCRAFT.DeType.AIRBASE then
|
||||
|
||||
---
|
||||
-- Airbase
|
||||
---
|
||||
|
||||
-- Departure airbase.
|
||||
local airbase=AIRBASE:FindByName(departure.name)
|
||||
|
||||
if airbase then
|
||||
|
||||
-- Template group.
|
||||
local group=self.templategroup --Wrapper.Group#GROUP
|
||||
|
||||
-- Get number of free parking spots.
|
||||
--local parking=airbase:FindFreeParkingSpotForAircraft(group)
|
||||
local parking=self:GetParkingDeparture(departure)
|
||||
|
||||
|
||||
-- Check if parking is sufficient.
|
||||
if parking and #parking>=self.Nunits then
|
||||
|
||||
self:I(string.format("Returning departure %s", departure.name))
|
||||
return departure, parking
|
||||
|
||||
else
|
||||
|
||||
self:I(string.format("Removing departure %s", departure.name))
|
||||
|
||||
-- Remove from table so it does not get selected again.
|
||||
table.remove(departures, r)
|
||||
|
||||
end
|
||||
|
||||
else
|
||||
self:E("ERROR: Could not find departure airbase!")
|
||||
end
|
||||
|
||||
else
|
||||
|
||||
---
|
||||
-- Zone
|
||||
---
|
||||
|
||||
-- Departure airbase.
|
||||
local zone=ZONE:New(departure.name)
|
||||
|
||||
if zone then
|
||||
|
||||
-- TODO: check if in range.
|
||||
|
||||
return departure
|
||||
|
||||
else
|
||||
self:E("ERROR: Could not find departure zone!")
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Get parking at departure.
|
||||
-- @param #RATCRAFT self
|
||||
-- @param #RATCRAFT.Departure departure The chosen departure.
|
||||
-- @return #table Parking data table or nil if not enough spots.
|
||||
function RATCRAFT:GetParkingDeparture(departure)
|
||||
|
||||
if departure.type==RATCRAFT.DeType.AIRBASE then
|
||||
|
||||
local airbase=AIRBASE:FindByName(departure.name)
|
||||
|
||||
if airbase then
|
||||
|
||||
local parkingdata=airbase:GetParkingSpotsTable()
|
||||
|
||||
if departure.parking then
|
||||
---
|
||||
-- User specified parking spots
|
||||
---
|
||||
|
||||
-- Check if free?
|
||||
local parking={}
|
||||
for _,_spot in pairs(parkingdata) do
|
||||
local spot=_spot --Wrapper.Airbase#AIRBASE.ParkingSpot
|
||||
self:I(string.format("Checking spot %d", spot.TerminalID))
|
||||
-- Check if spot if free?
|
||||
if spot.Free then
|
||||
|
||||
self:I(string.format("Spot %d is FREE", spot.TerminalID))
|
||||
-- Check if spot is in user table?
|
||||
for _,_park in pairs(departure.parking) do
|
||||
local park=_park --Wrapper.Airbase#AIRBASE.ParkingSpot
|
||||
|
||||
if park.TerminalID==spot.TerminalID then
|
||||
self:I(string.format("Adding spot %d to parking", spot.TerminalID))
|
||||
table.insert(parking, spot)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
-- Only return data if sufficient spots available.
|
||||
if #parking>=self.Nunits then
|
||||
self:I(string.format("Found valid parking for %d spots", #parking))
|
||||
return parking
|
||||
end
|
||||
|
||||
else
|
||||
---
|
||||
-- Choose parking from what is free
|
||||
---
|
||||
|
||||
local parking={}
|
||||
for _,_spot in pairs(parkingdata) do
|
||||
local spot=_spot --Wrapper.Airbase#AIRBASE.ParkingSpot
|
||||
|
||||
if spot.Free then
|
||||
table.insert(parking, spot)
|
||||
end
|
||||
|
||||
if #parking>=self.Nunits then
|
||||
return parking
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
self:I(string.format("Could not find valid parking data"))
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
--- Get destination.
|
||||
-- @param #RATCRAFT self
|
||||
-- @param #RATCRAFT.Departure departure The chosen departure.
|
||||
-- @return #RATCRAFT.Departure
|
||||
function RATCRAFT:GetDestination(departure)
|
||||
|
||||
if departure==nil then
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
-- TODO: for commute, switch departure and destination.
|
||||
|
||||
-- Create copy of all destinations.
|
||||
local destinations=UTILS.DeepCopy(self.destinations)
|
||||
|
||||
-- Try each destination in random order.
|
||||
for i=1,#destinations do
|
||||
|
||||
-- Roll dice.
|
||||
local r=math.random(#destinations)
|
||||
|
||||
-- Get random departure
|
||||
local destination=destinations[r] --#RATCRAFT.Departure
|
||||
|
||||
-- Check if airbase or zone.
|
||||
if destination.type==RATCRAFT.DeType.AIRBASE then
|
||||
|
||||
---
|
||||
-- Airbase
|
||||
---
|
||||
|
||||
-- Get airbase.
|
||||
local airbase=AIRBASE:FindByName(destination.name)
|
||||
|
||||
local depart=AIRBASE:FindByName(departure.name)
|
||||
|
||||
if airbase then
|
||||
-- TODO: check
|
||||
-- coalition, terminal type, distance
|
||||
|
||||
-- Distance between departure and destination.
|
||||
local distance=depart:GetCoordinate():Get2DDistance(airbase:GetCoordinate())
|
||||
|
||||
if distance then
|
||||
|
||||
self:I(string.format("Returning destination %s", destination.name))
|
||||
return destination
|
||||
|
||||
else
|
||||
|
||||
self:I(string.format("Removing destination %s", destination.name))
|
||||
|
||||
-- Remove from table so it does not get selected again.
|
||||
table.remove(destinations, r)
|
||||
|
||||
end
|
||||
|
||||
else
|
||||
self:E("ERROR: Could not find airbase!")
|
||||
end
|
||||
|
||||
else
|
||||
|
||||
---
|
||||
-- Zone
|
||||
---
|
||||
|
||||
-- Departure airbase.
|
||||
local zone=ZONE:New(destination.name)
|
||||
|
||||
if zone then
|
||||
|
||||
-- TODO: check if in range.
|
||||
return destination
|
||||
|
||||
else
|
||||
self:E("ERROR: Could not find destination zone!")
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Status Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Get number of alive groups
|
||||
-- @param #RATCRAFT self
|
||||
-- @return #number N live.
|
||||
function RATCRAFT:_GetAliveGroups()
|
||||
local n=0
|
||||
|
||||
for _,_flight in pairs(self.flights) do
|
||||
local flight=_flight --Ops.FlightGroup#FLIGHTGROUP
|
||||
|
||||
if not flight:IsDead() then
|
||||
n=n+1
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
return n
|
||||
end
|
||||
@@ -0,0 +1,757 @@
|
||||
--- **Functional** - (R2.5) - Simple and Persistent Player Scoring.
|
||||
--
|
||||
--
|
||||
-- Score!
|
||||
--
|
||||
--
|
||||
-- ## Main Features:
|
||||
--
|
||||
-- * Good stuff.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **funkyfranky**
|
||||
-- @module Functional.SimpleScore
|
||||
-- @image Functional_SimpleScore.png
|
||||
|
||||
|
||||
--- SCORE class.
|
||||
-- @type SCORE
|
||||
-- @field #string ClassName Name of the class.
|
||||
-- @field #boolean Debug Debug mode. Messages to all about status.
|
||||
-- @field #string lid Class id string for output to DCS log file.
|
||||
-- @field #table menuadded Table of groups the menu was added for.
|
||||
-- @field #boolean menudisabled If true, F10 menu for players is disabled.
|
||||
-- @field #table playerscores Player score table.
|
||||
-- @extends Core.Fsm#FSM
|
||||
|
||||
--- Goooooaaaallllll!
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- # The Scoring Concept
|
||||
--
|
||||
-- Score
|
||||
--
|
||||
-- # Basic Script
|
||||
--
|
||||
-- -- Create a new score object.
|
||||
-- score=SCORE:New()
|
||||
--
|
||||
-- -- Start score.
|
||||
-- SCORE:Start()
|
||||
--
|
||||
-- # Training Zones
|
||||
--
|
||||
-- Players are only protected if they are inside one of the training zones.
|
||||
--
|
||||
-- -- Create a new missile trainer object.
|
||||
-- SCORE=SCORE:New()
|
||||
--
|
||||
-- -- Add training zones.
|
||||
-- SCORE:AddSafeZone(ZONE:New("Training Zone Alpha")
|
||||
-- SCORE:AddSafeZone(ZONE:New("Training Zone Bravo")
|
||||
--
|
||||
-- -- Start missile trainer.
|
||||
-- SCORE:Start()
|
||||
--
|
||||
--
|
||||
-- Todo!
|
||||
--
|
||||
--
|
||||
-- @field #SCORE
|
||||
SCORE = {
|
||||
ClassName = "SCORE",
|
||||
Debug = false,
|
||||
lid = nil,
|
||||
menuadded = {},
|
||||
menudisabled = nil,
|
||||
playerscores = nil,
|
||||
}
|
||||
|
||||
|
||||
--- Player data table holding all important parameters of each player.
|
||||
-- @type SCORE.PlayerData
|
||||
-- @field Wrapper.Unit#UNIT unit Aircraft of the player.
|
||||
-- @field #string unitname Name of the unit.
|
||||
-- @field Wrapper.Client#CLIENT client Client object of player.
|
||||
-- @field #string callsign Callsign of player.
|
||||
-- @field Wrapper.Group#GROUP group Aircraft group of player.
|
||||
-- @field #string groupname Name of the the player aircraft group.
|
||||
-- @field #string name Player name.
|
||||
-- @field #number coalition Coalition number of player.
|
||||
-- @field #table hit Table of hit units.
|
||||
|
||||
--- Hit unit data.
|
||||
-- @type SCORE.HitData
|
||||
-- @field #string unitname Name of the hit unit.
|
||||
-- @field #string category Category of hit unit.
|
||||
-- @field #number score Score
|
||||
-- @field #boolean friendly If true, unit is friendly.
|
||||
-- @field #boolean destroyed If true, unit was destroyed.
|
||||
|
||||
|
||||
|
||||
--- Create a new SCORE class object.
|
||||
-- @param #SCORE self
|
||||
-- @return #SCORE self.
|
||||
function SCORE:New()
|
||||
|
||||
self.lid="SCORE | "
|
||||
|
||||
-- Inherit everthing from FSM class.
|
||||
local self=BASE:Inherit(self, FSM:New()) -- #SCORE
|
||||
|
||||
-- Defaults:
|
||||
self:SetDefaultMissileDestruction(true)
|
||||
self:SetDefaultLaunchAlerts(true)
|
||||
self:SetDefaultLaunchMarks(true)
|
||||
self:SetExplosionPower()
|
||||
|
||||
-- Start State.
|
||||
self:SetStartState("Stopped")
|
||||
|
||||
-- Add FSM transitions.
|
||||
-- From State --> Event --> To State
|
||||
self:AddTransition("Stopped", "Start", "Running") -- Start SCORE script.
|
||||
self:AddTransition("*", "Status", "*") -- Status update.
|
||||
|
||||
------------------------
|
||||
--- Pseudo Functions ---
|
||||
------------------------
|
||||
|
||||
--- Triggers the FSM event "Start". Starts the SCORE. Initializes parameters and starts event handlers.
|
||||
-- @function [parent=#SCORE] Start
|
||||
-- @param #SCORE self
|
||||
|
||||
--- Triggers the FSM event "Start" after a delay. Starts the SCORE. Initializes parameters and starts event handlers.
|
||||
-- @function [parent=#SCORE] __Start
|
||||
-- @param #SCORE self
|
||||
-- @param #number delay Delay in seconds.
|
||||
|
||||
--- Triggers the FSM event "Stop". Stops the SCORE and all its event handlers.
|
||||
-- @param #SCORE self
|
||||
|
||||
--- Triggers the FSM event "Stop" after a delay. Stops the SCORE and all its event handlers.
|
||||
-- @function [parent=#SCORE] __Stop
|
||||
-- @param #SCORE self
|
||||
-- @param #number delay Delay in seconds.
|
||||
|
||||
--- Triggers the FSM event "Status".
|
||||
-- @function [parent=#SCORE] Status
|
||||
-- @param #SCORE self
|
||||
|
||||
--- Triggers the FSM event "Status" after a delay.
|
||||
-- @function [parent=#SCORE] __Status
|
||||
-- @param #SCORE self
|
||||
-- @param #number delay Delay in seconds.
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- On after Start event. Starts the missile trainer and adds event handlers.
|
||||
-- @param #SCORE self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
function SCORE:onafterStart(From, Event, To)
|
||||
|
||||
-- Short info.
|
||||
local text=string.format("Starting SCORE v%s", SCORE.version)
|
||||
env.info(text)
|
||||
|
||||
-- Init player scores table.
|
||||
self.playerscores={}
|
||||
|
||||
-- Event handlers
|
||||
self:HandleEvent(EVENTS.Dead, self._EventDeadOrCrash)
|
||||
self:HandleEvent(EVENTS.Crash, self._EventDeadOrCrash)
|
||||
self:HandleEvent(EVENTS.Hit, self._EventHit)
|
||||
self:HandleEvent(EVENTS.Birth)
|
||||
self:HandleEvent(EVENTS.PlayerLeaveUnit)
|
||||
|
||||
-- Handle events:
|
||||
self:HandleEvent(EVENTS.Birth)
|
||||
self:HandleEvent(EVENTS.Shot)
|
||||
|
||||
if self.Debug then
|
||||
self:TraceClass(self.ClassName)
|
||||
self:TraceLevel(2)
|
||||
end
|
||||
|
||||
self:__Status(-10)
|
||||
end
|
||||
|
||||
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Event Functions
|
||||
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- SCORE event handler for event birth.
|
||||
-- @param #SCORE self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function SCORE:OnEventBirth(EventData)
|
||||
self:F3({eventbirth = EventData})
|
||||
|
||||
-- Nil checks.
|
||||
if EventData==nil then
|
||||
self:E(self.lid.."ERROR: EventData=nil in event BIRTH!")
|
||||
self:E(EventData)
|
||||
return
|
||||
end
|
||||
if EventData.IniUnit==nil then
|
||||
self:E(self.lid.."ERROR: EventData.IniUnit=nil in event BIRTH!")
|
||||
self:E(EventData)
|
||||
return
|
||||
end
|
||||
|
||||
-- Player unit and name.
|
||||
local _unitName=EventData.IniUnitName
|
||||
local playerunit, playername=self:_GetPlayerUnitAndName(_unitName)
|
||||
|
||||
-- Debug info.
|
||||
self:T(self.lid.."BIRTH: unit = "..tostring(EventData.IniUnitName))
|
||||
self:T(self.lid.."BIRTH: group = "..tostring(EventData.IniGroupName))
|
||||
self:T(self.lid.."BIRTH: player = "..tostring(playername))
|
||||
|
||||
-- Check if player entered.
|
||||
if playerunit and playername then
|
||||
|
||||
local _uid=playerunit:GetID()
|
||||
local _group=playerunit:GetGroup()
|
||||
local _callsign=playerunit:GetCallsign()
|
||||
|
||||
-- Debug output.
|
||||
local text=string.format("Pilot %s, callsign %s entered unit %s of group %s.", playername, _callsign, _unitName, _group:GetName())
|
||||
self:T(self.lid..text)
|
||||
MESSAGE:New(text, 5):ToAllIf(self.Debug)
|
||||
|
||||
-- Add F10 radio menu for player.
|
||||
if not self.menudisabled then
|
||||
SCHEDULER:New(nil, self._AddF10Commands, {self,_unitName}, 0.1)
|
||||
end
|
||||
|
||||
-- Player data.
|
||||
local playerData={} --#SCORE.PlayerData
|
||||
|
||||
-- Player unit, client and callsign.
|
||||
playerData.unit = playerunit
|
||||
playerData.unitname = _unitName
|
||||
playerData.group = _group
|
||||
playerData.groupname = _group:GetName()
|
||||
playerData.name = playername
|
||||
playerData.callsign = playerData.unit:GetCallsign()
|
||||
playerData.client = CLIENT:FindByName(_unitName, nil, true)
|
||||
playerData.coalition = _group:GetCoalition()
|
||||
|
||||
playerData.hit = playerData.hit or {}
|
||||
|
||||
-- Init player data.
|
||||
self.players[playername]=playerData
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- Handles the OnHit event for the scoring.
|
||||
-- @param #SCORE self
|
||||
-- @param Core.Event#EVENTDATA Event
|
||||
function SCORE:_EventOnHit(Event)
|
||||
self:F( { Event } )
|
||||
|
||||
local InitUnit = nil
|
||||
local InitUNIT = nil
|
||||
local InitUnitName = ""
|
||||
local InitGroup = nil
|
||||
local InitGroupName = ""
|
||||
local InitPlayerName = nil
|
||||
|
||||
local InitCoalition = nil
|
||||
local InitCategory = nil
|
||||
local InitType = nil
|
||||
local InitUnitCoalition = nil
|
||||
local InitUnitCategory = nil
|
||||
local InitUnitType = nil
|
||||
|
||||
local TargetUnit = nil
|
||||
local TargetUNIT = nil
|
||||
local TargetUnitName = ""
|
||||
local TargetGroup = nil
|
||||
local TargetGroupName = ""
|
||||
local TargetPlayerName = nil
|
||||
|
||||
local TargetCoalition = nil
|
||||
local TargetCategory = nil
|
||||
local TargetType = nil
|
||||
local TargetUnitCoalition = nil
|
||||
local TargetUnitCategory = nil
|
||||
local TargetUnitType = nil
|
||||
|
||||
if Event.IniDCSUnit then
|
||||
|
||||
InitUnit = Event.IniDCSUnit
|
||||
InitUNIT = Event.IniUnit
|
||||
InitUnitName = Event.IniDCSUnitName
|
||||
InitGroup = Event.IniDCSGroup
|
||||
InitGroupName = Event.IniDCSGroupName
|
||||
InitPlayerName = Event.IniPlayerName
|
||||
|
||||
InitCoalition = Event.IniCoalition
|
||||
InitCategory = Event.IniCategory
|
||||
InitType = Event.IniTypeName
|
||||
|
||||
InitUnitCoalition = _SCORINGCoalition[InitCoalition]
|
||||
InitUnitCategory = _SCORINGCategory[InitCategory]
|
||||
InitUnitType = InitType
|
||||
|
||||
self:T( { InitUnitName, InitGroupName, InitPlayerName, InitCoalition, InitCategory, InitType , InitUnitCoalition, InitUnitCategory, InitUnitType } )
|
||||
end
|
||||
|
||||
|
||||
if Event.TgtDCSUnit then
|
||||
|
||||
TargetUnit = Event.TgtDCSUnit
|
||||
TargetUNIT = Event.TgtUnit
|
||||
TargetUnitName = Event.TgtDCSUnitName
|
||||
TargetGroup = Event.TgtDCSGroup
|
||||
TargetGroupName = Event.TgtDCSGroupName
|
||||
TargetPlayerName = Event.TgtPlayerName
|
||||
|
||||
TargetCoalition = Event.TgtCoalition
|
||||
TargetCategory = Event.TgtCategory
|
||||
TargetType = Event.TgtTypeName
|
||||
|
||||
TargetUnitCoalition = _SCORINGCoalition[TargetCoalition]
|
||||
TargetUnitCategory = _SCORINGCategory[TargetCategory]
|
||||
TargetUnitType = TargetType
|
||||
|
||||
end
|
||||
|
||||
-- Current time.
|
||||
local Tnow=timer.getTime()
|
||||
|
||||
if InitPlayerName~=nil then -- It is a player that is hitting something
|
||||
|
||||
self:T("Hitting Something")
|
||||
|
||||
-- What is he hitting?
|
||||
if TargetCategory then
|
||||
|
||||
-- A target got hit, score it.
|
||||
-- Player contains the score data from self.Players[InitPlayerName]
|
||||
local player=self.Players[InitPlayerName] --#SCORE.PlayerData
|
||||
|
||||
-- Hit table.
|
||||
player.hit[TargetUnitName]=player.hit[TargetUnitName] or {}
|
||||
|
||||
local hit=player.hit[TargetUnitName] --#SCORE.HitData
|
||||
|
||||
hit.score = hit.score or 0
|
||||
hit.penalty = hit.penalty or 0
|
||||
hit.timeStamp = hit.timeStamp or 0
|
||||
hit.unit = hit.unit or TargetUNIT
|
||||
hit.threatLevel, hit.threatType = hit.unit:GetThreatLevel()
|
||||
|
||||
-- Only grant hit scores if there was more than one second between the last hit.
|
||||
if Tnow-hit.timeStamp>1 then
|
||||
|
||||
-- Update timestamp
|
||||
hit.timeStamp = Tnow
|
||||
|
||||
if TargetPlayerName~=nil then
|
||||
-- It is a player hitting another player ...
|
||||
hit.otherplayer=true
|
||||
end
|
||||
|
||||
local Score = 0
|
||||
|
||||
if InitCoalition then -- A coalition object was hit.
|
||||
if InitCoalition == TargetCoalition then
|
||||
|
||||
------------------
|
||||
-- Hit Friendly --
|
||||
------------------
|
||||
|
||||
hit.friendly=true
|
||||
|
||||
player.penalty = player.penalty-10
|
||||
hit.penalty=hit.penalty-10
|
||||
|
||||
-- Check if target was another player.
|
||||
if TargetPlayerName~=nil then -- It is a player hitting another player ...
|
||||
|
||||
else
|
||||
|
||||
end
|
||||
|
||||
-- Write penalty to CSV file.
|
||||
--self:ScoreCSV( InitPlayerName, TargetPlayerName, "HIT_PENALTY", 1, -10, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, TargetUnitCoalition, TargetUnitCategory, TargetUnitType )
|
||||
|
||||
else
|
||||
|
||||
----------------
|
||||
-- Hit Enemey --
|
||||
----------------
|
||||
|
||||
hit.friendly=true
|
||||
|
||||
player.score=player.score+1
|
||||
hit.score=hit.score+1
|
||||
|
||||
--hit.scoreHit = hit.ScoreHit + 1
|
||||
|
||||
-- Check if it was a player.
|
||||
if TargetPlayerName~=nil then -- It is a player hitting another player ...
|
||||
|
||||
else
|
||||
|
||||
end
|
||||
|
||||
-- Write score to CSV file. Score 1 PT.
|
||||
|
||||
end
|
||||
|
||||
else -- A scenery object was hit.
|
||||
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Track DEAD or CRASH events for the scoring.
|
||||
-- @param #SCORING self
|
||||
-- @param Core.Event#EVENTDATA Event
|
||||
function SCORING:_EventOnDeadOrCrash( Event )
|
||||
self:F( { Event } )
|
||||
|
||||
local TargetUnit = nil
|
||||
local TargetGroup = nil
|
||||
local TargetUnitName = ""
|
||||
local TargetGroupName = ""
|
||||
local TargetPlayerName = ""
|
||||
local TargetCoalition = nil
|
||||
local TargetCategory = nil
|
||||
local TargetType = nil
|
||||
local TargetUnitCoalition = nil
|
||||
local TargetUnitCategory = nil
|
||||
local TargetUnitType = nil
|
||||
|
||||
if Event.IniDCSUnit then
|
||||
|
||||
TargetUnit = Event.IniUnit
|
||||
TargetUnitName = Event.IniDCSUnitName
|
||||
TargetGroup = Event.IniDCSGroup
|
||||
TargetGroupName = Event.IniDCSGroupName
|
||||
TargetPlayerName = Event.IniPlayerName
|
||||
|
||||
TargetCoalition = Event.IniCoalition
|
||||
TargetCategory = Event.IniCategory
|
||||
TargetType = Event.IniTypeName
|
||||
|
||||
TargetUnitCoalition = _SCORINGCoalition[TargetCoalition]
|
||||
TargetUnitCategory = _SCORINGCategory[TargetCategory]
|
||||
TargetUnitType = TargetType
|
||||
|
||||
self:T( { TargetUnitName, TargetGroupName, TargetPlayerName, TargetCoalition, TargetCategory, TargetType } )
|
||||
end
|
||||
|
||||
-- Player contains the score and reference data for the player.
|
||||
for PlayerName, _player in pairs(self.Players) do
|
||||
local player=_player --#SCORE.PlayerData
|
||||
|
||||
if player and player.unit and player.unit:IsAlive() then
|
||||
|
||||
self:T( "Something got destroyed" )
|
||||
|
||||
-- Some variables
|
||||
local InitUnitName = Player.UnitName
|
||||
local InitUnitType = Player.UnitType
|
||||
local InitCoalition = Player.UnitCoalition
|
||||
local InitCategory = Player.UnitCategory
|
||||
local InitUnitCoalition = _SCORINGCoalition[InitCoalition]
|
||||
local InitUnitCategory = _SCORINGCategory[InitCategory]
|
||||
|
||||
self:T( { InitUnitName, InitUnitType, InitUnitCoalition, InitCoalition, InitUnitCategory, InitCategory } )
|
||||
|
||||
local Destroyed = false
|
||||
|
||||
local hit=player.hit[TargetUnitName] --#SCORE.HitData
|
||||
|
||||
-- What is the player destroying?
|
||||
if hit and hit.timeStamp~=0 then -- Was there a hit for this unit for this player before registered?
|
||||
|
||||
local TargetThreatLevel = hit.threatLevel
|
||||
local TargetThreatType = hit.threatType
|
||||
|
||||
Player.Destroy[TargetCategory] = Player.Destroy[TargetCategory] or {}
|
||||
Player.Destroy[TargetCategory][TargetType] = Player.Destroy[TargetCategory][TargetType] or {}
|
||||
|
||||
local destroy={} --#SCORE.DestroyData
|
||||
|
||||
-- PlayerDestroy contains the destroy score data per category and target type of the player.
|
||||
local TargetDestroy = Player.Destroy[TargetCategory][TargetType]
|
||||
TargetDestroy.Score = TargetDestroy.Score or 0
|
||||
TargetDestroy.ScoreDestroy = TargetDestroy.ScoreDestroy or 0
|
||||
TargetDestroy.Penalty = TargetDestroy.Penalty or 0
|
||||
TargetDestroy.PenaltyDestroy = TargetDestroy.PenaltyDestroy or 0
|
||||
|
||||
if TargetCoalition then
|
||||
if InitCoalition == TargetCoalition then
|
||||
|
||||
---
|
||||
-- Hit Friendly
|
||||
---
|
||||
|
||||
local ThreatLevelTarget = TargetThreatLevel
|
||||
local ThreatTypeTarget = TargetThreatType
|
||||
local ThreatLevelPlayer = Player.ThreatLevel / 10 + 1
|
||||
local ThreatPenalty = math.ceil( ( ThreatLevelTarget / ThreatLevelPlayer ) * self.ScaleDestroyPenalty / 10 )
|
||||
self:F( { ThreatLevel = ThreatPenalty, ThreatLevelTarget = ThreatLevelTarget, ThreatTypeTarget = ThreatTypeTarget, ThreatLevelPlayer = ThreatLevelPlayer } )
|
||||
|
||||
Player.Penalty = Player.Penalty + ThreatPenalty
|
||||
TargetDestroy.Penalty = TargetDestroy.Penalty + ThreatPenalty
|
||||
TargetDestroy.PenaltyDestroy = TargetDestroy.PenaltyDestroy + 1
|
||||
|
||||
if Player.HitPlayers[TargetPlayerName] then
|
||||
-- A player destroyed another friendly player.
|
||||
else
|
||||
-- A player destroyed another friendly target.
|
||||
end
|
||||
|
||||
-- Write CSV penalty.
|
||||
Destroyed = true
|
||||
--self:ScoreCSV( PlayerName, TargetPlayerName, "DESTROY_PENALTY", 1, ThreatPenalty, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, TargetUnitCoalition, TargetUnitCategory, TargetUnitType )
|
||||
|
||||
else
|
||||
|
||||
---
|
||||
-- Hit Enemy
|
||||
---
|
||||
|
||||
local ThreatLevelTarget = TargetThreatLevel
|
||||
local ThreatTypeTarget = TargetThreatType
|
||||
local ThreatLevelPlayer = Player.ThreatLevel / 10 + 1
|
||||
local ThreatScore = math.ceil( ( ThreatLevelTarget / ThreatLevelPlayer ) * self.ScaleDestroyScore / 10 )
|
||||
|
||||
self:F( { ThreatLevel = ThreatScore, ThreatLevelTarget = ThreatLevelTarget, ThreatTypeTarget = ThreatTypeTarget, ThreatLevelPlayer = ThreatLevelPlayer } )
|
||||
|
||||
Player.Score = Player.Score + ThreatScore
|
||||
TargetDestroy.Score = TargetDestroy.Score + ThreatScore
|
||||
TargetDestroy.ScoreDestroy = TargetDestroy.ScoreDestroy + 1
|
||||
|
||||
if Player.HitPlayers[TargetPlayerName] then
|
||||
-- A player destroyed another enemy player.
|
||||
else
|
||||
-- A player destroyed another enemy target.
|
||||
end
|
||||
|
||||
-- Write CSV.
|
||||
Destroyed = true
|
||||
--self:ScoreCSV( PlayerName, TargetPlayerName, "DESTROY_SCORE", 1, ThreatScore, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, TargetUnitCoalition, TargetUnitCategory, TargetUnitType )
|
||||
|
||||
local UnitName = TargetUnit:GetName()
|
||||
|
||||
-- Scoring for special objects.
|
||||
local Score = self.ScoringObjects[UnitName]
|
||||
|
||||
if Score then
|
||||
|
||||
Player.Score = Player.Score + Score
|
||||
TargetDestroy.Score = TargetDestroy.Score + Score
|
||||
|
||||
MESSAGE
|
||||
:NewType( self.DisplayMessagePrefix .. "Special target '" .. TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " .. " destroyed! " ..
|
||||
"Player '" .. PlayerName .. "' receives an extra " .. Score .. " points! Total: " .. Player.Score - Player.Penalty,
|
||||
MESSAGE.Type.Information
|
||||
)
|
||||
:ToAllIf( self:IfMessagesScore() and self:IfMessagesToAll() )
|
||||
:ToCoalitionIf( InitCoalition, self:IfMessagesScore() and self:IfMessagesToCoalition() )
|
||||
|
||||
-- Write CSV file.
|
||||
self:ScoreCSV( PlayerName, TargetPlayerName, "DESTROY_SCORE", 1, Score, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, TargetUnitCoalition, TargetUnitCategory, TargetUnitType )
|
||||
Destroyed = true
|
||||
end
|
||||
|
||||
-- Check if there are Zones where the destruction happened.
|
||||
for ZoneName, ScoreZoneData in pairs(self.ScoringZones) do
|
||||
self:F( { ScoringZone = ScoreZoneData } )
|
||||
|
||||
local ScoreZone = ScoreZoneData.ScoreZone -- Core.Zone#ZONE_BASE
|
||||
|
||||
local Score = ScoreZoneData.Score
|
||||
|
||||
-- Check if target is in zone.
|
||||
if ScoreZone:IsVec2InZone( TargetUnit:GetVec2() ) then
|
||||
Player.Score = Player.Score + Score
|
||||
TargetDestroy.Score = TargetDestroy.Score + Score
|
||||
|
||||
MESSAGE
|
||||
:NewType( self.DisplayMessagePrefix .. "Target destroyed in zone '" .. ScoreZone:GetName() .. "'." ..
|
||||
"Player '" .. PlayerName .. "' receives an extra " .. Score .. " points! " ..
|
||||
"Total: " .. Player.Score - Player.Penalty,
|
||||
MESSAGE.Type.Information )
|
||||
:ToAllIf( self:IfMessagesZone() and self:IfMessagesToAll() )
|
||||
:ToCoalitionIf( InitCoalition, self:IfMessagesZone() and self:IfMessagesToCoalition() )
|
||||
|
||||
-- Write CSV.
|
||||
self:ScoreCSV( PlayerName, TargetPlayerName, "DESTROY_SCORE", 1, Score, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, TargetUnitCoalition, TargetUnitCategory, TargetUnitType )
|
||||
Destroyed = true
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
else
|
||||
|
||||
---
|
||||
-- Scenery Destroyed
|
||||
---
|
||||
|
||||
-- Check if there are Zones where the destruction happened.
|
||||
for ZoneName, ScoreZoneData in pairs(self.ScoringZones) do
|
||||
|
||||
local ScoreZone = ScoreZoneData.ScoreZone -- Core.Zone#ZONE_BASE
|
||||
local Score = ScoreZoneData.Score
|
||||
|
||||
if ScoreZone:IsVec2InZone(TargetUnit:GetVec2()) then
|
||||
|
||||
Player.Score = Player.Score + Score
|
||||
TargetDestroy.Score = TargetDestroy.Score + Score
|
||||
|
||||
-- Player destroyed scenery in scoring zone.
|
||||
|
||||
--Write CSV.
|
||||
Destroyed = true
|
||||
--self:ScoreCSV( PlayerName, "", "DESTROY_SCORE", 1, Score, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, "", "Scenery", TargetUnitType )
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-- Delete now the hit cache if the target was destroyed.
|
||||
-- Otherwise points will be granted every time a target gets killed by the players that hit that target.
|
||||
-- This is only relevant for player to player destroys.
|
||||
if Destroyed then
|
||||
Player.Hit[TargetCategory][TargetUnitName].TimeStamp = 0
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- On before "Save" event. Checks if io and lfs are available.
|
||||
-- @param #SCORE self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param #string path (Optional) Path where the file is saved. Default is the DCS root installation folder or your "Saved Games\\DCS" folder if the lfs module is desanitized.
|
||||
-- @param #string filename (Optional) File name for saving the player grades. Default is "SCORE-<ALIAS>_LSOgrades.csv".
|
||||
function SCORE:onbeforeSave(From, Event, To, path, filename)
|
||||
|
||||
-- Check io module is available.
|
||||
if not io then
|
||||
self:E(self.lid.."ERROR: io not desanitized. Can't save player grades.")
|
||||
return false
|
||||
end
|
||||
|
||||
-- Check default path.
|
||||
if path==nil and not lfs then
|
||||
self:E(self.lid.."WARNING: lfs not desanitized. Results will be saved in DCS installation root directory rather than your \"Saved Games/DCS\" folder.")
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- FSM Functions
|
||||
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
--- On after "Save" event. Player data is saved to file.
|
||||
-- @param #SCORE self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param #string path Path where the file is saved. If nil, file is saved in the DCS root installtion directory or your "Saved Games" folder if lfs was desanitized.
|
||||
-- @param #string filename (Optional) File name for saving the player grades. Default is "SCORE-<ALIAS>_LSOgrades.csv".
|
||||
function SCORE:onafterSave(From, Event, To, path, filename)
|
||||
|
||||
--- Function that saves data to file
|
||||
local function _savefile(filename, data)
|
||||
local f = io.open(filename, "wb")
|
||||
if f then
|
||||
f:write(data)
|
||||
f:close()
|
||||
end
|
||||
end
|
||||
|
||||
-- Set path or default.
|
||||
if lfs then
|
||||
path=path or lfs.writedir()
|
||||
end
|
||||
|
||||
-- Set file name.
|
||||
filename=filename or string.format("Score.csv")
|
||||
|
||||
-- Set path.
|
||||
if path~=nil then
|
||||
filename=path.."\\"..filename
|
||||
end
|
||||
|
||||
-- Header line
|
||||
local scores="Name,Pass,Points Final,Points Pass,Grade,Details,Wire,Tgroove,Case,Wind,Modex,Airframe,Carrier Type,Carrier Name,Theatre,Mission Time,Mission Date,OS Date\n"
|
||||
|
||||
local scores="Name,Player Unit,Player Coalition,Player Category,Score Type,Target Category,Target Name, Target Coalition,Target Player,Target Score,Mission Date, Mission Time, OS Date"
|
||||
|
||||
-- Loop over all players.
|
||||
local n=0
|
||||
for playername,grades in pairs(self.playerscores) do
|
||||
|
||||
-- Loop over player grades table.
|
||||
for i,_grade in pairs(grades) do
|
||||
local grade=_grade --#SCORE.LSOgrade
|
||||
|
||||
-- Check some stuff that could be nil.
|
||||
local wire="n/a"
|
||||
if grade.wire and grade.wire<=4 then
|
||||
wire=tostring(grade.wire)
|
||||
end
|
||||
|
||||
local Tgroove="n/a"
|
||||
if grade.Tgroove and grade.Tgroove<=360 and grade.case<3 then
|
||||
Tgroove=tostring(UTILS.Round(grade.Tgroove, 1))
|
||||
end
|
||||
|
||||
local finalscore="n/a"
|
||||
if grade.finalscore then
|
||||
finalscore=tostring(UTILS.Round(grade.finalscore, 1))
|
||||
end
|
||||
|
||||
-- Compile grade line.
|
||||
scores=scores..string.format("%s,%d,%s,%.1f,%s,%s,%s,%s,%d,%s,%s,%s,%s,%s,%s,%s,%s,%s\n",
|
||||
playername, i, finalscore, grade.points, grade.grade, grade.details, wire, Tgroove, grade.case,
|
||||
grade.wind, grade.modex, grade.airframe, grade.carriertype, grade.carriername, grade.theatre, grade.mitime, grade.midate, grade.osdate)
|
||||
n=n+1
|
||||
end
|
||||
end
|
||||
|
||||
-- Info
|
||||
local text=string.format("Saving %d player LSO grades to file %s", n, filename)
|
||||
self:I(self.lid..text)
|
||||
|
||||
-- Save file.
|
||||
_savefile(filename, scores)
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -284,9 +284,11 @@ function SCORING:New( GameName )
|
||||
|
||||
-- Default fratricide penalty level (maximum penalty that can be assigned to a player before he gets kicked).
|
||||
self:SetFratricide( self.ScaleDestroyPenalty * 3 )
|
||||
self.penaltyonfratricide = true
|
||||
|
||||
-- Default penalty when a player changes coalition.
|
||||
self:SetCoalitionChangePenalty( self.ScaleDestroyPenalty )
|
||||
self.penaltyoncoalitionchange = true
|
||||
|
||||
self:SetDisplayMessagePrefix()
|
||||
|
||||
@@ -582,6 +584,23 @@ function SCORING:SetFratricide( Fratricide )
|
||||
return self
|
||||
end
|
||||
|
||||
--- Decide if fratricide is leading to penalties (true) or not (false)
|
||||
-- @param #SCORING self
|
||||
-- @param #boolean OnOff Switch for Fratricide
|
||||
-- @return #SCORING
|
||||
function SCORING:SwitchFratricide( OnOff )
|
||||
self.penaltyonfratricide = OnOff
|
||||
return self
|
||||
end
|
||||
|
||||
--- Decide if a change of coalition is leading to penalties (true) or not (false)
|
||||
-- @param #SCORING self
|
||||
-- @param #boolean OnOff Switch for Coalition Changes.
|
||||
-- @return #SCORING
|
||||
function SCORING:SwitchTreason( OnOff )
|
||||
self.penaltyoncoalitionchange = OnOff
|
||||
return self
|
||||
end
|
||||
|
||||
--- When a player changes the coalition, he can receive a penalty score.
|
||||
-- Use the method @{#SCORING.SetCoalitionChangePenalty}() to define the penalty when a player changes coalition.
|
||||
@@ -647,14 +666,15 @@ function SCORING:_AddPlayerFromUnit( UnitData )
|
||||
if not self.Players[PlayerName].UnitCoalition then
|
||||
self.Players[PlayerName].UnitCoalition = UnitCoalition
|
||||
else
|
||||
if self.Players[PlayerName].UnitCoalition ~= UnitCoalition then
|
||||
self.Players[PlayerName].Penalty = self.Players[PlayerName].Penalty + 50
|
||||
-- TODO: switch for coalition changes, make penalty alterable
|
||||
if self.Players[PlayerName].UnitCoalition ~= UnitCoalition and self.penaltyoncoalitionchange then
|
||||
self.Players[PlayerName].Penalty = self.Players[PlayerName].Penalty + self.CoalitionChangePenalty or 50
|
||||
self.Players[PlayerName].PenaltyCoalition = self.Players[PlayerName].PenaltyCoalition + 1
|
||||
MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' changed coalition from " .. _SCORINGCoalition[self.Players[PlayerName].UnitCoalition] .. " to " .. _SCORINGCoalition[UnitCoalition] ..
|
||||
"(changed " .. self.Players[PlayerName].PenaltyCoalition .. " times the coalition). 50 Penalty points added.",
|
||||
"(changed " .. self.Players[PlayerName].PenaltyCoalition .. " times the coalition). ".. self.CoalitionChangePenalty .."Penalty points added.",
|
||||
MESSAGE.Type.Information
|
||||
):ToAll()
|
||||
self:ScoreCSV( PlayerName, "", "COALITION_PENALTY", 1, -50, self.Players[PlayerName].UnitName, _SCORINGCoalition[self.Players[PlayerName].UnitCoalition], _SCORINGCategory[self.Players[PlayerName].UnitCategory], self.Players[PlayerName].UnitType,
|
||||
self:ScoreCSV( PlayerName, "", "COALITION_PENALTY", 1, -1*self.CoalitionChangePenalty, self.Players[PlayerName].UnitName, _SCORINGCoalition[self.Players[PlayerName].UnitCoalition], _SCORINGCategory[self.Players[PlayerName].UnitCategory], self.Players[PlayerName].UnitType,
|
||||
UnitName, _SCORINGCoalition[UnitCoalition], _SCORINGCategory[UnitCategory], UnitData:GetTypeName() )
|
||||
end
|
||||
end
|
||||
@@ -666,10 +686,9 @@ function SCORING:_AddPlayerFromUnit( UnitData )
|
||||
self.Players[PlayerName].UNIT = UnitData
|
||||
self.Players[PlayerName].ThreatLevel = UnitThreatLevel
|
||||
self.Players[PlayerName].ThreatType = UnitThreatType
|
||||
|
||||
-- TODO: DCS bug concerning Units with skill level client don't get destroyed in multi player. This logic is deactivated until this bug gets fixed.
|
||||
--[[
|
||||
if self.Players[PlayerName].Penalty > self.Fratricide * 0.50 then
|
||||
|
||||
-- TODO: make fratricide switchable
|
||||
if self.Players[PlayerName].Penalty > self.Fratricide * 0.50 and self.penaltyonfratricide then
|
||||
if self.Players[PlayerName].PenaltyWarning < 1 then
|
||||
MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "': WARNING! If you continue to commit FRATRICIDE and have a PENALTY score higher than " .. self.Fratricide .. ", you will be COURT MARTIALED and DISMISSED from this mission! \nYour total penalty is: " .. self.Players[PlayerName].Penalty,
|
||||
MESSAGE.Type.Information
|
||||
@@ -678,14 +697,12 @@ function SCORING:_AddPlayerFromUnit( UnitData )
|
||||
end
|
||||
end
|
||||
|
||||
if self.Players[PlayerName].Penalty > self.Fratricide then
|
||||
if self.Players[PlayerName].Penalty > self.Fratricide and self.penaltyonfratricide then
|
||||
MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' committed FRATRICIDE, he will be COURT MARTIALED and is DISMISSED from this mission!",
|
||||
MESSAGE.Type.Information
|
||||
):ToAll()
|
||||
UnitData:GetGroup():Destroy()
|
||||
end
|
||||
--]]
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1125,9 +1142,9 @@ function SCORING:_EventOnHit( Event )
|
||||
if InitCoalition then -- A coalition object was hit, probably a static.
|
||||
if InitCoalition == TargetCoalition then
|
||||
-- TODO: Penalty according scale
|
||||
Player.Penalty = Player.Penalty + 10
|
||||
PlayerHit.Penalty = PlayerHit.Penalty + 10
|
||||
PlayerHit.PenaltyHit = PlayerHit.PenaltyHit + 1
|
||||
Player.Penalty = Player.Penalty + 10 --* self.ScaleDestroyPenalty
|
||||
PlayerHit.Penalty = PlayerHit.Penalty + 10 --* self.ScaleDestroyPenalty
|
||||
PlayerHit.PenaltyHit = PlayerHit.PenaltyHit + 1 * self.ScaleDestroyPenalty
|
||||
|
||||
MESSAGE
|
||||
:NewType( self.DisplayMessagePrefix .. "Player '" .. Event.WeaponPlayerName .. "' hit friendly target " ..
|
||||
@@ -1687,7 +1704,7 @@ function SCORING:ReportScoreGroupDetailed( PlayerGroup )
|
||||
self:F( { ReportMissions, ScoreMissions, PenaltyMissions } )
|
||||
|
||||
local PlayerScore = ScoreHits + ScoreDestroys + ScoreCoalitionChanges + ScoreGoals + ScoreMissions
|
||||
local PlayerPenalty = PenaltyHits + PenaltyDestroys + PenaltyCoalitionChanges + ScoreGoals + PenaltyMissions
|
||||
local PlayerPenalty = PenaltyHits + PenaltyDestroys + PenaltyCoalitionChanges + PenaltyGoals + PenaltyMissions
|
||||
|
||||
PlayerMessage =
|
||||
string.format( "Player '%s' Score = %d ( %d Score, -%d Penalties )%s%s%s%s%s",
|
||||
@@ -1743,7 +1760,7 @@ function SCORING:ReportScoreAllSummary( PlayerGroup )
|
||||
self:F( { ReportMissions, ScoreMissions, PenaltyMissions } )
|
||||
|
||||
local PlayerScore = ScoreHits + ScoreDestroys + ScoreCoalitionChanges + ScoreGoals + ScoreMissions
|
||||
local PlayerPenalty = PenaltyHits + PenaltyDestroys + PenaltyCoalitionChanges + ScoreGoals + PenaltyMissions
|
||||
local PlayerPenalty = PenaltyHits + PenaltyDestroys + PenaltyCoalitionChanges + PenaltyGoals + PenaltyMissions
|
||||
|
||||
PlayerMessage =
|
||||
string.format( "Player '%s' Score = %d ( %d Score, -%d Penalties )",
|
||||
|
||||
@@ -1,201 +1,520 @@
|
||||
--- **Functional** -- Make SAM sites execute evasive and defensive behaviour when being fired upon.
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- ## Features:
|
||||
--
|
||||
--
|
||||
-- * When SAM sites are being fired upon, the SAMs will take evasive action will reposition themselves when possible.
|
||||
-- * When SAM sites are being fired upon, the SAMs will take defensive action by shutting down their radars.
|
||||
--
|
||||
-- * SEAD calculates the time it takes for a HARM to reach the target - and will attempt to minimize the shut-down time.
|
||||
-- * Detection and evasion of shots has a random component based on the skill level of the SAM groups.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- ## Missions:
|
||||
--
|
||||
--
|
||||
-- [SEV - SEAD Evasion](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/SEV%20-%20SEAD%20Evasion)
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Authors: **FlightControl**
|
||||
--
|
||||
--
|
||||
-- ### Authors: **FlightControl**, **applevangelist**
|
||||
--
|
||||
-- Last Update: Feb 2022
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- @module Functional.Sead
|
||||
-- @image SEAD.JPG
|
||||
|
||||
--- @type SEAD
|
||||
---
|
||||
-- @type SEAD
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
--- Make SAM sites execute evasive and defensive behaviour when being fired upon.
|
||||
--
|
||||
--
|
||||
-- This class is very easy to use. Just setup a SEAD object by using @{#SEAD.New}() and SAMs will evade and take defensive action when being fired upon.
|
||||
-- Once a HARM attack is detected, SEAD will shut down the radars of the attacked SAM site and take evasive action by moving the SAM
|
||||
-- vehicles around (*if* they are drivable, that is). There's a component of randomness in detection and evasion, which is based on the
|
||||
-- skill set of the SAM set (the higher the skill, the more likely). When a missile is fired from far away, the SAM will stay active for a
|
||||
-- period of time to stay defensive, before it takes evasive actions.
|
||||
--
|
||||
-- # Constructor:
|
||||
--
|
||||
--
|
||||
-- Use the @{#SEAD.New}() constructor to create a new SEAD object.
|
||||
--
|
||||
--
|
||||
-- SEAD_RU_SAM_Defenses = SEAD:New( { 'RU SA-6 Kub', 'RU SA-6 Defenses', 'RU MI-26 Troops', 'RU Attack Gori' } )
|
||||
--
|
||||
--
|
||||
-- @field #SEAD
|
||||
SEAD = {
|
||||
ClassName = "SEAD",
|
||||
TargetSkill = {
|
||||
Average = { Evade = 50, DelayOff = { 10, 25 }, DelayOn = { 10, 30 } } ,
|
||||
Good = { Evade = 30, DelayOff = { 8, 20 }, DelayOn = { 20, 40 } } ,
|
||||
High = { Evade = 15, DelayOff = { 5, 17 }, DelayOn = { 30, 50 } } ,
|
||||
Excellent = { Evade = 10, DelayOff = { 3, 10 }, DelayOn = { 30, 60 } }
|
||||
},
|
||||
SEADGroupPrefixes = {}
|
||||
ClassName = "SEAD",
|
||||
TargetSkill = {
|
||||
Average = { Evade = 30, DelayOn = { 40, 60 } } ,
|
||||
Good = { Evade = 20, DelayOn = { 30, 50 } } ,
|
||||
High = { Evade = 15, DelayOn = { 20, 40 } } ,
|
||||
Excellent = { Evade = 10, DelayOn = { 10, 30 } }
|
||||
},
|
||||
SEADGroupPrefixes = {},
|
||||
SuppressedGroups = {},
|
||||
EngagementRange = 75, -- default 75% engagement range Feature Request #1355
|
||||
Padding = 10,
|
||||
CallBack = nil,
|
||||
UseCallBack = false,
|
||||
debug = false,
|
||||
}
|
||||
|
||||
--- Missile enumerators
|
||||
-- @field Harms
|
||||
SEAD.Harms = {
|
||||
["AGM_88"] = "AGM_88",
|
||||
["AGM_45"] = "AGM_45",
|
||||
["AGM_122"] = "AGM_122",
|
||||
["AGM_84"] = "AGM_84",
|
||||
["AGM_45"] = "AGM_45",
|
||||
["ALARM"] = "ALARM",
|
||||
["LD-10"] = "LD-10",
|
||||
["X_58"] = "X_58",
|
||||
["X_28"] = "X_28",
|
||||
["X_25"] = "X_25",
|
||||
["X_31"] = "X_31",
|
||||
["Kh25"] = "Kh25",
|
||||
["BGM_109"] = "BGM_109",
|
||||
["AGM_154"] = "AGM_154",
|
||||
["HY-2"] = "HY-2",
|
||||
}
|
||||
|
||||
--- Missile enumerators - from DCS ME and Wikipedia
|
||||
-- @field HarmData
|
||||
SEAD.HarmData = {
|
||||
-- km and mach
|
||||
["AGM_88"] = { 150, 3},
|
||||
["AGM_45"] = { 12, 2},
|
||||
["AGM_122"] = { 16.5, 2.3},
|
||||
["AGM_84"] = { 280, 0.8},
|
||||
["ALARM"] = { 45, 2},
|
||||
["LD-10"] = { 60, 4},
|
||||
["X_58"] = { 70, 4},
|
||||
["X_28"] = { 80, 2.5},
|
||||
["X_25"] = { 25, 0.76},
|
||||
["X_31"] = {150, 3},
|
||||
["Kh25"] = {25, 0.8},
|
||||
["BGM_109"] = {460, 0.705}, --in-game ~465kn
|
||||
["AGM_154"] = {130, 0.61},
|
||||
["HY-2"] = {90,1},
|
||||
}
|
||||
|
||||
--- Creates the main object which is handling defensive actions for SA sites or moving SA vehicles.
|
||||
-- When an anti radiation missile is fired (KH-58, KH-31P, KH-31A, KH-25MPU, HARM missiles), the SA will shut down their radars and will take evasive actions...
|
||||
-- Chances are big that the missile will miss.
|
||||
-- @param table{string,...}|string SEADGroupPrefixes which is a table of Prefixes of the SA Groups in the DCSRTE on which evasive actions need to be taken.
|
||||
-- @return SEAD
|
||||
-- @param #SEAD self
|
||||
-- @param #table SEADGroupPrefixes Table of #string entries or single #string, which is a table of Prefixes of the SA Groups in the DCS mission editor on which evasive actions need to be taken.
|
||||
-- @param #number Padding (Optional) Extra number of seconds to add to radar switch-back-on time
|
||||
-- @return #SEAD self
|
||||
-- @usage
|
||||
-- -- CCCP SEAD Defenses
|
||||
-- -- Defends the Russian SA installations from SEAD attacks.
|
||||
-- SEAD_RU_SAM_Defenses = SEAD:New( { 'RU SA-6 Kub', 'RU SA-6 Defenses', 'RU MI-26 Troops', 'RU Attack Gori' } )
|
||||
function SEAD:New( SEADGroupPrefixes )
|
||||
function SEAD:New( SEADGroupPrefixes, Padding )
|
||||
|
||||
local self = BASE:Inherit( self, BASE:New() )
|
||||
self:F( SEADGroupPrefixes )
|
||||
|
||||
if type( SEADGroupPrefixes ) == 'table' then
|
||||
for SEADGroupPrefixID, SEADGroupPrefix in pairs( SEADGroupPrefixes ) do
|
||||
self.SEADGroupPrefixes[SEADGroupPrefix] = SEADGroupPrefix
|
||||
end
|
||||
else
|
||||
self.SEADGroupNames[SEADGroupPrefixes] = SEADGroupPrefixes
|
||||
end
|
||||
|
||||
self:HandleEvent( EVENTS.Shot )
|
||||
|
||||
return self
|
||||
local self = BASE:Inherit( self, FSM:New() )
|
||||
self:T( SEADGroupPrefixes )
|
||||
|
||||
if type( SEADGroupPrefixes ) == 'table' then
|
||||
for SEADGroupPrefixID, SEADGroupPrefix in pairs( SEADGroupPrefixes ) do
|
||||
self.SEADGroupPrefixes[SEADGroupPrefix] = SEADGroupPrefix
|
||||
end
|
||||
else
|
||||
self.SEADGroupPrefixes[SEADGroupPrefixes] = SEADGroupPrefixes
|
||||
end
|
||||
|
||||
local padding = Padding or 10
|
||||
if padding < 10 then padding = 10 end
|
||||
self.Padding = padding
|
||||
self.UseEmissionsOnOff = true
|
||||
|
||||
self.debug = false
|
||||
|
||||
self.CallBack = nil
|
||||
self.UseCallBack = false
|
||||
|
||||
self:HandleEvent( EVENTS.Shot, self.HandleEventShot )
|
||||
|
||||
-- Start State.
|
||||
self:SetStartState("Running")
|
||||
self:AddTransition("*", "ManageEvasion", "*")
|
||||
self:AddTransition("*", "CalculateHitZone", "*")
|
||||
|
||||
self:I("*** SEAD - Started Version 0.4.3")
|
||||
return self
|
||||
end
|
||||
|
||||
--- Detects if an SA site was shot with an anti radiation missile. In this case, take evasive actions based on the skill level set within the ME.
|
||||
-- @see SEAD
|
||||
-- @param #SEAD
|
||||
--- Update the active SEAD Set (while running)
|
||||
-- @param #SEAD self
|
||||
-- @param #table SEADGroupPrefixes The prefixes to add, note: can also be a single #string
|
||||
-- @return #SEAD self
|
||||
function SEAD:UpdateSet( SEADGroupPrefixes )
|
||||
|
||||
self:T( SEADGroupPrefixes )
|
||||
|
||||
if type( SEADGroupPrefixes ) == 'table' then
|
||||
for SEADGroupPrefixID, SEADGroupPrefix in pairs( SEADGroupPrefixes ) do
|
||||
self.SEADGroupPrefixes[SEADGroupPrefix] = SEADGroupPrefix
|
||||
end
|
||||
else
|
||||
self.SEADGroupPrefixes[SEADGroupPrefixes] = SEADGroupPrefixes
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets the engagement range of the SAMs. Defaults to 75% to make it more deadly. Feature Request #1355
|
||||
-- @param #SEAD self
|
||||
-- @param #number range Set the engagement range in percent, e.g. 55 (default 75)
|
||||
-- @return #SEAD self
|
||||
function SEAD:SetEngagementRange(range)
|
||||
self:T( { range } )
|
||||
range = range or 75
|
||||
if range < 0 or range > 100 then
|
||||
range = 75
|
||||
end
|
||||
self.EngagementRange = range
|
||||
self:T(string.format("*** SEAD - Engagement range set to %s",range))
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set the padding in seconds, which extends the radar off time calculated by SEAD
|
||||
-- @param #SEAD self
|
||||
-- @param #number Padding Extra number of seconds to add for the switch-on (default 10 seconds)
|
||||
-- @return #SEAD self
|
||||
function SEAD:SetPadding(Padding)
|
||||
self:T( { Padding } )
|
||||
local padding = Padding or 10
|
||||
if padding < 10 then padding = 10 end
|
||||
self.Padding = padding
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set SEAD to use emissions on/off in addition to alarm state.
|
||||
-- @param #SEAD self
|
||||
-- @param #boolean Switch True for on, false for off.
|
||||
-- @return #SEAD self
|
||||
function SEAD:SwitchEmissions(Switch)
|
||||
self:T({Switch})
|
||||
self.UseEmissionsOnOff = Switch
|
||||
return self
|
||||
end
|
||||
|
||||
--- Add an object to call back when going evasive.
|
||||
-- @param #SEAD self
|
||||
-- @param #table Object The object to call. Needs to have object functions as follows:
|
||||
-- `:SeadSuppressionPlanned(Group, Name, SuppressionStartTime, SuppressionEndTime)`
|
||||
-- `:SeadSuppressionStart(Group, Name)`,
|
||||
-- `:SeadSuppressionEnd(Group, Name)`,
|
||||
-- @return #SEAD self
|
||||
function SEAD:AddCallBack(Object)
|
||||
self:T({Class=Object.ClassName})
|
||||
self.CallBack = Object
|
||||
self.UseCallBack = true
|
||||
return self
|
||||
end
|
||||
|
||||
--- (Internal) Check if a known HARM was fired
|
||||
-- @param #SEAD self
|
||||
-- @param #string WeaponName
|
||||
-- @return #boolean Returns true for a match
|
||||
-- @return #string name Name of hit in table
|
||||
function SEAD:_CheckHarms(WeaponName)
|
||||
self:T( { WeaponName } )
|
||||
local hit = false
|
||||
local name = ""
|
||||
for _,_name in pairs (SEAD.Harms) do
|
||||
if string.find(WeaponName,_name,1,true) then
|
||||
hit = true
|
||||
name = _name
|
||||
break
|
||||
end
|
||||
end
|
||||
return hit, name
|
||||
end
|
||||
|
||||
--- (Internal) Return distance in meters between two coordinates or -1 on error.
|
||||
-- @param #SEAD self
|
||||
-- @param Core.Point#COORDINATE _point1 Coordinate one
|
||||
-- @param Core.Point#COORDINATE _point2 Coordinate two
|
||||
-- @return #number Distance in meters
|
||||
function SEAD:_GetDistance(_point1, _point2)
|
||||
self:T("_GetDistance")
|
||||
if _point1 and _point2 then
|
||||
local distance1 = _point1:Get2DDistance(_point2)
|
||||
local distance2 = _point1:DistanceFromPointVec2(_point2)
|
||||
--self:T({dist1=distance1, dist2=distance2})
|
||||
if distance1 and type(distance1) == "number" then
|
||||
return distance1
|
||||
elseif distance2 and type(distance2) == "number" then
|
||||
return distance2
|
||||
else
|
||||
self:E("*****Cannot calculate distance!")
|
||||
self:E({_point1,_point2})
|
||||
return -1
|
||||
end
|
||||
else
|
||||
self:E("******Cannot calculate distance!")
|
||||
self:E({_point1,_point2})
|
||||
return -1
|
||||
end
|
||||
end
|
||||
|
||||
--- (Internal) Calculate hit zone of an AGM-88
|
||||
-- @param #SEAD self
|
||||
-- @param #table SEADWeapon DCS.Weapon object
|
||||
-- @param Core.Point#COORDINATE pos0 Position of the plane when it fired
|
||||
-- @param #number height Height when the missile was fired
|
||||
-- @param Wrapper.Group#GROUP SEADGroup Attacker group
|
||||
-- @param #string SEADWeaponName Weapon Name
|
||||
-- @return #SEAD self
|
||||
function SEAD:onafterCalculateHitZone(From,Event,To,SEADWeapon,pos0,height,SEADGroup,SEADWeaponName)
|
||||
self:T("**** Calculating hit zone for " .. (SEADWeaponName or "None"))
|
||||
if SEADWeapon and SEADWeapon:isExist() then
|
||||
--local pos = SEADWeapon:getPoint()
|
||||
|
||||
-- postion and height
|
||||
local position = SEADWeapon:getPosition()
|
||||
local mheight = height
|
||||
-- heading
|
||||
local wph = math.atan2(position.x.z, position.x.x)
|
||||
if wph < 0 then
|
||||
wph=wph+2*math.pi
|
||||
end
|
||||
wph=math.deg(wph)
|
||||
|
||||
-- velocity
|
||||
local wpndata = SEAD.HarmData["AGM_88"]
|
||||
if string.find(SEADWeaponName,"154",1) then
|
||||
wpndata = SEAD.HarmData["AGM_154"]
|
||||
end
|
||||
local mveloc = math.floor(wpndata[2] * 340.29)
|
||||
local c1 = (2*mheight*9.81)/(mveloc^2)
|
||||
local c2 = (mveloc^2) / 9.81
|
||||
local Ropt = c2 * math.sqrt(c1+1)
|
||||
if height <= 5000 then
|
||||
Ropt = Ropt * 0.72
|
||||
elseif height <= 7500 then
|
||||
Ropt = Ropt * 0.82
|
||||
elseif height <= 10000 then
|
||||
Ropt = Ropt * 0.87
|
||||
elseif height <= 12500 then
|
||||
Ropt = Ropt * 0.98
|
||||
end
|
||||
|
||||
-- look at a couple of zones across the trajectory
|
||||
for n=1,3 do
|
||||
local dist = Ropt - ((n-1)*20000)
|
||||
local predpos= pos0:Translate(dist,wph)
|
||||
if predpos then
|
||||
|
||||
local targetzone = ZONE_RADIUS:New("Target Zone",predpos:GetVec2(),20000)
|
||||
|
||||
if self.debug then
|
||||
predpos:MarkToAll(string.format("height=%dm | heading=%d | velocity=%ddeg | Ropt=%dm",mheight,wph,mveloc,Ropt),false)
|
||||
targetzone:DrawZone(coalition.side.BLUE,{0,0,1},0.2,nil,nil,3,true)
|
||||
end
|
||||
|
||||
local seadset = SET_GROUP:New():FilterPrefixes(self.SEADGroupPrefixes):FilterZones({targetzone}):FilterOnce()
|
||||
local tgtcoord = targetzone:GetRandomPointVec2()
|
||||
--if tgtcoord and tgtcoord.ClassName == "COORDINATE" then
|
||||
--local tgtgrp = seadset:FindNearestGroupFromPointVec2(tgtcoord)
|
||||
local tgtgrp = seadset:GetRandom()
|
||||
local _targetgroup = nil
|
||||
local _targetgroupname = "none"
|
||||
local _targetskill = "Random"
|
||||
if tgtgrp and tgtgrp:IsAlive() then
|
||||
_targetgroup = tgtgrp
|
||||
_targetgroupname = tgtgrp:GetName() -- group name
|
||||
_targetskill = tgtgrp:GetUnit(1):GetSkill()
|
||||
self:T("*** Found Target = ".. _targetgroupname)
|
||||
self:ManageEvasion(_targetskill,_targetgroup,pos0,"AGM_88",SEADGroup, 20)
|
||||
end
|
||||
--end
|
||||
end
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- (Internal) Handle Evasion
|
||||
-- @param #SEAD self
|
||||
-- @param #string _targetskill
|
||||
-- @param Wrapper.Group#GROUP _targetgroup
|
||||
-- @param Core.Point#COORDINATE SEADPlanePos
|
||||
-- @param #string SEADWeaponName
|
||||
-- @param Wrapper.Group#GROUP SEADGroup Attacker Group
|
||||
-- @param #number timeoffset Offset for tti calc
|
||||
-- @return #SEAD self
|
||||
function SEAD:onafterManageEvasion(From,Event,To,_targetskill,_targetgroup,SEADPlanePos,SEADWeaponName,SEADGroup,timeoffset)
|
||||
local timeoffset = timeoffset or 0
|
||||
if _targetskill == "Random" then -- when skill is random, choose a skill
|
||||
local Skills = { "Average", "Good", "High", "Excellent" }
|
||||
_targetskill = Skills[ math.random(1,4) ]
|
||||
end
|
||||
--self:T( _targetskill )
|
||||
if self.TargetSkill[_targetskill] then
|
||||
local _evade = math.random (1,100) -- random number for chance of evading action
|
||||
if (_evade > self.TargetSkill[_targetskill].Evade) then
|
||||
self:T("*** SEAD - Evading")
|
||||
-- calculate distance of attacker
|
||||
local _targetpos = _targetgroup:GetCoordinate()
|
||||
local _distance = self:_GetDistance(SEADPlanePos, _targetpos)
|
||||
-- weapon speed
|
||||
local hit, data = self:_CheckHarms(SEADWeaponName)
|
||||
local wpnspeed = 666 -- ;)
|
||||
local reach = 10
|
||||
if hit then
|
||||
local wpndata = SEAD.HarmData[data]
|
||||
reach = wpndata[1] * 1,1
|
||||
local mach = wpndata[2]
|
||||
wpnspeed = math.floor(mach * 340.29)
|
||||
end
|
||||
-- time to impact
|
||||
local _tti = math.floor(_distance / wpnspeed) - timeoffset -- estimated impact time
|
||||
if _distance > 0 then
|
||||
_distance = math.floor(_distance / 1000) -- km
|
||||
else
|
||||
_distance = 0
|
||||
end
|
||||
|
||||
self:T( string.format("*** SEAD - target skill %s, distance %dkm, reach %dkm, tti %dsec", _targetskill, _distance,reach,_tti ))
|
||||
|
||||
if reach >= _distance then
|
||||
self:T("*** SEAD - Shot in Reach")
|
||||
|
||||
local function SuppressionStart(args)
|
||||
self:T(string.format("*** SEAD - %s Radar Off & Relocating",args[2]))
|
||||
local grp = args[1] -- Wrapper.Group#GROUP
|
||||
local name = args[2] -- #string Group Name
|
||||
local attacker = args[3] -- Wrapper.Group#GROUP
|
||||
if self.UseEmissionsOnOff then
|
||||
grp:EnableEmission(false)
|
||||
end
|
||||
grp:OptionAlarmStateGreen() -- needed else we cannot move around
|
||||
grp:RelocateGroundRandomInRadius(20,300,false,false,"Diamond")
|
||||
if self.UseCallBack then
|
||||
local object = self.CallBack
|
||||
object:SeadSuppressionStart(grp,name,attacker)
|
||||
end
|
||||
end
|
||||
|
||||
local function SuppressionStop(args)
|
||||
self:T(string.format("*** SEAD - %s Radar On",args[2]))
|
||||
local grp = args[1] -- Wrapper.Group#GROUP
|
||||
local name = args[2] -- #string Group Nam
|
||||
if self.UseEmissionsOnOff then
|
||||
grp:EnableEmission(true)
|
||||
end
|
||||
grp:OptionAlarmStateRed()
|
||||
grp:OptionEngageRange(self.EngagementRange)
|
||||
self.SuppressedGroups[name] = false
|
||||
if self.UseCallBack then
|
||||
local object = self.CallBack
|
||||
object:SeadSuppressionEnd(grp,name)
|
||||
end
|
||||
end
|
||||
|
||||
-- randomize switch-on time
|
||||
local delay = math.random(self.TargetSkill[_targetskill].DelayOn[1], self.TargetSkill[_targetskill].DelayOn[2])
|
||||
if delay > _tti then delay = delay / 2 end -- speed up
|
||||
if _tti > 600 then delay = _tti - 90 end -- shot from afar, 600 is default shorad ontime
|
||||
|
||||
local SuppressionStartTime = timer.getTime() + delay
|
||||
local SuppressionEndTime = timer.getTime() + _tti + self.Padding
|
||||
local _targetgroupname = _targetgroup:GetName()
|
||||
if not self.SuppressedGroups[_targetgroupname] then
|
||||
self:T(string.format("*** SEAD - %s | Parameters TTI %ds | Switch-Off in %ds",_targetgroupname,_tti,delay))
|
||||
timer.scheduleFunction(SuppressionStart,{_targetgroup,_targetgroupname, SEADGroup},SuppressionStartTime)
|
||||
timer.scheduleFunction(SuppressionStop,{_targetgroup,_targetgroupname},SuppressionEndTime)
|
||||
self.SuppressedGroups[_targetgroupname] = true
|
||||
if self.UseCallBack then
|
||||
local object = self.CallBack
|
||||
object:SeadSuppressionPlanned(_targetgroup,_targetgroupname,SuppressionStartTime,SuppressionEndTime, SEADGroup)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- (Internal) Detects if an SAM site was shot with an anti radiation missile. In this case, take evasive actions based on the skill level set within the ME.
|
||||
-- @param #SEAD self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function SEAD:OnEventShot( EventData )
|
||||
self:F( { EventData } )
|
||||
-- @return #SEAD self
|
||||
function SEAD:HandleEventShot( EventData )
|
||||
self:T( { EventData.id } )
|
||||
local SEADPlane = EventData.IniUnit -- Wrapper.Unit#UNIT
|
||||
local SEADGroup = EventData.IniGroup -- Wrapper.Group#GROUP
|
||||
local SEADPlanePos = SEADPlane:GetCoordinate() -- Core.Point#COORDINATE
|
||||
local SEADUnit = EventData.IniDCSUnit
|
||||
local SEADUnitName = EventData.IniDCSUnitName
|
||||
local SEADWeapon = EventData.Weapon -- Identify the weapon fired
|
||||
local SEADWeaponName = EventData.WeaponName -- return weapon type
|
||||
|
||||
local SEADUnit = EventData.IniDCSUnit
|
||||
local SEADUnitName = EventData.IniDCSUnitName
|
||||
local SEADWeapon = EventData.Weapon -- Identify the weapon fired
|
||||
local SEADWeaponName = EventData.WeaponName -- return weapon type
|
||||
-- Start of the 2nd loop
|
||||
self:T( "Missile Launched = " .. SEADWeaponName )
|
||||
|
||||
--if SEADWeaponName == "KH-58" or SEADWeaponName == "KH-25MPU" or SEADWeaponName == "AGM-88" or SEADWeaponName == "KH-31A" or SEADWeaponName == "KH-31P" then -- Check if the missile is a SEAD
|
||||
if SEADWeaponName == "weapons.missiles.X_58" --Kh-58U anti-radiation missiles fired
|
||||
or
|
||||
SEADWeaponName == "weapons.missiles.Kh25MP_PRGS1VP" --Kh-25MP anti-radiation missiles fired
|
||||
or
|
||||
SEADWeaponName == "weapons.missiles.X_25MP" --Kh-25MPU anti-radiation missiles fired
|
||||
or
|
||||
SEADWeaponName == "weapons.missiles.X_28" --Kh-28 anti-radiation missiles fired
|
||||
or
|
||||
SEADWeaponName == "weapons.missiles.X_31P" --Kh-31P anti-radiation missiles fired
|
||||
or
|
||||
SEADWeaponName == "weapons.missiles.AGM_45A" --AGM-45A anti-radiation missiles fired
|
||||
or
|
||||
SEADWeaponName == "weapons.missiles.AGM_45" --AGM-45B anti-radiation missiles fired
|
||||
or
|
||||
SEADWeaponName == "weapons.missiles.AGM_88" --AGM-88C anti-radiation missiles fired
|
||||
or
|
||||
SEADWeaponName == "weapons.missiles.AGM_122" --AGM-122 Sidearm anti-radiation missiles fired
|
||||
or
|
||||
SEADWeaponName == "weapons.missiles.ALARM" --ALARM anti-radiation missiles fired
|
||||
then
|
||||
|
||||
local _evade = math.random (1,100) -- random number for chance of evading action
|
||||
local _targetMim = EventData.Weapon:getTarget() -- Identify target
|
||||
local _targetMimname = Unit.getName(_targetMim)
|
||||
local _targetMimgroup = Unit.getGroup(Weapon.getTarget(SEADWeapon))
|
||||
local _targetMimgroupName = _targetMimgroup:getName()
|
||||
local _targetMimcont= _targetMimgroup:getController()
|
||||
local _targetskill = _DATABASE.Templates.Units[_targetMimname].Template.skill
|
||||
self:T( self.SEADGroupPrefixes )
|
||||
self:T( _targetMimgroupName )
|
||||
local SEADGroupFound = false
|
||||
for SEADGroupPrefixID, SEADGroupPrefix in pairs( self.SEADGroupPrefixes ) do
|
||||
if string.find( _targetMimgroupName, SEADGroupPrefix, 1, true ) then
|
||||
SEADGroupFound = true
|
||||
self:T( 'Group Found' )
|
||||
break
|
||||
end
|
||||
end
|
||||
if SEADGroupFound == true then
|
||||
if _targetskill == "Random" then -- when skill is random, choose a skill
|
||||
local Skills = { "Average", "Good", "High", "Excellent" }
|
||||
_targetskill = Skills[ math.random(1,4) ]
|
||||
end
|
||||
self:T( _targetskill )
|
||||
if self.TargetSkill[_targetskill] then
|
||||
if (_evade > self.TargetSkill[_targetskill].Evade) then
|
||||
|
||||
self:T( string.format("Evading, target skill " ..string.format(_targetskill)) )
|
||||
|
||||
local _targetMim = Weapon.getTarget(SEADWeapon)
|
||||
local _targetMimname = Unit.getName(_targetMim)
|
||||
local _targetMimgroup = Unit.getGroup(Weapon.getTarget(SEADWeapon))
|
||||
local _targetMimcont= _targetMimgroup:getController()
|
||||
|
||||
routines.groupRandomDistSelf(_targetMimgroup,300,'Diamond',250,20) -- move randomly
|
||||
|
||||
local SuppressedGroups1 = {} -- unit suppressed radar off for a random time
|
||||
|
||||
local function SuppressionEnd1(id)
|
||||
id.ctrl:setOption(AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.GREEN)
|
||||
SuppressedGroups1[id.groupName] = nil
|
||||
end
|
||||
|
||||
local id = {
|
||||
groupName = _targetMimgroup,
|
||||
ctrl = _targetMimcont
|
||||
}
|
||||
|
||||
local delay1 = math.random(self.TargetSkill[_targetskill].DelayOff[1], self.TargetSkill[_targetskill].DelayOff[2])
|
||||
|
||||
if SuppressedGroups1[id.groupName] == nil then
|
||||
|
||||
SuppressedGroups1[id.groupName] = {
|
||||
SuppressionEndTime1 = timer.getTime() + delay1,
|
||||
SuppressionEndN1 = SuppressionEndCounter1 --Store instance of SuppressionEnd() scheduled function
|
||||
}
|
||||
|
||||
Controller.setOption(_targetMimcont, AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.GREEN)
|
||||
timer.scheduleFunction(SuppressionEnd1, id, SuppressedGroups1[id.groupName].SuppressionEndTime1) --Schedule the SuppressionEnd() function
|
||||
--trigger.action.outText( string.format("Radar Off " ..string.format(delay1)), 20)
|
||||
end
|
||||
|
||||
local SuppressedGroups = {}
|
||||
|
||||
local function SuppressionEnd(id)
|
||||
id.ctrl:setOption(AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.RED)
|
||||
SuppressedGroups[id.groupName] = nil
|
||||
end
|
||||
|
||||
local id = {
|
||||
groupName = _targetMimgroup,
|
||||
ctrl = _targetMimcont
|
||||
}
|
||||
|
||||
local delay = math.random(self.TargetSkill[_targetskill].DelayOn[1], self.TargetSkill[_targetskill].DelayOn[2])
|
||||
|
||||
if SuppressedGroups[id.groupName] == nil then
|
||||
SuppressedGroups[id.groupName] = {
|
||||
SuppressionEndTime = timer.getTime() + delay,
|
||||
SuppressionEndN = SuppressionEndCounter --Store instance of SuppressionEnd() scheduled function
|
||||
}
|
||||
|
||||
timer.scheduleFunction(SuppressionEnd, id, SuppressedGroups[id.groupName].SuppressionEndTime) --Schedule the SuppressionEnd() function
|
||||
--trigger.action.outText( string.format("Radar On " ..string.format(delay)), 20)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
self:T( "*** SEAD - Missile Launched = " .. SEADWeaponName)
|
||||
--self:T({ SEADWeapon })
|
||||
|
||||
if self:_CheckHarms(SEADWeaponName) then
|
||||
self:T( '*** SEAD - Weapon Match' )
|
||||
local _targetskill = "Random"
|
||||
local _targetgroupname = "none"
|
||||
local _target = EventData.Weapon:getTarget() -- Identify target
|
||||
if not _target or self.debug then -- AGM-88 or 154 w/o target data
|
||||
self:E("***** SEAD - No target data for " .. (SEADWeaponName or "None"))
|
||||
if string.find(SEADWeaponName,"AGM_88",1,true) or string.find(SEADWeaponName,"AGM_154",1,true) then
|
||||
self:I("**** Tracking AGM-88/154 with no target data.")
|
||||
local pos0 = SEADPlane:GetCoordinate()
|
||||
local fheight = SEADPlane:GetHeight()
|
||||
self:__CalculateHitZone(20,SEADWeapon,pos0,fheight,SEADGroup,SEADWeaponName)
|
||||
end
|
||||
return self
|
||||
end
|
||||
local targetcat = _target:getCategory() -- Identify category
|
||||
local _targetUnit = nil -- Wrapper.Unit#UNIT
|
||||
local _targetgroup = nil -- Wrapper.Group#GROUP
|
||||
self:T(string.format("*** Targetcat = %d",targetcat))
|
||||
if targetcat == Object.Category.UNIT then -- UNIT
|
||||
self:T("*** Target Category UNIT")
|
||||
_targetUnit = UNIT:Find(_target) -- Wrapper.Unit#UNIT
|
||||
if _targetUnit and _targetUnit:IsAlive() then
|
||||
_targetgroup = _targetUnit:GetGroup()
|
||||
_targetgroupname = _targetgroup:GetName() -- group name
|
||||
local _targetUnitName = _targetUnit:GetName()
|
||||
_targetUnit:GetSkill()
|
||||
_targetskill = _targetUnit:GetSkill()
|
||||
end
|
||||
elseif targetcat == Object.Category.STATIC then
|
||||
self:T("*** Target Category STATIC")
|
||||
local seadset = SET_GROUP:New():FilterPrefixes(self.SEADGroupPrefixes):FilterOnce()
|
||||
local targetpoint = _target:getPoint() or {x=0,y=0,z=0}
|
||||
local tgtcoord = COORDINATE:NewFromVec3(targetpoint)
|
||||
local tgtgrp = seadset:FindNearestGroupFromPointVec2(tgtcoord)
|
||||
if tgtgrp and tgtgrp:IsAlive() then
|
||||
_targetgroup = tgtgrp
|
||||
_targetgroupname = tgtgrp:GetName() -- group name
|
||||
_targetskill = tgtgrp:GetUnit(1):GetSkill()
|
||||
self:T("*** Found Target = ".. _targetgroupname)
|
||||
end
|
||||
end
|
||||
-- see if we are shot at
|
||||
local SEADGroupFound = false
|
||||
for SEADGroupPrefixID, SEADGroupPrefix in pairs( self.SEADGroupPrefixes ) do
|
||||
self:T("Target = ".. _targetgroupname .. " | Prefix = " .. SEADGroupPrefix )
|
||||
if string.find( _targetgroupname, SEADGroupPrefix,1,true ) then
|
||||
SEADGroupFound = true
|
||||
self:T( '*** SEAD - Group Match Found' )
|
||||
break
|
||||
end
|
||||
end
|
||||
if SEADGroupFound == true then -- yes we are being attacked
|
||||
self:ManageEvasion(_targetskill,_targetgroup,SEADPlanePos,SEADWeaponName,SEADGroup)
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -0,0 +1,667 @@
|
||||
--- **Functional** -- Short Range Air Defense System
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- **SHORAD** - Short Range Air Defense System
|
||||
-- Controls a network of short range air/missile defense groups.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ## Missions:
|
||||
--
|
||||
-- ### [SHORAD - Short Range Air Defense](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/SRD%20-%20SHORAD%20Defense)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author : **applevangelist **
|
||||
--
|
||||
-- @module Functional.Shorad
|
||||
-- @image Functional.Shorad.jpg
|
||||
--
|
||||
-- Date: Nov 2021
|
||||
|
||||
-------------------------------------------------------------------------
|
||||
--- **SHORAD** class, extends Core.Base#BASE
|
||||
-- @type SHORAD
|
||||
-- @field #string ClassName
|
||||
-- @field #string name Name of this Shorad
|
||||
-- @field #boolean debug Set the debug state
|
||||
-- @field #string Prefixes String to be used to build the @{#Core.Set#SET_GROUP}
|
||||
-- @field #number Radius Shorad defense radius in meters
|
||||
-- @field Core.Set#SET_GROUP Groupset The set of Shorad groups
|
||||
-- @field Core.Set#SET_GROUP Samset The set of SAM groups to defend
|
||||
-- @field #string Coalition The coalition of this Shorad
|
||||
-- @field #number ActiveTimer How long a Shorad stays active after wake-up in seconds
|
||||
-- @field #table ActiveGroups Table for the timer function
|
||||
-- @field #string lid The log ID for the dcs.log
|
||||
-- @field #boolean DefendHarms Default true, intercept incoming HARMS
|
||||
-- @field #boolean DefendMavs Default true, intercept incoming AG-Missiles
|
||||
-- @field #number DefenseLowProb Default 70, minimum detection limit
|
||||
-- @field #number DefenseHighProb Default 90, maximim detection limit
|
||||
-- @field #boolean UseEmOnOff Decide if we are using Emission on/off (default) or AlarmState red/green.
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
|
||||
--- *Good friends are worth defending.* Mr Tushman, Wonder (the Movie)
|
||||
--
|
||||
-- Simple Class for a more intelligent Short Range Air Defense System
|
||||
--
|
||||
-- #SHORAD
|
||||
-- Moose derived missile intercepting short range defense system.
|
||||
-- Protects a network of SAM sites. Uses events to switch on the defense groups closest to the enemy.
|
||||
-- Easily integrated with @{Functional.Mantis#MANTIS} to complete the defensive system setup.
|
||||
--
|
||||
-- ## Usage
|
||||
--
|
||||
-- Set up a #SET_GROUP for the SAM sites to be protected:
|
||||
--
|
||||
-- `local SamSet = SET_GROUP:New():FilterPrefixes("Red SAM"):FilterCoalitions("red"):FilterStart()`
|
||||
--
|
||||
-- By default, SHORAD will defense against both HARMs and AG-Missiles with short to medium range. The default defense probability is 70-90%.
|
||||
-- When a missile is detected, SHORAD will activate defense groups in the given radius around the target for 10 minutes. It will *not* react to friendly fire.
|
||||
--
|
||||
-- ### Start a new SHORAD system, parameters are:
|
||||
--
|
||||
-- * Name: Name of this SHORAD.
|
||||
-- * ShoradPrefix: Filter for the Shorad #SET_GROUP.
|
||||
-- * Samset: The #SET_GROUP of SAM sites to defend.
|
||||
-- * Radius: Defense radius in meters.
|
||||
-- * ActiveTimer: Determines how many seconds the systems stay on red alert after wake-up call.
|
||||
-- * Coalition: Coalition, i.e. "blue", "red", or "neutral".*
|
||||
--
|
||||
-- `myshorad = SHORAD:New("RedShorad", "Red SHORAD", SamSet, 25000, 600, "red")`
|
||||
--
|
||||
-- ## Customize options
|
||||
--
|
||||
-- * SHORAD:SwitchDebug(debug)
|
||||
-- * SHORAD:SwitchHARMDefense(onoff)
|
||||
-- * SHORAD:SwitchAGMDefense(onoff)
|
||||
-- * SHORAD:SetDefenseLimits(low,high)
|
||||
-- * SHORAD:SetActiveTimer(seconds)
|
||||
-- * SHORAD:SetDefenseRadius(meters)
|
||||
--
|
||||
-- @field #SHORAD
|
||||
SHORAD = {
|
||||
ClassName = "SHORAD",
|
||||
name = "MyShorad",
|
||||
debug = false,
|
||||
Prefixes = "",
|
||||
Radius = 20000,
|
||||
Groupset = nil,
|
||||
Samset = nil,
|
||||
Coalition = nil,
|
||||
ActiveTimer = 600, --stay on 10 mins
|
||||
ActiveGroups = {},
|
||||
lid = "",
|
||||
DefendHarms = true,
|
||||
DefendMavs = true,
|
||||
DefenseLowProb = 70,
|
||||
DefenseHighProb = 90,
|
||||
UseEmOnOff = false,
|
||||
}
|
||||
|
||||
-----------------------------------------------------------------------
|
||||
-- SHORAD System
|
||||
-----------------------------------------------------------------------
|
||||
|
||||
do
|
||||
-- TODO Complete list?
|
||||
--- Missile enumerators
|
||||
-- @field Harms
|
||||
SHORAD.Harms = {
|
||||
["AGM_88"] = "AGM_88",
|
||||
["AGM_45"] = "AGM_45",
|
||||
["AGM_122"] = "AGM_122",
|
||||
["AGM_84"] = "AGM_84",
|
||||
["AGM_45"] = "AGM_45",
|
||||
["ALARM"] = "ALARM",
|
||||
["LD-10"] = "LD-10",
|
||||
["X_58"] = "X_58",
|
||||
["X_28"] = "X_28",
|
||||
["X_25"] = "X_25",
|
||||
["X_31"] = "X_31",
|
||||
["Kh25"] = "Kh25",
|
||||
}
|
||||
|
||||
--- TODO complete list?
|
||||
-- @field Mavs
|
||||
SHORAD.Mavs = {
|
||||
["AGM"] = "AGM",
|
||||
["C-701"] = "C-701",
|
||||
["Kh25"] = "Kh25",
|
||||
["Kh29"] = "Kh29",
|
||||
["Kh31"] = "Kh31",
|
||||
["Kh66"] = "Kh66",
|
||||
--["BGM_109"] = "BGM_109",
|
||||
}
|
||||
|
||||
--- Instantiates a new SHORAD object
|
||||
-- @param #SHORAD self
|
||||
-- @param #string Name Name of this SHORAD
|
||||
-- @param #string ShoradPrefix Filter for the Shorad #SET_GROUP
|
||||
-- @param Core.Set#SET_GROUP Samset The #SET_GROUP of SAM sites to defend
|
||||
-- @param #number Radius Defense radius in meters, used to switch on SHORAD groups **within** this radius
|
||||
-- @param #number ActiveTimer Determines how many seconds the systems stay on red alert after wake-up call
|
||||
-- @param #string Coalition Coalition, i.e. "blue", "red", or "neutral"
|
||||
-- @param #boolean UseEmOnOff Use Emissions On/Off rather than Alarm State Red/Green (default: use Emissions switch)
|
||||
-- @retunr #SHORAD self
|
||||
function SHORAD:New(Name, ShoradPrefix, Samset, Radius, ActiveTimer, Coalition, UseEmOnOff)
|
||||
local self = BASE:Inherit( self, FSM:New() )
|
||||
self:T({Name, ShoradPrefix, Samset, Radius, ActiveTimer, Coalition})
|
||||
|
||||
local GroupSet = SET_GROUP:New():FilterPrefixes(ShoradPrefix):FilterCoalitions(Coalition):FilterCategoryGround():FilterStart()
|
||||
|
||||
self.name = Name or "MyShorad"
|
||||
self.Prefixes = ShoradPrefix or "SAM SHORAD"
|
||||
self.Radius = Radius or 20000
|
||||
self.Coalition = Coalition or "blue"
|
||||
self.Samset = Samset or GroupSet
|
||||
self.ActiveTimer = ActiveTimer or 600
|
||||
self.ActiveGroups = {}
|
||||
self.Groupset = GroupSet
|
||||
self.DefendHarms = true
|
||||
self.DefendMavs = true
|
||||
self.DefenseLowProb = 70 -- probability to detect a missile shot, low margin
|
||||
self.DefenseHighProb = 90 -- probability to detect a missile shot, high margin
|
||||
self.UseEmOnOff = UseEmOnOff or false -- Decide if we are using Emission on/off (default) or AlarmState red/green
|
||||
self:I("*** SHORAD - Started Version 0.3.1")
|
||||
-- Set the string id for output to DCS.log file.
|
||||
self.lid=string.format("SHORAD %s | ", self.name)
|
||||
self:_InitState()
|
||||
self:HandleEvent(EVENTS.Shot, self.HandleEventShot)
|
||||
|
||||
-- Start State.
|
||||
self:SetStartState("Running")
|
||||
self:AddTransition("*", "WakeUpShorad", "*")
|
||||
self:AddTransition("*", "CalculateHitZone", "*")
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Initially set all groups to alarm state GREEN
|
||||
-- @param #SHORAD self
|
||||
function SHORAD:_InitState()
|
||||
self:T(self.lid .. " _InitState")
|
||||
local table = {}
|
||||
local set = self.Groupset
|
||||
self:T({set = set})
|
||||
local aliveset = set:GetAliveSet() --#table
|
||||
for _,_group in pairs (aliveset) do
|
||||
if self.UseEmOnOff then
|
||||
--_group:SetAIOff()
|
||||
_group:EnableEmission(false)
|
||||
_group:OptionAlarmStateRed() --Wrapper.Group#GROUP
|
||||
else
|
||||
_group:OptionAlarmStateGreen() --Wrapper.Group#GROUP
|
||||
end
|
||||
_group:OptionDisperseOnAttack(30)
|
||||
end
|
||||
-- gather entropy
|
||||
for i=1,100 do
|
||||
math.random()
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Switch debug state on
|
||||
-- @param #SHORAD self
|
||||
-- @param #boolean debug Switch debug on (true) or off (false)
|
||||
function SHORAD:SwitchDebug(onoff)
|
||||
self:T( { onoff } )
|
||||
if onoff then
|
||||
self:SwitchDebugOn()
|
||||
else
|
||||
self.SwitchDebugOff()
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Switch debug state on
|
||||
-- @param #SHORAD self
|
||||
function SHORAD:SwitchDebugOn()
|
||||
self.debug = true
|
||||
--tracing
|
||||
BASE:TraceOn()
|
||||
BASE:TraceClass("SHORAD")
|
||||
return self
|
||||
end
|
||||
|
||||
--- Switch debug state off
|
||||
-- @param #SHORAD self
|
||||
function SHORAD:SwitchDebugOff()
|
||||
self.debug = false
|
||||
BASE:TraceOff()
|
||||
return self
|
||||
end
|
||||
|
||||
--- Switch defense for HARMs
|
||||
-- @param #SHORAD self
|
||||
-- @param #boolean onoff
|
||||
function SHORAD:SwitchHARMDefense(onoff)
|
||||
self:T( { onoff } )
|
||||
local onoff = onoff or true
|
||||
self.DefendHarms = onoff
|
||||
return self
|
||||
end
|
||||
|
||||
--- Switch defense for AGMs
|
||||
-- @param #SHORAD self
|
||||
-- @param #boolean onoff
|
||||
function SHORAD:SwitchAGMDefense(onoff)
|
||||
self:T( { onoff } )
|
||||
local onoff = onoff or true
|
||||
self.DefendMavs = onoff
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set defense probability limits
|
||||
-- @param #SHORAD self
|
||||
-- @param #number low Minimum detection limit, integer 1-100
|
||||
-- @param #number high Maximum detection limit integer 1-100
|
||||
function SHORAD:SetDefenseLimits(low,high)
|
||||
self:T( { low, high } )
|
||||
local low = low or 70
|
||||
local high = high or 90
|
||||
if (low < 0) or (low > 100) or (low > high) then
|
||||
low = 70
|
||||
end
|
||||
if (high < 0) or (high > 100) or (high < low ) then
|
||||
high = 90
|
||||
end
|
||||
self.DefenseLowProb = low
|
||||
self.DefenseHighProb = high
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set the number of seconds a SHORAD site will stay active
|
||||
-- @param #SHORAD self
|
||||
-- @param #number seconds Number of seconds systems stay active
|
||||
function SHORAD:SetActiveTimer(seconds)
|
||||
self:T(self.lid .. " SetActiveTimer")
|
||||
local timer = seconds or 600
|
||||
if timer < 0 then
|
||||
timer = 600
|
||||
end
|
||||
self.ActiveTimer = timer
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set the number of meters for the SHORAD defense zone
|
||||
-- @param #SHORAD self
|
||||
-- @param #number meters Radius of the defense search zone in meters. #SHORADs in this range around a targeted group will go active
|
||||
function SHORAD:SetDefenseRadius(meters)
|
||||
self:T(self.lid .. " SetDefenseRadius")
|
||||
local radius = meters or 20000
|
||||
if radius < 0 then
|
||||
radius = 20000
|
||||
end
|
||||
self.Radius = radius
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set using Emission on/off instead of changing alarm state
|
||||
-- @param #SHORAD self
|
||||
-- @param #boolean switch Decide if we are changing alarm state or AI state
|
||||
function SHORAD:SetUsingEmOnOff(switch)
|
||||
self:T(self.lid .. " SetUsingEmOnOff")
|
||||
self.UseEmOnOff = switch or false
|
||||
return self
|
||||
end
|
||||
|
||||
--- Check if a HARM was fired
|
||||
-- @param #SHORAD self
|
||||
-- @param #string WeaponName
|
||||
-- @return #boolean Returns true for a match
|
||||
function SHORAD:_CheckHarms(WeaponName)
|
||||
self:T(self.lid .. " _CheckHarms")
|
||||
self:T( { WeaponName } )
|
||||
local hit = false
|
||||
if self.DefendHarms then
|
||||
for _,_name in pairs (SHORAD.Harms) do
|
||||
if string.find(WeaponName,_name,1,true) then hit = true end
|
||||
end
|
||||
end
|
||||
return hit
|
||||
end
|
||||
|
||||
--- Check if an AGM was fired
|
||||
-- @param #SHORAD self
|
||||
-- @param #string WeaponName
|
||||
-- @return #boolean Returns true for a match
|
||||
function SHORAD:_CheckMavs(WeaponName)
|
||||
self:T(self.lid .. " _CheckMavs")
|
||||
self:T( { WeaponName } )
|
||||
local hit = false
|
||||
if self.DefendMavs then
|
||||
for _,_name in pairs (SHORAD.Mavs) do
|
||||
if string.find(WeaponName,_name,1,true) then hit = true end
|
||||
end
|
||||
end
|
||||
return hit
|
||||
end
|
||||
|
||||
--- Check the coalition of the attacker
|
||||
-- @param #SHORAD self
|
||||
-- @param #string Coalition name
|
||||
-- @return #boolean Returns false for a match
|
||||
function SHORAD:_CheckCoalition(Coalition)
|
||||
self:T(self.lid .. " _CheckCoalition")
|
||||
local owncoalition = self.Coalition
|
||||
local othercoalition = ""
|
||||
if Coalition == 0 then
|
||||
othercoalition = "neutral"
|
||||
elseif Coalition == 1 then
|
||||
othercoalition = "red"
|
||||
else
|
||||
othercoalition = "blue"
|
||||
end
|
||||
self:T({owncoalition = owncoalition, othercoalition = othercoalition})
|
||||
if owncoalition ~= othercoalition then
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
--- Check if the missile is aimed at a SHORAD
|
||||
-- @param #SHORAD self
|
||||
-- @param #string TargetGroupName Name of the target group
|
||||
-- @return #boolean Returns true for a match, else false
|
||||
function SHORAD:_CheckShotAtShorad(TargetGroupName)
|
||||
self:T(self.lid .. " _CheckShotAtShorad")
|
||||
local tgtgrp = TargetGroupName
|
||||
local shorad = self.Groupset
|
||||
local shoradset = shorad:GetAliveSet() --#table
|
||||
local returnname = false
|
||||
for _,_groups in pairs (shoradset) do
|
||||
local groupname = _groups:GetName()
|
||||
if string.find(groupname, tgtgrp, 1, true) then
|
||||
returnname = true
|
||||
--_groups:RelocateGroundRandomInRadius(7,100,false,false) -- be a bit evasive
|
||||
end
|
||||
end
|
||||
return returnname
|
||||
end
|
||||
|
||||
--- Check if the missile is aimed at a SAM site
|
||||
-- @param #SHORAD self
|
||||
-- @param #string TargetGroupName Name of the target group
|
||||
-- @return #boolean Returns true for a match, else false
|
||||
function SHORAD:_CheckShotAtSams(TargetGroupName)
|
||||
self:T(self.lid .. " _CheckShotAtSams")
|
||||
local tgtgrp = TargetGroupName
|
||||
local shorad = self.Samset
|
||||
--local shoradset = shorad:GetAliveSet() --#table
|
||||
local shoradset = shorad:GetSet() --#table
|
||||
local returnname = false
|
||||
for _,_groups in pairs (shoradset) do
|
||||
local groupname = _groups:GetName()
|
||||
if string.find(groupname, tgtgrp, 1, true) then
|
||||
returnname = true
|
||||
end
|
||||
end
|
||||
return returnname
|
||||
end
|
||||
|
||||
--- Calculate if the missile shot is detected
|
||||
-- @param #SHORAD self
|
||||
-- @return #boolean Returns true for a detection, else false
|
||||
function SHORAD:_ShotIsDetected()
|
||||
self:T(self.lid .. " _ShotIsDetected")
|
||||
if self.debug then return true end
|
||||
local IsDetected = false
|
||||
local DetectionProb = math.random(self.DefenseLowProb, self.DefenseHighProb) -- reference value
|
||||
local ActualDetection = math.random(1,100) -- value for this shot
|
||||
if ActualDetection <= DetectionProb then
|
||||
IsDetected = true
|
||||
end
|
||||
return IsDetected
|
||||
end
|
||||
|
||||
--- Wake up #SHORADs in a zone with diameter Radius for ActiveTimer seconds
|
||||
-- @param #SHORAD self
|
||||
-- @param #string TargetGroup Name of the target group used to build the #ZONE
|
||||
-- @param #number Radius Radius of the #ZONE
|
||||
-- @param #number ActiveTimer Number of seconds to stay active
|
||||
-- @param #number TargetCat (optional) Category, i.e. Object.Category.UNIT or Object.Category.STATIC
|
||||
-- @usage Use this function to integrate with other systems, example
|
||||
--
|
||||
-- local SamSet = SET_GROUP:New():FilterPrefixes("Blue SAM"):FilterCoalitions("blue"):FilterStart()
|
||||
-- myshorad = SHORAD:New("BlueShorad", "Blue SHORAD", SamSet, 22000, 600, "blue")
|
||||
-- myshorad:SwitchDebug(true)
|
||||
-- mymantis = MANTIS:New("BlueMantis","Blue SAM","Blue EWR",nil,"blue",false,"Blue Awacs")
|
||||
-- mymantis:AddShorad(myshorad,720)
|
||||
-- mymantis:Start()
|
||||
function SHORAD:onafterWakeUpShorad(From, Event, To, TargetGroup, Radius, ActiveTimer, TargetCat)
|
||||
self:T(self.lid .. " WakeUpShorad")
|
||||
self:T({TargetGroup, Radius, ActiveTimer, TargetCat})
|
||||
local targetcat = TargetCat or Object.Category.UNIT
|
||||
local targetgroup = TargetGroup
|
||||
local targetvec2 = nil
|
||||
if targetcat == Object.Category.UNIT then
|
||||
targetvec2 = GROUP:FindByName(targetgroup):GetVec2()
|
||||
elseif targetcat == Object.Category.STATIC then
|
||||
targetvec2 = STATIC:FindByName(targetgroup,false):GetVec2()
|
||||
else
|
||||
local samset = self.Samset
|
||||
local sam = samset:GetRandom()
|
||||
targetvec2 = sam:GetVec2()
|
||||
end
|
||||
local targetzone = ZONE_RADIUS:New("Shorad",targetvec2,Radius) -- create a defense zone to check
|
||||
local groupset = self.Groupset --Core.Set#SET_GROUP
|
||||
local shoradset = groupset:GetAliveSet() --#table
|
||||
-- local function to switch off shorad again
|
||||
local function SleepShorad(group)
|
||||
local groupname = group:GetName()
|
||||
self.ActiveGroups[groupname] = nil
|
||||
if self.UseEmOnOff then
|
||||
group:EnableEmission(false)
|
||||
--group:SetAIOff()
|
||||
else
|
||||
group:OptionAlarmStateGreen()
|
||||
end
|
||||
local text = string.format("Sleeping SHORAD %s", group:GetName())
|
||||
self:T(text)
|
||||
local m = MESSAGE:New(text,10,"SHORAD"):ToAllIf(self.debug)
|
||||
end
|
||||
-- go through set and find the one(s) to activate
|
||||
for _,_group in pairs (shoradset) do
|
||||
if _group:IsAnyInZone(targetzone) then
|
||||
local text = string.format("Waking up SHORAD %s", _group:GetName())
|
||||
self:T(text)
|
||||
local m = MESSAGE:New(text,10,"SHORAD"):ToAllIf(self.debug)
|
||||
if self.UseEmOnOff then
|
||||
--_group:SetAIOn()
|
||||
_group:EnableEmission(true)
|
||||
end
|
||||
_group:OptionAlarmStateRed()
|
||||
local groupname = _group:GetName()
|
||||
if self.ActiveGroups[groupname] == nil then -- no timer yet for this group
|
||||
self.ActiveGroups[groupname] = { Timing = ActiveTimer }
|
||||
local endtime = timer.getTime() + (ActiveTimer * math.random(75,100) / 100 ) -- randomize wakeup a bit
|
||||
timer.scheduleFunction(SleepShorad, _group, endtime)
|
||||
end
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- (Internal) Calculate hit zone of an AGM-88
|
||||
-- @param #SHORAD self
|
||||
-- @param #table SEADWeapon DCS.Weapon object
|
||||
-- @param Core.Point#COORDINATE pos0 Position of the plane when it fired
|
||||
-- @param #number height Height when the missile was fired
|
||||
-- @param Wrapper.Group#GROUP SEADGroup Attacker group
|
||||
-- @return #SHORAD self
|
||||
function SHORAD:onafterCalculateHitZone(From,Event,To,SEADWeapon,pos0,height,SEADGroup)
|
||||
self:T("**** Calculating hit zone")
|
||||
if SEADWeapon and SEADWeapon:isExist() then
|
||||
--local pos = SEADWeapon:getPoint()
|
||||
|
||||
-- postion and height
|
||||
local position = SEADWeapon:getPosition()
|
||||
local mheight = height
|
||||
-- heading
|
||||
local wph = math.atan2(position.x.z, position.x.x)
|
||||
if wph < 0 then
|
||||
wph=wph+2*math.pi
|
||||
end
|
||||
wph=math.deg(wph)
|
||||
|
||||
-- velocity
|
||||
local wpndata = SEAD.HarmData["AGM_88"]
|
||||
local mveloc = math.floor(wpndata[2] * 340.29)
|
||||
local c1 = (2*mheight*9.81)/(mveloc^2)
|
||||
local c2 = (mveloc^2) / 9.81
|
||||
local Ropt = c2 * math.sqrt(c1+1)
|
||||
if height <= 5000 then
|
||||
Ropt = Ropt * 0.72
|
||||
elseif height <= 7500 then
|
||||
Ropt = Ropt * 0.82
|
||||
elseif height <= 10000 then
|
||||
Ropt = Ropt * 0.87
|
||||
elseif height <= 12500 then
|
||||
Ropt = Ropt * 0.98
|
||||
end
|
||||
|
||||
-- look at a couple of zones across the trajectory
|
||||
for n=1,3 do
|
||||
local dist = Ropt - ((n-1)*20000)
|
||||
local predpos= pos0:Translate(dist,wph)
|
||||
if predpos then
|
||||
|
||||
local targetzone = ZONE_RADIUS:New("Target Zone",predpos:GetVec2(),20000)
|
||||
|
||||
if self.debug then
|
||||
predpos:MarkToAll(string.format("height=%dm | heading=%d | velocity=%ddeg | Ropt=%dm",mheight,wph,mveloc,Ropt),false)
|
||||
targetzone:DrawZone(coalition.side.BLUE,{0,0,1},0.2,nil,nil,3,true)
|
||||
end
|
||||
|
||||
local seadset = self.Groupset
|
||||
local tgtcoord = targetzone:GetRandomPointVec2()
|
||||
local tgtgrp = seadset:FindNearestGroupFromPointVec2(tgtcoord)
|
||||
local _targetgroup = nil
|
||||
local _targetgroupname = "none"
|
||||
local _targetskill = "Random"
|
||||
if tgtgrp and tgtgrp:IsAlive() then
|
||||
_targetgroup = tgtgrp
|
||||
_targetgroupname = tgtgrp:GetName() -- group name
|
||||
_targetskill = tgtgrp:GetUnit(1):GetSkill()
|
||||
self:T("*** Found Target = ".. _targetgroupname)
|
||||
self:WakeUpShorad(_targetgroupname, self.Radius, self.ActiveTimer, Object.Category.UNIT)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Main function - work on the EventData
|
||||
-- @param #SHORAD self
|
||||
-- @param Core.Event#EVENTDATA EventData The event details table data set
|
||||
function SHORAD:HandleEventShot( EventData )
|
||||
self:T( { EventData } )
|
||||
self:T(self.lid .. " HandleEventShot")
|
||||
--local ShootingUnit = EventData.IniDCSUnit
|
||||
--local ShootingUnitName = EventData.IniDCSUnitName
|
||||
local ShootingWeapon = EventData.Weapon -- Identify the weapon fired
|
||||
local ShootingWeaponName = EventData.WeaponName -- return weapon type
|
||||
-- get firing coalition
|
||||
local weaponcoalition = EventData.IniGroup:GetCoalition()
|
||||
-- get detection probability
|
||||
if self:_CheckCoalition(weaponcoalition) then --avoid overhead on friendly fire
|
||||
local IsDetected = self:_ShotIsDetected()
|
||||
-- convert to text
|
||||
local DetectedText = "false"
|
||||
if IsDetected then
|
||||
DetectedText = "true"
|
||||
end
|
||||
local text = string.format("%s Missile Launched = %s | Detected probability state is %s", self.lid, ShootingWeaponName, DetectedText)
|
||||
self:T( text )
|
||||
local m = MESSAGE:New(text,10,"Info"):ToAllIf(self.debug)
|
||||
--
|
||||
if (self:_CheckHarms(ShootingWeaponName) or self:_CheckMavs(ShootingWeaponName)) and IsDetected then
|
||||
-- get target data
|
||||
local targetdata = EventData.Weapon:getTarget() -- Identify target
|
||||
-- Is there target data?
|
||||
if not targetdata or self.debug then
|
||||
if string.find(ShootingWeaponName,"AGM_88",1,true) then
|
||||
self:I("**** Tracking AGM-88 with no target data.")
|
||||
local pos0 = EventData.IniUnit:GetCoordinate()
|
||||
local fheight = EventData.IniUnit:GetHeight()
|
||||
self:__CalculateHitZone(20,ShootingWeapon,pos0,fheight,EventData.IniGroup)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
local targetcat = targetdata:getCategory() -- Identify category
|
||||
self:T(string.format("Target Category (3=STATIC, 1=UNIT)= %s",tostring(targetcat)))
|
||||
self:T({targetdata})
|
||||
local targetunit = nil
|
||||
if targetcat == Object.Category.UNIT then -- UNIT
|
||||
targetunit = UNIT:Find(targetdata)
|
||||
elseif targetcat == Object.Category.STATIC then -- STATIC
|
||||
--self:T("Static Target Data")
|
||||
--self:T({targetdata:isExist()})
|
||||
--self:T({targetdata:getPoint()})
|
||||
local tgtcoord = COORDINATE:NewFromVec3(targetdata:getPoint())
|
||||
--tgtcoord:MarkToAll("Missile Target",true)
|
||||
|
||||
local tgtgrp1 = self.Samset:FindNearestGroupFromPointVec2(tgtcoord)
|
||||
local tgtcoord1 = tgtgrp1:GetCoordinate()
|
||||
--tgtcoord1:MarkToAll("Close target SAM",true)
|
||||
|
||||
local tgtgrp2 = self.Groupset:FindNearestGroupFromPointVec2(tgtcoord)
|
||||
local tgtcoord2 = tgtgrp2:GetCoordinate()
|
||||
--tgtcoord2:MarkToAll("Close target SHORAD",true)
|
||||
|
||||
local dist1 = tgtcoord:Get2DDistance(tgtcoord1)
|
||||
local dist2 = tgtcoord:Get2DDistance(tgtcoord2)
|
||||
|
||||
if dist1 < dist2 then
|
||||
targetunit = tgtgrp1
|
||||
targetcat = Object.Category.UNIT
|
||||
else
|
||||
targetunit = tgtgrp2
|
||||
targetcat = Object.Category.UNIT
|
||||
end
|
||||
end
|
||||
--local targetunitname = Unit.getName(targetdata) -- Unit name
|
||||
if targetunit and targetunit:IsAlive() then
|
||||
local targetunitname = targetunit:GetName()
|
||||
--local targetgroup = Unit.getGroup(Weapon.getTarget(ShootingWeapon)) --targeted group
|
||||
local targetgroup = nil
|
||||
local targetgroupname = "none"
|
||||
if targetcat == Object.Category.UNIT then
|
||||
if targetunit.ClassName == "UNIT" then
|
||||
targetgroup = targetunit:GetGroup()
|
||||
elseif targetunit.ClassName == "GROUP" then
|
||||
targetgroup = targetunit
|
||||
end
|
||||
targetgroupname = targetgroup:GetName() -- group name
|
||||
elseif targetcat == Object.Category.STATIC then
|
||||
targetgroup = targetunit
|
||||
targetgroupname = targetunitname
|
||||
end
|
||||
local text = string.format("%s Missile Target = %s", self.lid, tostring(targetgroupname))
|
||||
self:T( text )
|
||||
local m = MESSAGE:New(text,10,"Info"):ToAllIf(self.debug)
|
||||
-- check if we or a SAM site are the target
|
||||
--local TargetGroup = EventData.TgtGroup -- Wrapper.Group#GROUP
|
||||
local shotatus = self:_CheckShotAtShorad(targetgroupname) --#boolean
|
||||
local shotatsams = self:_CheckShotAtSams(targetgroupname) --#boolean
|
||||
-- if being shot at, find closest SHORADs to activate
|
||||
if shotatsams or shotatus then
|
||||
self:T({shotatsams=shotatsams,shotatus=shotatus})
|
||||
self:WakeUpShorad(targetgroupname, self.Radius, self.ActiveTimer, targetcat)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
--
|
||||
end
|
||||
-----------------------------------------------------------------------
|
||||
-- SHORAD end
|
||||
-----------------------------------------------------------------------
|
||||
File diff suppressed because it is too large
Load Diff
@@ -715,20 +715,21 @@ do -- ZONE_CAPTURE_COALITION
|
||||
|
||||
local UnitHit = EventData.TgtUnit
|
||||
|
||||
if UnitHit.ClassName ~= "SCENERY" then
|
||||
-- Check if unit is inside the capture zone and that it is of the defending coalition.
|
||||
if UnitHit and UnitHit:IsInZone(self) and UnitHit:GetCoalition()==self.Coalition then
|
||||
|
||||
-- Update last hit time.
|
||||
self.HitTimeLast=timer.getTime()
|
||||
|
||||
-- Only trigger attacked event if not already in state "Attacked".
|
||||
if self:GetState()~="Attacked" then
|
||||
self:F2("Hit ==> Attack")
|
||||
self:Attack()
|
||||
end
|
||||
|
||||
if UnitHit and UnitHit:IsInZone(self) and UnitHit:GetCoalition()==self.Coalition then
|
||||
|
||||
-- Update last hit time.
|
||||
self.HitTimeLast=timer.getTime()
|
||||
|
||||
-- Only trigger attacked event if not already in state "Attacked".
|
||||
if self:GetState()~="Attacked" then
|
||||
self:F2("Hit ==> Attack")
|
||||
self:Attack()
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
@@ -890,12 +891,14 @@ do -- ZONE_CAPTURE_COALITION
|
||||
end
|
||||
|
||||
-- Status text.
|
||||
local text=string.format("CAPTURE ZONE %s: Owner=%s (Previous=%s): #blue=%d, #red=%d, Status %s", self:GetZoneName(), self:GetCoalitionName(), UTILS.GetCoalitionName(self:GetPreviousCoalition()), nBlue, nRed, State)
|
||||
local NewState = self:GetState()
|
||||
if NewState~=State then
|
||||
text=text..string.format(" --> %s", NewState)
|
||||
if false then
|
||||
local text=string.format("CAPTURE ZONE %s: Owner=%s (Previous=%s): #blue=%d, #red=%d, Status %s", self:GetZoneName(), self:GetCoalitionName(), UTILS.GetCoalitionName(self:GetPreviousCoalition()), nBlue, nRed, State)
|
||||
local NewState = self:GetState()
|
||||
if NewState~=State then
|
||||
text=text..string.format(" --> %s", NewState)
|
||||
end
|
||||
self:I(text)
|
||||
end
|
||||
self:I(text)
|
||||
|
||||
end
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
-- The order of the declarations is important here. Don't touch it.
|
||||
|
||||
--- GLOBALS: The order of the declarations is important here. Don't touch it.
|
||||
|
||||
--- Declare the event dispatcher based on the EVENT class
|
||||
_EVENTDISPATCHER = EVENT:New() -- Core.Event#EVENT
|
||||
@@ -10,9 +9,38 @@ _SCHEDULEDISPATCHER = SCHEDULEDISPATCHER:New() -- Core.ScheduleDispatcher#SCHEDU
|
||||
--- Declare the main database object, which is used internally by the MOOSE classes.
|
||||
_DATABASE = DATABASE:New() -- Core.Database#DATABASE
|
||||
|
||||
--- Settings
|
||||
_SETTINGS = SETTINGS:Set()
|
||||
_SETTINGS:SetPlayerMenuOn()
|
||||
|
||||
--- Register cargos.
|
||||
_DATABASE:_RegisterCargos()
|
||||
_DATABASE:_RegisterZones()
|
||||
|
||||
--- Register zones.
|
||||
_DATABASE:_RegisterZones()
|
||||
_DATABASE:_RegisterAirbases()
|
||||
|
||||
--- Check if os etc is available.
|
||||
BASE:I("Checking de-sanitization of os, io and lfs:")
|
||||
local __na=false
|
||||
if os then
|
||||
BASE:I("- os available")
|
||||
else
|
||||
BASE:I("- os NOT available! Some functions may not work.")
|
||||
__na=true
|
||||
end
|
||||
if io then
|
||||
BASE:I("- io available")
|
||||
else
|
||||
BASE:I("- io NOT available! Some functions may not work.")
|
||||
__na=true
|
||||
end
|
||||
if lfs then
|
||||
BASE:I("- lfs available")
|
||||
else
|
||||
BASE:I("- lfs NOT available! Some functions may not work.")
|
||||
__na=true
|
||||
end
|
||||
if __na then
|
||||
BASE:I("Check <DCS install folder>/Scripts/MissionScripting.lua and comment out the lines with sanitizeModule(''). Use at your own risk!)")
|
||||
end
|
||||
|
||||
@@ -2,10 +2,13 @@ __Moose.Include( 'Scripts/Moose/Utilities/Enums.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Utilities/Routines.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Utilities/Utils.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Utilities/Profiler.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Utilities/Templates.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Utilities/STTS.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Utilities/FiFo.lua' )
|
||||
|
||||
__Moose.Include( 'Scripts/Moose/Core/Base.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/Beacon.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/UserFlag.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/UserSound.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/Report.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/Scheduler.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/ScheduleDispatcher.lua' )
|
||||
@@ -20,14 +23,14 @@ __Moose.Include( 'Scripts/Moose/Core/Point.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/Velocity.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/Message.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/Fsm.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/Radio.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/RadioQueue.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/RadioSpeech.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/Spawn.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/SpawnStatic.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/Timer.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/Goal.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/Spot.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/Astar.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/MarkerOps_Base.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/TextAndSound.lua' )
|
||||
|
||||
__Moose.Include( 'Scripts/Moose/Wrapper/Object.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Wrapper/Identifiable.lua' )
|
||||
@@ -40,6 +43,7 @@ __Moose.Include( 'Scripts/Moose/Wrapper/Static.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Wrapper/Airbase.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Wrapper/Scenery.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Wrapper/Marker.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Wrapper/DCSTask.lua' )
|
||||
|
||||
__Moose.Include( 'Scripts/Moose/Cargo/Cargo.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Cargo/CargoUnit.lua' )
|
||||
@@ -67,11 +71,37 @@ __Moose.Include( 'Scripts/Moose/Functional/Suppression.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Functional/PseudoATC.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Functional/Warehouse.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Functional/Fox.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Functional/Mantis.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Functional/Shorad.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Functional/Autolase.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Functional/AICSAR.lua' )
|
||||
|
||||
__Moose.Include( 'Scripts/Moose/Ops/Airboss.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Ops/RecoveryTanker.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Ops/RescueHelo.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Ops/ATIS.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Ops/Auftrag.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Ops/Target.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Ops/OpsGroup.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Ops/FlightGroup.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Ops/NavyGroup.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Ops/ArmyGroup.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Ops/Cohort.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Ops/Squadron.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Ops/Platoon.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Ops/Legion.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Ops/AirWing.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Ops/Brigade.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Ops/Intelligence.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Ops/Commander.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Ops/OpsTransport.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Ops/CSAR.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Ops/CTLD.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Ops/OpsZone.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Ops/Chief.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Ops/Flotilla.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Ops/Fleet.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Ops/Awacs.lua' )
|
||||
|
||||
__Moose.Include( 'Scripts/Moose/AI/AI_Balancer.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/AI/AI_Air.lua' )
|
||||
@@ -98,16 +128,25 @@ __Moose.Include( 'Scripts/Moose/AI/AI_Cargo.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/AI/AI_Cargo_APC.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/AI/AI_Cargo_Helicopter.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/AI/AI_Cargo_Airplane.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/AI/AI_Cargo_Ship.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/AI/AI_Cargo_Dispatcher.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/AI/AI_Cargo_Dispatcher_APC.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/AI/AI_Cargo_Dispatcher_Helicopter.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/AI/AI_Cargo_Dispatcher_Airplane.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/AI/AI_Cargo_Dispatcher_Ship.lua' )
|
||||
|
||||
__Moose.Include( 'Scripts/Moose/Actions/Act_Assign.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Actions/Act_Route.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Actions/Act_Account.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Actions/Act_Assist.lua' )
|
||||
|
||||
__Moose.Include( 'Scripts/Moose/Sound/UserSound.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Sound/SoundOutput.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Sound/Radio.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Sound/RadioQueue.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Sound/RadioSpeech.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Sound/SRS.lua' )
|
||||
|
||||
__Moose.Include( 'Scripts/Moose/Tasking/CommandCenter.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Tasking/Mission.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Tasking/Task.lua' )
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+6284
-6333
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,600 @@
|
||||
--- **Ops** - Brigade Warehouse.
|
||||
--
|
||||
-- **Main Features:**
|
||||
--
|
||||
-- * Manage platoons
|
||||
-- * Carry out ARTY and PATROLZONE missions (AUFTRAG)
|
||||
-- * Define rearming zones
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ## Example Missions:
|
||||
--
|
||||
-- Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/OPS%20-%20Brigade).
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **funkyfranky**
|
||||
--
|
||||
-- ===
|
||||
-- @module Ops.Brigade
|
||||
-- @image OPS_Brigade_.png
|
||||
|
||||
|
||||
--- BRIGADE class.
|
||||
-- @type BRIGADE
|
||||
-- @field #string ClassName Name of the class.
|
||||
-- @field #number verbose Verbosity of output.
|
||||
-- @field #table rearmingZones Rearming zones. Each element is of type `#BRIGADE.SupplyZone`.
|
||||
-- @field #table refuellingZones Refuelling zones. Each element is of type `#BRIGADE.SupplyZone`.
|
||||
-- @field Core.Set#SET_ZONE retreatZones Retreat zone set.
|
||||
-- @extends Ops.Legion#LEGION
|
||||
|
||||
--- *I am not afraid of an Army of lions lead by a sheep; I am afraid of sheep lead by a lion* -- Alexander the Great
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- # The BRIGADE Concept
|
||||
--
|
||||
-- A BRIGADE consists of one or multiple PLATOONs. These platoons "live" in a WAREHOUSE that has a phyiscal struction (STATIC or UNIT) and can be captured or destroyed.
|
||||
--
|
||||
--
|
||||
-- @field #BRIGADE
|
||||
BRIGADE = {
|
||||
ClassName = "BRIGADE",
|
||||
verbose = 0,
|
||||
rearmingZones = {},
|
||||
refuellingZones = {},
|
||||
}
|
||||
|
||||
--- Supply Zone.
|
||||
-- @type BRIGADE.SupplyZone
|
||||
-- @field Core.Zone#ZONE zone The zone.
|
||||
-- @field Ops.Auftrag#AUFTRAG mission Mission assigned to supply ammo or fuel.
|
||||
-- @field #boolean markerOn If `true`, marker is on.
|
||||
-- @field Wrapper.Marker#MARKER marker F10 marker.
|
||||
|
||||
--- BRIGADE class version.
|
||||
-- @field #string version
|
||||
BRIGADE.version="0.1.1"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- ToDo list
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
-- TODO: Spawn when hosting warehouse is a ship or oil rig or gas platform.
|
||||
-- TODO: Rearming zones.
|
||||
-- TODO: Retreat zones.
|
||||
-- DONE: Add weapon range.
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Constructor
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Create a new BRIGADE class object.
|
||||
-- @param #BRIGADE self
|
||||
-- @param #string WarehouseName Name of the warehouse STATIC or UNIT object representing the warehouse.
|
||||
-- @param #string BrigadeName Name of the brigade.
|
||||
-- @return #BRIGADE self
|
||||
function BRIGADE:New(WarehouseName, BrigadeName)
|
||||
|
||||
-- Inherit everything from LEGION class.
|
||||
local self=BASE:Inherit(self, LEGION:New(WarehouseName, BrigadeName)) -- #BRIGADE
|
||||
|
||||
-- Nil check.
|
||||
if not self then
|
||||
BASE:E(string.format("ERROR: Could not find warehouse %s!", WarehouseName))
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Set some string id for output to DCS.log file.
|
||||
self.lid=string.format("BRIGADE %s | ", self.alias)
|
||||
|
||||
-- Defaults
|
||||
self:SetRetreatZones()
|
||||
|
||||
-- Add FSM transitions.
|
||||
-- From State --> Event --> To State
|
||||
self:AddTransition("*", "ArmyOnMission", "*") -- An ARMYGROUP was send on a Mission (AUFTRAG).
|
||||
|
||||
------------------------
|
||||
--- Pseudo Functions ---
|
||||
------------------------
|
||||
|
||||
--- Triggers the FSM event "Start". Starts the BRIGADE. Initializes parameters and starts event handlers.
|
||||
-- @function [parent=#BRIGADE] Start
|
||||
-- @param #BRIGADE self
|
||||
|
||||
--- Triggers the FSM event "Start" after a delay. Starts the BRIGADE. Initializes parameters and starts event handlers.
|
||||
-- @function [parent=#BRIGADE] __Start
|
||||
-- @param #BRIGADE self
|
||||
-- @param #number delay Delay in seconds.
|
||||
|
||||
|
||||
--- Triggers the FSM event "Stop". Stops the BRIGADE and all its event handlers.
|
||||
-- @param #BRIGADE self
|
||||
|
||||
--- Triggers the FSM event "Stop" after a delay. Stops the BRIGADE and all its event handlers.
|
||||
-- @function [parent=#BRIGADE] __Stop
|
||||
-- @param #BRIGADE self
|
||||
-- @param #number delay Delay in seconds.
|
||||
|
||||
|
||||
--- Triggers the FSM event "ArmyOnMission".
|
||||
-- @function [parent=#BRIGADE] ArmyOnMission
|
||||
-- @param #BRIGADE self
|
||||
-- @param Ops.ArmyGroup#ARMYGROUP ArmyGroup The ARMYGROUP on mission.
|
||||
-- @param Ops.Auftrag#AUFTRAG Mission The mission.
|
||||
|
||||
--- Triggers the FSM event "ArmyOnMission" after a delay.
|
||||
-- @function [parent=#BRIGADE] __ArmyOnMission
|
||||
-- @param #BRIGADE self
|
||||
-- @param #number delay Delay in seconds.
|
||||
-- @param Ops.ArmyGroup#ARMYGROUP ArmyGroup The ARMYGROUP on mission.
|
||||
-- @param Ops.Auftrag#AUFTRAG Mission The mission.
|
||||
|
||||
--- On after "ArmyOnMission" event.
|
||||
-- @function [parent=#BRIGADE] OnAfterArmyOnMission
|
||||
-- @param #BRIGADE self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param Ops.ArmyGroup#ARMYGROUP ArmyGroup The ARMYGROUP on mission.
|
||||
-- @param Ops.Auftrag#AUFTRAG Mission The mission.
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- User Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Add a platoon to the brigade.
|
||||
-- @param #BRIGADE self
|
||||
-- @param Ops.Platoon#PLATOON Platoon The platoon object.
|
||||
-- @return #BRIGADE self
|
||||
function BRIGADE:AddPlatoon(Platoon)
|
||||
|
||||
-- Add platoon to brigade.
|
||||
table.insert(self.cohorts, Platoon)
|
||||
|
||||
-- Add assets to platoon.
|
||||
self:AddAssetToPlatoon(Platoon, Platoon.Ngroups)
|
||||
|
||||
-- Set brigade of platoon.
|
||||
Platoon:SetBrigade(self)
|
||||
|
||||
-- Start platoon.
|
||||
if Platoon:IsStopped() then
|
||||
Platoon:Start()
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Add asset group(s) to platoon.
|
||||
-- @param #BRIGADE self
|
||||
-- @param Ops.Platoon#PLATOON Platoon The platoon object.
|
||||
-- @param #number Nassets Number of asset groups to add.
|
||||
-- @return #BRIGADE self
|
||||
function BRIGADE:AddAssetToPlatoon(Platoon, Nassets)
|
||||
|
||||
if Platoon then
|
||||
|
||||
-- Get the template group of the platoon.
|
||||
local Group=GROUP:FindByName(Platoon.templatename)
|
||||
|
||||
if Group then
|
||||
|
||||
-- Debug text.
|
||||
local text=string.format("Adding asset %s to platoon %s", Group:GetName(), Platoon.name)
|
||||
self:T(self.lid..text)
|
||||
|
||||
-- Add assets to airwing warehouse.
|
||||
self:AddAsset(Group, Nassets, nil, nil, nil, nil, Platoon.skill, Platoon.livery, Platoon.name)
|
||||
|
||||
else
|
||||
self:E(self.lid.."ERROR: Group does not exist!")
|
||||
end
|
||||
|
||||
else
|
||||
self:E(self.lid.."ERROR: Platoon does not exit!")
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Define a set of retreat zones.
|
||||
-- @param #BRIGADE self
|
||||
-- @param Core.Set#SET_ZONE RetreatZoneSet Set of retreat zones.
|
||||
-- @return #BRIGADE self
|
||||
function BRIGADE:SetRetreatZones(RetreatZoneSet)
|
||||
self.retreatZones=RetreatZoneSet or SET_ZONE:New()
|
||||
return self
|
||||
end
|
||||
|
||||
--- Add a retreat zone.
|
||||
-- @param #BRIGADE self
|
||||
-- @param Core.Zone#ZONE RetreatZone Retreat zone.
|
||||
-- @return #BRIGADE self
|
||||
function BRIGADE:AddRetreatZone(RetreatZone)
|
||||
self.retreatZones:AddZone(RetreatZone)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get retreat zones.
|
||||
-- @param #BRIGADE self
|
||||
-- @return Core.Set#SET_ZONE Set of retreat zones.
|
||||
function BRIGADE:GetRetreatZones()
|
||||
return self.retreatZones
|
||||
end
|
||||
|
||||
--- Add a rearming zone.
|
||||
-- @param #BRIGADE self
|
||||
-- @param Core.Zone#ZONE RearmingZone Rearming zone.
|
||||
-- @return #BRIGADE.SupplyZone The rearming zone data.
|
||||
function BRIGADE:AddRearmingZone(RearmingZone)
|
||||
|
||||
local rearmingzone={} --#BRIGADE.SupplyZone
|
||||
|
||||
rearmingzone.zone=RearmingZone
|
||||
rearmingzone.mission=nil
|
||||
rearmingzone.marker=MARKER:New(rearmingzone.zone:GetCoordinate(), "Rearming Zone"):ToCoalition(self:GetCoalition())
|
||||
|
||||
table.insert(self.rearmingZones, rearmingzone)
|
||||
|
||||
return rearmingzone
|
||||
end
|
||||
|
||||
|
||||
--- Add a refuelling zone.
|
||||
-- @param #BRIGADE self
|
||||
-- @param Core.Zone#ZONE RefuellingZone Refuelling zone.
|
||||
-- @return #BRIGADE.SupplyZone The refuelling zone data.
|
||||
function BRIGADE:AddRefuellingZone(RefuellingZone)
|
||||
|
||||
local supplyzone={} --#BRIGADE.SupplyZone
|
||||
|
||||
supplyzone.zone=RefuellingZone
|
||||
supplyzone.mission=nil
|
||||
supplyzone.marker=MARKER:New(supplyzone.zone:GetCoordinate(), "Refuelling Zone"):ToCoalition(self:GetCoalition())
|
||||
|
||||
table.insert(self.refuellingZones, supplyzone)
|
||||
|
||||
return supplyzone
|
||||
end
|
||||
|
||||
|
||||
--- Get platoon by name.
|
||||
-- @param #BRIGADE self
|
||||
-- @param #string PlatoonName Name of the platoon.
|
||||
-- @return Ops.Platoon#PLATOON The Platoon object.
|
||||
function BRIGADE:GetPlatoon(PlatoonName)
|
||||
local platoon=self:_GetCohort(PlatoonName)
|
||||
return platoon
|
||||
end
|
||||
|
||||
--- Get platoon of an asset.
|
||||
-- @param #BRIGADE self
|
||||
-- @param Functional.Warehouse#WAREHOUSE.Assetitem Asset The platoon asset.
|
||||
-- @return Ops.Platoon#PLATOON The platoon object.
|
||||
function BRIGADE:GetPlatoonOfAsset(Asset)
|
||||
local platoon=self:GetPlatoon(Asset.squadname)
|
||||
return platoon
|
||||
end
|
||||
|
||||
--- Remove asset from platoon.
|
||||
-- @param #BRIGADE self
|
||||
-- @param Functional.Warehouse#WAREHOUSE.Assetitem Asset The platoon asset.
|
||||
function BRIGADE:RemoveAssetFromPlatoon(Asset)
|
||||
local platoon=self:GetPlatoonOfAsset(Asset)
|
||||
if platoon then
|
||||
platoon:DelAsset(Asset)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- [ GROUND ] Function to load back an asset in the field that has been filed before.
|
||||
-- @param #BRIGADE self
|
||||
-- @param #string Templatename e.g."1 PzDv LogRg I\_AID-976" - that's the alias (name) of an platoon spawned as `"platoon - alias"_AID-"asset-ID"`
|
||||
-- @param Core.Point#COORDINATE Position where to spawn the platoon
|
||||
-- @return #BRIGADE self
|
||||
-- @usage
|
||||
-- Prerequisites:
|
||||
-- Save the assets spawned by BRIGADE/CHIEF regularly (~every 5 mins) into a file, e.g. like this:
|
||||
--
|
||||
-- local Path = FilePath or "C:\\Users\\<yourname>\\Saved Games\\DCS\\Missions\\" -- example path
|
||||
-- local BlueOpsFilename = BlueFileName or "ExamplePlatoonSave.csv" -- example filename
|
||||
-- local BlueSaveOps = SET_GROUP:New():FilterCoalitions("blue"):FilterPrefixes("AID"):FilterCategoryGround():FilterOnce()
|
||||
-- UTILS.SaveSetOfGroups(BlueSaveOps,Path,BlueOpsFilename)
|
||||
--
|
||||
-- where Path and Filename are strings, as chosen by you.
|
||||
-- You can then load back the assets at the start of your next mission run. Be aware that it takes a couple of seconds for the
|
||||
-- platoon data to arrive in brigade, so make this an action after ~20 seconds, e.g. like so:
|
||||
--
|
||||
-- function LoadBackAssets()
|
||||
-- local Path = FilePath or "C:\\Users\\<yourname>\\Saved Games\\DCS\\Missions\\" -- example path
|
||||
-- local BlueOpsFilename = BlueFileName or "ExamplePlatoonSave.csv" -- example filename
|
||||
-- if UTILS.CheckFileExists(Path,BlueOpsFilename) then
|
||||
-- local loadback = UTILS.LoadSetOfGroups(Path,BlueOpsFilename,false)
|
||||
-- for _,_platoondata in pairs (loadback) do
|
||||
-- local groupname = _platoondata.groupname -- #string
|
||||
-- local coordinate = _platoondata.coordinate -- Core.Point#COORDINATE
|
||||
-- Your_Brigade:LoadBackAssetInPosition(groupname,coordinate)
|
||||
-- end
|
||||
-- end
|
||||
-- end
|
||||
--
|
||||
-- local AssetLoader = TIMER:New(LoadBackAssets)
|
||||
-- AssetLoader:Start(20)
|
||||
--
|
||||
-- The assets loaded back into the mission will be considered for AUFTRAG type missions from CHIEF and BRIGADE.
|
||||
function BRIGADE:LoadBackAssetInPosition(Templatename,Position)
|
||||
self:T(self.lid .. "LoadBackAssetInPosition: " .. tostring(Templatename))
|
||||
|
||||
-- get Platoon alias from Templatename
|
||||
local nametbl = UTILS.Split(Templatename,"_")
|
||||
|
||||
local name = nametbl[1]
|
||||
|
||||
self:T(string.format("*** Target Platoon = %s ***",name))
|
||||
|
||||
-- find a matching asset table from BRIGADE
|
||||
local cohorts = self.cohorts or {}
|
||||
local thisasset = nil --Functional.Warehouse#WAREHOUSE.Assetitem
|
||||
local found = false
|
||||
|
||||
for _,_cohort in pairs(cohorts) do
|
||||
local asset = _cohort:GetName()
|
||||
self:T(string.format("*** Looking at Platoon = %s ***",asset))
|
||||
if asset == name then
|
||||
self:T("**** Found Platoon ****")
|
||||
local cohassets = _cohort.assets or {}
|
||||
for _,_zug in pairs (cohassets) do
|
||||
local zug = _zug -- Functional.Warehouse#WAREHOUSE.Assetitem
|
||||
if zug.assignment == name and zug.requested == false then
|
||||
self:T("**** Found Asset ****")
|
||||
found = true
|
||||
thisasset = zug --Functional.Warehouse#WAREHOUSE.Assetitem
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if found then
|
||||
|
||||
-- prep asset
|
||||
thisasset.rid = thisasset.uid
|
||||
thisasset.requested = false
|
||||
thisasset.score=100
|
||||
thisasset.missionTask="CAS"
|
||||
thisasset.spawned = true
|
||||
local template = thisasset.templatename
|
||||
local alias = thisasset.spawngroupname
|
||||
|
||||
-- Spawn group
|
||||
local spawnasset = SPAWN:NewWithAlias(template,alias)
|
||||
:InitDelayOff()
|
||||
:SpawnFromCoordinate(Position)
|
||||
|
||||
-- build a new self request
|
||||
local request = {} --Functional.Warehouse#WAREHOUSE.Pendingitem
|
||||
request.assignment = name
|
||||
request.warehouse = self
|
||||
request.assets = {thisasset}
|
||||
request.ntransporthome = 0
|
||||
request.ndelivered = 0
|
||||
request.ntransport = 0
|
||||
request.cargoattribute = thisasset.attribute
|
||||
request.category = thisasset.category
|
||||
request.cargoassets = {thisasset}
|
||||
request.assetdesc = WAREHOUSE.Descriptor.ASSETLIST
|
||||
request.cargocategory = thisasset.category
|
||||
request.toself = true
|
||||
request.transporttype = WAREHOUSE.TransportType.SELFPROPELLED
|
||||
request.assetproblem = {}
|
||||
request.born = true
|
||||
request.prio = 50
|
||||
request.uid = thisasset.uid
|
||||
request.airbase = nil
|
||||
request.timestamp = timer.getAbsTime()
|
||||
request.assetdescval = {thisasset}
|
||||
request.nasset = 1
|
||||
request.cargogroupset = SET_GROUP:New()
|
||||
request.cargogroupset:AddGroup(spawnasset)
|
||||
request.iscargo = true
|
||||
|
||||
-- Call Brigade self
|
||||
self:__AssetSpawned(2, spawnasset, thisasset, request)
|
||||
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- FSM Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Start BRIGADE FSM.
|
||||
-- @param #BRIGADE self
|
||||
function BRIGADE:onafterStart(From, Event, To)
|
||||
|
||||
-- Start parent Warehouse.
|
||||
self:GetParent(self, BRIGADE).onafterStart(self, From, Event, To)
|
||||
|
||||
-- Info.
|
||||
self:I(self.lid..string.format("Starting BRIGADE v%s", BRIGADE.version))
|
||||
|
||||
end
|
||||
|
||||
--- Update status.
|
||||
-- @param #BRIGADE self
|
||||
function BRIGADE:onafterStatus(From, Event, To)
|
||||
|
||||
-- Status of parent Warehouse.
|
||||
self:GetParent(self).onafterStatus(self, From, Event, To)
|
||||
|
||||
-- FSM state.
|
||||
local fsmstate=self:GetState()
|
||||
|
||||
----------------
|
||||
-- Transport ---
|
||||
----------------
|
||||
|
||||
self:CheckTransportQueue()
|
||||
|
||||
--------------
|
||||
-- Mission ---
|
||||
--------------
|
||||
|
||||
-- Check if any missions should be cancelled.
|
||||
self:CheckMissionQueue()
|
||||
|
||||
---------------------
|
||||
-- Rearming Zones ---
|
||||
---------------------
|
||||
|
||||
for _,_rearmingzone in pairs(self.rearmingZones) do
|
||||
local rearmingzone=_rearmingzone --#BRIGADE.SupplyZone
|
||||
if (not rearmingzone.mission) or rearmingzone.mission:IsOver() then
|
||||
rearmingzone.mission=AUFTRAG:NewAMMOSUPPLY(rearmingzone.zone)
|
||||
self:AddMission(rearmingzone.mission)
|
||||
end
|
||||
end
|
||||
|
||||
-----------------------
|
||||
-- Refuelling Zones ---
|
||||
-----------------------
|
||||
|
||||
-- Check refuelling zones.
|
||||
for _,_supplyzone in pairs(self.refuellingZones) do
|
||||
local supplyzone=_supplyzone --#BRIGADE.SupplyZone
|
||||
-- Check if mission is nil or over.
|
||||
if (not supplyzone.mission) or supplyzone.mission:IsOver() then
|
||||
supplyzone.mission=AUFTRAG:NewFUELSUPPLY(supplyzone.zone)
|
||||
self:AddMission(supplyzone.mission)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-----------
|
||||
-- Info ---
|
||||
-----------
|
||||
|
||||
-- General info:
|
||||
if self.verbose>=1 then
|
||||
|
||||
-- Count missions not over yet.
|
||||
local Nmissions=self:CountMissionsInQueue()
|
||||
|
||||
-- Asset count.
|
||||
local Npq, Np, Nq=self:CountAssetsOnMission()
|
||||
|
||||
-- Asset string.
|
||||
local assets=string.format("%d [OnMission: Total=%d, Active=%d, Queued=%d]", self:CountAssets(), Npq, Np, Nq)
|
||||
|
||||
-- Output.
|
||||
local text=string.format("%s: Missions=%d, Platoons=%d, Assets=%s", fsmstate, Nmissions, #self.cohorts, assets)
|
||||
self:I(self.lid..text)
|
||||
end
|
||||
|
||||
------------------
|
||||
-- Mission Info --
|
||||
------------------
|
||||
if self.verbose>=2 then
|
||||
local text=string.format("Missions Total=%d:", #self.missionqueue)
|
||||
for i,_mission in pairs(self.missionqueue) do
|
||||
local mission=_mission --Ops.Auftrag#AUFTRAG
|
||||
|
||||
local prio=string.format("%d/%s", mission.prio, tostring(mission.importance)) ; if mission.urgent then prio=prio.." (!)" end
|
||||
local assets=string.format("%d/%d", mission:CountOpsGroups(), mission.Nassets or 0)
|
||||
local target=string.format("%d/%d Damage=%.1f", mission:CountMissionTargets(), mission:GetTargetInitialNumber(), mission:GetTargetDamage())
|
||||
|
||||
text=text..string.format("\n[%d] %s %s: Status=%s, Prio=%s, Assets=%s, Targets=%s", i, mission.name, mission.type, mission.status, prio, assets, target)
|
||||
end
|
||||
self:I(self.lid..text)
|
||||
end
|
||||
|
||||
--------------------
|
||||
-- Transport Info --
|
||||
--------------------
|
||||
if self.verbose>=2 then
|
||||
local text=string.format("Transports Total=%d:", #self.transportqueue)
|
||||
for i,_transport in pairs(self.transportqueue) do
|
||||
local transport=_transport --Ops.OpsTransport#OPSTRANSPORT
|
||||
|
||||
local prio=string.format("%d/%s", transport.prio, tostring(transport.importance)) ; if transport.urgent then prio=prio.." (!)" end
|
||||
local carriers=string.format("Ncargo=%d/%d, Ncarriers=%d", transport.Ncargo, transport.Ndelivered, transport.Ncarrier)
|
||||
|
||||
text=text..string.format("\n[%d] UID=%d: Status=%s, Prio=%s, Cargo: %s", i, transport.uid, transport:GetState(), prio, carriers)
|
||||
end
|
||||
self:I(self.lid..text)
|
||||
end
|
||||
|
||||
-------------------
|
||||
-- Platoon Info --
|
||||
-------------------
|
||||
if self.verbose>=3 then
|
||||
local text="Platoons:"
|
||||
for i,_platoon in pairs(self.cohorts) do
|
||||
local platoon=_platoon --Ops.Platoon#PLATOON
|
||||
|
||||
local callsign=platoon.callsignName and UTILS.GetCallsignName(platoon.callsignName) or "N/A"
|
||||
local modex=platoon.modex and platoon.modex or -1
|
||||
local skill=platoon.skill and tostring(platoon.skill) or "N/A"
|
||||
|
||||
-- Platoon text.
|
||||
text=text..string.format("\n* %s %s: %s*%d/%d, Callsign=%s, Modex=%d, Skill=%s", platoon.name, platoon:GetState(), platoon.aircrafttype, platoon:CountAssets(true), #platoon.assets, callsign, modex, skill)
|
||||
end
|
||||
self:I(self.lid..text)
|
||||
end
|
||||
|
||||
-------------------
|
||||
-- Rearming Info --
|
||||
-------------------
|
||||
if self.verbose>=4 then
|
||||
local text="Rearming Zones:"
|
||||
for i,_rearmingzone in pairs(self.rearmingZones) do
|
||||
local rearmingzone=_rearmingzone --#BRIGADE.SupplyZone
|
||||
-- Info text.
|
||||
text=text..string.format("\n* %s: Mission status=%s, suppliers=%d", rearmingzone.zone:GetName(), rearmingzone.mission:GetState(), rearmingzone.mission:CountOpsGroups())
|
||||
end
|
||||
self:I(self.lid..text)
|
||||
end
|
||||
|
||||
-------------------
|
||||
-- Refuelling Info --
|
||||
-------------------
|
||||
if self.verbose>=4 then
|
||||
local text="Refuelling Zones:"
|
||||
for i,_refuellingzone in pairs(self.refuellingZones) do
|
||||
local refuellingzone=_refuellingzone --#BRIGADE.SupplyZone
|
||||
-- Info text.
|
||||
text=text..string.format("\n* %s: Mission status=%s, suppliers=%d", refuellingzone.zone:GetName(), refuellingzone.mission:GetState(), refuellingzone.mission:CountOpsGroups())
|
||||
end
|
||||
self:I(self.lid..text)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- FSM Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- On after "ArmyOnMission".
|
||||
-- @param #BRIGADE self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param Ops.ArmyGroup#ARMYGROUP ArmyGroup Ops army group on mission.
|
||||
-- @param Ops.Auftrag#AUFTRAG Mission The requested mission.
|
||||
function BRIGADE:onafterArmyOnMission(From, Event, To, ArmyGroup, Mission)
|
||||
-- Debug info.
|
||||
self:T(self.lid..string.format("Group %s on %s mission %s", ArmyGroup:GetName(), Mission:GetType(), Mission:GetName()))
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,416 @@
|
||||
--- **Ops** - Fleet Warehouse.
|
||||
--
|
||||
-- **Main Features:**
|
||||
--
|
||||
-- * Manage flotillas
|
||||
-- * Carry out ARTY and PATROLZONE missions (AUFTRAG)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ## Example Missions:
|
||||
--
|
||||
-- Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/OPS%20-%20Fleet).
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **funkyfranky**
|
||||
--
|
||||
-- ===
|
||||
-- @module Ops.Fleet
|
||||
-- @image OPS_Fleet.png
|
||||
|
||||
|
||||
--- FLEET class.
|
||||
-- @type FLEET
|
||||
-- @field #string ClassName Name of the class.
|
||||
-- @field #number verbose Verbosity of output.
|
||||
-- @field Core.Set#SET_ZONE retreatZones Retreat zone set.
|
||||
-- @field #boolean pathfinding Set pathfinding on for all spawned navy groups.
|
||||
-- @extends Ops.Legion#LEGION
|
||||
|
||||
--- *A fleet of British ships at war are the best negotiators.* -- Horatio Nelson
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- # The FLEET Concept
|
||||
--
|
||||
-- A FLEET consists of one or multiple FLOTILLAs. These flotillas "live" in a WAREHOUSE that has a phyiscal struction (STATIC or UNIT) and can be captured or destroyed.
|
||||
--
|
||||
-- # Basic Setup
|
||||
--
|
||||
-- A new `FLEET` object can be created with the @{#FLEET.New}(`WarehouseName`, `FleetName`) function, where `WarehouseName` is the name of the static or unit object hosting the fleet
|
||||
-- and `FleetName` is the name you want to give the fleet. This must be *unique*!
|
||||
--
|
||||
-- myFleet=FLEET:New("myWarehouseName", "1st Fleet")
|
||||
-- myFleet:SetPortZone(ZonePort1stFleet)
|
||||
-- myFleet:Start()
|
||||
--
|
||||
-- A fleet needs a *port zone*, which is set via the @{#FLEET.SetPortZone}(`PortZone`) function. This is the zone where the naval assets are spawned and return to.
|
||||
--
|
||||
-- Finally, the fleet needs to be started using the @{#FLEET.Start}() function. If the fleet is not started, it will not process any requests.
|
||||
--
|
||||
-- ## Adding Flotillas
|
||||
--
|
||||
-- Flotillas can be added via the @{#FLEET.AddFlotilla}(`Flotilla`) function. See @{Ops.Flotilla#FLOTILLA} for how to create a flotilla.
|
||||
--
|
||||
-- myFleet:AddFlotilla(FlotillaTiconderoga)
|
||||
-- myFleet:AddFlotilla(FlotillaPerry)
|
||||
--
|
||||
--
|
||||
--
|
||||
-- @field #FLEET
|
||||
FLEET = {
|
||||
ClassName = "FLEET",
|
||||
verbose = 0,
|
||||
pathfinding = false,
|
||||
}
|
||||
|
||||
--- Supply Zone.
|
||||
-- @type FLEET.SupplyZone
|
||||
-- @field Core.Zone#ZONE zone The zone.
|
||||
-- @field Ops.Auftrag#AUFTRAG mission Mission assigned to supply ammo or fuel.
|
||||
-- @field #boolean markerOn If `true`, marker is on.
|
||||
-- @field Wrapper.Marker#MARKER marker F10 marker.
|
||||
|
||||
--- FLEET class version.
|
||||
-- @field #string version
|
||||
FLEET.version="0.0.1"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- ToDo list
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
-- TODO: Add routes?
|
||||
-- DONE: Add weapon range.
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Constructor
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Create a new FLEET class object.
|
||||
-- @param #FLEET self
|
||||
-- @param #string WarehouseName Name of the warehouse STATIC or UNIT object representing the warehouse.
|
||||
-- @param #string FleetName Name of the fleet.
|
||||
-- @return #FLEET self
|
||||
function FLEET:New(WarehouseName, FleetName)
|
||||
|
||||
-- Inherit everything from LEGION class.
|
||||
local self=BASE:Inherit(self, LEGION:New(WarehouseName, FleetName)) -- #FLEET
|
||||
|
||||
-- Nil check.
|
||||
if not self then
|
||||
BASE:E(string.format("ERROR: Could not find warehouse %s!", WarehouseName))
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Set some string id for output to DCS.log file.
|
||||
self.lid=string.format("FLEET %s | ", self.alias)
|
||||
|
||||
-- Defaults
|
||||
self:SetRetreatZones()
|
||||
|
||||
-- Add FSM transitions.
|
||||
-- From State --> Event --> To State
|
||||
self:AddTransition("*", "NavyOnMission", "*") -- An NAVYGROUP was send on a Mission (AUFTRAG).
|
||||
|
||||
------------------------
|
||||
--- Pseudo Functions ---
|
||||
------------------------
|
||||
|
||||
--- Triggers the FSM event "Start". Starts the FLEET. Initializes parameters and starts event handlers.
|
||||
-- @function [parent=#FLEET] Start
|
||||
-- @param #FLEET self
|
||||
|
||||
--- Triggers the FSM event "Start" after a delay. Starts the FLEET. Initializes parameters and starts event handlers.
|
||||
-- @function [parent=#FLEET] __Start
|
||||
-- @param #FLEET self
|
||||
-- @param #number delay Delay in seconds.
|
||||
|
||||
|
||||
--- Triggers the FSM event "Stop". Stops the FLEET and all its event handlers.
|
||||
-- @param #FLEET self
|
||||
|
||||
--- Triggers the FSM event "Stop" after a delay. Stops the FLEET and all its event handlers.
|
||||
-- @function [parent=#FLEET] __Stop
|
||||
-- @param #FLEET self
|
||||
-- @param #number delay Delay in seconds.
|
||||
|
||||
|
||||
--- Triggers the FSM event "NavyOnMission".
|
||||
-- @function [parent=#FLEET] NavyOnMission
|
||||
-- @param #FLEET self
|
||||
-- @param Ops.NavyGroup#NAVYGROUP ArmyGroup The NAVYGROUP on mission.
|
||||
-- @param Ops.Auftrag#AUFTRAG Mission The mission.
|
||||
|
||||
--- Triggers the FSM event "NavyOnMission" after a delay.
|
||||
-- @function [parent=#FLEET] __NavyOnMission
|
||||
-- @param #FLEET self
|
||||
-- @param #number delay Delay in seconds.
|
||||
-- @param Ops.NavyGroup#NAVYGROUP ArmyGroup The NAVYGROUP on mission.
|
||||
-- @param Ops.Auftrag#AUFTRAG Mission The mission.
|
||||
|
||||
--- On after "NavyOnMission" event.
|
||||
-- @function [parent=#FLEET] OnAfterNavyOnMission
|
||||
-- @param #FLEET self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param Ops.NavyGroup#NAVYGROUP NavyGroup The NAVYGROUP on mission.
|
||||
-- @param Ops.Auftrag#AUFTRAG Mission The mission.
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- User Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Add a flotilla to the fleet.
|
||||
-- @param #FLEET self
|
||||
-- @param Ops.Flotilla#FLOTILLA Flotilla The flotilla object.
|
||||
-- @return #FLEET self
|
||||
function FLEET:AddFlotilla(Flotilla)
|
||||
|
||||
-- Add flotilla to fleet.
|
||||
table.insert(self.cohorts, Flotilla)
|
||||
|
||||
-- Add assets to flotilla.
|
||||
self:AddAssetToFlotilla(Flotilla, Flotilla.Ngroups)
|
||||
|
||||
-- Set fleet of flotilla.
|
||||
Flotilla:SetFleet(self)
|
||||
|
||||
-- Start flotilla.
|
||||
if Flotilla:IsStopped() then
|
||||
Flotilla:Start()
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Add asset group(s) to flotilla.
|
||||
-- @param #FLEET self
|
||||
-- @param Ops.Flotilla#FLOTILLA Flotilla The flotilla object.
|
||||
-- @param #number Nassets Number of asset groups to add.
|
||||
-- @return #FLEET self
|
||||
function FLEET:AddAssetToFlotilla(Flotilla, Nassets)
|
||||
|
||||
if Flotilla then
|
||||
|
||||
-- Get the template group of the flotilla.
|
||||
local Group=GROUP:FindByName(Flotilla.templatename)
|
||||
|
||||
if Group then
|
||||
|
||||
-- Debug text.
|
||||
local text=string.format("Adding asset %s to flotilla %s", Group:GetName(), Flotilla.name)
|
||||
self:T(self.lid..text)
|
||||
|
||||
-- Add assets to airwing warehouse.
|
||||
self:AddAsset(Group, Nassets, nil, nil, nil, nil, Flotilla.skill, Flotilla.livery, Flotilla.name)
|
||||
|
||||
else
|
||||
self:E(self.lid.."ERROR: Group does not exist!")
|
||||
end
|
||||
|
||||
else
|
||||
self:E(self.lid.."ERROR: Flotilla does not exit!")
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set pathfinding for all spawned naval groups.
|
||||
-- @param #FLEET self
|
||||
-- @param #boolean Switch If `true`, pathfinding is used.
|
||||
-- @return #FLEET self
|
||||
function FLEET:SetPathfinding(Switch)
|
||||
self.pathfinding=Switch
|
||||
return self
|
||||
end
|
||||
|
||||
--- Define a set of retreat zones.
|
||||
-- @param #FLEET self
|
||||
-- @param Core.Set#SET_ZONE RetreatZoneSet Set of retreat zones.
|
||||
-- @return #FLEET self
|
||||
function FLEET:SetRetreatZones(RetreatZoneSet)
|
||||
self.retreatZones=RetreatZoneSet or SET_ZONE:New()
|
||||
return self
|
||||
end
|
||||
|
||||
--- Add a retreat zone.
|
||||
-- @param #FLEET self
|
||||
-- @param Core.Zone#ZONE RetreatZone Retreat zone.
|
||||
-- @return #FLEET self
|
||||
function FLEET:AddRetreatZone(RetreatZone)
|
||||
self.retreatZones:AddZone(RetreatZone)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get retreat zones.
|
||||
-- @param #FLEET self
|
||||
-- @return Core.Set#SET_ZONE Set of retreat zones.
|
||||
function FLEET:GetRetreatZones()
|
||||
return self.retreatZones
|
||||
end
|
||||
|
||||
--- Get flotilla by name.
|
||||
-- @param #FLEET self
|
||||
-- @param #string FlotillaName Name of the flotilla.
|
||||
-- @return Ops.Flotilla#FLOTILLA The Flotilla object.
|
||||
function FLEET:GetFlotilla(FlotillaName)
|
||||
local flotilla=self:_GetCohort(FlotillaName)
|
||||
return flotilla
|
||||
end
|
||||
|
||||
--- Get flotilla of an asset.
|
||||
-- @param #FLEET self
|
||||
-- @param Functional.Warehouse#WAREHOUSE.Assetitem Asset The flotilla asset.
|
||||
-- @return Ops.Flotilla#FLOTILLA The flotilla object.
|
||||
function FLEET:GetFlotillaOfAsset(Asset)
|
||||
local flotilla=self:GetFlotilla(Asset.squadname)
|
||||
return flotilla
|
||||
end
|
||||
|
||||
--- Remove asset from flotilla.
|
||||
-- @param #FLEET self
|
||||
-- @param Functional.Warehouse#WAREHOUSE.Assetitem Asset The flotilla asset.
|
||||
function FLEET:RemoveAssetFromFlotilla(Asset)
|
||||
local flotilla=self:GetFlotillaOfAsset(Asset)
|
||||
if flotilla then
|
||||
flotilla:DelAsset(Asset)
|
||||
end
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- FSM Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Start FLEET FSM.
|
||||
-- @param #FLEET self
|
||||
function FLEET:onafterStart(From, Event, To)
|
||||
|
||||
-- Start parent Warehouse.
|
||||
self:GetParent(self, FLEET).onafterStart(self, From, Event, To)
|
||||
|
||||
-- Info.
|
||||
self:I(self.lid..string.format("Starting FLEET v%s", FLEET.version))
|
||||
|
||||
end
|
||||
|
||||
--- Update status.
|
||||
-- @param #FLEET self
|
||||
function FLEET:onafterStatus(From, Event, To)
|
||||
|
||||
-- Status of parent Warehouse.
|
||||
self:GetParent(self).onafterStatus(self, From, Event, To)
|
||||
|
||||
-- FSM state.
|
||||
local fsmstate=self:GetState()
|
||||
|
||||
----------------
|
||||
-- Transport ---
|
||||
----------------
|
||||
|
||||
self:CheckTransportQueue()
|
||||
|
||||
--------------
|
||||
-- Mission ---
|
||||
--------------
|
||||
|
||||
-- Check if any missions should be cancelled.
|
||||
self:CheckMissionQueue()
|
||||
|
||||
-----------
|
||||
-- Info ---
|
||||
-----------
|
||||
|
||||
-- General info:
|
||||
if self.verbose>=1 then
|
||||
|
||||
-- Count missions not over yet.
|
||||
local Nmissions=self:CountMissionsInQueue()
|
||||
|
||||
-- Asset count.
|
||||
local Npq, Np, Nq=self:CountAssetsOnMission()
|
||||
|
||||
-- Asset string.
|
||||
local assets=string.format("%d [OnMission: Total=%d, Active=%d, Queued=%d]", self:CountAssets(), Npq, Np, Nq)
|
||||
|
||||
-- Output.
|
||||
local text=string.format("%s: Missions=%d, Flotillas=%d, Assets=%s", fsmstate, Nmissions, #self.cohorts, assets)
|
||||
self:I(self.lid..text)
|
||||
end
|
||||
|
||||
------------------
|
||||
-- Mission Info --
|
||||
------------------
|
||||
if self.verbose>=2 then
|
||||
local text=string.format("Missions Total=%d:", #self.missionqueue)
|
||||
for i,_mission in pairs(self.missionqueue) do
|
||||
local mission=_mission --Ops.Auftrag#AUFTRAG
|
||||
|
||||
local prio=string.format("%d/%s", mission.prio, tostring(mission.importance)) ; if mission.urgent then prio=prio.." (!)" end
|
||||
local assets=string.format("%d/%d", mission:CountOpsGroups(), mission.Nassets or 0)
|
||||
local target=string.format("%d/%d Damage=%.1f", mission:CountMissionTargets(), mission:GetTargetInitialNumber(), mission:GetTargetDamage())
|
||||
|
||||
text=text..string.format("\n[%d] %s %s: Status=%s, Prio=%s, Assets=%s, Targets=%s", i, mission.name, mission.type, mission.status, prio, assets, target)
|
||||
end
|
||||
self:I(self.lid..text)
|
||||
end
|
||||
|
||||
--------------------
|
||||
-- Transport Info --
|
||||
--------------------
|
||||
if self.verbose>=2 then
|
||||
local text=string.format("Transports Total=%d:", #self.transportqueue)
|
||||
for i,_transport in pairs(self.transportqueue) do
|
||||
local transport=_transport --Ops.OpsTransport#OPSTRANSPORT
|
||||
|
||||
local prio=string.format("%d/%s", transport.prio, tostring(transport.importance)) ; if transport.urgent then prio=prio.." (!)" end
|
||||
local carriers=string.format("Ncargo=%d/%d, Ncarriers=%d", transport.Ncargo, transport.Ndelivered, transport.Ncarrier)
|
||||
|
||||
text=text..string.format("\n[%d] UID=%d: Status=%s, Prio=%s, Cargo: %s", i, transport.uid, transport:GetState(), prio, carriers)
|
||||
end
|
||||
self:I(self.lid..text)
|
||||
end
|
||||
|
||||
-------------------
|
||||
-- Flotilla Info --
|
||||
-------------------
|
||||
if self.verbose>=3 then
|
||||
local text="Flotillas:"
|
||||
for i,_flotilla in pairs(self.cohorts) do
|
||||
local flotilla=_flotilla --Ops.Flotilla#FLOTILLA
|
||||
|
||||
local callsign=flotilla.callsignName and UTILS.GetCallsignName(flotilla.callsignName) or "N/A"
|
||||
local modex=flotilla.modex and flotilla.modex or -1
|
||||
local skill=flotilla.skill and tostring(flotilla.skill) or "N/A"
|
||||
|
||||
-- Flotilla text.
|
||||
text=text..string.format("\n* %s %s: %s*%d/%d, Callsign=%s, Modex=%d, Skill=%s", flotilla.name, flotilla:GetState(), flotilla.aircrafttype, flotilla:CountAssets(true), #flotilla.assets, callsign, modex, skill)
|
||||
end
|
||||
self:I(self.lid..text)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- FSM Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- On after "NavyOnMission".
|
||||
-- @param #FLEET self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param Ops.ArmyGroup#ARMYGROUP ArmyGroup Ops army group on mission.
|
||||
-- @param Ops.Auftrag#AUFTRAG Mission The requested mission.
|
||||
function FLEET:onafterNavyOnMission(From, Event, To, NavyGroup, Mission)
|
||||
-- Debug info.
|
||||
self:T(self.lid..string.format("Group %s on %s mission %s", NavyGroup:GetName(), Mission:GetType(), Mission:GetName()))
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,172 @@
|
||||
--- **Ops** - Flotilla is a small naval group belonging to a fleet.
|
||||
--
|
||||
-- **Main Features:**
|
||||
--
|
||||
-- * Set parameters like livery, skill valid for all flotilla members.
|
||||
-- * Define mission types, this flotilla can perform (see Ops.Auftrag#AUFTRAG).
|
||||
-- * Pause/unpause flotilla operations.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **funkyfranky**
|
||||
--
|
||||
-- ===
|
||||
-- @module Ops.Flotilla
|
||||
-- @image OPS_Flotilla.png
|
||||
|
||||
|
||||
--- FLOTILLA class.
|
||||
-- @type FLOTILLA
|
||||
-- @field #string ClassName Name of the class.
|
||||
-- @field #number verbose Verbosity level.
|
||||
-- @field Ops.OpsGroup#OPSGROUP.WeaponData weaponData Weapon data table with key=BitType.
|
||||
-- @extends Ops.Cohort#COHORT
|
||||
|
||||
--- *No captain can do very wrong if he places his ship alongside that of an enemy.* -- Horation Nelson
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- # The FLOTILLA Concept
|
||||
--
|
||||
-- A FLOTILLA is an essential part of a FLEET.
|
||||
--
|
||||
--
|
||||
--
|
||||
-- @field #FLOTILLA
|
||||
FLOTILLA = {
|
||||
ClassName = "FLOTILLA",
|
||||
verbose = 0,
|
||||
weaponData = {},
|
||||
}
|
||||
|
||||
--- FLOTILLA class version.
|
||||
-- @field #string version
|
||||
FLOTILLA.version="0.1.0"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- TODO list
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
-- TODO: A lot.
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Constructor
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Create a new FLOTILLA object and start the FSM.
|
||||
-- @param #FLOTILLA self
|
||||
-- @param #string TemplateGroupName Name of the template group.
|
||||
-- @param #number Ngroups Number of asset groups of this flotilla. Default 3.
|
||||
-- @param #string FlotillaName Name of the flotilla, e.g. "VFA-37".
|
||||
-- @return #FLOTILLA self
|
||||
function FLOTILLA:New(TemplateGroupName, Ngroups, FlotillaName)
|
||||
|
||||
-- Inherit everything from COHORT class.
|
||||
local self=BASE:Inherit(self, COHORT:New(TemplateGroupName, Ngroups, FlotillaName)) -- #FLOTILLA
|
||||
|
||||
-- All flotillas get mission type Nothing.
|
||||
self:AddMissionCapability(AUFTRAG.Type.NOTHING, 50)
|
||||
|
||||
-- Get initial ammo.
|
||||
self.ammo=self:_CheckAmmo()
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- User functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
-- TODO: Flotilla specific user functions.
|
||||
|
||||
--- Set fleet of this flotilla.
|
||||
-- @param #FLOTILLA self
|
||||
-- @param Ops.Fleet#FLEET Fleet The fleet.
|
||||
-- @return #FLOTILLA self
|
||||
function FLOTILLA:SetFleet(Fleet)
|
||||
self.legion=Fleet
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get fleet of this flotilla.
|
||||
-- @param #FLOTILLA self
|
||||
-- @return Ops.Fleet#FLEET The fleet.
|
||||
function FLOTILLA:GetFleet()
|
||||
return self.legion
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Start & Status
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- On after Start event. Starts the FLIGHTGROUP FSM and event handlers.
|
||||
-- @param #FLOTILLA self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
function FLOTILLA:onafterStart(From, Event, To)
|
||||
|
||||
-- Short info.
|
||||
local text=string.format("Starting %s v%s %s", self.ClassName, self.version, self.name)
|
||||
self:I(self.lid..text)
|
||||
|
||||
-- Start the status monitoring.
|
||||
self:__Status(-1)
|
||||
end
|
||||
|
||||
--- On after "Status" event.
|
||||
-- @param #FLOTILLA self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
function FLOTILLA:onafterStatus(From, Event, To)
|
||||
|
||||
if self.verbose>=1 then
|
||||
|
||||
-- FSM state.
|
||||
local fsmstate=self:GetState()
|
||||
|
||||
local callsign=self.callsignName and UTILS.GetCallsignName(self.callsignName) or "N/A"
|
||||
local skill=self.skill and tostring(self.skill) or "N/A"
|
||||
|
||||
local NassetsTot=#self.assets
|
||||
local NassetsInS=self:CountAssets(true)
|
||||
local NassetsQP=0 ; local NassetsP=0 ; local NassetsQ=0
|
||||
if self.legion then
|
||||
NassetsQP, NassetsP, NassetsQ=self.legion:CountAssetsOnMission(nil, self)
|
||||
end
|
||||
|
||||
-- Short info.
|
||||
local text=string.format("%s [Type=%s, Call=%s, Skill=%s]: Assets Total=%d, Stock=%d, Mission=%d [Active=%d, Queue=%d]",
|
||||
fsmstate, self.aircrafttype, callsign, skill, NassetsTot, NassetsInS, NassetsQP, NassetsP, NassetsQ)
|
||||
self:T(self.lid..text)
|
||||
|
||||
-- Weapon data info.
|
||||
if self.verbose>=3 and self.weaponData then
|
||||
local text="Weapon Data:"
|
||||
for bit,_weapondata in pairs(self.weaponData) do
|
||||
local weapondata=_weapondata --Ops.OpsGroup#OPSGROUP.WeaponData
|
||||
text=text..string.format("\n- Bit=%s: Rmin=%.1f km, Rmax=%.1f km", bit, weapondata.RangeMin/1000, weapondata.RangeMax/1000)
|
||||
end
|
||||
self:I(self.lid..text)
|
||||
end
|
||||
|
||||
-- Check if group has detected any units.
|
||||
self:_CheckAssetStatus()
|
||||
|
||||
end
|
||||
|
||||
if not self:IsStopped() then
|
||||
self:__Status(-60)
|
||||
end
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Misc Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,170 @@
|
||||
--- **Ops** - Brigade Platoon.
|
||||
--
|
||||
-- **Main Features:**
|
||||
--
|
||||
-- * Set parameters like livery, skill valid for all platoon members.
|
||||
-- * Define modex and callsigns.
|
||||
-- * Define mission types, this platoon can perform (see Ops.Auftrag#AUFTRAG).
|
||||
-- * Pause/unpause platoon operations.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **funkyfranky**
|
||||
-- @module Ops.Platoon
|
||||
-- @image OPS_Platoon.png
|
||||
|
||||
|
||||
--- PLATOON class.
|
||||
-- @type PLATOON
|
||||
-- @field #string ClassName Name of the class.
|
||||
-- @field #number verbose Verbosity level.
|
||||
-- @field Ops.OpsGroup#OPSGROUP.WeaponData weaponData Weapon data table with key=BitType.
|
||||
-- @extends Ops.Cohort#COHORT
|
||||
|
||||
--- *Some cool cohort quote* -- Known Author
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- # The PLATOON Concept
|
||||
--
|
||||
-- A PLATOON is essential part of an BRIGADE.
|
||||
--
|
||||
--
|
||||
--
|
||||
-- @field #PLATOON
|
||||
PLATOON = {
|
||||
ClassName = "PLATOON",
|
||||
verbose = 0,
|
||||
weaponData = {},
|
||||
}
|
||||
|
||||
--- PLATOON class version.
|
||||
-- @field #string version
|
||||
PLATOON.version="0.1.0"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- TODO list
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
-- TODO: A lot.
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Constructor
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Create a new PLATOON object and start the FSM.
|
||||
-- @param #PLATOON self
|
||||
-- @param #string TemplateGroupName Name of the template group.
|
||||
-- @param #number Ngroups Number of asset groups of this platoon. Default 3.
|
||||
-- @param #string PlatoonName Name of the platoon, e.g. "VFA-37".
|
||||
-- @return #PLATOON self
|
||||
function PLATOON:New(TemplateGroupName, Ngroups, PlatoonName)
|
||||
|
||||
-- Inherit everything from COHORT class.
|
||||
local self=BASE:Inherit(self, COHORT:New(TemplateGroupName, Ngroups, PlatoonName)) -- #PLATOON
|
||||
|
||||
-- All platoons get mission type Nothing.
|
||||
self:AddMissionCapability(AUFTRAG.Type.NOTHING, 50)
|
||||
|
||||
-- Get ammo.
|
||||
self.ammo=self:_CheckAmmo()
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- User functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
-- TODO: Platoon specific user functions.
|
||||
|
||||
--- Set brigade of this platoon.
|
||||
-- @param #PLATOON self
|
||||
-- @param Ops.Brigade#BRIGADE Brigade The brigade.
|
||||
-- @return #PLATOON self
|
||||
function PLATOON:SetBrigade(Brigade)
|
||||
self.legion=Brigade
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get brigade of this platoon.
|
||||
-- @param #PLATOON self
|
||||
-- @return Ops.Brigade#BRIGADE The brigade.
|
||||
function PLATOON:GetBrigade()
|
||||
return self.legion
|
||||
end
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Start & Status
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- On after Start event. Starts the FLIGHTGROUP FSM and event handlers.
|
||||
-- @param #PLATOON self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
function PLATOON:onafterStart(From, Event, To)
|
||||
|
||||
-- Short info.
|
||||
local text=string.format("Starting %s v%s %s", self.ClassName, self.version, self.name)
|
||||
self:I(self.lid..text)
|
||||
|
||||
-- Start the status monitoring.
|
||||
self:__Status(-1)
|
||||
end
|
||||
|
||||
--- On after "Status" event.
|
||||
-- @param #PLATOON self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
function PLATOON:onafterStatus(From, Event, To)
|
||||
|
||||
if self.verbose>=1 then
|
||||
|
||||
-- FSM state.
|
||||
local fsmstate=self:GetState()
|
||||
|
||||
local callsign=self.callsignName and UTILS.GetCallsignName(self.callsignName) or "N/A"
|
||||
local skill=self.skill and tostring(self.skill) or "N/A"
|
||||
|
||||
local NassetsTot=#self.assets
|
||||
local NassetsInS=self:CountAssets(true)
|
||||
local NassetsQP=0 ; local NassetsP=0 ; local NassetsQ=0
|
||||
if self.legion then
|
||||
NassetsQP, NassetsP, NassetsQ=self.legion:CountAssetsOnMission(nil, self)
|
||||
end
|
||||
|
||||
-- Short info.
|
||||
local text=string.format("%s [Type=%s, Call=%s, Skill=%s]: Assets Total=%d, Stock=%d, Mission=%d [Active=%d, Queue=%d]",
|
||||
fsmstate, self.aircrafttype, callsign, skill, NassetsTot, NassetsInS, NassetsQP, NassetsP, NassetsQ)
|
||||
self:T(self.lid..text)
|
||||
|
||||
-- Weapon data info.
|
||||
if self.verbose>=3 and self.weaponData then
|
||||
local text="Weapon Data:"
|
||||
for bit,_weapondata in pairs(self.weaponData) do
|
||||
local weapondata=_weapondata --Ops.OpsGroup#OPSGROUP.WeaponData
|
||||
text=text..string.format("\n- Bit=%s: Rmin=%.1f km, Rmax=%.1f km", bit, weapondata.RangeMin/1000, weapondata.RangeMax/1000)
|
||||
end
|
||||
self:I(self.lid..text)
|
||||
end
|
||||
|
||||
-- Check if group has detected any units.
|
||||
self:_CheckAssetStatus()
|
||||
|
||||
end
|
||||
|
||||
if not self:IsStopped() then
|
||||
self:__Status(-60)
|
||||
end
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Misc Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
-- TODO: Misc functions.
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
@@ -31,7 +31,7 @@
|
||||
-- @field #string tankergroupname Name of the late activated tanker template group.
|
||||
-- @field Wrapper.Group#GROUP tanker Tanker group.
|
||||
-- @field Wrapper.Airbase#AIRBASE airbase The home airbase object of the tanker. Normally the aircraft carrier.
|
||||
-- @field Core.Radio#BEACON beacon Tanker TACAN beacon.
|
||||
-- @field Core.Beacon#BEACON beacon Tanker TACAN beacon.
|
||||
-- @field #number TACANchannel TACAN channel. Default 1.
|
||||
-- @field #string TACANmode TACAN mode, i.e. "X" or "Y". Default "Y". Use only "Y" for AA TACAN stations!
|
||||
-- @field #string TACANmorse TACAN morse code. Three letters identifying the TACAN station. Default "TKR".
|
||||
@@ -784,10 +784,11 @@ end
|
||||
-- @param #RECOVERYTANKER self
|
||||
-- @param #number channel TACAN channel. Default 1.
|
||||
-- @param #string morse TACAN morse code identifier. Three letters. Default "TKR".
|
||||
-- @param #string mode TACAN mode, which can be either "Y" (default) or "X".
|
||||
-- @return #RECOVERYTANKER self
|
||||
function RECOVERYTANKER:SetTACAN(channel, morse)
|
||||
function RECOVERYTANKER:SetTACAN(channel, morse, mode)
|
||||
self.TACANchannel=channel or 1
|
||||
self.TACANmode="Y"
|
||||
self.TACANmode=mode or "Y"
|
||||
self.TACANmorse=morse or "TKR"
|
||||
self.TACANon=true
|
||||
return self
|
||||
@@ -1625,7 +1626,6 @@ function RECOVERYTANKER:_ActivateTACAN(delay)
|
||||
if delay and delay>0 then
|
||||
|
||||
-- Schedule TACAN activation.
|
||||
--SCHEDULER:New(nil, self._ActivateTACAN, {self}, delay)
|
||||
self:ScheduleOnce(delay, RECOVERYTANKER._ActivateTACAN, self)
|
||||
|
||||
else
|
||||
|
||||
@@ -802,7 +802,7 @@ function RESCUEHELO:_OnEventCrashOrEject(EventData)
|
||||
-- Debug.
|
||||
local text=string.format("Unit %s crashed or ejected.", unitname)
|
||||
MESSAGE:New(text, 10, "DEBUG"):ToAllIf(self.Debug)
|
||||
self:I(self.lid..text)
|
||||
self:T(self.lid..text)
|
||||
|
||||
-- Get coordinate of unit.
|
||||
local coord=unit:GetCoordinate()
|
||||
|
||||
@@ -0,0 +1,911 @@
|
||||
--- **Ops** - (R2.5) - Manage behavior of the Carrier Strike Group.
|
||||
--
|
||||
-- **Main Features:**
|
||||
--
|
||||
-- * Nice stuff.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **funkyfranky**
|
||||
-- @module Ops.Skipper
|
||||
-- @image OPS_Skipper.png
|
||||
|
||||
|
||||
--- SKIPPER class.
|
||||
-- @type SKIPPER
|
||||
-- @field #string ClassName Name of the class.
|
||||
-- @field #boolean Debug Debug mode. Messages to all about status.
|
||||
-- @field #string sid Class id string for output to DCS log file.
|
||||
-- @field #string carriername The name of the carrier unit.
|
||||
-- @field Wrapper.Group#GROUP group The carrier strike group.
|
||||
-- @field #table menu Table of menu items.
|
||||
-- @field Functional.Warehouse#WAREHOUSE warehouse The warehouse of the carrier.
|
||||
-- @field Functional.Artillery#ARTY arty The artillery object of the carrier.
|
||||
-- @field #table intruders Table of intruders, i.e. groups inside the CCA. Each element is of type #SKIPPPER.Intruder.
|
||||
-- @field Ops.CarrierAirWing#CVW cvw Carrier Air Wing.
|
||||
-- @extends Ops.Airboss#AIRBOSS
|
||||
|
||||
--- Be surprised!
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- # The SKIPPER Concept
|
||||
--
|
||||
--
|
||||
--
|
||||
-- @field #SKIPPER
|
||||
SKIPPER = {
|
||||
ClassName = "SKIPPER",
|
||||
sid = nil,
|
||||
carriername = nil,
|
||||
group = nil,
|
||||
menu = nil,
|
||||
warehouse = nil,
|
||||
arty = nil,
|
||||
intruders = {},
|
||||
cvw = nil,
|
||||
}
|
||||
|
||||
--- Intruder.
|
||||
-- @type SKIPPER.Intruder
|
||||
-- @field Wrapper.Group#GROUP group Intruder group object.
|
||||
-- @field #string groupname Name of the intruder group.
|
||||
-- @field #number time0 Abs. mission time first detected inside CCA.
|
||||
-- @field #number dist0 Distance first detected inside CCA.
|
||||
-- @field DCS#Coalition.Side coalition Coalition side.
|
||||
-- @field #number threadlevel Thread level of intruder.
|
||||
-- @field #string threadtext Thread text.
|
||||
-- @field #number category Group category.
|
||||
-- @field #string categoryname Group category name.
|
||||
-- @field #string typename Type name of group.
|
||||
-- @field #table menu The defence menu entries.
|
||||
|
||||
--- FlightControl class version.
|
||||
-- @field #string version
|
||||
SKIPPER.version="0.0.5"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- TODO list
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
-- TODO: A lot!
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Constructor
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Create a new SKIPPER class object for a specific aircraft carrier unit.
|
||||
-- @param #SKIPPER self
|
||||
-- @param #string carriername Name of the carrier.
|
||||
-- @param #string alias Skipper alias.
|
||||
-- @return #SKIPPER self
|
||||
function SKIPPER:New(carriername, alias)
|
||||
|
||||
-- Inherit everything from FSM class.
|
||||
--local self=BASE:Inherit(self, FSM:New()) -- #SKIPPER
|
||||
local self=BASE:Inherit(self, AIRBOSS:New(carriername, alias)) -- #SKIPPER
|
||||
|
||||
if not self.carrier then
|
||||
BASE:E(string.format("ERROR: Could not find carrier %s!", carriername))
|
||||
return nil
|
||||
end
|
||||
|
||||
--self.alias=alias or carriername
|
||||
|
||||
--self:
|
||||
|
||||
self.carriername=carriername
|
||||
|
||||
self.group=self.carrier:GetGroup()
|
||||
|
||||
self.arty=ARTY:New(self.group, carriername, self.alias)
|
||||
|
||||
self.warehouse=WAREHOUSE:New(carriername, self.alias)
|
||||
|
||||
local skipper=self
|
||||
|
||||
--- Function for WAREHOUSE self request events.
|
||||
function self.warehouse:OnAfterSelfRequest(From,Event,To,groupset,request)
|
||||
env.info("FF Warehous self request!")
|
||||
skipper:_SelfRequest(self, groupset, request)
|
||||
end
|
||||
|
||||
|
||||
-- Set some string id for output to DCS.log file.
|
||||
self.sid=string.format("SKIPPER %s |", self.carriername)
|
||||
|
||||
-- Add FSM transitions.
|
||||
-- From State --> Event --> To State
|
||||
self:AddTransition("*", "SkipperStatus", "*") -- Skipper status update.
|
||||
self:AddTransition("*", "IntruderAlert", "*") -- New intruder detected.
|
||||
|
||||
------------------------
|
||||
--- Pseudo Functions ---
|
||||
------------------------
|
||||
|
||||
--- Triggers the FSM event "Start". Starts the SKIPPER. Initializes parameters and starts event handlers.
|
||||
-- @function [parent=#SKIPPER] Start
|
||||
-- @param #SKIPPER self
|
||||
|
||||
--- Triggers the FSM event "Start" after a delay. Starts the SKIPPER. Initializes parameters and starts event handlers.
|
||||
-- @function [parent=#SKIPPER] __Start
|
||||
-- @param #SKIPPER self
|
||||
-- @param #number delay Delay in seconds.
|
||||
|
||||
--- Triggers the FSM event "Stop". Stops the SKIPPER and all its event handlers.
|
||||
-- @param #SKIPPER self
|
||||
|
||||
--- Triggers the FSM event "Stop" after a delay. Stops the SKIPPER and all its event handlers.
|
||||
-- @function [parent=#SKIPPER] __Stop
|
||||
-- @param #SKIPPER self
|
||||
-- @param #number delay Delay in seconds.
|
||||
|
||||
--- Triggers the FSM event "SkipperStatus".
|
||||
-- @function [parent=#SKIPPER] SkipperStatus
|
||||
-- @param #SKIPPER self
|
||||
|
||||
--- Triggers the FSM event "SkipperStatus" after a delay.
|
||||
-- @function [parent=#SKIPPER] __SkipperStatus
|
||||
-- @param #SKIPPER self
|
||||
-- @param #number delay Delay in seconds.
|
||||
|
||||
--- Triggers the FSM event "IntruderAlert".
|
||||
-- @function [parent=#SKIPPER] IntruderAlert
|
||||
-- @param #SKIPPER self
|
||||
-- @param #SKIPPER.Intruder Intruder data table.
|
||||
|
||||
--- Triggers the FSM delayed event "IntruderAlert".
|
||||
-- @function [parent=#SKIPPER] __IntruderAlert
|
||||
-- @param #SKIPPER self
|
||||
-- @param #number delay Delay in seconds before the function is called.
|
||||
-- @param #SKIPPER.Intruder Intruder data table.
|
||||
|
||||
--- On after "IntruderAlert" event user function. Called when a missile was launched.
|
||||
-- @function [parent=#SKIPPER] OnAfterIntruderAlert
|
||||
-- @param #SKIPPER self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param #SKIPPER.Intruder Intruder data table.
|
||||
|
||||
|
||||
-- Debug trace.
|
||||
if true then
|
||||
self.Debug=true
|
||||
BASE:TraceOnOff(true)
|
||||
BASE:TraceClass(self.ClassName)
|
||||
BASE:TraceLevel(1)
|
||||
self.arty:GetAmmo(true)
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- User Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Optimized F10 radio menu for a single carrier. The menu entries will be stored directly under F10 Other/Skipper/ and not F10 Other/Skipper/"Carrier Alias"/.
|
||||
-- **WARNING**: If you use this with two SKIPPER objects/carriers, the radio menu will be screwed up!
|
||||
-- @param #SKIPPER self
|
||||
-- @param #boolean switch If true or nil single menu is enabled. If false, menu is for multiple carriers in the mission.
|
||||
-- @return #SKIPPER self
|
||||
function SKIPPER:SetMenuSingleCarrier(switch)
|
||||
if switch==true or switch==nil then
|
||||
self.menusingle=true
|
||||
else
|
||||
self.menusingle=false
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Get carrier coalition.
|
||||
-- @param #SKIPPER self
|
||||
-- @return #number Coalition side of carrier.
|
||||
function SKIPPER:GetCoalition()
|
||||
return self.carrier:GetCoalition()
|
||||
end
|
||||
|
||||
--- Get carrier coordinate.
|
||||
-- @param #SKIPPER self
|
||||
-- @return Core.Point#COORDINATE Carrier coordinate.
|
||||
function SKIPPER:GetCoordinate()
|
||||
return self.carrier:GetCoordinate()
|
||||
end
|
||||
|
||||
--- Get WAREHOUSE object associated with the carrier.
|
||||
-- @param #SKIPPER self
|
||||
-- @return Functional.Warehouse#WAREHOUSE Warehouse object.
|
||||
function SKIPPER:GetWarehouseCarrier()
|
||||
return self.warehouse
|
||||
end
|
||||
|
||||
--- Get ARTY object associated with the carrier strike group.
|
||||
-- @param #SKIPPER self
|
||||
-- @return Functional.Artillery#ARTY Arty object.
|
||||
function SKIPPER:GetArty()
|
||||
return self.arty
|
||||
end
|
||||
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Status
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Start SKIPPER FSM. Handle events.
|
||||
-- @param #SKIPPER self
|
||||
function SKIPPER:onafterStart(From, Event, To)
|
||||
|
||||
-- Start parent airboss.
|
||||
self:GetParent(self).onafterStart(self, From, Event, To)
|
||||
|
||||
-- Info.
|
||||
self:I(self.sid..string.format("Starting SKIPPER v%s for carrier %s on map %s.", SKIPPER.version, self.carriername, self.theatre))
|
||||
|
||||
-- Start ARTY.
|
||||
self.arty:Start()
|
||||
|
||||
-- Start Warehouse.
|
||||
self.warehouse:Start()
|
||||
|
||||
-- Add F10 radio menu.
|
||||
self:_SetMenuCoalition()
|
||||
|
||||
-- Handle events.
|
||||
self:HandleEvent(EVENTS.Birth)
|
||||
self:HandleEvent(EVENTS.EngineStartup)
|
||||
self:HandleEvent(EVENTS.Takeoff)
|
||||
self:HandleEvent(EVENTS.Land)
|
||||
self:HandleEvent(EVENTS.EngineShutdown)
|
||||
self:HandleEvent(EVENTS.Crash)
|
||||
|
||||
-- Init status updates.
|
||||
self:__SkipperStatus(-1)
|
||||
end
|
||||
|
||||
--- Update status.
|
||||
-- @param #SKIPPER self
|
||||
function SKIPPER:onafterSkipperStatus(From, Event, To)
|
||||
|
||||
local fsmstate=self:GetState()
|
||||
|
||||
-- Check zone for flights inbound.
|
||||
self:_CheckIntruder()
|
||||
|
||||
-- Info text.
|
||||
local text=string.format("State %s", fsmstate)
|
||||
self:I(self.sid..text)
|
||||
|
||||
self:__SkipperStatus(-30)
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- FSM Events
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- CCA Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Scan carrier zone for (new) units.
|
||||
-- @param #SKIPPER self
|
||||
function SKIPPER:_CheckIntruder()
|
||||
|
||||
-- Carrier position.
|
||||
local coord=self:GetCoordinate()
|
||||
|
||||
-- Scan radius = radius of the CCA.
|
||||
local RCCZ=self.zoneCCA:GetRadius()
|
||||
|
||||
-- Debug info.
|
||||
self:T(self.sid..string.format("Scanning Carrier Controlled Area. Radius=%.1f NM.", UTILS.MetersToNM(RCCZ)))
|
||||
|
||||
-- Scan units in carrier zone.
|
||||
local _,_,_,unitscan=coord:ScanObjects(RCCZ, true, false, false)
|
||||
|
||||
|
||||
-- Make a table with all groups currently in the CCA zone.
|
||||
local insideCCA={}
|
||||
for _,_unit in pairs(unitscan) do
|
||||
local unit=_unit --Wrapper.Unit#UNIT
|
||||
|
||||
-- Necessary conditions to be met:
|
||||
local airborne=unit:IsAir()
|
||||
local inzone=unit:IsInZone(self.zoneCCA)
|
||||
local friendly=self:GetCoalition()==unit:GetCoalition()
|
||||
|
||||
-- Check if this an aircraft and that it is airborne and closing in.
|
||||
if inzone then
|
||||
|
||||
local group=unit:GetGroup()
|
||||
local groupname=group:GetName()
|
||||
|
||||
if insideCCA[groupname]==nil and groupname~=self.group:GetName() then
|
||||
insideCCA[groupname]=group
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
-- Intruder menu needs update.
|
||||
local updateintrudermenu=false
|
||||
|
||||
-- Find out if any known intruder is not in the CCA any more.
|
||||
for i=#self.intruders,1,-1 do
|
||||
local intruder=self.intruders[i] --#SKIPPER.Intruder
|
||||
|
||||
-- Loop over current groups in CCA.
|
||||
local gotit=false
|
||||
for groupname,_group in pairs(insideCCA) do
|
||||
local group=_group --Wrapper.Group#GROUP
|
||||
|
||||
if groupname==intruder.groupname then
|
||||
gotit=true
|
||||
end
|
||||
end
|
||||
|
||||
-- Intruder is gone!
|
||||
if not gotit then
|
||||
|
||||
-- Remove from table.
|
||||
table.remove(self.intruders, i)
|
||||
|
||||
-- Menu update required.
|
||||
updateintrudermenu=true
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
for groupname,_group in pairs(insideCCA) do
|
||||
local group=_group --Wrapper.Group#GROUP
|
||||
|
||||
-- Find out if any known intruder is not in the CCA any more.
|
||||
local gotit=false
|
||||
for i=1,#self.intruders do
|
||||
local intruder=self.intruders[i] --#SKIPPER.Intruder
|
||||
if groupname==intruder.groupname then
|
||||
gotit=true
|
||||
end
|
||||
end
|
||||
|
||||
if not gotit then
|
||||
|
||||
-- Create a new intruder table.
|
||||
local intruder={} --#SKIPPER.Intruder
|
||||
intruder.coalition=group:GetCoalition()
|
||||
intruder.group=group
|
||||
intruder.threadlevel, intruder.threadtext=group:GetUnit(1):GetThreatLevel()
|
||||
intruder.time0=timer.getAbsTime()
|
||||
intruder.dist0=self:GetCoordinate():Get2DDistance(group:GetCoordinate())
|
||||
intruder.groupname=groupname
|
||||
intruder.typename=group:GetTypeName()
|
||||
intruder.category=group:GetCategory()
|
||||
intruder.categoryname=group:GetCategoryName()
|
||||
|
||||
-- Add intruder to list.
|
||||
table.insert(self.intruders, intruder)
|
||||
|
||||
-- Trigger alert!
|
||||
self:IntruderAlert(intruder)
|
||||
|
||||
-- Menu update required.
|
||||
updateintrudermenu=true
|
||||
end
|
||||
end
|
||||
|
||||
-- Update intruder menu if necessary.
|
||||
if updateintrudermenu then
|
||||
self:_SortIntruders()
|
||||
self:_UpdateIntruderMenu()
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Misc Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Menu Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Patrol carrier.
|
||||
-- @param #SKIPPER self
|
||||
-- @return #SKIPPER self
|
||||
function SKIPPER:_SetMenuCoalition()
|
||||
|
||||
-- Get coalition.
|
||||
local Coalition=self:GetCoalition()
|
||||
|
||||
-- Init menu table.
|
||||
self.menu=self.menu or {}
|
||||
|
||||
-- Init menu coalition table.
|
||||
self.menu[Coalition]=self.menu[Coalition] or {}
|
||||
|
||||
-- Shortcut.
|
||||
local menu=self.menu[Coalition]
|
||||
|
||||
if self.menusingle then
|
||||
-- F10/Skipper/...
|
||||
if not menu.Skipper then
|
||||
menu.Skipper=MENU_COALITION:New(Coalition, "Skipper")
|
||||
end
|
||||
else
|
||||
-- F10/Skipper/<Carrier Alias>/...
|
||||
if not menu.Root then
|
||||
menu.Root=MENU_COALITION:New(Coalition, "Skipper")
|
||||
end
|
||||
menu.Skipper=MENU_COALITION:New(Coalition, self.alias, menu.Root)
|
||||
end
|
||||
|
||||
------------------
|
||||
-- Defence Menu --
|
||||
------------------
|
||||
|
||||
menu.SetSpeed = MENU_COALITION:New(Coalition, "Set Speed", menu.Skipper)
|
||||
menu.SetSpeed_00 = MENU_COALITION_COMMAND:New(Coalition, "Hold Position", menu.SetSpeed, self._SetSpeed, self, 0)
|
||||
menu.SetSpeed_05 = MENU_COALITION_COMMAND:New(Coalition, "5 knots", menu.SetSpeed, self._SetSpeed, self, 5)
|
||||
menu.SetSpeed_10 = MENU_COALITION_COMMAND:New(Coalition, "10 knots", menu.SetSpeed, self._SetSpeed, self, 10)
|
||||
menu.SetSpeed_15 = MENU_COALITION_COMMAND:New(Coalition, "15 knots", menu.SetSpeed, self._SetSpeed, self, 15)
|
||||
menu.SetSpeed_20 = MENU_COALITION_COMMAND:New(Coalition, "20 knots", menu.SetSpeed, self._SetSpeed, self, 20)
|
||||
menu.SetSpeed_25 = MENU_COALITION_COMMAND:New(Coalition, "25 knots", menu.SetSpeed, self._SetSpeed, self, 25)
|
||||
menu.SetSpeed_30 = MENU_COALITION_COMMAND:New(Coalition, "30 knots", menu.SetSpeed, self._SetSpeed, self, 30)
|
||||
menu.SetSpeed_99 = MENU_COALITION_COMMAND:New(Coalition, "Restore Route", menu.SetSpeed, self.CarrierResume, self)
|
||||
|
||||
menu.Recovery=MENU_COALITION:New(Coalition, "Recovery", menu.Skipper)
|
||||
|
||||
-- Set wind on deck.
|
||||
menu.SetWoD = MENU_COALITION:New(Coalition, "Wind on Deck", menu.Recovery)
|
||||
menu.SetWoD_10 = MENU_COALITION_COMMAND:New(Coalition, "10 knots", menu.SetWoD, self._SetWoD, self, 10)
|
||||
menu.SetWoD_15 = MENU_COALITION_COMMAND:New(Coalition, "15 knots", menu.SetWoD, self._SetWoD, self, 15)
|
||||
menu.SetWoD_20 = MENU_COALITION_COMMAND:New(Coalition, "20 knots", menu.SetWoD, self._SetWoD, self, 20)
|
||||
menu.SetWoD_25 = MENU_COALITION_COMMAND:New(Coalition, "25 knots", menu.SetWoD, self._SetWoD, self, 25)
|
||||
menu.SetWoD_30 = MENU_COALITION_COMMAND:New(Coalition, "30 knots", menu.SetWoD, self._SetWoD, self, 30)
|
||||
|
||||
-- Set Duration.
|
||||
menu.SetRtime = MENU_COALITION:New(Coalition, "Duration", menu.Recovery)
|
||||
menu.SetRtime_15 = MENU_COALITION_COMMAND:New(Coalition, "15 min", menu.SetRtime, self._SetRtime, self, 15)
|
||||
menu.SetRtime_30 = MENU_COALITION_COMMAND:New(Coalition, "30 min", menu.SetRtime, self._SetRtime, self, 30)
|
||||
menu.SetRtime_45 = MENU_COALITION_COMMAND:New(Coalition, "45 min", menu.SetRtime, self._SetRtime, self, 45)
|
||||
menu.SetRtime_60 = MENU_COALITION_COMMAND:New(Coalition, "60 min", menu.SetRtime, self._SetRtime, self, 60)
|
||||
menu.SetRtime_90 = MENU_COALITION_COMMAND:New(Coalition, "90 min", menu.SetRtime, self._SetRtime, self, 90)
|
||||
|
||||
-- Start/Stop.
|
||||
menu.SetUturn = MENU_COALITION_COMMAND:New(Coalition, "U-turn On/Off", menu.Recovery, self._SetUturn, self)
|
||||
menu.CaseI = MENU_COALITION_COMMAND:New(Coalition, "Start CASE I", menu.Recovery, self._StartCaseX, self, 1)
|
||||
menu.CaseII = MENU_COALITION_COMMAND:New(Coalition, "Start CASE II", menu.Recovery, self._StartCaseX, self, 2)
|
||||
menu.CaseIII = MENU_COALITION_COMMAND:New(Coalition, "Start CASE III", menu.Recovery, self._StartCaseX, self, 3)
|
||||
menu.Rstop = MENU_COALITION_COMMAND:New(Coalition, "Pause/Unpause", menu.Recovery, self._Rpause, self)
|
||||
menu.Rstop = MENU_COALITION_COMMAND:New(Coalition, "Stop Recovery", menu.Recovery, self._Rstop, self)
|
||||
|
||||
|
||||
------------------
|
||||
-- Defence Menu --
|
||||
------------------
|
||||
|
||||
menu.Defence = MENU_COALITION:New(Coalition, "Defence", menu.Skipper)
|
||||
menu.Defence_SetROE = MENU_COALITION:New(Coalition, "Set ROE", menu.Defence)
|
||||
menu.Defence_SetROE_Hold = MENU_COALITION_COMMAND:New(Coalition, "Weapon Hold", menu.Defence_SetROE, self._SetROE, self, "Hold")
|
||||
menu.Defence_SetROE_Free = MENU_COALITION_COMMAND:New(Coalition, "Weapon Free", menu.Defence_SetROE, self._SetROE, self, "Free")
|
||||
menu.Defence_SetROE_Return = MENU_COALITION_COMMAND:New(Coalition, "Return Fire", menu.Defence_SetROE, self._SetROE, self, "Return")
|
||||
|
||||
menu.Defence_Intruders = MENU_COALITION:New(Coalition, "Intruders", menu.Defence)
|
||||
menu.Defence_Ammo = MENU_COALITION_COMMAND:New(Coalition, "Report Ammo", menu.Defence, self.arty.GetAmmo, self.arty, true)
|
||||
menu.Defence_IntruderR = MENU_COALITION_COMMAND:New(Coalition, "Report Intruders", menu.Defence, self._ListIntruders, self)
|
||||
menu.Defence_LaunchCAP = MENU_COALITION_COMMAND:New(Coalition, "Lauch CAP", menu.Defence, self.LaunchCAP, self)
|
||||
|
||||
--------------------
|
||||
-- Warehouse Menu --
|
||||
--------------------
|
||||
|
||||
if self.warehouse then
|
||||
menu.Warehouse=MENU_COALITION:New(Coalition, "Warehouse", menu.Skipper)
|
||||
menu.Warehouse_Assets = MENU_COALITION_COMMAND:New(Coalition, "Reports On/Off", menu.Warehouse, self.WarehouseReportsToggle, self)
|
||||
menu.Warehouse_Assets = MENU_COALITION_COMMAND:New(Coalition, "Report Assets", menu.Warehouse, self.ReportWarehouseStock, self)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Warehouse reports on/off.
|
||||
-- @param #SKIPPER self
|
||||
function SKIPPER:WarehouseReportsToggle()
|
||||
self.warehouse.Report=not self.warehouse.Report
|
||||
MESSAGE:New(string.format("Warehouse reports are now %s", tostring(self.warehouse.Report)), 10, "SKIPPER", true):ToCoalition(self:GetCoalition())
|
||||
end
|
||||
|
||||
|
||||
--- Report warehouse stock.
|
||||
-- @param #SKIPPER self
|
||||
function SKIPPER:ReportWarehouseStock()
|
||||
local text=self.warehouse:_GetStockAssetsText(false)
|
||||
MESSAGE:New(text, 10, "SKIPPER", true):ToCoalition(self:GetCoalition())
|
||||
end
|
||||
|
||||
--- Handle self requests from carrier warehouse.
|
||||
-- @param #SKIPPER self
|
||||
-- @param Functional.Warehouse#WAREHOUSE warehouse The (carrier) warehouse object.
|
||||
-- @param Core.Set#SET_GROUP groupset Group set.
|
||||
-- @param Functional.Warehouse#WAREHOUSE.Pendingitem request Request.
|
||||
function SKIPPER:_SelfRequest(warehouse, groupset, request)
|
||||
|
||||
local assignment=request.assignment
|
||||
|
||||
env.info(string.format("FF warehouse self request assignment %s", tostring(assignment)))
|
||||
|
||||
end
|
||||
|
||||
--- Launch a Intercept flight.
|
||||
-- @param #SKIPPER self
|
||||
-- @param #SKIPPER.Intruder intruder Intruder group.
|
||||
function SKIPPER:LaunchIntercept(intruder)
|
||||
|
||||
--local n=self.warehouse:GetNumberOfAssets(WAREHOUSE.Descriptor.ASSIGNMENT, WAREHOUSE.Attribute.AIR_FIGHTER, false)
|
||||
local n=self.warehouse:GetNumberOfAssets(WAREHOUSE.Descriptor.ASSIGNMENT, "Intercept", false)
|
||||
|
||||
if n>0 then
|
||||
|
||||
self.warehouse:AddRequest(self.warehouse, WAREHOUSE.Descriptor.ASSIGNMENT, "Intercept", 1, nil, nil, nil, "Intercept")
|
||||
|
||||
local capcoord=intruder.group:GetCoordinate()
|
||||
|
||||
capcoord:MarkToAll("Intruder coord")
|
||||
|
||||
function self.warehouse.OnAfterSelfRequest(warehouse, From, Event, To, Groupset, Request)
|
||||
local request=Request --Functional.Warehouse#WAREHOUSE.Pendingitem
|
||||
local groupset=Groupset --Core.Set#SET_GROUP
|
||||
|
||||
for _,_group in pairs(groupset:GetSet()) do
|
||||
local group=_group --Wrapper.Group#GROUP
|
||||
|
||||
if request.assignment=="Intercept" then
|
||||
|
||||
-- Task orbit.
|
||||
local tasks={}
|
||||
|
||||
for _,unit in pairs(intruder.group:GetUnits()) do
|
||||
tasks[#tasks+1]=group:TaskAttackUnit(unit)
|
||||
end
|
||||
|
||||
local speed=group:GetSpeedMax()
|
||||
local altitude=intruder.group:GetAltitude()
|
||||
|
||||
-- Create waypoints.
|
||||
local wp={}
|
||||
wp[1]=self:GetCoordinate():WaypointAirTakeOffParking()
|
||||
wp[2]=self:GetCoordinate():SetAltitude(altitude):WaypointAirTurningPoint(COORDINATE.WaypointAltType.BARO, speed, {tasks}, "Attack Intruder")
|
||||
|
||||
-- Start uncontrolled group.
|
||||
group:StartUncontrolled()
|
||||
|
||||
--airboss:SetExcludeAI()
|
||||
|
||||
-- Route group
|
||||
group:Route(wp)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
else
|
||||
MESSAGE:New("No INTERCEPT fighters currently available", 5, "SKIPPER"):ToCoalition(self:GetCoalition())
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- Launch a CAP flight.
|
||||
-- @param #SKIPPER self
|
||||
function SKIPPER:LaunchCAP()
|
||||
|
||||
--local n=self.warehouse:GetNumberOfAssets(WAREHOUSE.Descriptor.ASSIGNMENT, WAREHOUSE.Attribute.AIR_FIGHTER, false)
|
||||
local n=self.warehouse:GetNumberOfAssets(WAREHOUSE.Descriptor.ASSIGNMENT, "CAP", false)
|
||||
|
||||
if n>0 then
|
||||
|
||||
self.warehouse:AddRequest(self.warehouse, WAREHOUSE.Descriptor.ATTRIBUTE, WAREHOUSE.Attribute.AIR_FIGHTER, 1, nil, nil, nil, "CAP")
|
||||
|
||||
local capcoord=self.zoneCCA:GetRandomCoordinate(self.zoneCCA:GetRadius()/2)
|
||||
|
||||
capcoord:MarkToAll("CAP coord")
|
||||
|
||||
function self.warehouse.OnAfterSelfRequest(warehouse, From, Event, To, Groupset, Request)
|
||||
local request=Request --Functional.Warehouse#WAREHOUSE.Pendingitem
|
||||
local groupset=Groupset --Core.Set#SET_GROUP
|
||||
|
||||
for _,_group in pairs(groupset:GetSet()) do
|
||||
local group=_group --Wrapper.Group#GROUP
|
||||
|
||||
if request.assignment=="CAP" then
|
||||
|
||||
local alt=UTILS.FeetToMeters(20000)
|
||||
|
||||
-- Task orbit.
|
||||
local taskOrbit=group:TaskOrbit(capcoord, alt, UTILS.KnotsToMps(400), capcoord:Translate(UTILS.NMToMeters(15), 0))
|
||||
|
||||
local wp={}
|
||||
wp[1]=self:GetCoordinate():WaypointAirTakeOffParking()
|
||||
wp[2]=self:GetCoordinate():SetAltitude(alt):WaypointAirTurningPoint(COORDINATE.WaypointAltType.BARO, UTILS.KnotsToKmph(350), {taskOrbit}, "CAP")
|
||||
|
||||
-- Start uncontrolled group.
|
||||
group:StartUncontrolled()
|
||||
|
||||
--airboss:SetExcludeAI()
|
||||
|
||||
-- Route group.
|
||||
group:Route(wp)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
else
|
||||
MESSAGE:New("No CAP fighters currently available", 5, "SKIPPER"):ToCoalition(self:GetCoalition())
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- Enage an intruder by ARTY.
|
||||
-- @param #SKIPPER self
|
||||
-- @param #SKIPPER.Intruder intruder The intruder data.
|
||||
-- @param #number weapontype Type of weapon.
|
||||
function SKIPPER:EngageArty(intruder, weapontype)
|
||||
|
||||
weapontype=weapontype or ARTY.WeaponType.Auto
|
||||
|
||||
if intruder.group and intruder.group:IsAlive() then
|
||||
|
||||
-- Current position.
|
||||
local coord=intruder.group:GetCoordinate()
|
||||
|
||||
if intruder.category==Group.Category.GROUND or intruder.category==Group.Category.TRAIN then
|
||||
self.arty:AssignTargetCoord(coord, 50, 100, 5, 1, nil, weapontype, intruder.groupname, false)
|
||||
elseif intruder.category==Group.Category.SHIP then
|
||||
self.arty:AssignAttackGroup(intruder.group, 50, 100, 5, 1, nil, weapontype, intruder.groupname, false)
|
||||
elseif intruder.category==Group.Category.AIRPLANE or intruder.category==Group.Category.HELICOPTER then
|
||||
self.arty:AssignAttackGroup(intruder.group, 50, 100, 5, 1, nil, weapontype, intruder.groupname, false)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
--- Sort intruders table.
|
||||
-- @param #SKIPPER self
|
||||
function SKIPPER:_SortIntruders()
|
||||
|
||||
-- Sort potential section members wrt to distance to lead.
|
||||
local function _sort(_a,_b)
|
||||
local a=_a --#SKIPPER.Intruder
|
||||
local b=_b --#SKIPPER.Intruder
|
||||
return a.threadlevel>b.threadlevel
|
||||
end
|
||||
|
||||
table.sort(self.intruders, _sort)
|
||||
end
|
||||
|
||||
--- Add sub menu for this intruder.
|
||||
-- @param #SKIPPER self
|
||||
-- @param #SKIPPER.Intruder intruder The intruder data.
|
||||
-- @param #boolean removed If true, an intruder was removed. If false or nil, a new intruder was added.
|
||||
function SKIPPER:_UpdateIntruderMenu()
|
||||
|
||||
for _,_intruder in ipairs(self.intruders) do
|
||||
local intruder=_intruder --#SKIPPER.Intruder
|
||||
self:_AddIntruderMenu(intruder)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Add sub menu for this intruder.
|
||||
-- @param #SKIPPER self
|
||||
-- @param #SKIPPER.Intruder intruder The intruder data.
|
||||
function SKIPPER:_AddIntruderMenu(intruder)
|
||||
|
||||
local Coalition=self:GetCoalition()
|
||||
|
||||
local root=self.menu[Coalition].Defence_Intruders
|
||||
|
||||
local menu=MENU_COALITION:New(Coalition, intruder.typename, root)
|
||||
|
||||
if intruder.category==Group.Category.GROUND then
|
||||
MENU_COALITION_COMMAND:New(Coalition, "Engage Shells", menu, self.EngageArty, self, intruder, ARTY.WeaponType.Cannon)
|
||||
MENU_COALITION_COMMAND:New(Coalition, "Engage Cruise M", menu, self.EngageArty, self, intruder, ARTY.WeaponType.CruiseMissile)
|
||||
elseif intruder.category==Group.Category.SHIP then
|
||||
MENU_COALITION_COMMAND:New(Coalition, "Engage", menu, self.EngageArty, self, intruder)
|
||||
elseif intruder.category==Group.Category.AIRPLANE or intruder.category==Group.Category.HELICOPTER then
|
||||
MENU_COALITION_COMMAND:New(Coalition, "Engage A2A", menu, self.LaunchIntercept, self, intruder)
|
||||
end
|
||||
|
||||
-- Set menu.
|
||||
intruder.menu=menu
|
||||
|
||||
end
|
||||
|
||||
--- Remove sub menu for this intruder.
|
||||
-- @param #SKIPPER self
|
||||
-- @param #SKIPPER.Intruder intruder The intruder data.
|
||||
function SKIPPER:_RemoveIntruderMenu(intruder)
|
||||
|
||||
if intruder.menu then
|
||||
local menu=intruder.menu --Core.Menu#MENU_COALITION
|
||||
|
||||
menu:Remove()
|
||||
|
||||
intruder.menu=nil
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- Intruders.
|
||||
-- @param #SKIPPER self
|
||||
function SKIPPER:_ListIntruders()
|
||||
|
||||
local text="Current Intruders:"
|
||||
|
||||
for i,_intruder in pairs(self.intruders) do
|
||||
local intruder=_intruder --#SKIPPER.Intruder
|
||||
text=text..string.format("\n[%d] %s*%d, %s [%d/10]", i, intruder.typename, #intruder.group:GetUnits(), intruder.categoryname, intruder.threadlevel)
|
||||
end
|
||||
|
||||
if #self.intruders==0 then
|
||||
text=text.." none."
|
||||
end
|
||||
|
||||
MESSAGE:New(text, 10, self.ClassName):ToCoalition(self:GetCoalition())
|
||||
end
|
||||
|
||||
--- Start Case X recovery.
|
||||
-- @param #SKIPPER self
|
||||
-- @param #number case Recovery case (1,2,3).
|
||||
function SKIPPER:_StartCaseX(case)
|
||||
|
||||
--[[
|
||||
self.skipperTime=self.skipperTime or 30
|
||||
self.skipperSpeed=self.skipperSpeed or 25
|
||||
if self.skipperUturn==nil then
|
||||
self.skipperUturn=false
|
||||
end
|
||||
]]
|
||||
|
||||
-- Inform player.
|
||||
local text=string.format("Case %d recovery will start in 5 min for %d min. Wind on deck %d knots. U-turn=%s.", case, self.skipperTime, self.skipperSpeed, tostring(self.skipperUturn))
|
||||
|
||||
if self:IsRecovering() then
|
||||
text="negative, carrier is already recovering."
|
||||
MESSAGE:New(string.format(text), 5, self.ClassName):ToCoalition(self:GetCoalition())
|
||||
return
|
||||
end
|
||||
|
||||
-- Recovery staring in 5 min for 30 min.
|
||||
local t0=timer.getAbsTime()+5*60
|
||||
local t9=t0+self.skipperTime*60
|
||||
local C0=UTILS.SecondsToClock(t0)
|
||||
local C9=UTILS.SecondsToClock(t9)
|
||||
|
||||
-- Carrier will turn into the wind. Wind on deck 25 knots. U-turn on.
|
||||
self:AddRecoveryWindow(C0, C9, case, 30, true, self.skipperSpeed, self.skipperUturn)
|
||||
|
||||
MESSAGE:New(string.format(text), 5, self.ClassName):ToCoalition(self:GetCoalition())
|
||||
|
||||
end
|
||||
|
||||
|
||||
|
||||
--- Toggle recovery U-turn option.
|
||||
-- @param #SKIPPER self
|
||||
function SKIPPER:_SetUturn()
|
||||
|
||||
self.skipperUturn=not self.skipperUturn
|
||||
|
||||
MESSAGE:New(string.format("Recovery U-turn is now %s.", tostring(self.skipperUturn)), 5, self.ClassName):ToCoalition(self:GetCoalition())
|
||||
end
|
||||
|
||||
--- Set manual recovery duration.
|
||||
-- @param #SKIPPER self
|
||||
-- @param #number time Duration in minutes.
|
||||
function SKIPPER:_SetRtime(time)
|
||||
|
||||
self.skipperTime=time
|
||||
|
||||
MESSAGE:New(string.format("Recovery duration set to %d min.", time), 5, self.ClassName):ToCoalition(self:GetCoalition())
|
||||
end
|
||||
|
||||
|
||||
--- Set wind on deck for manual recovery start.
|
||||
-- @param #SKIPPER self
|
||||
-- @param #number speed Speed in knots.
|
||||
function SKIPPER:_SetWoD(speed)
|
||||
|
||||
self.skipperSpeed=speed
|
||||
|
||||
MESSAGE:New(string.format("Wind on Deck set to %d knots.", speed), 5, self.ClassName):ToCoalition(self:GetCoalition())
|
||||
end
|
||||
|
||||
--- Set new speed for all waypoints.
|
||||
-- @param #SKIPPER self
|
||||
-- @param #number speed Speed in knots.
|
||||
function SKIPPER:_SetSpeed(speed)
|
||||
|
||||
-- Loop over waypoints.
|
||||
for n=1,#self.waypoints do
|
||||
local coord=self.waypoints[n] --Core.Point#COORDINATE
|
||||
|
||||
coord.Velocity=UTILS.KnotsToMps(speed)
|
||||
end
|
||||
|
||||
self:_PatrolRoute()
|
||||
|
||||
end
|
||||
|
||||
--- Set rules of engagement.
|
||||
-- @param #SKIPPER self
|
||||
-- @param #string roe "Hold", "Free", "Return".
|
||||
function SKIPPER:_SetROE(roe)
|
||||
|
||||
if roe=="Hold" then
|
||||
self.group:OptionROEHoldFire()
|
||||
elseif roe=="Free" then
|
||||
self.group:OptionROEOpenFire()
|
||||
elseif roe=="Return" then
|
||||
self.group:OptionROEReturnFire()
|
||||
end
|
||||
|
||||
MESSAGE:New(string.format("ROE set to %s", roe), 5, self.ClassName):ToCoalition(self:GetCoalition())
|
||||
end
|
||||
|
||||
--- Set alarm state. (Not useful/working for ships.)
|
||||
-- @param #SKIPPER self
|
||||
-- @param #string state "Green", "Red", "Auto".
|
||||
function SKIPPER:_SetALS(state)
|
||||
|
||||
if state=="Green" then
|
||||
self.group:OptionAlarmStateGreen()
|
||||
elseif state=="Red" then
|
||||
self.group:OptionAlarmStateRed()
|
||||
elseif state=="Auto" then
|
||||
self.group:OptionAlarmStateAuto()
|
||||
end
|
||||
|
||||
MESSAGE:New(string.format("Alarm state set to %s", state), 5, self.ClassName):ToCoalition(self:GetCoalition())
|
||||
end
|
||||
|
||||
--- Function to stop the carrier.
|
||||
-- @param #SKIPPER self
|
||||
function SKIPPER:CarrierHold()
|
||||
env.info("Carrier Hold!")
|
||||
|
||||
-- Get current position.
|
||||
local pos=self.group:GetCoordinate()
|
||||
|
||||
-- Create a new waypoint.
|
||||
local wp=pos:WaypointGround(0)
|
||||
|
||||
-- Create new route consisting of only this position ==> Stop!
|
||||
self.group:Route({wp})
|
||||
|
||||
MESSAGE:New(string.format("Carrier is holding current position."), 5, self.ClassName):ToCoalition(self:GetCoalition())
|
||||
end
|
||||
|
||||
--- Function to stop the carrier.
|
||||
-- @param #SKIPPER self
|
||||
function SKIPPER:CarrierResume()
|
||||
env.info("Carrier Resume Route!")
|
||||
|
||||
self:_InitWaypoints()
|
||||
|
||||
local nextWP,n=self:_GetNextWaypoint()
|
||||
|
||||
self:_PatrolRoute(n)
|
||||
|
||||
MESSAGE:New(string.format("Carrier is resuming route to waypoint #%d.", n), 5, self.ClassName):ToCoalition(self:GetCoalition())
|
||||
end
|
||||
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
@@ -0,0 +1,321 @@
|
||||
--- **Ops** - Airwing Squadron.
|
||||
--
|
||||
-- **Main Features:**
|
||||
--
|
||||
-- * Set parameters like livery, skill valid for all squadron members.
|
||||
-- * Define modex and callsigns.
|
||||
-- * Define mission types, this squadron can perform (see Ops.Auftrag#AUFTRAG).
|
||||
-- * Pause/unpause squadron operations.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **funkyfranky**
|
||||
-- @module Ops.Squadron
|
||||
-- @image OPS_Squadron.png
|
||||
|
||||
|
||||
--- SQUADRON class.
|
||||
-- @type SQUADRON
|
||||
-- @field #string ClassName Name of the class.
|
||||
-- @field #number verbose Verbosity level.
|
||||
-- @field #string lid Class id string for output to DCS log file.
|
||||
-- @field #string name Name of the squadron.
|
||||
-- @field #string templatename Name of the template group.
|
||||
-- @field #string aircrafttype Type of the airframe the squadron is using.
|
||||
-- @field Wrapper.Group#GROUP templategroup Template group.
|
||||
-- @field #number ngrouping User defined number of units in the asset group.
|
||||
-- @field #table assets Squadron assets.
|
||||
-- @field #table missiontypes Capabilities (mission types and performances) of the squadron.
|
||||
-- @field #number fuellow Low fuel threshold.
|
||||
-- @field #boolean fuellowRefuel If `true`, flight tries to refuel at the nearest tanker.
|
||||
-- @field #number maintenancetime Time in seconds needed for maintenance of a returned flight.
|
||||
-- @field #number repairtime Time in seconds for each
|
||||
-- @field #string livery Livery of the squadron.
|
||||
-- @field #number skill Skill of squadron members.
|
||||
-- @field #number modex Modex.
|
||||
-- @field #number modexcounter Counter to incease modex number for assets.
|
||||
-- @field #string callsignName Callsign name.
|
||||
-- @field #number callsigncounter Counter to increase callsign names for new assets.
|
||||
-- @field #number Ngroups Number of asset flight groups this squadron has.
|
||||
-- @field #number engageRange Mission range in meters.
|
||||
-- @field #string attribute Generalized attribute of the squadron template group.
|
||||
-- @field #number tankerSystem For tanker squads, the refuel system used (boom=0 or probpe=1). Default nil.
|
||||
-- @field #number refuelSystem For refuelable squads, the refuel system used (boom=0 or probe=1). Default nil.
|
||||
-- @field #table tacanChannel List of TACAN channels available to the squadron.
|
||||
-- @field #number radioFreq Radio frequency in MHz the squad uses.
|
||||
-- @field #number radioModu Radio modulation the squad uses.
|
||||
-- @field #string takeoffType Take of type.
|
||||
-- @field #table parkingIDs Parking IDs for this squadron.
|
||||
-- @field #boolean despawnAfterLanding Aircraft are despawned after landing.
|
||||
-- @field #boolean despawnAfterHolding Aircraft are despawned after holding.
|
||||
-- @extends Ops.Cohort#COHORT
|
||||
|
||||
--- *It is unbelievable what a squadron of twelve aircraft did to tip the balance* -- Adolf Galland
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- # The SQUADRON Concept
|
||||
--
|
||||
-- A SQUADRON is essential part of an @{Ops.Airwing#AIRWING} and consists of **one** type of aircraft.
|
||||
--
|
||||
--
|
||||
--
|
||||
-- @field #SQUADRON
|
||||
SQUADRON = {
|
||||
ClassName = "SQUADRON",
|
||||
verbose = 0,
|
||||
modex = nil,
|
||||
modexcounter = 0,
|
||||
callsignName = nil,
|
||||
callsigncounter= 11,
|
||||
tankerSystem = nil,
|
||||
refuelSystem = nil,
|
||||
}
|
||||
|
||||
--- SQUADRON class version.
|
||||
-- @field #string version
|
||||
SQUADRON.version="0.8.1"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- TODO list
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
-- DONE: Parking spots for squadrons?
|
||||
-- DONE: Engage radius.
|
||||
-- DONE: Modex.
|
||||
-- DONE: Call signs.
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Constructor
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Create a new SQUADRON object and start the FSM.
|
||||
-- @param #SQUADRON self
|
||||
-- @param #string TemplateGroupName Name of the template group.
|
||||
-- @param #number Ngroups Number of asset groups of this squadron. Default 3.
|
||||
-- @param #string SquadronName Name of the squadron, e.g. "VFA-37".
|
||||
-- @return #SQUADRON self
|
||||
function SQUADRON:New(TemplateGroupName, Ngroups, SquadronName)
|
||||
|
||||
-- Inherit everything from FSM class.
|
||||
local self=BASE:Inherit(self, COHORT:New(TemplateGroupName, Ngroups, SquadronName)) -- #SQUADRON
|
||||
|
||||
-- Everyone can ORBIT.
|
||||
self:AddMissionCapability(AUFTRAG.Type.ORBIT)
|
||||
|
||||
-- Refueling system.
|
||||
self.refuelSystem=select(2, self.templategroup:GetUnit(1):IsRefuelable())
|
||||
self.tankerSystem=select(2, self.templategroup:GetUnit(1):IsTanker())
|
||||
|
||||
------------------------
|
||||
--- Pseudo Functions ---
|
||||
------------------------
|
||||
|
||||
-- See COHORT class
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- User functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Set number of units in groups.
|
||||
-- @param #SQUADRON self
|
||||
-- @param #number nunits Number of units. Must be >=1 and <=4. Default 2.
|
||||
-- @return #SQUADRON self
|
||||
function SQUADRON:SetGrouping(nunits)
|
||||
self.ngrouping=nunits or 2
|
||||
if self.ngrouping<1 then self.ngrouping=1 end
|
||||
if self.ngrouping>4 then self.ngrouping=4 end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set valid parking spot IDs. Assets of this squad are only allowed to be spawned at these parking spots. **Note** that the IDs are different from the ones displayed in the mission editor!
|
||||
-- @param #SQUADRON self
|
||||
-- @param #table ParkingIDs Table of parking ID numbers or a single `#number`.
|
||||
-- @return #SQUADRON self
|
||||
function SQUADRON:SetParkingIDs(ParkingIDs)
|
||||
if type(ParkingIDs)~="table" then
|
||||
ParkingIDs={ParkingIDs}
|
||||
end
|
||||
self.parkingIDs=ParkingIDs
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Set takeoff type. All assets of this squadron will be spawned with cold (default) or hot engines.
|
||||
-- Spawning on runways is not supported.
|
||||
-- @param #SQUADRON self
|
||||
-- @param #string TakeoffType Take off type: "Cold" (default) or "Hot" with engines on or "Air" for spawning in air.
|
||||
-- @return #SQUADRON self
|
||||
function SQUADRON:SetTakeoffType(TakeoffType)
|
||||
TakeoffType=TakeoffType or "Cold"
|
||||
if TakeoffType:lower()=="hot" then
|
||||
self.takeoffType=COORDINATE.WaypointType.TakeOffParkingHot
|
||||
elseif TakeoffType:lower()=="cold" then
|
||||
self.takeoffType=COORDINATE.WaypointType.TakeOffParking
|
||||
elseif TakeoffType:lower()=="air" then
|
||||
self.takeoffType=COORDINATE.WaypointType.TurningPoint
|
||||
else
|
||||
self.takeoffType=COORDINATE.WaypointType.TakeOffParking
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set takeoff type cold (default). All assets of this squadron will be spawned with engines off (cold).
|
||||
-- @param #SQUADRON self
|
||||
-- @return #SQUADRON self
|
||||
function SQUADRON:SetTakeoffCold()
|
||||
self:SetTakeoffType("Cold")
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set takeoff type hot. All assets of this squadron will be spawned with engines on (hot).
|
||||
-- @param #SQUADRON self
|
||||
-- @return #SQUADRON self
|
||||
function SQUADRON:SetTakeoffHot()
|
||||
self:SetTakeoffType("Hot")
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set takeoff type air. All assets of this squadron will be spawned in air above the airbase.
|
||||
-- @param #SQUADRON self
|
||||
-- @return #SQUADRON self
|
||||
function SQUADRON:SetTakeoffAir()
|
||||
self:SetTakeoffType("Air")
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set despawn after landing. Aircraft will be despawned after the landing event.
|
||||
-- Can help to avoid DCS AI taxiing issues.
|
||||
-- @param #SQUADRON self
|
||||
-- @param #boolean Switch If `true` (default), activate despawn after landing.
|
||||
-- @return #SQUADRON self
|
||||
function SQUADRON:SetDespawnAfterLanding(Switch)
|
||||
if Switch then
|
||||
self.despawnAfterLanding=Switch
|
||||
else
|
||||
self.despawnAfterLanding=true
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set despawn after holding. Aircraft will be despawned when they arrive at their holding position at the airbase.
|
||||
-- Can help to avoid DCS AI taxiing issues.
|
||||
-- @param #SQUADRON self
|
||||
-- @param #boolean Switch If `true` (default), activate despawn after holding.
|
||||
-- @return #SQUADRON self
|
||||
function SQUADRON:SetDespawnAfterHolding(Switch)
|
||||
if Switch then
|
||||
self.despawnAfterHolding=Switch
|
||||
else
|
||||
self.despawnAfterHolding=true
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Set low fuel threshold.
|
||||
-- @param #SQUADRON self
|
||||
-- @param #number LowFuel Low fuel threshold in percent. Default 25.
|
||||
-- @return #SQUADRON self
|
||||
function SQUADRON:SetFuelLowThreshold(LowFuel)
|
||||
self.fuellow=LowFuel or 25
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set if low fuel threshold is reached, flight tries to refuel at the neares tanker.
|
||||
-- @param #SQUADRON self
|
||||
-- @param #boolean switch If true or nil, flight goes for refuelling. If false, turn this off.
|
||||
-- @return #SQUADRON self
|
||||
function SQUADRON:SetFuelLowRefuel(switch)
|
||||
if switch==false then
|
||||
self.fuellowRefuel=false
|
||||
else
|
||||
self.fuellowRefuel=true
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set airwing.
|
||||
-- @param #SQUADRON self
|
||||
-- @param Ops.AirWing#AIRWING Airwing The airwing.
|
||||
-- @return #SQUADRON self
|
||||
function SQUADRON:SetAirwing(Airwing)
|
||||
self.legion=Airwing
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get airwing.
|
||||
-- @param #SQUADRON self
|
||||
-- @return Ops.AirWing#AIRWING The airwing.
|
||||
function SQUADRON:GetAirwing(Airwing)
|
||||
return self.legion
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Start & Status
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- On after Start event. Starts the FLIGHTGROUP FSM and event handlers.
|
||||
-- @param #SQUADRON self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
function SQUADRON:onafterStart(From, Event, To)
|
||||
|
||||
-- Short info.
|
||||
local text=string.format("Starting SQUADRON", self.name)
|
||||
self:T(self.lid..text)
|
||||
|
||||
-- Start the status monitoring.
|
||||
self:__Status(-1)
|
||||
end
|
||||
|
||||
--- On after "Status" event.
|
||||
-- @param #SQUADRON self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
function SQUADRON:onafterStatus(From, Event, To)
|
||||
|
||||
if self.verbose>=1 then
|
||||
|
||||
-- FSM state.
|
||||
local fsmstate=self:GetState()
|
||||
|
||||
local callsign=self.callsignName and UTILS.GetCallsignName(self.callsignName) or "N/A"
|
||||
local modex=self.modex and self.modex or -1
|
||||
local skill=self.skill and tostring(self.skill) or "N/A"
|
||||
|
||||
local NassetsTot=#self.assets
|
||||
local NassetsInS=self:CountAssets(true)
|
||||
local NassetsQP=0 ; local NassetsP=0 ; local NassetsQ=0
|
||||
if self.legion then
|
||||
NassetsQP, NassetsP, NassetsQ=self.legion:CountAssetsOnMission(nil, self)
|
||||
end
|
||||
|
||||
-- Short info.
|
||||
local text=string.format("%s [Type=%s, Call=%s, Modex=%d, Skill=%s]: Assets Total=%d, Stock=%d, Mission=%d [Active=%d, Queue=%d]",
|
||||
fsmstate, self.aircrafttype, callsign, modex, skill, NassetsTot, NassetsInS, NassetsQP, NassetsP, NassetsQ)
|
||||
self:I(self.lid..text)
|
||||
|
||||
-- Check if group has detected any units.
|
||||
self:_CheckAssetStatus()
|
||||
|
||||
end
|
||||
|
||||
if not self:IsStopped() then
|
||||
self:__Status(-60)
|
||||
end
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Misc Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user