mirror of
https://github.com/FlightControl-Master/MOOSE.git
synced 2026-07-18 20:12:45 +00:00
Compare commits
1479 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 | |||
| 902c001aa4 | |||
| 0cf2a4353d | |||
| 13c8cc90f2 | |||
| db231fafeb | |||
| c3f7deb602 | |||
| 300e3f2f58 | |||
| 513406f0e5 | |||
| 27e21e77f9 | |||
| dadfd803f7 | |||
| 02b0ba2278 | |||
| 3e1005aef1 | |||
| e2eb13739c | |||
| c52e30ceae | |||
| 07754cdf5f | |||
| c74c475a29 | |||
| ad36ab520b | |||
| bc32ff5db9 | |||
| 6009432933 | |||
| 99eaa37c76 | |||
| ed6d5f727a | |||
| c6995d6e58 | |||
| cb0f453a8d | |||
| d9866846ad | |||
| de5d9195f3 | |||
| fd1fe8562c | |||
| b1298223aa | |||
| c48128d92e | |||
| 0f4d466953 | |||
| 1b752fcaf6 | |||
| d06db9909c | |||
| 921024035c | |||
| 3b1c8c3deb | |||
| 69862ab67d | |||
| b05c321306 | |||
| 9b74a58dde | |||
| d112ffaf6a | |||
| 554f063def | |||
| f6dce02203 | |||
| 653dfe82fa | |||
| 5dbf743052 | |||
| f7e7e2e41c | |||
| b28b4db7fd | |||
| c34f30e4a3 | |||
| aa7f11185d | |||
| 1790d19809 | |||
| fa6fbca67b | |||
| cd79c57a27 | |||
| 4c3a97e2b2 | |||
| f126dceeae | |||
| 414521acc5 | |||
| 9147422340 | |||
| 920646ea74 | |||
| c57d839927 | |||
| e7fd5db2c2 | |||
| b76486ef5f | |||
| 5c06584676 | |||
| 2ae2ee64be | |||
| 17ba35e237 | |||
| 53e98e70aa | |||
| 359e18eb58 | |||
| 3377459df5 | |||
| 3e30d15405 | |||
| 2b50cf2243 | |||
| b72e2b6bf9 | |||
| c7a2b34f59 | |||
| b48958995a | |||
| 58c496eacd | |||
| 7bd867c9dd | |||
| be0558849c | |||
| a1c7ec2ac9 | |||
| 8bf073c0c6 | |||
| 994689f05a | |||
| 3e10f9f451 | |||
| 972fa9f674 | |||
| 8e64d8e334 | |||
| eccd81d40a | |||
| 649136f98f | |||
| d7dae1366d | |||
| 19e2ebcd03 | |||
| 021c907d16 | |||
| 8ac3e28fdf | |||
| c5af279730 | |||
| e9377e93f1 | |||
| 6747c69511 | |||
| 9b6cae6c49 | |||
| 7148bd1d42 | |||
| 73940fffc6 | |||
| 09015449cd | |||
| 4dfdb99731 | |||
| bbbca04066 | |||
| e755bba608 | |||
| 9161cec238 | |||
| dc14b60fcc | |||
| da625d51f6 | |||
| ce69055758 | |||
| 589ebd5bca | |||
| 35b50e1a9d | |||
| f038564b1b | |||
| 1d0eb9806d | |||
| c50a9bd318 | |||
| cd9006d29b | |||
| d3b88f9065 | |||
| 6a6cb1961d | |||
| 884c51a69a | |||
| 6f126e6cd4 | |||
| b0c2e5409a | |||
| 9516ec3cd2 | |||
| aecb92ccd3 | |||
| bdf13f29f7 | |||
| 81120abfcb | |||
| 08a8170990 | |||
| 316ea910c2 | |||
| 0b5edfc21f | |||
| 76c08095ab | |||
| 538864519e | |||
| cd6631049b | |||
| 079f8030b7 | |||
| ad0b32c0ee | |||
| 8235b51a14 | |||
| 1bd5e7472f | |||
| 7433a7144c | |||
| 27632ecdd9 | |||
| 0b1e25b073 | |||
| 19a93f0026 | |||
| 3256a6d596 | |||
| 28ddfa5243 | |||
| 05c560be3b | |||
| 8ddd810fe2 | |||
| f20760bae5 | |||
| cea7320a9b | |||
| fe6826016c | |||
| f0167b3e88 | |||
| 0edbf7f517 | |||
| a8a8dcff3f | |||
| 1b0ad13529 | |||
| 418d6c882c | |||
| 1e6899c40b | |||
| 259b201e65 | |||
| 8fb113a04e | |||
| c6ebbc6122 | |||
| 755903a08e | |||
| 34e4825cd0 | |||
| a68eeae9bd | |||
| e4e31b554e | |||
| eba6e3f5f1 | |||
| d73ebaca76 | |||
| 94167152e5 | |||
| 830f76e909 | |||
| 8ae04944fe | |||
| c55b8d29f7 | |||
| 30623f7d38 | |||
| 16964520df | |||
| 3050fdb3c3 | |||
| 77fc2c5cf1 | |||
| b21c3ed4e9 | |||
| 93f4b345c5 | |||
| 88e59a2739 | |||
| 86bb256bf1 | |||
| 9a05f5bc93 | |||
| bce28cf389 | |||
| 629c5e7739 | |||
| ed402e2f5f | |||
| 4a56c7523d | |||
| 218d9f48b0 | |||
| f67cf99477 | |||
| 2201b2adda | |||
| 6b36421bce | |||
| 8c573c65d4 | |||
| 730dabd9cf | |||
| 69175e1b19 | |||
| a5ad58e516 | |||
| 82b4631341 | |||
| a1c9c1538d | |||
| 1a53f58540 | |||
| 1b414b840e | |||
| 642cc0e98f | |||
| 63431bb54b | |||
| d64de26ded | |||
| 8b45067226 | |||
| 061032e3d7 | |||
| 82432686aa | |||
| b66d2039be | |||
| d245a73d7f | |||
| f0037151e6 | |||
| 52d494f573 | |||
| 36a2d71731 | |||
| 615a220acb | |||
| c718584755 | |||
| 4db0ea616d | |||
| 5439e60078 | |||
| a6880d4569 | |||
| 65e852f341 | |||
| 4ffdf9e536 | |||
| 8f698e3e62 | |||
| 61481e6e9a | |||
| 49397df90b | |||
| 7b254a08fb | |||
| 5bebbcf5eb | |||
| 83fd48e2f4 | |||
| f5b25370b0 | |||
| f68d3209ac | |||
| 2ab7f784c9 | |||
| 1e8d99a591 | |||
| d517cba765 | |||
| 552eb5b9d8 | |||
| 684c4ea113 | |||
| 4dfaca610f | |||
| 8a53998251 | |||
| 6690f70b05 | |||
| 96d1d3cb66 | |||
| 09785ef451 | |||
| 25e118f3dc | |||
| 78b3e3c60b | |||
| efe41a5e21 | |||
| 835041e5f6 | |||
| 93a8086ff6 | |||
| 1ac40684de | |||
| 433d1bbf57 | |||
| e33de03522 | |||
| 86fedbfaae | |||
| c0f4eef896 | |||
| a2f3bf3a8c | |||
| 1b717e4683 | |||
| 52e2ac7174 | |||
| 3d6b053eb4 | |||
| 268eb1d60d | |||
| a69865b8c9 | |||
| 4a1df3d5cc | |||
| 6dfd757ea1 | |||
| fc1adf3b94 | |||
| be2d1d7895 | |||
| 9e7c360912 | |||
| 51acd33d19 | |||
| af3dbe2f86 | |||
| 97668e5413 | |||
| 464fde0ed2 | |||
| 65bd7909e1 | |||
| 5d3ea57d4d | |||
| b7bc3cfbcf | |||
| 04ef32420b | |||
| a3d8a48b92 | |||
| ef86d1d7cb | |||
| 00b3e9b371 | |||
| 0d02f56855 | |||
| 48aa841add | |||
| 4ba52212a9 | |||
| a14bca1059 | |||
| a7e07af24f | |||
| e14d655447 | |||
| 74bdeaf4f7 | |||
| 9591c62175 | |||
| 39c46dcab0 | |||
| 299e08f53d | |||
| 12555a6ff1 | |||
| 1cc012c7d1 | |||
| 28c6810878 | |||
| 1ed36fdff4 | |||
| 26801cf206 | |||
| c80cebb824 | |||
| 3c6e8d6d01 | |||
| 76a53ab154 | |||
| fa3e387dd1 | |||
| dca626bbcb | |||
| 3289ad2817 | |||
| 5a022a2246 | |||
| b012c5b2aa | |||
| a861f8d9d4 | |||
| 36669c80da | |||
| 2ff128f184 | |||
| 3b44aba341 | |||
| 978be4e383 | |||
| d4cdfcc48c | |||
| 5123ab0720 | |||
| 5021a1e1f3 | |||
| 576281a612 | |||
| b84c0aba59 | |||
| ee503a378e | |||
| f235037cb9 | |||
| 0cd1cd97a6 | |||
| 74513f1b5c | |||
| 231c5bfea7 | |||
| cfd0e4ba85 | |||
| bf33e4ed4f | |||
| 6290db035d | |||
| cd1935be1d | |||
| 64262d6ecc | |||
| 270c69344f | |||
| 10a0793af7 | |||
| 1b37af321f | |||
| 7cde279be1 | |||
| 0396741f3d | |||
| 117cf8888a | |||
| 83db7b32e8 | |||
| d496d3d16e | |||
| 6b801247ab | |||
| 4c6cd31bfc | |||
| a8ef096f24 | |||
| c8d2a7e833 | |||
| 783391838b | |||
| 7c37d8b204 | |||
| 65ed8825b0 | |||
| af9324dd5f | |||
| 064f727676 | |||
| 8025862fb9 | |||
| 2544ec5587 | |||
| 6c823b0137 | |||
| 88d44fa654 | |||
| 8b6f9f9de7 | |||
| cb14961dcd | |||
| dab486ec99 | |||
| 75849fbfb5 | |||
| f984ff044b | |||
| aaf5f295da | |||
| 8f612f2017 | |||
| a2808163a7 | |||
| e4aa23ce3d | |||
| 1b3cac9424 | |||
| e49020b905 | |||
| d68dcaf69a | |||
| 856c3578f6 | |||
| 18c6ea39ce | |||
| 1ce46c016c | |||
| 296944c23c | |||
| 4f772fecdc | |||
| cdd22444b9 | |||
| 15848bc8c4 | |||
| 9eba6d607c | |||
| e9d3c7c2e7 | |||
| ad16c6d44f | |||
| 2046958bd6 | |||
| 0046533d53 | |||
| ce7e152e13 | |||
| edcdc057e6 | |||
| cfa851beea | |||
| f5d8efe86e | |||
| 7c8e5033ec | |||
| 22ec94b374 | |||
| 5cd3a172f1 | |||
| a89a3b38ce | |||
| 3202702612 | |||
| 725e32f439 | |||
| 7da1905ad3 | |||
| 421005a3b6 | |||
| d5bf28f799 | |||
| 990f748e42 | |||
| a466365949 | |||
| b12f71c430 | |||
| 236a69e5dc | |||
| 9458243926 | |||
| 41274e7801 | |||
| 0259e41ce8 | |||
| e78f9cbd46 | |||
| 9d6851adec | |||
| 4668f072af | |||
| a1a414925f | |||
| 19eb9dca30 | |||
| 8464a2ba43 | |||
| a8549e2b7d | |||
| 2531a25c9e | |||
| 2f7b7eeddd | |||
| ab1ec54c03 | |||
| a1ca605206 | |||
| a7b444dfea | |||
| 64b35b9cb3 | |||
| 4a8d52b328 | |||
| 3cbf6fff90 | |||
| 2693e76483 | |||
| b232a5da37 | |||
| e50fc00583 | |||
| 4b8802af37 | |||
| ebd69fd10d | |||
| c1a8c08880 | |||
| 647fb6acbf | |||
| ede692ea2a | |||
| ca44e2762b | |||
| eda70742eb | |||
| b81e3e9e6d | |||
| e766eaeccc | |||
| bab61271f2 | |||
| b1465d89a6 | |||
| 05b1ddf013 | |||
| 903e8711f7 | |||
| 31b93fb42c | |||
| e16bbfc2b9 | |||
| 220e004d73 | |||
| 3136861b24 | |||
| dc2693bead | |||
| d7da8c4253 | |||
| c1ff5831ad | |||
| ac5646075b | |||
| 5a1faba796 | |||
| 4ab16cb276 | |||
| eb1e885c0a | |||
| 00ff9a11b8 | |||
| 8eabc024bf | |||
| a848af0753 | |||
| f4e7a3a314 | |||
| d83283c709 | |||
| a35112a6f3 | |||
| 66c012a30d | |||
| 7b92b7853d | |||
| 6c9f10e6a8 | |||
| 6db2c4465d | |||
| c0cc960df5 | |||
| 968240ad44 | |||
| 50f3016711 | |||
| 56e2b06e9d | |||
| c02f69720a | |||
| 96df7d1e42 | |||
| 1d4caf7588 | |||
| b0e3201d3c | |||
| bab097958f | |||
| f4a3f6d433 | |||
| e73ab89a18 | |||
| 187643f6ae | |||
| 3c9bdb2915 | |||
| 5241642447 | |||
| 3b2cbea1c4 | |||
| 80e3da49cb | |||
| 1df7a12587 | |||
| 663a59bd71 | |||
| ced859424b | |||
| 4a6377c204 | |||
| fd22558207 | |||
| a6faadea48 | |||
| 42a26100b7 | |||
| e8f4770abe | |||
| 7751d4ee88 | |||
| cdd829e7be | |||
| 16bf3a89b5 | |||
| 11cdae098b | |||
| a63ee3fa8b | |||
| 5cc023d1fe | |||
| c4084156ac | |||
| 302018feff | |||
| c50f6b72e7 | |||
| c0401447dd | |||
| 43680c2fc3 | |||
| 2c92bb9d61 | |||
| dcf1a56756 | |||
| 5990ab1cc9 | |||
| 084499fa0e | |||
| 74e84a73dd | |||
| 96b0393f91 | |||
| 0656c33e05 | |||
| ec1ab49b54 | |||
| 1daa2857b2 | |||
| 8c55541d0e | |||
| c5a4776b3a | |||
| 8bb9f0d7c0 | |||
| 7d83c251a8 | |||
| ee3ead9aac | |||
| 54896e0e31 | |||
| 016198e7f2 | |||
| 0933eef4c3 | |||
| fe55555c67 | |||
| bcf8973eed | |||
| 50d2e42cc3 | |||
| 261d1cbeba | |||
| 242462b9ba | |||
| fd4e478dbf | |||
| 6b91e68668 | |||
| 7516d2af56 | |||
| c247a98402 | |||
| 83bcd55a7a | |||
| 5a43936e35 | |||
| 35b776179e | |||
| f4cb6df8d4 | |||
| a259b88f1d | |||
| 5b4f3d1c98 | |||
| c62c21c386 | |||
| 17c56a11bd | |||
| 796561b8b6 | |||
| 209b7a64ed | |||
| 8f56299380 | |||
| 73b1394ca7 | |||
| f3a2cf7284 | |||
| df79c765f8 | |||
| d09737c218 | |||
| 6b0c42ae50 | |||
| 7363da1b54 | |||
| 684f19c060 | |||
| d08e4db0eb | |||
| f1e7d8bd06 | |||
| 1328ac061e | |||
| 391f0fc9af | |||
| 38cf5be738 | |||
| 1bb70b8697 | |||
| 6e35b80daf | |||
| a171c22ce4 | |||
| f56457bc28 | |||
| 9708de70c7 | |||
| d68589bb70 | |||
| 16eb38782a | |||
| 2175e9c0d6 | |||
| 9abd71f681 | |||
| d1dc829136 | |||
| eda672bdc6 | |||
| 0e5dbbea79 | |||
| f5809ef302 | |||
| 457ce1ece1 | |||
| 9c9841332f | |||
| 9470d3a2ef | |||
| 8356271f8d | |||
| 42a6f3272d | |||
| 362b242c41 | |||
| 7ace7b49ca | |||
| 7cd902e924 | |||
| e3c14678e2 | |||
| bfaca75a7d | |||
| 5aecd8c255 | |||
| cfc0f1a1f7 | |||
| edf657c65c | |||
| 801d00fa26 | |||
| eeffa31282 | |||
| 206528a530 | |||
| 41690fdf7b | |||
| 5b83f8e45b | |||
| 94863ed8ae | |||
| e63b99c890 | |||
| e8542743b4 | |||
| 309e1208c2 | |||
| 3e0db05664 | |||
| 9573d92cc0 | |||
| 1abc0dcb6a | |||
| 770fb3228f | |||
| d6aa251c5e | |||
| 9305b870d2 | |||
| 824a98d7a8 | |||
| 7a55ba105c | |||
| 122425338e | |||
| 1d7f874a78 | |||
| afb63a7331 | |||
| 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 | |||
| 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 | |||
| 9d3341ab9f | |||
| fd432dc0d1 | |||
| ee12dc0a7d | |||
| 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 |
@@ -28,6 +28,13 @@ local.properties
|
||||
.buildpath
|
||||
|
||||
|
||||
#####################
|
||||
## Visual Studio Code
|
||||
#####################
|
||||
*.code-workspace
|
||||
.vscode/
|
||||
|
||||
|
||||
#################
|
||||
## Visual Studio
|
||||
#################
|
||||
|
||||
@@ -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
@@ -1,33 +0,0 @@
|
||||
# See https://github.com/Koihik/LuaFormatter
|
||||
# Use '-- LuaFormatter off' and '-- LuaFormatter on' around code blocks to inhibit formatting
|
||||
|
||||
column_limit: 500
|
||||
indent_width: 2
|
||||
use_tab: false
|
||||
continuation_indent_width: 2
|
||||
keep_simple_control_block_one_line: false
|
||||
keep_simple_function_one_line: false
|
||||
align_args: true
|
||||
break_after_functioncall_lp: false
|
||||
break_before_functioncall_rp: false
|
||||
align_parameter: true
|
||||
chop_down_parameter: true
|
||||
break_after_functiondef_lp: false
|
||||
break_before_functiondef_rp: false
|
||||
align_table_field: true
|
||||
break_after_table_lb: true
|
||||
break_before_table_rb: true
|
||||
chop_down_table: true
|
||||
chop_down_kv_table: true
|
||||
column_table_limit: 500
|
||||
table_sep: ','
|
||||
extra_sep_at_table_end: true
|
||||
break_after_operator: true
|
||||
single_quote_to_double_quote: false
|
||||
double_quote_to_single_quote: false
|
||||
spaces_before_call: 1
|
||||
spaces_inside_functiondef_parens: true
|
||||
spaces_inside_functioncall_parens: true
|
||||
spaces_inside_table_braces: true
|
||||
spaces_around_equals_in_field: true
|
||||
line_breaks_after_function_body: 1
|
||||
@@ -40,8 +40,8 @@
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- Until a fuel or damage threshold has been reached by the AI, or when the AI is commanded to RTB.
|
||||
-- When the fuel threshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
|
||||
-- Until a fuel or damage treshold has been reached by the AI, or when the AI is commanded to RTB.
|
||||
-- When the fuel treshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
@@ -71,7 +71,7 @@
|
||||
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detected}**: The AI has detected new targets.
|
||||
-- * **@{#AI_A2A_CAP.Destroy}**: The AI has destroyed a bogey @{Wrapper.Unit}.
|
||||
-- * **@{#AI_A2A_CAP.Destroyed}**: The AI has destroyed all bogeys @{Wrapper.Unit}s assigned in the CAS task.
|
||||
-- * **Status** ( Group ): The AI is checking status (fuel and damage). When the thresholds have been reached, the AI will RTB.
|
||||
-- * **Status** ( Group ): The AI is checking status (fuel and damage). When the tresholds have been reached, the AI will RTB.
|
||||
--
|
||||
-- ## 3. Set the Range of Engagement
|
||||
--
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -42,8 +42,8 @@
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- Until a fuel or damage threshold has been reached by the AI, or when the AI is commanded to RTB.
|
||||
-- When the fuel threshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
|
||||
-- Until a fuel or damage treshold has been reached by the AI, or when the AI is commanded to RTB.
|
||||
-- When the fuel treshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
@@ -73,7 +73,7 @@
|
||||
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detected}**: The AI has detected new targets.
|
||||
-- * **@{#AI_A2A_GCI.Destroy}**: The AI has destroyed a bogey @{Wrapper.Unit}.
|
||||
-- * **@{#AI_A2A_GCI.Destroyed}**: The AI has destroyed all bogeys @{Wrapper.Unit}s assigned in the CAS task.
|
||||
-- * **Status** ( Group ): The AI is checking status (fuel and damage). When the thresholds have been reached, the AI will RTB.
|
||||
-- * **Status** ( Group ): The AI is checking status (fuel and damage). When the tresholds have been reached, the AI will RTB.
|
||||
--
|
||||
-- ## 3. Set the Range of Engagement
|
||||
--
|
||||
|
||||
@@ -39,8 +39,8 @@
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- Until a fuel or damage threshold has been reached by the AI, or when the AI is commanded to RTB.
|
||||
-- When the fuel threshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
|
||||
-- Until a fuel or damage treshold has been reached by the AI, or when the AI is commanded to RTB.
|
||||
-- When the fuel treshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
@@ -68,7 +68,7 @@
|
||||
-- * **RTB** ( Group ): Route the AI to the home base.
|
||||
-- * **Detect** ( Group ): The AI is detecting targets.
|
||||
-- * **Detected** ( Group ): The AI has detected new targets.
|
||||
-- * **Status** ( Group ): The AI is checking status (fuel and damage). When the thresholds have been reached, the AI will RTB.
|
||||
-- * **Status** ( Group ): The AI is checking status (fuel and damage). When the tresholds have been reached, the AI will RTB.
|
||||
--
|
||||
-- ## 3. Set or Get the AI controllable
|
||||
--
|
||||
@@ -100,8 +100,8 @@
|
||||
-- ## 6. Manage the "out of fuel" in the AI_A2A_PATROL
|
||||
--
|
||||
-- When the AI is out of fuel, it is required that a new AI is started, before the old AI can return to the home base.
|
||||
-- Therefore, with a parameter and a calculation of the distance to the home base, the fuel threshold is calculated.
|
||||
-- When the fuel threshold is reached, the AI will continue for a given time its patrol task in orbit,
|
||||
-- Therefore, with a parameter and a calculation of the distance to the home base, the fuel treshold is calculated.
|
||||
-- When the fuel treshold is reached, the AI will continue for a given time its patrol task in orbit,
|
||||
-- while a new AI is targetted to the AI_A2A_PATROL.
|
||||
-- Once the time is finished, the old AI will return to the base.
|
||||
-- Use the method @{#AI_A2A_PATROL.ManageFuel}() to have this proces in place.
|
||||
@@ -109,7 +109,7 @@
|
||||
-- ## 7. Manage "damage" behaviour of the AI in the AI_A2A_PATROL
|
||||
--
|
||||
-- When the AI is damaged, it is required that a new Patrol is started. However, damage cannon be foreseen early on.
|
||||
-- Therefore, when the damage threshold is reached, the AI will return immediately to the home base (RTB).
|
||||
-- Therefore, when the damage treshold is reached, the AI will return immediately to the home base (RTB).
|
||||
-- Use the method @{#AI_A2A_PATROL.ManageDamage}() to have this proces in place.
|
||||
--
|
||||
-- ===
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -42,8 +42,8 @@
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- Until a fuel or damage threshold has been reached by the AI, or when the AI is commanded to RTB.
|
||||
-- When the fuel threshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
|
||||
-- Until a fuel or damage treshold has been reached by the AI, or when the AI is commanded to RTB.
|
||||
-- When the fuel treshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
|
||||
@@ -370,11 +370,11 @@ end
|
||||
|
||||
|
||||
--- When the AI is out of fuel, it is required that a new AI is started, before the old AI can return to the home base.
|
||||
-- Therefore, with a parameter and a calculation of the distance to the home base, the fuel threshold is calculated.
|
||||
-- When the fuel threshold is reached, the AI will continue for a given time its patrol task in orbit, while a new AIControllable is targetted to the AI_AIR.
|
||||
-- Therefore, with a parameter and a calculation of the distance to the home base, the fuel treshold is calculated.
|
||||
-- When the fuel treshold is reached, the AI will continue for a given time its patrol task in orbit, while a new AIControllable is targetted to the AI_AIR.
|
||||
-- Once the time is finished, the old AI will return to the base.
|
||||
-- @param #AI_AIR self
|
||||
-- @param #number FuelThresholdPercentage The threshold in percentage (between 0 and 1) when the AIControllable is considered to get out of fuel.
|
||||
-- @param #number FuelThresholdPercentage The treshold in percentage (between 0 and 1) when the AIControllable is considered to get out of fuel.
|
||||
-- @param #number OutOfFuelOrbitTime The amount of seconds the out of fuel AIControllable will orbit before returning to the base.
|
||||
-- @return #AI_AIR self
|
||||
function AI_AIR:SetFuelThreshold( FuelThresholdPercentage, OutOfFuelOrbitTime )
|
||||
@@ -387,14 +387,14 @@ function AI_AIR:SetFuelThreshold( FuelThresholdPercentage, OutOfFuelOrbitTime )
|
||||
return self
|
||||
end
|
||||
|
||||
--- When the AI is damaged beyond a certain threshold, it is required that the AI returns to the home base.
|
||||
--- When the AI is damaged beyond a certain treshold, it is required that the AI returns to the home base.
|
||||
-- However, damage cannot be foreseen early on.
|
||||
-- Therefore, when the damage threshold is reached,
|
||||
-- Therefore, when the damage treshold is reached,
|
||||
-- the AI will return immediately to the home base (RTB).
|
||||
-- Note that for groups, the average damage of the complete group will be calculated.
|
||||
-- So, in a group of 4 airplanes, 2 lost and 2 with damage 0.2, the damage threshold will be 0.25.
|
||||
-- So, in a group of 4 airplanes, 2 lost and 2 with damage 0.2, the damage treshold will be 0.25.
|
||||
-- @param #AI_AIR self
|
||||
-- @param #number PatrolDamageThreshold The threshold in percentage (between 0 and 1) when the AI is considered to be damaged.
|
||||
-- @param #number PatrolDamageThreshold The treshold in percentage (between 0 and 1) when the AI is considered to be damaged.
|
||||
-- @return #AI_AIR self
|
||||
function AI_AIR:SetDamageThreshold( PatrolDamageThreshold )
|
||||
|
||||
@@ -476,7 +476,7 @@ function AI_AIR:onafterStatus()
|
||||
|
||||
local Fuel = self.Controllable:GetFuelMin()
|
||||
|
||||
-- If the fuel in the controllable is below the threshold percentage,
|
||||
-- If the fuel in the controllable is below the treshold percentage,
|
||||
-- then send for refuel in case of a tanker, otherwise RTB.
|
||||
if Fuel < self.FuelThresholdPercentage then
|
||||
|
||||
@@ -589,7 +589,9 @@ function AI_AIR:onafterRTB( AIGroup, From, Event, To )
|
||||
|
||||
self:ClearTargetDistance()
|
||||
--AIGroup:ClearTasks()
|
||||
|
||||
|
||||
AIGroup:OptionProhibitAfterburner(true)
|
||||
|
||||
local EngageRoute = {}
|
||||
|
||||
--- Calculate the target route point.
|
||||
|
||||
@@ -10,11 +10,11 @@
|
||||
-- * Setup (CAS) Controlled Air Support squadrons, to attack closeby enemy ground units near friendly installations.
|
||||
-- * Setup (BAI) Battleground Air Interdiction squadrons to attack remote enemy ground units and targets.
|
||||
-- * Define and use a detection network controlled by recce.
|
||||
-- * Define AIR defense squadrons at airbases, FARPs and carriers.
|
||||
-- * Define AIR defense squadrons at airbases, farps and carriers.
|
||||
-- * Enable airbases for AIR defenses.
|
||||
-- * Add different planes and helicopter templates to squadrons.
|
||||
-- * Assign squadrons to execute a specific engagement type depending on threat level of the detected ground enemy unit composition.
|
||||
-- * Add multiple squadrons to different airbases, FARPs or carriers.
|
||||
-- * Add multiple squadrons to different airbases, farps or carriers.
|
||||
-- * Define different ranges to engage upon.
|
||||
-- * Establish an automatic in air refuel process for planes using refuel tankers.
|
||||
-- * Setup default settings for all squadrons and AIR defenses.
|
||||
@@ -40,7 +40,7 @@
|
||||
--
|
||||
-- AI_AIR_DISPATCHER is the main AIR defense class that models the AIR defense system.
|
||||
--
|
||||
-- Before you start using the AI_AIR_DISPATCHER, ask yourself the following questions.
|
||||
-- Before you start using the AI_AIR_DISPATCHER, ask youself the following questions.
|
||||
--
|
||||
--
|
||||
-- ## 1. Which coalition am I modeling an AIR defense system for? blue or red?
|
||||
@@ -128,7 +128,7 @@
|
||||
-- Depending on the defense type, different payloads will be needed. See further points on squadron definition.
|
||||
--
|
||||
--
|
||||
-- ## 7. Where will the Squadrons be located? On Airbases? On Carrier Ships? On FARPs?
|
||||
-- ## 7. Where will the Squadrons be located? On Airbases? On Carrier Ships? On Farps?
|
||||
--
|
||||
-- Squadrons are placed at the **home base** on an **airfield**, **carrier** or **farp**.
|
||||
-- Carefully plan where each Squadron will be located as part of the defense system required for mission effective defenses.
|
||||
@@ -354,7 +354,7 @@ do -- AI_AIR_DISPATCHER
|
||||
-- **DetectionSetGroup** is then calling `FilterStart()`, which is starting the dynamic filtering or inclusion of these groups.
|
||||
-- Note that any destroy or new spawn of a group having a name, starting with the above prefix, will be removed or added to the set.
|
||||
--
|
||||
-- Then a new detection object is created from the class `DETECTION_AREAS`. A grouping radius of 1000 meters (1km) is chosen.
|
||||
-- Then a new detection object is created from the class `DETECTION_AREAS`. A grouping radius of 1000 meters (1km) is choosen.
|
||||
--
|
||||
-- The `Detection` object is then passed to the @{#AI_AIR_DISPATCHER.New}() method to indicate the reconnaissance network
|
||||
-- configuration and setup the AIR defense detection mechanism.
|
||||
@@ -647,7 +647,7 @@ do -- AI_AIR_DISPATCHER
|
||||
-- * @{#AI_AIR_DISPATCHER.SetSquadronLandingAtRunway}() will despawn the returning aircraft directly after landing at the runway.
|
||||
-- * @{#AI_AIR_DISPATCHER.SetSquadronLandingAtEngineShutdown}() will despawn the returning aircraft when the aircraft has returned to its parking spot and has turned off its engines.
|
||||
--
|
||||
-- You can use these methods to minimize the airbase coordination overhead and to increase the airbase efficiency.
|
||||
-- You can use these methods to minimize the airbase coodination overhead and to increase the airbase efficiency.
|
||||
-- When there are lots of aircraft returning for landing, at the same airbase, the takeoff process will be halted, which can cause a complete failure of the
|
||||
-- A2A defense system, as no new CAP or GCI planes can takeoff.
|
||||
-- Note that the method @{#AI_AIR_DISPATCHER.SetSquadronLandingNearAirbase}() will only work for returning aircraft, not for damaged or out of fuel aircraft.
|
||||
@@ -724,13 +724,13 @@ do -- AI_AIR_DISPATCHER
|
||||
--
|
||||
-- Use the method @{#AI_AIR_DISPATCHER.SetSquadronEngageLimit}() to limit the amount of aircraft that will engage with the enemy, per squadron.
|
||||
--
|
||||
-- ## 4. Set the **fuel threshold**.
|
||||
-- ## 4. Set the **fuel treshold**.
|
||||
--
|
||||
-- When aircraft get **out of fuel** to a certain %, which is by default **15% (0.15)**, there are two possible actions that can be taken:
|
||||
-- When aircraft get **out of fuel** to a certain %-tage, which is by default **15% (0.15)**, there are two possible actions that can be taken:
|
||||
-- - The aircraft will go RTB, and will be replaced with a new aircraft if possible.
|
||||
-- - The aircraft will refuel at a tanker, if a tanker has been specified for the squadron.
|
||||
--
|
||||
-- Use the method @{#AI_AIR_DISPATCHER.SetSquadronFuelThreshold}() to set the **squadron fuel threshold** of the aircraft for all squadrons.
|
||||
-- Use the method @{#AI_AIR_DISPATCHER.SetSquadronFuelThreshold}() to set the **squadron fuel treshold** of the aircraft for all squadrons.
|
||||
--
|
||||
-- ## 6. Other configuration options
|
||||
--
|
||||
@@ -786,17 +786,17 @@ do -- AI_AIR_DISPATCHER
|
||||
--
|
||||
-- Use the method @{#AI_AIR_DISPATCHER.SetDefaultGrouping}() to set the **default grouping** of spawned airplanes for all squadrons.
|
||||
--
|
||||
-- ## 10.5. Default RTB fuel threshold.
|
||||
-- ## 10.5. Default RTB fuel treshold.
|
||||
--
|
||||
-- When an airplane gets **out of fuel** to a certain %, which is **15% (0.15)**, it will go RTB, and will be replaced with a new airplane when applicable.
|
||||
-- When an airplane gets **out of fuel** to a certain %-tage, which is **15% (0.15)**, it will go RTB, and will be replaced with a new airplane when applicable.
|
||||
--
|
||||
-- Use the method @{#AI_AIR_DISPATCHER.SetDefaultFuelThreshold}() to set the **default fuel threshold** of spawned airplanes for all squadrons.
|
||||
-- Use the method @{#AI_AIR_DISPATCHER.SetDefaultFuelThreshold}() to set the **default fuel treshold** of spawned airplanes for all squadrons.
|
||||
--
|
||||
-- ## 10.6. Default RTB damage threshold.
|
||||
-- ## 10.6. Default RTB damage treshold.
|
||||
--
|
||||
-- When an airplane is **damaged** to a certain %, which is **40% (0.40)**, it will go RTB, and will be replaced with a new airplane when applicable.
|
||||
-- When an airplane is **damaged** to a certain %-tage, which is **40% (0.40)**, it will go RTB, and will be replaced with a new airplane when applicable.
|
||||
--
|
||||
-- Use the method @{#AI_AIR_DISPATCHER.SetDefaultDamageThreshold}() to set the **default damage threshold** of spawned airplanes for all squadrons.
|
||||
-- Use the method @{#AI_AIR_DISPATCHER.SetDefaultDamageThreshold}() to set the **default damage treshold** of spawned airplanes for all squadrons.
|
||||
--
|
||||
-- ## 10.7. Default settings for **patrol**.
|
||||
--
|
||||
@@ -829,7 +829,7 @@ do -- AI_AIR_DISPATCHER
|
||||
--
|
||||
-- In the mission editor, setup a group with task Refuelling. A tanker unit of the correct coalition will be automatically selected.
|
||||
-- Then, use the method @{#AI_AIR_DISPATCHER.SetDefaultTanker}() to set the tanker for the dispatcher.
|
||||
-- Use the method @{#AI_AIR_DISPATCHER.SetDefaultFuelThreshold}() to set the % left in the defender airplane tanks when a refuel action is needed.
|
||||
-- Use the method @{#AI_AIR_DISPATCHER.SetDefaultFuelThreshold}() to set the %-tage left in the defender airplane tanks when a refuel action is needed.
|
||||
--
|
||||
-- When the tanker specified is alive and in the air, the tanker will be used for refuelling.
|
||||
--
|
||||
@@ -843,7 +843,7 @@ do -- AI_AIR_DISPATCHER
|
||||
-- A2ADispatcher:SetSquadronCapInterval("Sochi", 2, 30, 600, 1 )
|
||||
-- A2ADispatcher:SetSquadronGci( "Sochi", 900, 1200 )
|
||||
--
|
||||
-- -- Set the default tanker for refuelling to "Tanker", when the default fuel threshold has reached 90% fuel left.
|
||||
-- -- Set the default tanker for refuelling to "Tanker", when the default fuel treshold has reached 90% fuel left.
|
||||
-- A2ADispatcher:SetDefaultFuelThreshold( 0.9 )
|
||||
-- A2ADispatcher:SetDefaultTanker( "Tanker" )
|
||||
--
|
||||
@@ -882,7 +882,10 @@ do -- AI_AIR_DISPATCHER
|
||||
-- As a result, the GCI and CAP will stop!
|
||||
-- However, the squadron will still stay alive. Any airplane that is airborne will continue its operations until all airborne airplanes
|
||||
-- of the squadron will be destroyed. This to keep consistency of air operations not to confuse the players.
|
||||
--
|
||||
--
|
||||
--
|
||||
--
|
||||
--
|
||||
-- @field #AI_AIR_DISPATCHER
|
||||
AI_AIR_DISPATCHER = {
|
||||
ClassName = "AI_AIR_DISPATCHER",
|
||||
@@ -911,10 +914,10 @@ do -- AI_AIR_DISPATCHER
|
||||
--- Enumerator for spawns at airbases
|
||||
-- @type AI_AIR_DISPATCHER.Takeoff
|
||||
-- @extends Wrapper.Group#GROUP.Takeoff
|
||||
|
||||
|
||||
--- @field #AI_AIR_DISPATCHER.Takeoff Takeoff
|
||||
AI_AIR_DISPATCHER.Takeoff = GROUP.Takeoff
|
||||
|
||||
|
||||
--- Defnes Landing location.
|
||||
-- @field #AI_AIR_DISPATCHER.Landing
|
||||
AI_AIR_DISPATCHER.Landing = {
|
||||
@@ -922,7 +925,7 @@ do -- AI_AIR_DISPATCHER
|
||||
AtRunway = 2,
|
||||
AtEngineShutdown = 3,
|
||||
}
|
||||
|
||||
|
||||
--- A defense queue item description
|
||||
-- @type AI_AIR_DISPATCHER.DefenseQueueItem
|
||||
-- @field Squadron
|
||||
@@ -933,7 +936,7 @@ do -- AI_AIR_DISPATCHER
|
||||
-- @field Functional.Detection#DETECTION_BASE AttackerDetection
|
||||
-- @field DefenderGrouping
|
||||
-- @field #string SquadronName The name of the squadron.
|
||||
|
||||
|
||||
--- Queue of planned defenses to be launched.
|
||||
-- This queue exists because defenses must be launched on FARPS, or in the air, or on an airbase, or on carriers.
|
||||
-- And some of these platforms have very limited amount of "launching" platforms.
|
||||
@@ -942,39 +945,40 @@ do -- AI_AIR_DISPATCHER
|
||||
-- This guarantees that launched defenders are also directly existing ...
|
||||
-- @type AI_AIR_DISPATCHER.DefenseQueue
|
||||
-- @list<#AI_AIR_DISPATCHER.DefenseQueueItem> DefenseQueueItem A list of all defenses being queued ...
|
||||
|
||||
|
||||
--- @field #AI_AIR_DISPATCHER.DefenseQueue DefenseQueue
|
||||
AI_AIR_DISPATCHER.DefenseQueue = {}
|
||||
|
||||
|
||||
|
||||
--- Defense approach types
|
||||
-- @type #AI_AIR_DISPATCHER.DefenseApproach
|
||||
AI_AIR_DISPATCHER.DefenseApproach = {
|
||||
Random = 1,
|
||||
Distance = 2,
|
||||
}
|
||||
|
||||
|
||||
--- AI_AIR_DISPATCHER constructor.
|
||||
-- This is defining the AIR DISPATCHER for one coalition.
|
||||
-- This is defining the AIR DISPATCHER for one coaliton.
|
||||
-- The Dispatcher works with a @{Functional.Detection#DETECTION_BASE} object that is taking of the detection of targets using the EWR units.
|
||||
-- The Detection object is polymorphic, depending on the type of detection object chosen, the detection will work differently.
|
||||
-- The Detection object is polymorphic, depending on the type of detection object choosen, the detection will work differently.
|
||||
-- @param #AI_AIR_DISPATCHER self
|
||||
-- @param Functional.Detection#DETECTION_BASE Detection The DETECTION object that will detects targets using the the Early Warning Radar network.
|
||||
-- @return #AI_AIR_DISPATCHER self
|
||||
-- @usage
|
||||
--
|
||||
-- -- Setup the Detection, using DETECTION_AREAS.
|
||||
-- -- First define the SET of GROUPs that are defining the EWR network.
|
||||
-- -- Here with prefixes DF CCCP AWACS, DF CCCP EWR.
|
||||
-- DetectionSetGroup = SET_GROUP:New()
|
||||
-- DetectionSetGroup:FilterPrefixes( { "DF CCCP AWACS", "DF CCCP EWR" } )
|
||||
-- DetectionSetGroup:FilterStart()
|
||||
--
|
||||
-- -- Define the DETECTION_AREAS, using the DetectionSetGroup, with a 30km grouping radius.
|
||||
-- Detection = DETECTION_AREAS:New( DetectionSetGroup, 30000 )
|
||||
--
|
||||
-- -- Now Setup the AIR dispatcher, and initialize it using the Detection object.
|
||||
-- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection )
|
||||
--
|
||||
--
|
||||
-- -- Setup the Detection, using DETECTION_AREAS.
|
||||
-- -- First define the SET of GROUPs that are defining the EWR network.
|
||||
-- -- Here with prefixes DF CCCP AWACS, DF CCCP EWR.
|
||||
-- DetectionSetGroup = SET_GROUP:New()
|
||||
-- DetectionSetGroup:FilterPrefixes( { "DF CCCP AWACS", "DF CCCP EWR" } )
|
||||
-- DetectionSetGroup:FilterStart()
|
||||
--
|
||||
-- -- Define the DETECTION_AREAS, using the DetectionSetGroup, with a 30km grouping radius.
|
||||
-- Detection = DETECTION_AREAS:New( DetectionSetGroup, 30000 )
|
||||
--
|
||||
-- -- Now Setup the AIR dispatcher, and initialize it using the Detection object.
|
||||
-- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection ) --
|
||||
--
|
||||
function AI_AIR_DISPATCHER:New( Detection )
|
||||
|
||||
-- Inherits from DETECTION_MANAGER
|
||||
@@ -1431,17 +1435,17 @@ do -- AI_AIR_DISPATCHER
|
||||
end
|
||||
|
||||
|
||||
--- Set the default damage threshold when defenders will RTB.
|
||||
-- The default damage threshold is by default set to 40%, which means that when the airplane is 40% damaged, it will go RTB.
|
||||
--- Set the default damage treshold when defenders will RTB.
|
||||
-- The default damage treshold is by default set to 40%, which means that when the airplane is 40% damaged, it will go RTB.
|
||||
-- @param #AI_AIR_DISPATCHER self
|
||||
-- @param #number DamageThreshold A decimal number between 0 and 1, that expresses the % of the damage threshold before going RTB.
|
||||
-- @param #number DamageThreshold A decimal number between 0 and 1, that expresses the %-tage of the damage treshold before going RTB.
|
||||
-- @return #AI_AIR_DISPATCHER
|
||||
-- @usage
|
||||
--
|
||||
-- -- Now Setup the AIR dispatcher, and initialize it using the Detection object.
|
||||
-- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection )
|
||||
--
|
||||
-- -- Now Setup the default damage threshold.
|
||||
-- -- Now Setup the default damage treshold.
|
||||
-- AIRDispatcher:SetDefaultDamageThreshold( 0.90 ) -- Go RTB when the airplane 90% damaged.
|
||||
--
|
||||
function AI_AIR_DISPATCHER:SetDefaultDamageThreshold( DamageThreshold )
|
||||
@@ -1985,7 +1989,7 @@ do -- AI_AIR_DISPATCHER
|
||||
|
||||
--- Defines the default amount of extra planes that will take-off as part of the defense system.
|
||||
-- @param #AI_AIR_DISPATCHER self
|
||||
-- @param #number Overhead The % of Units that dispatching command will allocate to intercept in surplus of detected amount of units.
|
||||
-- @param #number Overhead The %-tage of Units that dispatching command will allocate to intercept in surplus of detected amount of units.
|
||||
-- The default overhead is 1, so equal balance. The @{#AI_AIR_DISPATCHER.SetOverhead}() method can be used to tweak the defense strength,
|
||||
-- taking into account the plane types of the squadron. For example, a MIG-31 with full long-distance AIR missiles payload, may still be less effective than a F-15C with short missiles...
|
||||
-- So in this case, one may want to use the Overhead method to allocate more defending planes as the amount of detected attacking planes.
|
||||
@@ -2024,7 +2028,7 @@ do -- AI_AIR_DISPATCHER
|
||||
--- Defines the amount of extra planes that will take-off as part of the defense system.
|
||||
-- @param #AI_AIR_DISPATCHER self
|
||||
-- @param #string SquadronName The name of the squadron.
|
||||
-- @param #number Overhead The % of Units that dispatching command will allocate to intercept in surplus of detected amount of units.
|
||||
-- @param #number Overhead The %-tage of Units that dispatching command will allocate to intercept in surplus of detected amount of units.
|
||||
-- The default overhead is 1, so equal balance. The @{#AI_AIR_DISPATCHER.SetOverhead}() method can be used to tweak the defense strength,
|
||||
-- taking into account the plane types of the squadron. For example, a MIG-31 with full long-distance AIR missiles payload, may still be less effective than a F-15C with short missiles...
|
||||
-- So in this case, one may want to use the Overhead method to allocate more defending planes as the amount of detected attacking planes.
|
||||
@@ -2064,7 +2068,7 @@ do -- AI_AIR_DISPATCHER
|
||||
--- Gets the overhead of planes as part of the defense system, in comparison with the attackers.
|
||||
-- @param #AI_AIR_DISPATCHER self
|
||||
-- @param #string SquadronName The name of the squadron.
|
||||
-- @return #number The % of Units that dispatching command will allocate to intercept in surplus of detected amount of units.
|
||||
-- @return #number The %-tage of Units that dispatching command will allocate to intercept in surplus of detected amount of units.
|
||||
-- The default overhead is 1, so equal balance. The @{#AI_AIR_DISPATCHER.SetOverhead}() method can be used to tweak the defense strength,
|
||||
-- taking into account the plane types of the squadron. For example, a MIG-31 with full long-distance AIR missiles payload, may still be less effective than a F-15C with short missiles...
|
||||
-- So in this case, one may want to use the Overhead method to allocate more defending planes as the amount of detected attacking planes.
|
||||
@@ -2670,17 +2674,17 @@ do -- AI_AIR_DISPATCHER
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set the default fuel threshold when defenders will RTB or Refuel in the air.
|
||||
-- The fuel threshold is by default set to 15%, which means that an airplane will stay in the air until 15% of its fuel has been consumed.
|
||||
--- Set the default fuel treshold when defenders will RTB or Refuel in the air.
|
||||
-- The fuel treshold is by default set to 15%, which means that an airplane will stay in the air until 15% of its fuel has been consumed.
|
||||
-- @param #AI_AIR_DISPATCHER self
|
||||
-- @param #number FuelThreshold A decimal number between 0 and 1, that expresses the % of the threshold of fuel remaining in the tank when the plane will go RTB or Refuel.
|
||||
-- @param #number FuelThreshold A decimal number between 0 and 1, that expresses the %-tage of the treshold of fuel remaining in the tank when the plane will go RTB or Refuel.
|
||||
-- @return #AI_AIR_DISPATCHER
|
||||
-- @usage
|
||||
--
|
||||
-- -- Now Setup the AIR dispatcher, and initialize it using the Detection object.
|
||||
-- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection )
|
||||
--
|
||||
-- -- Now Setup the default fuel threshold.
|
||||
-- -- Now Setup the default fuel treshold.
|
||||
-- AIRDispatcher:SetDefaultFuelThreshold( 0.30 ) -- Go RTB when only 30% of fuel remaining in the tank.
|
||||
--
|
||||
function AI_AIR_DISPATCHER:SetDefaultFuelThreshold( FuelThreshold )
|
||||
@@ -2691,18 +2695,18 @@ do -- AI_AIR_DISPATCHER
|
||||
end
|
||||
|
||||
|
||||
--- Set the fuel threshold for the squadron when defenders will RTB or Refuel in the air.
|
||||
-- The fuel threshold is by default set to 15%, which means that an airplane will stay in the air until 15% of its fuel has been consumed.
|
||||
--- Set the fuel treshold for the squadron when defenders will RTB or Refuel in the air.
|
||||
-- The fuel treshold is by default set to 15%, which means that an airplane will stay in the air until 15% of its fuel has been consumed.
|
||||
-- @param #AI_AIR_DISPATCHER self
|
||||
-- @param #string SquadronName The name of the squadron.
|
||||
-- @param #number FuelThreshold A decimal number between 0 and 1, that expresses the % of the threshold of fuel remaining in the tank when the plane will go RTB or Refuel.
|
||||
-- @param #number FuelThreshold A decimal number between 0 and 1, that expresses the %-tage of the treshold of fuel remaining in the tank when the plane will go RTB or Refuel.
|
||||
-- @return #AI_AIR_DISPATCHER
|
||||
-- @usage
|
||||
--
|
||||
-- -- Now Setup the AIR dispatcher, and initialize it using the Detection object.
|
||||
-- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection )
|
||||
--
|
||||
-- -- Now Setup the default fuel threshold.
|
||||
-- -- Now Setup the default fuel treshold.
|
||||
-- AIRDispatcher:SetSquadronRefuelThreshold( "SquadronName", 0.30 ) -- Go RTB when only 30% of fuel remaining in the tank.
|
||||
--
|
||||
function AI_AIR_DISPATCHER:SetSquadronFuelThreshold( SquadronName, FuelThreshold )
|
||||
@@ -2722,7 +2726,7 @@ do -- AI_AIR_DISPATCHER
|
||||
-- -- Now Setup the AIR dispatcher, and initialize it using the Detection object.
|
||||
-- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection )
|
||||
--
|
||||
-- -- Now Setup the default fuel threshold.
|
||||
-- -- Now Setup the default fuel treshold.
|
||||
-- AIRDispatcher:SetDefaultFuelThreshold( 0.30 ) -- Go RTB when only 30% of fuel remaining in the tank.
|
||||
--
|
||||
-- -- Now Setup the default tanker.
|
||||
@@ -2745,7 +2749,7 @@ do -- AI_AIR_DISPATCHER
|
||||
-- -- Now Setup the AIR dispatcher, and initialize it using the Detection object.
|
||||
-- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection )
|
||||
--
|
||||
-- -- Now Setup the squadron fuel threshold.
|
||||
-- -- Now Setup the squadron fuel treshold.
|
||||
-- AIRDispatcher:SetSquadronRefuelThreshold( "SquadronName", 0.30 ) -- Go RTB when only 30% of fuel remaining in the tank.
|
||||
--
|
||||
-- -- Now Setup the squadron tanker.
|
||||
@@ -2843,7 +2847,7 @@ do -- AI_AIR_DISPATCHER
|
||||
-- @param #AI_AIR_DISPATCHER self
|
||||
function AI_AIR_DISPATCHER:CountDefendersEngaged( AttackerDetection, AttackerCount )
|
||||
|
||||
-- First, count the active AIGroups Units, targeting the DetectedSet
|
||||
-- First, count the active AIGroups Units, targetting the DetectedSet
|
||||
local DefendersEngaged = 0
|
||||
local DefendersTotal = 0
|
||||
|
||||
|
||||
@@ -42,8 +42,8 @@
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- Until a fuel or damage threshold has been reached by the AI, or when the AI is commanded to RTB.
|
||||
-- When the fuel threshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
|
||||
-- Until a fuel or damage treshold has been reached by the AI, or when the AI is commanded to RTB.
|
||||
-- When the fuel treshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
@@ -533,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 )
|
||||
|
||||
@@ -39,8 +39,8 @@
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- Until a fuel or damage threshold has been reached by the AI, or when the AI is commanded to RTB.
|
||||
-- When the fuel threshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
|
||||
-- Until a fuel or damage treshold has been reached by the AI, or when the AI is commanded to RTB.
|
||||
-- When the fuel treshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
@@ -70,7 +70,7 @@
|
||||
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detected}**: The AI has detected new targets.
|
||||
-- * **@{#AI_AIR_PATROL.Destroy}**: The AI has destroyed a bogey @{Wrapper.Unit}.
|
||||
-- * **@{#AI_AIR_PATROL.Destroyed}**: The AI has destroyed all bogeys @{Wrapper.Unit}s assigned in the CAS task.
|
||||
-- * **Status** ( Group ): The AI is checking status (fuel and damage). When the thresholds have been reached, the AI will RTB.
|
||||
-- * **Status** ( Group ): The AI is checking status (fuel and damage). When the tresholds have been reached, the AI will RTB.
|
||||
--
|
||||
-- ## 3. Set the Range of Engagement
|
||||
--
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
-- Upon started, The AI will **Route** itself towards the random 3D point within a patrol zone,
|
||||
-- using a random speed within the given altitude and speed limits.
|
||||
-- Upon arrival at the 3D point, a new random 3D point will be selected within the patrol zone using the given limits.
|
||||
-- This cycle will continue until a fuel or damage threshold has been reached by the AI, or when the AI is commanded to RTB.
|
||||
-- This cycle will continue until a fuel or damage treshold has been reached by the AI, or when the AI is commanded to RTB.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
@@ -87,7 +87,7 @@
|
||||
-- It will keep patrolling there, until it is notified to RTB or move to another BOMB Zone.
|
||||
-- It can be notified to go RTB through the **RTB** event.
|
||||
--
|
||||
-- When the fuel threshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
|
||||
-- When the fuel treshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
@@ -117,7 +117,7 @@
|
||||
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detected}**: The AI has detected new targets.
|
||||
-- * **@{#AI_BAI_ZONE.Destroy}**: The AI has destroyed a target @{Wrapper.Unit}.
|
||||
-- * **@{#AI_BAI_ZONE.Destroyed}**: The AI has destroyed all target @{Wrapper.Unit}s assigned in the BOMB task.
|
||||
-- * **Status**: The AI is checking status (fuel and damage). When the thresholds have been reached, the AI will RTB.
|
||||
-- * **Status**: The AI is checking status (fuel and damage). When the tresholds have been reached, the AI will RTB.
|
||||
--
|
||||
-- ## 3. Modify the Engage Zone behaviour to pinpoint a **map object** or **scenery object**
|
||||
--
|
||||
@@ -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(
|
||||
@@ -602,7 +602,7 @@ function AI_BAI_ZONE:onafterEngage( Controllable, From, Event, To,
|
||||
|
||||
self:SetRefreshTimeInterval( 2 )
|
||||
self:SetDetectionActivated()
|
||||
self:__Target( -2 ) -- Start targeting
|
||||
self:__Target( -2 ) -- Start Targetting
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
--
|
||||
-- * Patrol AI airplanes within a given zone.
|
||||
-- * Trigger detected events when enemy airplanes are detected.
|
||||
-- * Manage a fuel threshold to RTB on time.
|
||||
-- * Manage a fuel treshold to RTB on time.
|
||||
-- * Engage the enemy when detected.
|
||||
--
|
||||
--
|
||||
@@ -65,8 +65,8 @@
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- Until a fuel or damage threshold has been reached by the AI, or when the AI is commanded to RTB.
|
||||
-- When the fuel threshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
|
||||
-- Until a fuel or damage treshold has been reached by the AI, or when the AI is commanded to RTB.
|
||||
-- When the fuel treshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
@@ -96,7 +96,7 @@
|
||||
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detected}**: The AI has detected new targets.
|
||||
-- * **@{#AI_CAP_ZONE.Destroy}**: The AI has destroyed a bogey @{Wrapper.Unit}.
|
||||
-- * **@{#AI_CAP_ZONE.Destroyed}**: The AI has destroyed all bogeys @{Wrapper.Unit}s assigned in the CAS task.
|
||||
-- * **Status** ( Group ): The AI is checking status (fuel and damage). When the thresholds have been reached, the AI will RTB.
|
||||
-- * **Status** ( Group ): The AI is checking status (fuel and damage). When the tresholds have been reached, the AI will RTB.
|
||||
--
|
||||
-- ## 3. Set the Range of Engagement
|
||||
--
|
||||
@@ -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(
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
-- Upon started, The AI will **Route** itself towards the random 3D point within a patrol zone,
|
||||
-- using a random speed within the given altitude and speed limits.
|
||||
-- Upon arrival at the 3D point, a new random 3D point will be selected within the patrol zone using the given limits.
|
||||
-- This cycle will continue until a fuel or damage threshold has been reached by the AI, or when the AI is commanded to RTB.
|
||||
-- This cycle will continue until a fuel or damage treshold has been reached by the AI, or when the AI is commanded to RTB.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
@@ -87,7 +87,7 @@
|
||||
-- It will keep patrolling there, until it is notified to RTB or move to another CAS Zone.
|
||||
-- It can be notified to go RTB through the **RTB** event.
|
||||
--
|
||||
-- When the fuel threshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
|
||||
-- When the fuel treshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
@@ -117,7 +117,7 @@
|
||||
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detected}**: The AI has detected new targets.
|
||||
-- * **@{#AI_CAS_ZONE.Destroy}**: The AI has destroyed a target @{Wrapper.Unit}.
|
||||
-- * **@{#AI_CAS_ZONE.Destroyed}**: The AI has destroyed all target @{Wrapper.Unit}s assigned in the CAS task.
|
||||
-- * **Status**: The AI is checking status (fuel and damage). When the thresholds have been reached, the AI will RTB.
|
||||
-- * **Status**: The AI is checking status (fuel and damage). When the tresholds have been reached, the AI will RTB.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
@@ -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(
|
||||
@@ -520,7 +520,7 @@ function AI_CAS_ZONE:onafterEngage( Controllable, From, Event, To,
|
||||
|
||||
self:SetRefreshTimeInterval( 2 )
|
||||
self:SetDetectionActivated()
|
||||
self:__Target( -2 ) -- Start targeting
|
||||
self:__Target( -2 ) -- Start Targetting
|
||||
end
|
||||
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
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
-- ===
|
||||
--
|
||||
-- @module AI.AI_Cargo_Dispatcher_Ship
|
||||
-- @image AI_Cargo_Dispatching_For_Ship.JPG
|
||||
-- @image AI_Cargo_Dispatcher.JPG
|
||||
|
||||
--- @type AI_CARGO_DISPATCHER_SHIP
|
||||
-- @extends AI.AI_Cargo_Dispatcher#AI_CARGO_DISPATCHER
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
-- ===
|
||||
--
|
||||
-- @module AI.AI_Cargo_Ship
|
||||
-- @image AI_Cargo_Dispatching_For_Ship.JPG
|
||||
-- @image AI_Cargo_Dispatcher.JPG
|
||||
|
||||
--- @type AI_CARGO_SHIP
|
||||
-- @extends AI.AI_Cargo#AI_CARGO
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
--
|
||||
-- * Patrol AI airplanes within a given zone.
|
||||
-- * Trigger detected events when enemy airplanes are detected.
|
||||
-- * Manage a fuel threshold to RTB on time.
|
||||
-- * Manage a fuel treshold to RTB on time.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
@@ -72,8 +72,8 @@
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- Until a fuel or damage threshold has been reached by the AI, or when the AI is commanded to RTB.
|
||||
-- When the fuel threshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
|
||||
-- Until a fuel or damage treshold has been reached by the AI, or when the AI is commanded to RTB.
|
||||
-- When the fuel treshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
@@ -101,7 +101,7 @@
|
||||
-- * **RTB** ( Group ): Route the AI to the home base.
|
||||
-- * **Detect** ( Group ): The AI is detecting targets.
|
||||
-- * **Detected** ( Group ): The AI has detected new targets.
|
||||
-- * **Status** ( Group ): The AI is checking status (fuel and damage). When the thresholds have been reached, the AI will RTB.
|
||||
-- * **Status** ( Group ): The AI is checking status (fuel and damage). When the tresholds have been reached, the AI will RTB.
|
||||
--
|
||||
-- ## 3. Set or Get the AI controllable
|
||||
--
|
||||
@@ -133,8 +133,8 @@
|
||||
-- ## 6. Manage the "out of fuel" in the AI_PATROL_ZONE
|
||||
--
|
||||
-- When the AI is out of fuel, it is required that a new AI is started, before the old AI can return to the home base.
|
||||
-- Therefore, with a parameter and a calculation of the distance to the home base, the fuel threshold is calculated.
|
||||
-- When the fuel threshold is reached, the AI will continue for a given time its patrol task in orbit,
|
||||
-- Therefore, with a parameter and a calculation of the distance to the home base, the fuel treshold is calculated.
|
||||
-- When the fuel treshold is reached, the AI will continue for a given time its patrol task in orbit,
|
||||
-- while a new AI is targetted to the AI_PATROL_ZONE.
|
||||
-- Once the time is finished, the old AI will return to the base.
|
||||
-- Use the method @{#AI_PATROL_ZONE.ManageFuel}() to have this proces in place.
|
||||
@@ -142,7 +142,7 @@
|
||||
-- ## 7. Manage "damage" behaviour of the AI in the AI_PATROL_ZONE
|
||||
--
|
||||
-- When the AI is damaged, it is required that a new AIControllable is started. However, damage cannon be foreseen early on.
|
||||
-- Therefore, when the damage threshold is reached, the AI will return immediately to the home base (RTB).
|
||||
-- Therefore, when the damage treshold is reached, the AI will return immediately to the home base (RTB).
|
||||
-- Use the method @{#AI_PATROL_ZONE.ManageDamage}() to have this proces in place.
|
||||
--
|
||||
-- ===
|
||||
@@ -581,11 +581,11 @@ function AI_PATROL_ZONE:ClearDetectedUnits()
|
||||
end
|
||||
|
||||
--- When the AI is out of fuel, it is required that a new AI is started, before the old AI can return to the home base.
|
||||
-- Therefore, with a parameter and a calculation of the distance to the home base, the fuel threshold is calculated.
|
||||
-- When the fuel threshold is reached, the AI will continue for a given time its patrol task in orbit, while a new AIControllable is targetted to the AI_PATROL_ZONE.
|
||||
-- Therefore, with a parameter and a calculation of the distance to the home base, the fuel treshold is calculated.
|
||||
-- When the fuel treshold is reached, the AI will continue for a given time its patrol task in orbit, while a new AIControllable is targetted to the AI_PATROL_ZONE.
|
||||
-- Once the time is finished, the old AI will return to the base.
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @param #number PatrolFuelThresholdPercentage The threshold in percentage (between 0 and 1) when the AIControllable is considered to get out of fuel.
|
||||
-- @param #number PatrolFuelThresholdPercentage The treshold in percentage (between 0 and 1) when the AIControllable is considered to get out of fuel.
|
||||
-- @param #number PatrolOutOfFuelOrbitTime The amount of seconds the out of fuel AIControllable will orbit before returning to the base.
|
||||
-- @return #AI_PATROL_ZONE self
|
||||
function AI_PATROL_ZONE:ManageFuel( PatrolFuelThresholdPercentage, PatrolOutOfFuelOrbitTime )
|
||||
@@ -596,14 +596,14 @@ function AI_PATROL_ZONE:ManageFuel( PatrolFuelThresholdPercentage, PatrolOutOfFu
|
||||
return self
|
||||
end
|
||||
|
||||
--- When the AI is damaged beyond a certain threshold, it is required that the AI returns to the home base.
|
||||
--- When the AI is damaged beyond a certain treshold, it is required that the AI returns to the home base.
|
||||
-- However, damage cannot be foreseen early on.
|
||||
-- Therefore, when the damage threshold is reached,
|
||||
-- Therefore, when the damage treshold is reached,
|
||||
-- the AI will return immediately to the home base (RTB).
|
||||
-- Note that for groups, the average damage of the complete group will be calculated.
|
||||
-- So, in a group of 4 airplanes, 2 lost and 2 with damage 0.2, the damage threshold will be 0.25.
|
||||
-- So, in a group of 4 airplanes, 2 lost and 2 with damage 0.2, the damage treshold will be 0.25.
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @param #number PatrolDamageThreshold The threshold in percentage (between 0 and 1) when the AI is considered to be damaged.
|
||||
-- @param #number PatrolDamageThreshold The treshold in percentage (between 0 and 1) when the AI is considered to be damaged.
|
||||
-- @return #AI_PATROL_ZONE self
|
||||
function AI_PATROL_ZONE:ManageDamage( PatrolDamageThreshold )
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -194,7 +194,7 @@
|
||||
-- * is of type `Workmaterials`
|
||||
-- * will report when a carrier is within 500 meters
|
||||
-- * will board to carriers when the carrier is within 500 meters from the cargo object
|
||||
-- * will disappear when the cargo is within 25 meters from the carrier during boarding
|
||||
-- * will dissapear when the cargo is within 25 meters from the carrier during boarding
|
||||
--
|
||||
-- So the overall syntax of the #CARGO naming tag and arguments are:
|
||||
--
|
||||
@@ -220,7 +220,7 @@
|
||||
-- * is of type `Workmaterials`
|
||||
-- * will report when a carrier is within 500 meters
|
||||
-- * will board to carriers when the carrier is within 500 meters from the cargo object
|
||||
-- * will disappear when the cargo is within 25 meters from the carrier during boarding
|
||||
-- * will dissapear when the cargo is within 25 meters from the carrier during boarding
|
||||
--
|
||||
-- So the overall syntax of the #CARGO naming tag and arguments are:
|
||||
--
|
||||
|
||||
@@ -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
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,9 +1,9 @@
|
||||
--- **Core** - TACAN and other beacons.
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- ## Features:
|
||||
--
|
||||
--
|
||||
-- * Provide beacon functionality to assist pilots.
|
||||
--
|
||||
-- ===
|
||||
@@ -14,34 +14,34 @@
|
||||
-- @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.
|
||||
--
|
||||
-- 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",
|
||||
ClassName = "BEACON",
|
||||
Positionable = nil,
|
||||
name = nil,
|
||||
name = nil,
|
||||
}
|
||||
|
||||
--- Beacon types supported by DCS.
|
||||
--- Beacon types supported by DCS.
|
||||
-- @type BEACON.Type
|
||||
-- @field #number NULL
|
||||
-- @field #number VOR
|
||||
@@ -65,19 +65,19 @@ BEACON = {
|
||||
-- @field #number ICLS_GLIDESLOPE
|
||||
-- @field #number NAUTICAL_HOMER
|
||||
BEACON.Type={
|
||||
NULL = 0,
|
||||
NULL = 0,
|
||||
VOR = 1,
|
||||
DME = 2,
|
||||
VOR_DME = 3,
|
||||
VOR_DME = 3,
|
||||
TACAN = 4,
|
||||
VORTAC = 5,
|
||||
VORTAC = 5,
|
||||
RSBN = 128,
|
||||
BROADCAST_STATION = 1024,
|
||||
BROADCAST_STATION = 1024,
|
||||
HOMER = 8,
|
||||
AIRPORT_HOMER = 4104,
|
||||
AIRPORT_HOMER_WITH_MARKER = 4136,
|
||||
AIRPORT_HOMER = 4104,
|
||||
AIRPORT_HOMER_WITH_MARKER = 4136,
|
||||
ILS_FAR_HOMER = 16408,
|
||||
ILS_NEAR_HOMER = 16424,
|
||||
ILS_NEAR_HOMER = 16424,
|
||||
ILS_LOCALIZER = 16640,
|
||||
ILS_GLIDESLOPE = 16896,
|
||||
PRMG_LOCALIZER = 33024,
|
||||
@@ -95,11 +95,11 @@ BEACON.Type={
|
||||
-- @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 Omnidirectional Range
|
||||
-- @field #number VOR Very High Frequency Omni-Directional Range
|
||||
-- @field #number ILS_LOCALIZER ILS localizer
|
||||
-- @field #number ILS_GLIDESLOPE ILS glide slope.
|
||||
-- @field #number ILS_GLIDESLOPE ILS glideslope.
|
||||
-- @field #number PRGM_LOCALIZER PRGM localizer.
|
||||
-- @field #number PRGM_GLIDESLOPE PRGM glide slope.
|
||||
-- @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.
|
||||
@@ -108,13 +108,13 @@ BEACON.Type={
|
||||
-- @field #number ICLS_LOCALIZER Carrier landing system.
|
||||
-- @field #number ICLS_GLIDESLOPE Carrier landing system.
|
||||
BEACON.System={
|
||||
PAR_10 = 1,
|
||||
RSBN_5 = 2,
|
||||
TACAN = 3,
|
||||
PAR_10 = 1,
|
||||
RSBN_5 = 2,
|
||||
TACAN = 3,
|
||||
TACAN_TANKER_X = 4,
|
||||
TACAN_TANKER_Y = 5,
|
||||
VOR = 6,
|
||||
ILS_LOCALIZER = 7,
|
||||
VOR = 6,
|
||||
ILS_LOCALIZER = 7,
|
||||
ILS_GLIDESLOPE = 8,
|
||||
PRMG_LOCALIZER = 9,
|
||||
PRMG_GLIDESLOPE = 10,
|
||||
@@ -131,27 +131,28 @@ BEACON.System={
|
||||
-- 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 )
|
||||
-- @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
|
||||
|
||||
local self=BASE:Inherit(self, BASE:New()) --#BEACON
|
||||
|
||||
-- Debug.
|
||||
self:F( Positionable )
|
||||
|
||||
self:F(Positionable)
|
||||
|
||||
-- Set positionable.
|
||||
if Positionable:GetPointVec2() then -- It's stupid, but the only way I found to make sure POSITIONABLE is valid
|
||||
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 ) ) )
|
||||
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 } )
|
||||
|
||||
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".
|
||||
@@ -161,55 +162,60 @@ end
|
||||
-- @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 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 } )
|
||||
|
||||
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 )
|
||||
|
||||
local Frequency=UTILS.TACANToFrequency(Channel, Mode)
|
||||
|
||||
-- Check.
|
||||
if not Frequency then
|
||||
self:E( { "The passed TACAN channel is invalid, the BEACON is not emitting" } )
|
||||
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
|
||||
|
||||
local Type=BEACON.Type.TACAN
|
||||
|
||||
-- Beacon system.
|
||||
local System = BEACON.System.TACAN
|
||||
|
||||
local System=BEACON.System.TACAN
|
||||
|
||||
-- Check if unit is an aircraft and set system accordingly.
|
||||
local AA = self.Positionable:IsAir()
|
||||
local AA=self.Positionable:IsAir()
|
||||
|
||||
|
||||
if AA then
|
||||
System = 5 -- NOTE: 5 is how you cat the correct tanker behaviour! --BEACON.System.TACAN_TANKER
|
||||
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 } )
|
||||
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()
|
||||
|
||||
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 ) ) } )
|
||||
|
||||
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 scheduler.
|
||||
self.Positionable:CommandActivateBeacon(Type, System, Frequency, UnitID, Channel, Mode, AA, Message, Bearing)
|
||||
|
||||
-- Stop sheduler.
|
||||
if Duration then
|
||||
self.Positionable:DeactivateBeacon( Duration )
|
||||
self.Positionable:DeactivateBeacon(Duration)
|
||||
end
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -219,23 +225,23 @@ end
|
||||
-- @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 } )
|
||||
|
||||
function BEACON:ActivateICLS(Channel, Callsign, Duration)
|
||||
self:F({Channel=Channel, Callsign=Callsign, Duration=Duration})
|
||||
|
||||
-- Attached unit.
|
||||
local UnitID = self.Positionable:GetID()
|
||||
|
||||
local UnitID=self.Positionable:GetID()
|
||||
|
||||
-- Debug
|
||||
self:T2( { "ICLS BEACON started!" } )
|
||||
|
||||
self:T2({"ICLS BEACON started!"})
|
||||
|
||||
-- Start beacon.
|
||||
self.Positionable:CommandActivateICLS( Channel, UnitID, Callsign )
|
||||
|
||||
-- Stop scheduler
|
||||
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 )
|
||||
self.Positionable:DeactivateBeacon(Duration)
|
||||
end
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -247,57 +253,58 @@ end
|
||||
-- @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 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 } )
|
||||
|
||||
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 } )
|
||||
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" } )
|
||||
|
||||
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
|
||||
|
||||
-- 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
|
||||
System = BEACON.System.TACAN_TANKER_Y
|
||||
else
|
||||
System = 14
|
||||
System = BEACON.System.TACAN_AA_MODE_Y
|
||||
end
|
||||
|
||||
|
||||
if IsValid then -- Starts the BEACON
|
||||
self:T2( { "AA TACAN BEACON started !" } )
|
||||
self.Positionable:SetCommand( {
|
||||
self:T2({"AA TACAN BEACON started !"})
|
||||
self.Positionable:SetCommand({
|
||||
id = "ActivateBeacon",
|
||||
params = {
|
||||
type = 4,
|
||||
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()
|
||||
SCHEDULER:New(nil,
|
||||
function()
|
||||
self:StopAATACAN()
|
||||
end, {}, BeaconDuration )
|
||||
end, {}, BeaconDuration)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -307,19 +314,21 @@ end
|
||||
function BEACON:StopAATACAN()
|
||||
self:F()
|
||||
if not self.Positionable then
|
||||
self:E( { "Start the beacon first before stopping it!" } )
|
||||
self:E({"Start the beacon first before stoping it !"})
|
||||
else
|
||||
self.Positionable:SetCommand( {
|
||||
id = 'DeactivateBeacon',
|
||||
params = {},
|
||||
} )
|
||||
self.Positionable:SetCommand({
|
||||
id = 'DeactivateBeacon',
|
||||
params = {
|
||||
}
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
--- Activates a general purpose Radio Beacon
|
||||
|
||||
--- 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 :
|
||||
-- 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
|
||||
@@ -342,54 +351,55 @@ end
|
||||
--
|
||||
-- -- 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 } )
|
||||
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
|
||||
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 } )
|
||||
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 } )
|
||||
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 } )
|
||||
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 } )
|
||||
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
|
||||
|
||||
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 } )
|
||||
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
|
||||
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
|
||||
@@ -398,7 +408,7 @@ end
|
||||
function BEACON:StopRadioBeacon()
|
||||
self:F()
|
||||
-- The unique name of the transmission is the class ID
|
||||
trigger.action.stopRadioTransmission( tostring( self.ID ) )
|
||||
trigger.action.stopRadioTransmission(tostring(self.ID))
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -406,26 +416,26 @@ end
|
||||
-- @param #BEACON self
|
||||
-- @param #number TACANChannel
|
||||
-- @param #string TACANMode
|
||||
-- @return #number Frequency
|
||||
-- @return #number Frequecy
|
||||
-- @return #nil if parameters are invalid
|
||||
function BEACON:_TACANToFrequency( TACANChannel, TACANMode )
|
||||
self:F3( { TACANChannel, TACANMode } )
|
||||
function BEACON:_TACANToFrequency(TACANChannel, TACANMode)
|
||||
self:F3({TACANChannel, TACANMode})
|
||||
|
||||
if type( TACANChannel ) ~= "number" then
|
||||
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
|
||||
|
||||
-- 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
|
||||
|
||||
local B = 64 -- channel >= 64
|
||||
|
||||
if TACANChannel < 64 then
|
||||
B = 1
|
||||
end
|
||||
|
||||
|
||||
if TACANMode == 'Y' then
|
||||
A = 1025
|
||||
if TACANChannel < 64 then
|
||||
@@ -436,6 +446,6 @@ function BEACON:_TACANToFrequency( TACANChannel, TACANMode )
|
||||
A = 962
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
return (A + TACANChannel - B) * 1000000
|
||||
end
|
||||
end
|
||||
File diff suppressed because it is too large
Load Diff
@@ -14,7 +14,7 @@
|
||||
-- 
|
||||
--
|
||||
-- Within a running mission, various DCS events occur. Units are dynamically created, crash, die, shoot stuff, get hit etc.
|
||||
-- This module provides a mechanism to dispatch those events occurring within your running mission, to the different objects orchestrating your mission.
|
||||
-- This module provides a mechanism to dispatch those events occuring within your running mission, to the different objects orchestrating your mission.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
@@ -32,11 +32,11 @@
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- There are 5 types/levels of objects that the _EVENTDISPATCHER services:
|
||||
-- There are 5 levels of kind of objects that the _EVENTDISPATCHER services:
|
||||
--
|
||||
-- * _DATABASE object: The core of the MOOSE objects. Any object that is created, deleted or updated, is done in this database.
|
||||
-- * SET_ derived classes: These are subsets of the _DATABASE object. These subsets are updated by the _EVENTDISPATCHER as the second priority.
|
||||
-- * UNIT objects: UNIT objects can subscribe to DCS events. Each DCS event will be directly published to the subscribed UNIT object.
|
||||
-- * SET_ derived classes: Subsets of the _DATABASE object. These subsets are updated by the _EVENTDISPATCHER as the second priority.
|
||||
-- * UNIT objects: UNIT objects can subscribe to DCS events. Each DCS event will be directly published to teh subscribed UNIT object.
|
||||
-- * GROUP objects: GROUP objects can subscribe to DCS events. Each DCS event will be directly published to the subscribed GROUP object.
|
||||
-- * Any other object: Various other objects can subscribe to DCS events. Each DCS event triggered will be published to each subscribed object.
|
||||
--
|
||||
@@ -131,8 +131,6 @@
|
||||
-- * Weapon data: Certain events populate weapon information.
|
||||
-- * Place data: Certain events populate place information.
|
||||
--
|
||||
-- Example code snippet:
|
||||
--
|
||||
-- --- This function is an Event Handling function that will be called when Tank1 is Dead.
|
||||
-- -- EventData is an EVENTDATA structure.
|
||||
-- -- We use the EventData.IniUnit to smoke the tank Green.
|
||||
@@ -143,6 +141,7 @@
|
||||
-- EventData.IniUnit:SmokeGreen()
|
||||
-- end
|
||||
--
|
||||
--
|
||||
-- Find below an overview which events populate which information categories:
|
||||
--
|
||||
-- 
|
||||
@@ -152,7 +151,6 @@
|
||||
-- In case a STATIC object is involved, the documentation indicates which fields will and won't not be populated.
|
||||
-- The fields **IniObjectCategory** and **TgtObjectCategory** contain the indicator which **kind of object is involved** in the event.
|
||||
-- You can use the enumerator **Object.Category.UNIT** and **Object.Category.STATIC** to check on IniObjectCategory and TgtObjectCategory.
|
||||
--
|
||||
-- Example code snippet:
|
||||
--
|
||||
-- if Event.IniObjectCategory == Object.Category.UNIT then
|
||||
@@ -174,6 +172,7 @@
|
||||
-- @module Core.Event
|
||||
-- @image Core_Event.JPG
|
||||
|
||||
|
||||
--- @type EVENT
|
||||
-- @field #EVENT.Events Events
|
||||
-- @extends Core.Base#BASE
|
||||
@@ -195,6 +194,7 @@ world.event.S_EVENT_DELETE_ZONE_GOAL = world.event.S_EVENT_MAX + 1005
|
||||
world.event.S_EVENT_REMOVE_UNIT = world.event.S_EVENT_MAX + 1006
|
||||
world.event.S_EVENT_PLAYER_ENTER_AIRCRAFT = world.event.S_EVENT_MAX + 1007
|
||||
|
||||
|
||||
--- The different types of events supported by MOOSE.
|
||||
-- Use this structure to subscribe to events using the @{Core.Base#BASE.HandleEvent}() method.
|
||||
-- @type EVENTS
|
||||
@@ -227,13 +227,13 @@ EVENTS = {
|
||||
MarkChange = world.event.S_EVENT_MARK_CHANGE,
|
||||
MarkRemoved = world.event.S_EVENT_MARK_REMOVED,
|
||||
-- Moose Events
|
||||
NewCargo = world.event.S_EVENT_NEW_CARGO,
|
||||
DeleteCargo = world.event.S_EVENT_DELETE_CARGO,
|
||||
NewZone = world.event.S_EVENT_NEW_ZONE,
|
||||
DeleteZone = world.event.S_EVENT_DELETE_ZONE,
|
||||
NewZoneGoal = world.event.S_EVENT_NEW_ZONE_GOAL,
|
||||
DeleteZoneGoal = world.event.S_EVENT_DELETE_ZONE_GOAL,
|
||||
RemoveUnit = world.event.S_EVENT_REMOVE_UNIT,
|
||||
NewCargo = world.event.S_EVENT_NEW_CARGO,
|
||||
DeleteCargo = world.event.S_EVENT_DELETE_CARGO,
|
||||
NewZone = world.event.S_EVENT_NEW_ZONE,
|
||||
DeleteZone = world.event.S_EVENT_DELETE_ZONE,
|
||||
NewZoneGoal = world.event.S_EVENT_NEW_ZONE_GOAL,
|
||||
DeleteZoneGoal = world.event.S_EVENT_DELETE_ZONE_GOAL,
|
||||
RemoveUnit = world.event.S_EVENT_REMOVE_UNIT,
|
||||
PlayerEnterAircraft = world.event.S_EVENT_PLAYER_ENTER_AIRCRAFT,
|
||||
-- Added with DCS 2.5.6
|
||||
DetailedFailure = world.event.S_EVENT_DETAILED_FAILURE or -1, --We set this to -1 for backward compatibility to DCS 2.5.5 and earlier
|
||||
@@ -304,6 +304,8 @@ EVENTS = {
|
||||
-- @field Core.ZONE#ZONE Zone The zone object.
|
||||
-- @field #string ZoneName The name of the zone.
|
||||
|
||||
|
||||
|
||||
local _EVENTMETA = {
|
||||
[world.event.S_EVENT_SHOT] = {
|
||||
Order = 1,
|
||||
@@ -560,6 +562,7 @@ local _EVENTMETA = {
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
--- The Events structure
|
||||
-- @type EVENT.Events
|
||||
-- @field #number IniUnit
|
||||
@@ -573,11 +576,12 @@ function EVENT:New()
|
||||
local self = BASE:Inherit( self, BASE:New() )
|
||||
|
||||
-- Add world event handler.
|
||||
self.EventHandler = world.addEventHandler( self )
|
||||
self.EventHandler = world.addEventHandler(self)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Initializes the Events structure for the event.
|
||||
-- @param #EVENT self
|
||||
-- @param DCS#world.event EventID Event ID.
|
||||
@@ -591,7 +595,7 @@ function EVENT:Init( EventID, EventClass )
|
||||
self.Events[EventID] = {}
|
||||
end
|
||||
|
||||
-- Each event has a sub-table of EventClasses, ordered by EventPriority.
|
||||
-- Each event has a subtable of EventClasses, ordered by EventPriority.
|
||||
local EventPriority = EventClass:GetEventPriority()
|
||||
|
||||
if not self.Events[EventID][EventPriority] then
|
||||
@@ -599,7 +603,7 @@ function EVENT:Init( EventID, EventClass )
|
||||
end
|
||||
|
||||
if not self.Events[EventID][EventPriority][EventClass] then
|
||||
self.Events[EventID][EventPriority][EventClass] = {}
|
||||
self.Events[EventID][EventPriority][EventClass] = {}
|
||||
end
|
||||
|
||||
return self.Events[EventID][EventPriority][EventClass]
|
||||
@@ -610,7 +614,7 @@ end
|
||||
-- @param Core.Base#BASE EventClass The self instance of the class for which the event is.
|
||||
-- @param DCS#world.event EventID Event ID.
|
||||
-- @return #EVENT self
|
||||
function EVENT:RemoveEvent( EventClass, EventID )
|
||||
function EVENT:RemoveEvent( EventClass, EventID )
|
||||
|
||||
-- Debug info.
|
||||
self:F2( { "Removing subscription for class: ", EventClass:GetClassNameAndID() } )
|
||||
@@ -634,7 +638,7 @@ end
|
||||
-- @param Core.Base#BASE EventClass The self instance of the class for which the event is.
|
||||
-- @param DCS#world.event EventID Event ID.
|
||||
-- @return #EVENT.Events
|
||||
function EVENT:Reset( EventObject ) -- R2.1
|
||||
function EVENT:Reset( EventObject ) --R2.1
|
||||
|
||||
self:F( { "Resetting subscriptions for class: ", EventObject:GetClassNameAndID() } )
|
||||
|
||||
@@ -653,11 +657,12 @@ function EVENT:Reset( EventObject ) -- R2.1
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- Clears all event subscriptions for a @{Core.Base#BASE} derived object.
|
||||
-- @param #EVENT self
|
||||
-- @param Core.Base#BASE EventClass The self class object for which the events are removed.
|
||||
-- @return #EVENT self
|
||||
function EVENT:RemoveAll( EventClass )
|
||||
function EVENT:RemoveAll(EventClass)
|
||||
|
||||
local EventClassName = EventClass:GetClassNameAndID()
|
||||
|
||||
@@ -671,6 +676,8 @@ function EVENT:RemoveAll( EventClass )
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
|
||||
--- Create an OnDead event handler for a group
|
||||
-- @param #EVENT self
|
||||
-- @param #table EventTemplate
|
||||
@@ -702,6 +709,7 @@ function EVENT:OnEventGeneric( EventFunction, EventClass, EventID )
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Set a new listener for an `S_EVENT_X` event for a UNIT.
|
||||
-- @param #EVENT self
|
||||
-- @param #string UnitName The name of the UNIT.
|
||||
@@ -789,6 +797,7 @@ do -- OnDead
|
||||
|
||||
end
|
||||
|
||||
|
||||
do -- OnLand
|
||||
|
||||
--- Create an OnLand event handler for a group
|
||||
@@ -855,7 +864,7 @@ do -- Event Creation
|
||||
id = EVENTS.NewCargo,
|
||||
time = timer.getTime(),
|
||||
cargo = Cargo,
|
||||
}
|
||||
}
|
||||
|
||||
world.onEvent( Event )
|
||||
end
|
||||
@@ -870,7 +879,7 @@ do -- Event Creation
|
||||
id = EVENTS.DeleteCargo,
|
||||
time = timer.getTime(),
|
||||
cargo = Cargo,
|
||||
}
|
||||
}
|
||||
|
||||
world.onEvent( Event )
|
||||
end
|
||||
@@ -885,7 +894,7 @@ do -- Event Creation
|
||||
id = EVENTS.NewZone,
|
||||
time = timer.getTime(),
|
||||
zone = Zone,
|
||||
}
|
||||
}
|
||||
|
||||
world.onEvent( Event )
|
||||
end
|
||||
@@ -900,7 +909,7 @@ do -- Event Creation
|
||||
id = EVENTS.DeleteZone,
|
||||
time = timer.getTime(),
|
||||
zone = Zone,
|
||||
}
|
||||
}
|
||||
|
||||
world.onEvent( Event )
|
||||
end
|
||||
@@ -915,11 +924,12 @@ do -- Event Creation
|
||||
id = EVENTS.NewZoneGoal,
|
||||
time = timer.getTime(),
|
||||
ZoneGoal = ZoneGoal,
|
||||
}
|
||||
}
|
||||
|
||||
world.onEvent( Event )
|
||||
end
|
||||
|
||||
|
||||
--- Creation of a ZoneGoal Deletion Event.
|
||||
-- @param #EVENT self
|
||||
-- @param Core.ZoneGoal#ZONE_GOAL ZoneGoal The ZoneGoal created.
|
||||
@@ -930,11 +940,12 @@ do -- Event Creation
|
||||
id = EVENTS.DeleteZoneGoal,
|
||||
time = timer.getTime(),
|
||||
ZoneGoal = ZoneGoal,
|
||||
}
|
||||
}
|
||||
|
||||
world.onEvent( Event )
|
||||
end
|
||||
|
||||
|
||||
--- Creation of a S_EVENT_PLAYER_ENTER_UNIT Event.
|
||||
-- @param #EVENT self
|
||||
-- @param Wrapper.Unit#UNIT PlayerUnit.
|
||||
@@ -944,8 +955,8 @@ do -- Event Creation
|
||||
local Event = {
|
||||
id = EVENTS.PlayerEnterUnit,
|
||||
time = timer.getTime(),
|
||||
initiator = PlayerUnit:GetDCSObject(),
|
||||
}
|
||||
initiator = PlayerUnit:GetDCSObject()
|
||||
}
|
||||
|
||||
world.onEvent( Event )
|
||||
end
|
||||
@@ -959,8 +970,8 @@ do -- Event Creation
|
||||
local Event = {
|
||||
id = EVENTS.PlayerEnterAircraft,
|
||||
time = timer.getTime(),
|
||||
initiator = PlayerUnit:GetDCSObject(),
|
||||
}
|
||||
initiator = PlayerUnit:GetDCSObject()
|
||||
}
|
||||
|
||||
world.onEvent( Event )
|
||||
end
|
||||
@@ -972,24 +983,25 @@ end
|
||||
-- @param #EVENTDATA Event Event data table.
|
||||
function EVENT:onEvent( Event )
|
||||
|
||||
--- Function to handle errors.
|
||||
local ErrorHandler = function( errmsg )
|
||||
|
||||
env.info( "Error in SCHEDULER function:" .. errmsg )
|
||||
if BASE.Debug ~= nil then
|
||||
env.info( debug.traceback() )
|
||||
end
|
||||
|
||||
return errmsg
|
||||
end
|
||||
|
||||
|
||||
-- Get event meta data.
|
||||
local EventMeta = _EVENTMETA[Event.id]
|
||||
|
||||
-- Check if this is a known event?
|
||||
if EventMeta then
|
||||
|
||||
if self and self.Events and self.Events[Event.id] and self.MissionEnd == false and (Event.initiator ~= nil or (Event.initiator == nil and Event.id ~= EVENTS.PlayerLeaveUnit)) then
|
||||
if self and self.Events and self.Events[Event.id] and self.MissionEnd==false and (Event.initiator~=nil or (Event.initiator==nil and Event.id~=EVENTS.PlayerLeaveUnit)) then
|
||||
|
||||
-- Check if mission has ended.
|
||||
if Event.id and Event.id == EVENTS.MissionEnd then
|
||||
self.MissionEnd = true
|
||||
end
|
||||
@@ -997,50 +1009,27 @@ function EVENT:onEvent( Event )
|
||||
if Event.initiator then
|
||||
|
||||
Event.IniObjectCategory = Event.initiator:getCategory()
|
||||
|
||||
if Event.IniObjectCategory == Object.Category.UNIT then
|
||||
Event.IniDCSUnit = Event.initiator
|
||||
Event.IniDCSUnitName = Event.IniDCSUnit:getName()
|
||||
Event.IniUnitName = Event.IniDCSUnitName
|
||||
Event.IniDCSGroup = Event.IniDCSUnit:getGroup()
|
||||
Event.IniUnit = UNIT:FindByName( Event.IniDCSUnitName )
|
||||
if not Event.IniUnit then
|
||||
-- Unit can be a CLIENT. Most likely this will be the case ...
|
||||
Event.IniUnit = CLIENT:FindByName( Event.IniDCSUnitName, '', true )
|
||||
end
|
||||
Event.IniDCSGroupName = ""
|
||||
if Event.IniDCSGroup and Event.IniDCSGroup:isExist() then
|
||||
Event.IniDCSGroupName = Event.IniDCSGroup:getName()
|
||||
Event.IniGroup = GROUP:FindByName( Event.IniDCSGroupName )
|
||||
-- if Event.IniGroup then
|
||||
Event.IniGroupName = Event.IniDCSGroupName
|
||||
-- end
|
||||
end
|
||||
Event.IniPlayerName = Event.IniDCSUnit:getPlayerName()
|
||||
Event.IniCoalition = Event.IniDCSUnit:getCoalition()
|
||||
Event.IniTypeName = Event.IniDCSUnit:getTypeName()
|
||||
Event.IniCategory = Event.IniDCSUnit:getDesc().category
|
||||
end
|
||||
|
||||
if Event.IniObjectCategory == Object.Category.STATIC then
|
||||
|
||||
if Event.id == 31 then
|
||||
|
||||
|
||||
if Event.IniObjectCategory == Object.Category.STATIC then
|
||||
---
|
||||
-- Static
|
||||
---
|
||||
if Event.id==31 then
|
||||
-- Event.initiator is a Static object representing the pilot. But getName() errors due to DCS bug.
|
||||
Event.IniDCSUnit = Event.initiator
|
||||
local ID = Event.initiator.id_
|
||||
Event.IniDCSUnitName = string.format( "Ejected Pilot ID %s", tostring( ID ) )
|
||||
local ID=Event.initiator.id_
|
||||
Event.IniDCSUnitName = string.format("Ejected Pilot ID %s", tostring(ID))
|
||||
Event.IniUnitName = Event.IniDCSUnitName
|
||||
Event.IniCoalition = 0
|
||||
Event.IniCategory = 0
|
||||
Event.IniCategory = 0
|
||||
Event.IniTypeName = "Ejected Pilot"
|
||||
elseif Event.id == 33 then -- ejection seat discarded
|
||||
elseif Event.id == 33 then -- ejection seat discarded
|
||||
Event.IniDCSUnit = Event.initiator
|
||||
local ID = Event.initiator.id_
|
||||
Event.IniDCSUnitName = string.format( "Ejection Seat ID %s", tostring( ID ) )
|
||||
local ID=Event.initiator.id_
|
||||
Event.IniDCSUnitName = string.format("Ejection Seat ID %s", tostring(ID))
|
||||
Event.IniUnitName = Event.IniDCSUnitName
|
||||
Event.IniCoalition = 0
|
||||
Event.IniCategory = 0
|
||||
Event.IniCategory = 0
|
||||
Event.IniTypeName = "Ejection Seat"
|
||||
else
|
||||
Event.IniDCSUnit = Event.initiator
|
||||
@@ -1051,9 +1040,47 @@ function EVENT:onEvent( Event )
|
||||
Event.IniCategory = Event.IniDCSUnit:getDesc().category
|
||||
Event.IniTypeName = Event.IniDCSUnit:getTypeName()
|
||||
end
|
||||
|
||||
-- Dead events of units can be delayed and the initiator changed to a static.
|
||||
-- Take care of that.
|
||||
local Unit=UNIT:FindByName(Event.IniDCSUnitName)
|
||||
if Unit then
|
||||
Event.IniObjectCategory = Object.Category.UNIT
|
||||
end
|
||||
end
|
||||
|
||||
if Event.IniObjectCategory == Object.Category.UNIT then
|
||||
---
|
||||
-- Unit
|
||||
---
|
||||
Event.IniDCSUnit = Event.initiator
|
||||
Event.IniDCSUnitName = Event.IniDCSUnit:getName()
|
||||
Event.IniUnitName = Event.IniDCSUnitName
|
||||
Event.IniDCSGroup = Event.IniDCSUnit:getGroup()
|
||||
Event.IniUnit = UNIT:FindByName( Event.IniDCSUnitName )
|
||||
|
||||
if not Event.IniUnit then
|
||||
-- Unit can be a CLIENT. Most likely this will be the case ...
|
||||
Event.IniUnit = CLIENT:FindByName( Event.IniDCSUnitName, '', true )
|
||||
end
|
||||
|
||||
Event.IniDCSGroupName = Event.IniUnit and Event.IniUnit.GroupName or ""
|
||||
if Event.IniDCSGroup and Event.IniDCSGroup:isExist() then
|
||||
Event.IniDCSGroupName = Event.IniDCSGroup:getName()
|
||||
Event.IniGroup = GROUP:FindByName( Event.IniDCSGroupName )
|
||||
Event.IniGroupName = Event.IniDCSGroupName
|
||||
end
|
||||
|
||||
Event.IniPlayerName = Event.IniDCSUnit:getPlayerName()
|
||||
Event.IniCoalition = Event.IniDCSUnit:getCoalition()
|
||||
Event.IniTypeName = Event.IniDCSUnit:getTypeName()
|
||||
Event.IniCategory = Event.IniDCSUnit:getDesc().category
|
||||
end
|
||||
|
||||
if Event.IniObjectCategory == Object.Category.CARGO then
|
||||
---
|
||||
-- Cargo
|
||||
---
|
||||
Event.IniDCSUnit = Event.initiator
|
||||
Event.IniDCSUnitName = Event.IniDCSUnit:getName()
|
||||
Event.IniUnitName = Event.IniDCSUnitName
|
||||
@@ -1064,19 +1091,26 @@ function EVENT:onEvent( Event )
|
||||
end
|
||||
|
||||
if Event.IniObjectCategory == Object.Category.SCENERY then
|
||||
---
|
||||
-- Scenery
|
||||
---
|
||||
|
||||
Event.IniDCSUnit = Event.initiator
|
||||
Event.IniDCSUnitName = Event.IniDCSUnit:getName()
|
||||
Event.IniUnitName = Event.IniDCSUnitName
|
||||
Event.IniUnit = SCENERY:Register( Event.IniDCSUnitName, Event.initiator )
|
||||
Event.IniCategory = Event.IniDCSUnit:getDesc().category
|
||||
Event.IniTypeName = Event.initiator:isExist() and Event.IniDCSUnit:getTypeName() or "SCENERY" -- TODO: Bug fix for 2.1!
|
||||
Event.IniTypeName = Event.initiator:isExist() and Event.IniDCSUnit:getTypeName() or "SCENERY"
|
||||
end
|
||||
|
||||
if Event.IniObjectCategory == Object.Category.BASE then
|
||||
---
|
||||
-- Base Object
|
||||
---
|
||||
Event.IniDCSUnit = Event.initiator
|
||||
Event.IniDCSUnitName = Event.IniDCSUnit:getName()
|
||||
Event.IniUnitName = Event.IniDCSUnitName
|
||||
Event.IniUnit = AIRBASE:FindByName( Event.IniDCSUnitName )
|
||||
Event.IniUnit = AIRBASE:FindByName(Event.IniDCSUnitName)
|
||||
Event.IniCoalition = Event.IniDCSUnit:getCoalition()
|
||||
Event.IniCategory = Event.IniDCSUnit:getDesc().category
|
||||
Event.IniTypeName = Event.IniDCSUnit:getTypeName()
|
||||
@@ -1084,7 +1118,12 @@ function EVENT:onEvent( Event )
|
||||
end
|
||||
|
||||
if Event.target then
|
||||
|
||||
---
|
||||
-- TARGET
|
||||
---
|
||||
|
||||
-- Target category.
|
||||
Event.TgtObjectCategory = Event.target:getCategory()
|
||||
|
||||
if Event.TgtObjectCategory == Object.Category.UNIT then
|
||||
@@ -1097,9 +1136,7 @@ function EVENT:onEvent( Event )
|
||||
if Event.TgtDCSGroup and Event.TgtDCSGroup:isExist() then
|
||||
Event.TgtDCSGroupName = Event.TgtDCSGroup:getName()
|
||||
Event.TgtGroup = GROUP:FindByName( Event.TgtDCSGroupName )
|
||||
-- if Event.TgtGroup then
|
||||
Event.TgtGroupName = Event.TgtDCSGroupName
|
||||
-- end
|
||||
end
|
||||
Event.TgtPlayerName = Event.TgtDCSUnit:getPlayerName()
|
||||
Event.TgtCoalition = Event.TgtDCSUnit:getCoalition()
|
||||
@@ -1118,18 +1155,18 @@ function EVENT:onEvent( Event )
|
||||
Event.TgtCategory = Event.TgtDCSUnit:getDesc().category
|
||||
Event.TgtTypeName = Event.TgtDCSUnit:getTypeName()
|
||||
else
|
||||
Event.TgtDCSUnitName = string.format( "No target object for Event ID %s", tostring( Event.id ) )
|
||||
Event.TgtDCSUnitName = string.format("No target object for Event ID %s", tostring(Event.id))
|
||||
Event.TgtUnitName = Event.TgtDCSUnitName
|
||||
Event.TgtUnit = nil
|
||||
Event.TgtCoalition = 0
|
||||
Event.TgtCategory = 0
|
||||
if Event.id == 6 then
|
||||
Event.TgtTypeName = "Ejected Pilot"
|
||||
Event.TgtDCSUnitName = string.format( "Ejected Pilot ID %s", tostring( Event.IniDCSUnitName ) )
|
||||
Event.TgtDCSUnitName = string.format("Ejected Pilot ID %s", tostring(Event.IniDCSUnitName))
|
||||
Event.TgtUnitName = Event.TgtDCSUnitName
|
||||
elseif Event.id == 33 then
|
||||
Event.TgtTypeName = "Ejection Seat"
|
||||
Event.TgtDCSUnitName = string.format( "Ejection Seat ID %s", tostring( Event.IniDCSUnitName ) )
|
||||
Event.TgtDCSUnitName = string.format("Ejection Seat ID %s", tostring(Event.IniDCSUnitName))
|
||||
Event.TgtUnitName = Event.TgtDCSUnitName
|
||||
else
|
||||
Event.TgtTypeName = "Static"
|
||||
@@ -1147,6 +1184,7 @@ function EVENT:onEvent( Event )
|
||||
end
|
||||
end
|
||||
|
||||
-- Weapon.
|
||||
if Event.weapon then
|
||||
Event.Weapon = Event.weapon
|
||||
Event.WeaponName = Event.Weapon:getTypeName()
|
||||
@@ -1155,49 +1193,48 @@ function EVENT:onEvent( Event )
|
||||
Event.WeaponCoalition = Event.WeaponUNIT and Event.Weapon:getCoalition()
|
||||
Event.WeaponCategory = Event.WeaponUNIT and Event.Weapon:getDesc().category
|
||||
Event.WeaponTypeName = Event.WeaponUNIT and Event.Weapon:getTypeName()
|
||||
-- Event.WeaponTgtDCSUnit = Event.Weapon:getTarget()
|
||||
--Event.WeaponTgtDCSUnit = Event.Weapon:getTarget()
|
||||
end
|
||||
|
||||
-- Place should be given for takeoff and landing events as well as base captured. It should be a DCS airbase.
|
||||
if Event.place then
|
||||
if Event.id == EVENTS.LandingAfterEjection then
|
||||
if Event.id==EVENTS.LandingAfterEjection then
|
||||
-- Place is here the UNIT of which the pilot ejected.
|
||||
-- local name=Event.place:getName() -- This returns a DCS error "Airbase doesn't exit" :(
|
||||
--local name=Event.place:getName() -- This returns a DCS error "Airbase doesn't exit" :(
|
||||
-- However, this is not a big thing, as the aircraft the pilot ejected from is usually long crashed before the ejected pilot touches the ground.
|
||||
-- Event.Place=UNIT:Find(Event.place)
|
||||
--Event.Place=UNIT:Find(Event.place)
|
||||
else
|
||||
Event.Place = AIRBASE:Find( Event.place )
|
||||
Event.PlaceName = Event.Place:GetName()
|
||||
Event.Place=AIRBASE:Find(Event.place)
|
||||
Event.PlaceName=Event.Place:GetName()
|
||||
end
|
||||
end
|
||||
|
||||
-- Mark points.
|
||||
if Event.idx then
|
||||
Event.MarkID = Event.idx
|
||||
Event.MarkVec3 = Event.pos
|
||||
Event.MarkCoordinate = COORDINATE:NewFromVec3( Event.pos )
|
||||
Event.MarkText = Event.text
|
||||
Event.MarkCoalition = Event.coalition
|
||||
Event.MarkID=Event.idx
|
||||
Event.MarkVec3=Event.pos
|
||||
Event.MarkCoordinate=COORDINATE:NewFromVec3(Event.pos)
|
||||
Event.MarkText=Event.text
|
||||
Event.MarkCoalition=Event.coalition
|
||||
Event.MarkGroupID = Event.groupID
|
||||
end
|
||||
|
||||
-- Cargo object.
|
||||
if Event.cargo then
|
||||
Event.Cargo = Event.cargo
|
||||
Event.CargoName = Event.cargo.Name
|
||||
end
|
||||
|
||||
-- Zone object.
|
||||
if Event.zone then
|
||||
Event.Zone = Event.zone
|
||||
Event.ZoneName = Event.zone.ZoneName
|
||||
end
|
||||
|
||||
-- Priority order.
|
||||
local PriorityOrder = EventMeta.Order
|
||||
local PriorityBegin = PriorityOrder == -1 and 5 or 1
|
||||
local PriorityEnd = PriorityOrder == -1 and 1 or 5
|
||||
|
||||
if Event.IniObjectCategory ~= Object.Category.STATIC then
|
||||
self:F( { EventMeta.Text, Event, Event.IniDCSUnitName, Event.TgtDCSUnitName, PriorityOrder } )
|
||||
end
|
||||
local PriorityEnd = PriorityOrder == -1 and 1 or 5
|
||||
|
||||
for EventPriority = PriorityBegin, PriorityEnd, PriorityOrder do
|
||||
|
||||
@@ -1206,12 +1243,12 @@ function EVENT:onEvent( Event )
|
||||
-- Okay, we got the event from DCS. Now loop the SORTED self.EventSorted[] table for the received Event.id, and for each EventData registered, check if a function needs to be called.
|
||||
for EventClass, EventData in pairs( self.Events[Event.id][EventPriority] ) do
|
||||
|
||||
-- if Event.IniObjectCategory ~= Object.Category.STATIC then
|
||||
--if Event.IniObjectCategory ~= Object.Category.STATIC then
|
||||
-- self:E( { "Evaluating: ", EventClass:GetClassNameAndID() } )
|
||||
-- end
|
||||
--end
|
||||
|
||||
Event.IniGroup = GROUP:FindByName( Event.IniDCSGroupName )
|
||||
Event.TgtGroup = GROUP:FindByName( Event.TgtDCSGroupName )
|
||||
Event.IniGroup = Event.IniGroup or GROUP:FindByName( Event.IniDCSGroupName )
|
||||
Event.TgtGroup = Event.TgtGroup or GROUP:FindByName( Event.TgtDCSGroupName )
|
||||
|
||||
-- If the EventData is for a UNIT, the call directly the EventClass EventFunction for that UNIT.
|
||||
if EventData.EventUnit then
|
||||
@@ -1221,39 +1258,35 @@ function EVENT:onEvent( Event )
|
||||
Event.id == EVENTS.PlayerEnterUnit or
|
||||
Event.id == EVENTS.Crash or
|
||||
Event.id == EVENTS.Dead or
|
||||
Event.id == EVENTS.RemoveUnit then
|
||||
Event.id == EVENTS.RemoveUnit or
|
||||
Event.id == EVENTS.UnitLost then
|
||||
|
||||
local UnitName = EventClass:GetName()
|
||||
|
||||
if (EventMeta.Side == "I" and UnitName == Event.IniDCSUnitName) or
|
||||
(EventMeta.Side == "T" and UnitName == Event.TgtDCSUnitName) then
|
||||
|
||||
if ( EventMeta.Side == "I" and UnitName == Event.IniDCSUnitName ) or
|
||||
( EventMeta.Side == "T" and UnitName == Event.TgtDCSUnitName ) then
|
||||
|
||||
-- First test if a EventFunction is Set, otherwise search for the default function
|
||||
if EventData.EventFunction then
|
||||
|
||||
if Event.IniObjectCategory ~= 3 then
|
||||
self:F( { "Calling EventFunction for UNIT ", EventClass:GetClassNameAndID(), ", Unit ", Event.IniUnitName, EventPriority } )
|
||||
end
|
||||
|
||||
local Result, Value = xpcall( function()
|
||||
return EventData.EventFunction( EventClass, Event )
|
||||
end, ErrorHandler )
|
||||
|
||||
local Result, Value = xpcall(
|
||||
function()
|
||||
return EventData.EventFunction( EventClass, Event )
|
||||
end, ErrorHandler )
|
||||
|
||||
else
|
||||
|
||||
-- There is no EventFunction defined, so try to find if a default OnEvent function is defined on the object.
|
||||
local EventFunction = EventClass[EventMeta.Event]
|
||||
local EventFunction = EventClass[ EventMeta.Event ]
|
||||
if EventFunction and type( EventFunction ) == "function" then
|
||||
|
||||
-- Now call the default event function.
|
||||
if Event.IniObjectCategory ~= 3 then
|
||||
self:F( { "Calling " .. EventMeta.Event .. " for Class ", EventClass:GetClassNameAndID(), EventPriority } )
|
||||
end
|
||||
|
||||
local Result, Value = xpcall( function()
|
||||
return EventFunction( EventClass, Event )
|
||||
end, ErrorHandler )
|
||||
local Result, Value = xpcall(
|
||||
function()
|
||||
return EventFunction( EventClass, Event )
|
||||
end, ErrorHandler )
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
else
|
||||
@@ -1271,48 +1304,43 @@ function EVENT:onEvent( Event )
|
||||
Event.id == EVENTS.PlayerEnterUnit or
|
||||
Event.id == EVENTS.Crash or
|
||||
Event.id == EVENTS.Dead or
|
||||
Event.id == EVENTS.RemoveUnit then
|
||||
Event.id == EVENTS.RemoveUnit or
|
||||
Event.id == EVENTS.UnitLost then
|
||||
|
||||
-- We can get the name of the EventClass, which is now always a GROUP object.
|
||||
local GroupName = EventClass:GetName()
|
||||
|
||||
if (EventMeta.Side == "I" and GroupName == Event.IniDCSGroupName) or
|
||||
(EventMeta.Side == "T" and GroupName == Event.TgtDCSGroupName) then
|
||||
if ( EventMeta.Side == "I" and GroupName == Event.IniDCSGroupName ) or
|
||||
( EventMeta.Side == "T" and GroupName == Event.TgtDCSGroupName ) then
|
||||
|
||||
-- First test if a EventFunction is Set, otherwise search for the default function
|
||||
if EventData.EventFunction then
|
||||
|
||||
if Event.IniObjectCategory ~= 3 then
|
||||
self:F( { "Calling EventFunction for GROUP ", EventClass:GetClassNameAndID(), ", Unit ", Event.IniUnitName, EventPriority } )
|
||||
end
|
||||
|
||||
local Result, Value = xpcall( function()
|
||||
return EventData.EventFunction( EventClass, Event, unpack( EventData.Params ) )
|
||||
end, ErrorHandler )
|
||||
local Result, Value = xpcall(
|
||||
function()
|
||||
return EventData.EventFunction( EventClass, Event, unpack( EventData.Params ) )
|
||||
end, ErrorHandler )
|
||||
|
||||
else
|
||||
|
||||
-- There is no EventFunction defined, so try to find if a default OnEvent function is defined on the object.
|
||||
local EventFunction = EventClass[EventMeta.Event]
|
||||
local EventFunction = EventClass[ EventMeta.Event ]
|
||||
if EventFunction and type( EventFunction ) == "function" then
|
||||
|
||||
-- Now call the default event function.
|
||||
if Event.IniObjectCategory ~= 3 then
|
||||
self:F( { "Calling " .. EventMeta.Event .. " for GROUP ", EventClass:GetClassNameAndID(), EventPriority } )
|
||||
end
|
||||
|
||||
local Result, Value = xpcall( function()
|
||||
return EventFunction( EventClass, Event, unpack( EventData.Params ) )
|
||||
end, ErrorHandler )
|
||||
local Result, Value = xpcall(
|
||||
function()
|
||||
return EventFunction( EventClass, Event, unpack( EventData.Params ) )
|
||||
end, ErrorHandler )
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
-- The EventClass is not alive anymore, we remove it from the EventHandlers...
|
||||
-- self:RemoveEvent( EventClass, Event.id )
|
||||
--self:RemoveEvent( EventClass, Event.id )
|
||||
end
|
||||
else
|
||||
|
||||
|
||||
-- If the EventData is not bound to a specific unit, then call the EventClass EventFunction.
|
||||
-- Note that here the EventFunction will need to implement and determine the logic for the relevant source- or target unit, or weapon.
|
||||
if not EventData.EventUnit then
|
||||
@@ -1321,28 +1349,25 @@ function EVENT:onEvent( Event )
|
||||
if EventData.EventFunction then
|
||||
|
||||
-- There is an EventFunction defined, so call the EventFunction.
|
||||
if Event.IniObjectCategory ~= 3 then
|
||||
self:F2( { "Calling EventFunction for Class ", EventClass:GetClassNameAndID(), EventPriority } )
|
||||
end
|
||||
local Result, Value = xpcall( function()
|
||||
return EventData.EventFunction( EventClass, Event )
|
||||
end, ErrorHandler )
|
||||
local Result, Value = xpcall(
|
||||
function()
|
||||
return EventData.EventFunction( EventClass, Event )
|
||||
end, ErrorHandler )
|
||||
else
|
||||
|
||||
-- There is no EventFunction defined, so try to find if a default OnEvent function is defined on the object.
|
||||
local EventFunction = EventClass[EventMeta.Event]
|
||||
local EventFunction = EventClass[ EventMeta.Event ]
|
||||
if EventFunction and type( EventFunction ) == "function" then
|
||||
|
||||
-- Now call the default event function.
|
||||
if Event.IniObjectCategory ~= 3 then
|
||||
self:F2( { "Calling " .. EventMeta.Event .. " for Class ", EventClass:GetClassNameAndID(), EventPriority } )
|
||||
end
|
||||
|
||||
local Result, Value = xpcall( function()
|
||||
local Result, Value = EventFunction( EventClass, Event )
|
||||
return Result, Value
|
||||
end, ErrorHandler )
|
||||
local Result, Value = xpcall(
|
||||
function()
|
||||
local Result, Value = EventFunction( EventClass, Event )
|
||||
return Result, Value
|
||||
end, ErrorHandler )
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
@@ -1365,7 +1390,7 @@ function EVENT:onEvent( Event )
|
||||
self:T( { EventMeta.Text, Event } )
|
||||
end
|
||||
else
|
||||
self:E( string.format( "WARNING: Could not get EVENTMETA data for event ID=%d! Is this an unknown/new DCS event?", tostring( Event.id ) ) )
|
||||
self:E(string.format("WARNING: Could not get EVENTMETA data for event ID=%d! Is this an unknown/new DCS event?", tostring(Event.id)))
|
||||
end
|
||||
|
||||
Event = nil
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,87 +1,89 @@
|
||||
--- **Core** - Models the process to achieve goal(s).
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- ## Features:
|
||||
--
|
||||
--
|
||||
-- * Define the goal.
|
||||
-- * Monitor the goal achievement.
|
||||
-- * Manage goal contribution by players.
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- Classes that implement a goal achievement, will derive from GOAL to implement the ways how the achievements can be realized.
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
-- ### Contributions: **funkyfranky**
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- @module Core.Goal
|
||||
-- @image Core_Goal.JPG
|
||||
|
||||
|
||||
do -- Goal
|
||||
|
||||
--- @type GOAL
|
||||
-- @extends Core.Fsm#FSM
|
||||
|
||||
|
||||
--- Models processes that have an objective with a defined achievement. Derived classes implement the ways how the achievements can be realized.
|
||||
--
|
||||
--
|
||||
-- # 1. GOAL constructor
|
||||
--
|
||||
--
|
||||
-- * @{#GOAL.New}(): Creates a new GOAL object.
|
||||
--
|
||||
--
|
||||
-- # 2. GOAL is a finite state machine (FSM).
|
||||
--
|
||||
--
|
||||
-- ## 2.1. GOAL States
|
||||
--
|
||||
--
|
||||
-- * **Pending**: The goal object is in progress.
|
||||
-- * **Achieved**: The goal objective is Achieved.
|
||||
--
|
||||
--
|
||||
-- ## 2.2. GOAL Events
|
||||
--
|
||||
--
|
||||
-- * **Achieved**: Set the goal objective to Achieved.
|
||||
--
|
||||
--
|
||||
-- # 3. Player contributions.
|
||||
--
|
||||
--
|
||||
-- Goals are most of the time achieved by players. These player achievements can be registered as part of the goal achievement.
|
||||
-- Use @{#GOAL.AddPlayerContribution}() to add a player contribution to the goal.
|
||||
-- The player contributions are based on a points system, an internal counter per player.
|
||||
-- So once the goal has been achieved, the player contributions can be queried using @{#GOAL.GetPlayerContributions}(),
|
||||
-- So once the goal has been achieved, the player contributions can be queried using @{#GOAL.GetPlayerContributions}(),
|
||||
-- that retrieves all contributions done by the players. For one player, the contribution can be queried using @{#GOAL.GetPlayerContribution}().
|
||||
-- The total amount of player contributions can be queried using @{#GOAL.GetTotalContributions}().
|
||||
--
|
||||
--
|
||||
-- # 4. Goal achievement.
|
||||
--
|
||||
--
|
||||
-- Once the goal is achieved, the mission designer will need to trigger the goal achievement using the **Achieved** event.
|
||||
-- The underlying 2 examples will achieve the goals for the `Goal` object:
|
||||
--
|
||||
--
|
||||
-- Goal:Achieved() -- Achieve the goal immediately.
|
||||
-- Goal:__Achieved( 30 ) -- Achieve the goal within 30 seconds.
|
||||
--
|
||||
--
|
||||
-- # 5. Check goal achievement.
|
||||
--
|
||||
--
|
||||
-- The method @{#GOAL.IsAchieved}() will return true if the goal is achieved (the trigger **Achieved** was executed).
|
||||
-- You can use this method to check asynchronously if a goal has been achieved, for example using a scheduler.
|
||||
--
|
||||
--
|
||||
-- @field #GOAL
|
||||
GOAL = {
|
||||
ClassName = "GOAL",
|
||||
}
|
||||
|
||||
|
||||
--- @field #table GOAL.Players
|
||||
GOAL.Players = {}
|
||||
|
||||
--- @field #number GOAL.TotalContributions
|
||||
GOAL.TotalContributions = 0
|
||||
|
||||
|
||||
--- GOAL Constructor.
|
||||
-- @param #GOAL self
|
||||
-- @return #GOAL
|
||||
function GOAL:New()
|
||||
|
||||
|
||||
local self = BASE:Inherit( self, FSM:New() ) -- #GOAL
|
||||
self:F( {} )
|
||||
|
||||
@@ -102,10 +104,11 @@ do -- Goal
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
|
||||
|
||||
|
||||
self:SetStartState( "Pending" )
|
||||
self:AddTransition( "*", "Achieved", "Achieved" )
|
||||
|
||||
self:AddTransition( "*", "Achieved", "Achieved" )
|
||||
|
||||
--- Achieved Handler OnBefore for GOAL
|
||||
-- @function [parent=#GOAL] OnBeforeAchieved
|
||||
-- @param #GOAL self
|
||||
@@ -113,44 +116,47 @@ do -- Goal
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @return #boolean
|
||||
|
||||
|
||||
--- Achieved Handler OnAfter for GOAL
|
||||
-- @function [parent=#GOAL] OnAfterAchieved
|
||||
-- @param #GOAL self
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
|
||||
|
||||
--- Achieved Trigger for GOAL
|
||||
-- @function [parent=#GOAL] Achieved
|
||||
-- @param #GOAL self
|
||||
|
||||
|
||||
--- Achieved Asynchronous Trigger for GOAL
|
||||
-- @function [parent=#GOAL] __Achieved
|
||||
-- @param #GOAL self
|
||||
-- @param #number Delay
|
||||
|
||||
|
||||
self:SetEventPriority( 5 )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
|
||||
--- Add a new contribution by a player.
|
||||
-- @param #GOAL self
|
||||
-- @param #string PlayerName The name of the player.
|
||||
function GOAL:AddPlayerContribution( PlayerName )
|
||||
self:F( { PlayerName } )
|
||||
self:F({PlayerName})
|
||||
self.Players[PlayerName] = self.Players[PlayerName] or 0
|
||||
self.Players[PlayerName] = self.Players[PlayerName] + 1
|
||||
self.TotalContributions = self.TotalContributions + 1
|
||||
end
|
||||
|
||||
|
||||
|
||||
--- @param #GOAL self
|
||||
-- @param #number Player contribution.
|
||||
function GOAL:GetPlayerContribution( PlayerName )
|
||||
return self.Players[PlayerName] or 0
|
||||
return self.Players[PlayerName] or 0
|
||||
end
|
||||
|
||||
|
||||
--- Get the players who contributed to achieve the goal.
|
||||
-- The result is a list of players, sorted by the name of the players.
|
||||
-- @param #GOAL self
|
||||
@@ -159,6 +165,7 @@ do -- Goal
|
||||
return self.Players or {}
|
||||
end
|
||||
|
||||
|
||||
--- Gets the total contributions that happened to achieve the goal.
|
||||
-- The result is a number.
|
||||
-- @param #GOAL self
|
||||
@@ -166,7 +173,9 @@ do -- Goal
|
||||
function GOAL:GetTotalContributions()
|
||||
return self.TotalContributions or 0
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
--- Validates if the goal is achieved.
|
||||
-- @param #GOAL self
|
||||
-- @return #boolean true if the goal is achieved.
|
||||
@@ -174,4 +183,4 @@ do -- Goal
|
||||
return self:Is( "Achieved" )
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@@ -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.
|
||||
--------------------------------------------------------------------------
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,18 +1,19 @@
|
||||
--- **Core** - Informs the players using messages during a simulation.
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- ## Features:
|
||||
--
|
||||
--
|
||||
-- * A more advanced messaging system using the DCS message system.
|
||||
-- * Time messages.
|
||||
-- * Send messages based on a message type, which has a pre-defined duration that can be tweaked in SETTINGS.
|
||||
-- * Send message to all players.
|
||||
-- * Send messages to a coalition.
|
||||
-- * Send messages to a specific group.
|
||||
--
|
||||
-- * Send messages to a specific unit or client.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- @module Core.Message
|
||||
-- @image Core_Message.JPG
|
||||
|
||||
@@ -23,42 +24,43 @@
|
||||
--- Message System to display Messages to Clients, Coalitions or All.
|
||||
-- Messages are shown on the display panel for an amount of seconds, and will then disappear.
|
||||
-- Messages can contain a category which is indicating the category of the message.
|
||||
--
|
||||
--
|
||||
-- ## MESSAGE construction
|
||||
--
|
||||
--
|
||||
-- Messages are created with @{#MESSAGE.New}. Note that when the MESSAGE object is created, no message is sent yet.
|
||||
-- To send messages, you need to use the To functions.
|
||||
--
|
||||
--
|
||||
-- ## Send messages to an audience
|
||||
--
|
||||
--
|
||||
-- Messages are sent:
|
||||
--
|
||||
-- * 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}().
|
||||
-- * To all Players using @{#MESSAGE.ToAll}().
|
||||
--
|
||||
--
|
||||
-- ## Send conditionally to an audience
|
||||
--
|
||||
--
|
||||
-- Messages can be sent conditionally to an audience (when a condition is true):
|
||||
--
|
||||
--
|
||||
-- * To all players using @{#MESSAGE.ToAllIf}().
|
||||
-- * To a coalition using @{#MESSAGE.ToCoalitionIf}().
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
-- ### Contributions:
|
||||
--
|
||||
-- ### Contributions:
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- @field #MESSAGE
|
||||
MESSAGE = {
|
||||
ClassName = "MESSAGE",
|
||||
MessageCategory = 0,
|
||||
MessageID = 0,
|
||||
ClassName = "MESSAGE",
|
||||
MessageCategory = 0,
|
||||
MessageID = 0,
|
||||
}
|
||||
|
||||
--- Message Types
|
||||
@@ -68,9 +70,10 @@ MESSAGE.Type = {
|
||||
Information = "Information",
|
||||
Briefing = "Briefing Report",
|
||||
Overview = "Overview Report",
|
||||
Detailed = "Detailed Report",
|
||||
Detailed = "Detailed Report"
|
||||
}
|
||||
|
||||
|
||||
--- Creates a new MESSAGE object. Note that these MESSAGE objects are not yet displayed on the display panel. You must use the functions @{ToClient} or @{ToCoalition} or @{ToAll} to send these Messages to the respective recipients.
|
||||
-- @param self
|
||||
-- @param #string MessageText is the text of the Message.
|
||||
@@ -79,52 +82,52 @@ MESSAGE.Type = {
|
||||
-- @param #boolean ClearScreen (optional) Clear all previous messages if true.
|
||||
-- @return #MESSAGE
|
||||
-- @usage
|
||||
--
|
||||
-- -- Create a series of new Messages.
|
||||
-- -- MessageAll is meant to be sent to all players, for 25 seconds, and is classified as "Score".
|
||||
-- -- MessageRED is meant to be sent to the RED players only, for 10 seconds, and is classified as "End of Mission", with ID "Win".
|
||||
-- -- MessageClient1 is meant to be sent to a Client, for 25 seconds, and is classified as "Score", with ID "Score".
|
||||
-- -- MessageClient1 is meant to be sent to a Client, for 25 seconds, and is classified as "Score", with ID "Score".
|
||||
-- MessageAll = MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", 25, "End of Mission" )
|
||||
-- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", 25, "Penalty" )
|
||||
-- MessageClient1 = MESSAGE:New( "Congratulations, you've just hit a target", 25, "Score" )
|
||||
-- MessageClient2 = MESSAGE:New( "Congratulations, you've just killed a target", 25, "Score")
|
||||
--
|
||||
-- -- Create a series of new Messages.
|
||||
-- -- MessageAll is meant to be sent to all players, for 25 seconds, and is classified as "Score".
|
||||
-- -- MessageRED is meant to be sent to the RED players only, for 10 seconds, and is classified as "End of Mission", with ID "Win".
|
||||
-- -- MessageClient1 is meant to be sent to a Client, for 25 seconds, and is classified as "Score", with ID "Score".
|
||||
-- -- MessageClient1 is meant to be sent to a Client, for 25 seconds, and is classified as "Score", with ID "Score".
|
||||
-- MessageAll = MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", 25, "End of Mission" )
|
||||
-- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", 25, "Penalty" )
|
||||
-- MessageClient1 = MESSAGE:New( "Congratulations, you've just hit a target", 25, "Score" )
|
||||
-- MessageClient2 = MESSAGE:New( "Congratulations, you've just killed a target", 25, "Score")
|
||||
function MESSAGE:New( MessageText, MessageDuration, MessageCategory, ClearScreen )
|
||||
local self = BASE:Inherit( self, BASE:New() )
|
||||
self:F( { MessageText, MessageDuration, MessageCategory } )
|
||||
local self = BASE:Inherit( self, BASE:New() )
|
||||
self:F( { MessageText, MessageDuration, MessageCategory } )
|
||||
|
||||
|
||||
self.MessageType = nil
|
||||
|
||||
|
||||
-- When no MessageCategory is given, we don't show it as a title...
|
||||
if MessageCategory and MessageCategory ~= "" then
|
||||
if MessageCategory:sub( -1 ) ~= "\n" then
|
||||
if MessageCategory and MessageCategory ~= "" then
|
||||
if MessageCategory:sub(-1) ~= "\n" then
|
||||
self.MessageCategory = MessageCategory .. ": "
|
||||
else
|
||||
self.MessageCategory = MessageCategory:sub( 1, -2 ) .. ":\n"
|
||||
self.MessageCategory = MessageCategory:sub( 1, -2 ) .. ":\n"
|
||||
end
|
||||
else
|
||||
self.MessageCategory = ""
|
||||
end
|
||||
|
||||
self.ClearScreen = false
|
||||
if ClearScreen ~= nil then
|
||||
self.ClearScreen = ClearScreen
|
||||
|
||||
self.ClearScreen=false
|
||||
if ClearScreen~=nil then
|
||||
self.ClearScreen=ClearScreen
|
||||
end
|
||||
|
||||
self.MessageDuration = MessageDuration or 5
|
||||
self.MessageTime = timer.getTime()
|
||||
self.MessageText = MessageText:gsub( "^\n", "", 1 ):gsub( "\n$", "", 1 )
|
||||
self.MessageDuration = MessageDuration or 5
|
||||
self.MessageTime = timer.getTime()
|
||||
self.MessageText = MessageText:gsub("^\n","",1):gsub("\n$","",1)
|
||||
|
||||
self.MessageSent = false
|
||||
self.MessageGroup = false
|
||||
self.MessageCoalition = false
|
||||
|
||||
self.MessageSent = false
|
||||
self.MessageGroup = false
|
||||
self.MessageCoalition = false
|
||||
|
||||
return self
|
||||
return self
|
||||
end
|
||||
|
||||
--- Creates a new MESSAGE object of a certain type.
|
||||
-- Note that these MESSAGE objects are not yet displayed on the display panel.
|
||||
|
||||
--- Creates a new MESSAGE object of a certain type.
|
||||
-- Note that these MESSAGE objects are not yet displayed on the display panel.
|
||||
-- You must use the functions @{ToClient} or @{ToCoalition} or @{ToAll} to send these Messages to the respective recipients.
|
||||
-- The message display times are automatically defined based on the timing settings in the @{Settings} menu.
|
||||
-- @param self
|
||||
@@ -133,83 +136,86 @@ end
|
||||
-- @param #boolean ClearScreen (optional) Clear all previous messages.
|
||||
-- @return #MESSAGE
|
||||
-- @usage
|
||||
--
|
||||
-- MessageAll = MESSAGE:NewType( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", MESSAGE.Type.Information )
|
||||
-- MessageRED = MESSAGE:NewType( "To the RED Players: You receive a penalty because you've killed one of your own units", MESSAGE.Type.Information )
|
||||
-- MessageClient1 = MESSAGE:NewType( "Congratulations, you've just hit a target", MESSAGE.Type.Update )
|
||||
-- MessageClient2 = MESSAGE:NewType( "Congratulations, you've just killed a target", MESSAGE.Type.Update )
|
||||
--
|
||||
function MESSAGE:NewType( MessageText, MessageType, ClearScreen )
|
||||
|
||||
local self = BASE:Inherit( self, BASE:New() )
|
||||
self:F( { MessageText } )
|
||||
|
||||
|
||||
self.MessageType = MessageType
|
||||
|
||||
self.ClearScreen = false
|
||||
if ClearScreen ~= nil then
|
||||
self.ClearScreen = ClearScreen
|
||||
|
||||
self.ClearScreen=false
|
||||
if ClearScreen~=nil then
|
||||
self.ClearScreen=ClearScreen
|
||||
end
|
||||
|
||||
self.MessageTime = timer.getTime()
|
||||
self.MessageText = MessageText:gsub( "^\n", "", 1 ):gsub( "\n$", "", 1 )
|
||||
|
||||
self.MessageText = MessageText:gsub("^\n","",1):gsub("\n$","",1)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Clears all previous messages from the screen before the new message is displayed. Not that this must come before all functions starting with ToX(), e.g. ToAll(), ToGroup() etc.
|
||||
|
||||
|
||||
--- Clears all previous messages from the screen before the new message is displayed. Not that this must come before all functions starting with ToX(), e.g. ToAll(), ToGroup() etc.
|
||||
-- @param #MESSAGE self
|
||||
-- @return #MESSAGE
|
||||
function MESSAGE:Clear()
|
||||
self:F()
|
||||
self.ClearScreen = true
|
||||
self.ClearScreen=true
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
|
||||
--- Sends a MESSAGE to a Client Group. Note that the Group needs to be defined within the ME with the skillset "Client" or "Player".
|
||||
-- @param #MESSAGE self
|
||||
-- @param Wrapper.Client#CLIENT Client is the Group of the Client.
|
||||
-- @param Core.Settings#SETTINGS Settings Settings used to display the message.
|
||||
-- @return #MESSAGE
|
||||
-- @usage
|
||||
-- -- Send the 2 messages created with the @{New} method to the Client Group.
|
||||
-- -- Note that the Message of MessageClient2 is overwriting the Message of MessageClient1.
|
||||
-- ClientGroup = Group.getByName( "ClientGroup" )
|
||||
--
|
||||
-- -- Send the 2 messages created with the @{New} method to the Client Group.
|
||||
-- -- Note that the Message of MessageClient2 is overwriting the Message of MessageClient1.
|
||||
-- ClientGroup = Group.getByName( "ClientGroup" )
|
||||
--
|
||||
-- MessageClient1 = MESSAGE:New( "Congratulations, you've just hit a target", "Score", 25, "Score" ):ToClient( ClientGroup )
|
||||
-- MessageClient2 = MESSAGE:New( "Congratulations, you've just killed a target", "Score", 25, "Score" ):ToClient( ClientGroup )
|
||||
-- or
|
||||
-- MESSAGE:New( "Congratulations, you've just hit a target", "Score", 25, "Score" ):ToClient( ClientGroup )
|
||||
-- MESSAGE:New( "Congratulations, you've just killed a target", "Score", 25, "Score" ):ToClient( ClientGroup )
|
||||
-- or
|
||||
-- MessageClient1 = MESSAGE:New( "Congratulations, you've just hit a target", "Score", 25, "Score" )
|
||||
-- MessageClient2 = MESSAGE:New( "Congratulations, you've just killed a target", "Score", 25, "Score" )
|
||||
-- MessageClient1:ToClient( ClientGroup )
|
||||
-- MessageClient2:ToClient( ClientGroup )
|
||||
--
|
||||
-- MessageClient1 = MESSAGE:New( "Congratulations, you've just hit a target", "Score", 25, "Score" ):ToClient( ClientGroup )
|
||||
-- MessageClient2 = MESSAGE:New( "Congratulations, you've just killed a target", "Score", 25, "Score" ):ToClient( ClientGroup )
|
||||
-- or
|
||||
-- MESSAGE:New( "Congratulations, you've just hit a target", "Score", 25, "Score" ):ToClient( ClientGroup )
|
||||
-- MESSAGE:New( "Congratulations, you've just killed a target", "Score", 25, "Score" ):ToClient( ClientGroup )
|
||||
-- or
|
||||
-- MessageClient1 = MESSAGE:New( "Congratulations, you've just hit a target", "Score", 25, "Score" )
|
||||
-- MessageClient2 = MESSAGE:New( "Congratulations, you've just killed a target", "Score", 25, "Score" )
|
||||
-- MessageClient1:ToClient( ClientGroup )
|
||||
-- MessageClient2:ToClient( ClientGroup )
|
||||
function MESSAGE:ToClient( Client, Settings )
|
||||
self:F( Client )
|
||||
self:F( Client )
|
||||
|
||||
if Client and Client:GetClientGroupID() then
|
||||
if Client and Client:GetClientGroupID() then
|
||||
|
||||
if self.MessageType then
|
||||
local Settings = Settings or (Client and _DATABASE:GetPlayerSettings( Client:GetPlayerName() )) or _SETTINGS -- Core.Settings#SETTINGS
|
||||
local Settings = Settings or ( Client and _DATABASE:GetPlayerSettings( Client:GetPlayerName() ) ) or _SETTINGS -- Core.Settings#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 )
|
||||
end
|
||||
end
|
||||
|
||||
return self
|
||||
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.outTextForUnit( Unit:GetID(), self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration , self.ClearScreen)
|
||||
end
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sends a MESSAGE to a Group.
|
||||
--- Sends a MESSAGE to a Group.
|
||||
-- @param #MESSAGE self
|
||||
-- @param Wrapper.Group#GROUP Group to which the message is displayed.
|
||||
-- @return #MESSAGE Message object.
|
||||
@@ -217,80 +223,99 @@ function MESSAGE:ToGroup( Group, Settings )
|
||||
self:F( Group.GroupName )
|
||||
|
||||
if Group then
|
||||
|
||||
|
||||
if self.MessageType then
|
||||
local Settings = Settings or (Group and _DATABASE:GetPlayerSettings( Group:GetPlayerName() )) or _SETTINGS -- Core.Settings#SETTINGS
|
||||
local Settings = Settings or ( Group and _DATABASE:GetPlayerSettings( Group: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.outTextForGroup( Group:GetID(), self.MessageCategory .. self.MessageText:gsub( "\n$", "" ):gsub( "\n$", "" ), self.MessageDuration, self.ClearScreen )
|
||||
self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration )
|
||||
trigger.action.outTextForGroup( Group:GetID(), self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration, self.ClearScreen )
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
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
|
||||
-- @param #MESSAGE self
|
||||
-- @return #MESSAGE
|
||||
-- @usage
|
||||
--
|
||||
-- -- Send a message created with the @{New} method to the BLUE coalition.
|
||||
-- MessageBLUE = MESSAGE:New( "To the BLUE Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToBlue()
|
||||
-- or
|
||||
-- MESSAGE:New( "To the BLUE Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToBlue()
|
||||
-- or
|
||||
-- MessageBLUE = MESSAGE:New( "To the BLUE Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" )
|
||||
-- MessageBLUE:ToBlue()
|
||||
--
|
||||
-- -- Send a message created with the @{New} method to the BLUE coalition.
|
||||
-- MessageBLUE = MESSAGE:New( "To the BLUE Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToBlue()
|
||||
-- or
|
||||
-- MESSAGE:New( "To the BLUE Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToBlue()
|
||||
-- or
|
||||
-- MessageBLUE = MESSAGE:New( "To the BLUE Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" )
|
||||
-- MessageBLUE:ToBlue()
|
||||
function MESSAGE:ToBlue()
|
||||
self:F()
|
||||
self:F()
|
||||
|
||||
self:ToCoalition( coalition.side.BLUE )
|
||||
|
||||
return self
|
||||
self:ToCoalition( coalition.side.BLUE )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sends a MESSAGE to the Red Coalition.
|
||||
--- Sends a MESSAGE to the Red Coalition.
|
||||
-- @param #MESSAGE self
|
||||
-- @return #MESSAGE
|
||||
-- @usage
|
||||
--
|
||||
-- -- Send a message created with the @{New} method to the RED coalition.
|
||||
-- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToRed()
|
||||
-- or
|
||||
-- MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToRed()
|
||||
-- or
|
||||
-- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" )
|
||||
-- MessageRED:ToRed()
|
||||
--
|
||||
function MESSAGE:ToRed()
|
||||
self:F()
|
||||
-- -- Send a message created with the @{New} method to the RED coalition.
|
||||
-- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToRed()
|
||||
-- or
|
||||
-- MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToRed()
|
||||
-- or
|
||||
-- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" )
|
||||
-- MessageRED:ToRed()
|
||||
function MESSAGE:ToRed( )
|
||||
self:F()
|
||||
|
||||
self:ToCoalition( coalition.side.RED )
|
||||
|
||||
return self
|
||||
self:ToCoalition( coalition.side.RED )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sends a MESSAGE to a Coalition.
|
||||
--- Sends a MESSAGE to a Coalition.
|
||||
-- @param #MESSAGE self
|
||||
-- @param #DCS.coalition.side CoalitionSide @{#DCS.coalition.side} to which the message is displayed.
|
||||
-- @param Core.Settings#SETTINGS Settings (Optional) Settings for message display.
|
||||
-- @return #MESSAGE Message object.
|
||||
-- @usage
|
||||
--
|
||||
-- -- Send a message created with the @{New} method to the RED coalition.
|
||||
-- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToCoalition( coalition.side.RED )
|
||||
-- or
|
||||
-- MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToCoalition( coalition.side.RED )
|
||||
-- or
|
||||
-- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" )
|
||||
-- MessageRED:ToCoalition( coalition.side.RED )
|
||||
--
|
||||
-- -- Send a message created with the @{New} method to the RED coalition.
|
||||
-- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToCoalition( coalition.side.RED )
|
||||
-- or
|
||||
-- MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToCoalition( coalition.side.RED )
|
||||
-- or
|
||||
-- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" )
|
||||
-- MessageRED:ToCoalition( coalition.side.RED )
|
||||
function MESSAGE:ToCoalition( CoalitionSide, Settings )
|
||||
self:F( CoalitionSide )
|
||||
self:F( CoalitionSide )
|
||||
|
||||
if self.MessageType then
|
||||
local Settings = Settings or _SETTINGS -- Core.Settings#SETTINGS
|
||||
@@ -298,20 +323,20 @@ function MESSAGE:ToCoalition( CoalitionSide, Settings )
|
||||
self.MessageCategory = "" -- self.MessageType .. ": "
|
||||
end
|
||||
|
||||
if CoalitionSide then
|
||||
if CoalitionSide then
|
||||
if self.MessageDuration ~= 0 then
|
||||
self:T( self.MessageCategory .. self.MessageText:gsub( "\n$", "" ):gsub( "\n$", "" ) .. " / " .. self.MessageDuration )
|
||||
trigger.action.outTextForCoalition( CoalitionSide, self.MessageText:gsub( "\n$", "" ):gsub( "\n$", "" ), self.MessageDuration, self.ClearScreen )
|
||||
self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration )
|
||||
trigger.action.outTextForCoalition( CoalitionSide, self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration, self.ClearScreen )
|
||||
end
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sends a MESSAGE to a Coalition if the given Condition is true.
|
||||
--- Sends a MESSAGE to a Coalition if the given Condition is true.
|
||||
-- @param #MESSAGE self
|
||||
-- @param CoalitionSide needs to be filled out by the defined structure of the standard scripting engine @{coalition.side}.
|
||||
-- @param #boolean Condition Sends the message only if the condition is true.
|
||||
-- @param #boolean Condition Sends the message only if the condition is true.
|
||||
-- @return #MESSAGE self
|
||||
function MESSAGE:ToCoalitionIf( CoalitionSide, Condition )
|
||||
self:F( CoalitionSide )
|
||||
@@ -319,7 +344,7 @@ function MESSAGE:ToCoalitionIf( CoalitionSide, Condition )
|
||||
if Condition and Condition == true then
|
||||
self:ToCoalition( CoalitionSide )
|
||||
end
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -328,16 +353,14 @@ end
|
||||
-- @param Core.Settings#Settings Settings (Optional) Settings for message display.
|
||||
-- @return #MESSAGE
|
||||
-- @usage
|
||||
--
|
||||
-- -- Send a message created to all players.
|
||||
-- MessageAll = MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", "End of Mission", 25, "Win" ):ToAll()
|
||||
-- or
|
||||
-- MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", "End of Mission", 25, "Win" ):ToAll()
|
||||
-- or
|
||||
-- MessageAll = MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", "End of Mission", 25, "Win" )
|
||||
-- MessageAll:ToAll()
|
||||
--
|
||||
function MESSAGE:ToAll( Settings )
|
||||
-- -- Send a message created to all players.
|
||||
-- MessageAll = MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", "End of Mission", 25, "Win" ):ToAll()
|
||||
-- or
|
||||
-- MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", "End of Mission", 25, "Win" ):ToAll()
|
||||
-- or
|
||||
-- MessageAll = MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", "End of Mission", 25, "Win" )
|
||||
-- MessageAll:ToAll()
|
||||
function MESSAGE:ToAll(Settings)
|
||||
self:F()
|
||||
|
||||
if self.MessageType then
|
||||
@@ -347,13 +370,14 @@ function MESSAGE:ToAll( Settings )
|
||||
end
|
||||
|
||||
if self.MessageDuration ~= 0 then
|
||||
self:T( self.MessageCategory .. self.MessageText:gsub( "\n$", "" ):gsub( "\n$", "" ) .. " / " .. self.MessageDuration )
|
||||
trigger.action.outText( self.MessageCategory .. self.MessageText:gsub( "\n$", "" ):gsub( "\n$", "" ), self.MessageDuration, self.ClearScreen )
|
||||
self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration )
|
||||
trigger.action.outText( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration, self.ClearScreen )
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Sends a MESSAGE to all players if the given Condition is true.
|
||||
-- @param #MESSAGE self
|
||||
-- @return #MESSAGE
|
||||
@@ -363,5 +387,5 @@ function MESSAGE:ToAllIf( Condition )
|
||||
self:ToAll()
|
||||
end
|
||||
|
||||
return self
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -194,7 +194,12 @@ do -- COORDINATE
|
||||
-- ## 9) Coordinate text generation
|
||||
--
|
||||
-- * @{#COORDINATE.ToStringBR}(): Generates a Bearing & Range text in the format of DDD for DI where DDD is degrees and DI is distance.
|
||||
-- * @{#COORDINATE.ToStringLL}(): Generates a Latutude & Longutude text.
|
||||
-- * @{#COORDINATE.ToStringBRA}(): Generates a Bearing, Range & Altitude text.
|
||||
-- * @{#COORDINATE.ToStringBRAANATO}(): Generates a Generates a Bearing, Range, Aspect & Altitude text in NATOPS.
|
||||
-- * @{#COORDINATE.ToStringLL}(): Generates a Latutide & Longitude text.
|
||||
-- * @{#COORDINATE.ToStringLLDMS}(): Generates a Lat, Lon, Degree, Minute, Second text.
|
||||
-- * @{#COORDINATE.ToStringLLDDM}(): Generates a Lat, Lon, Degree, decimal Minute text.
|
||||
-- * @{#COORDINATE.ToStringMGRS}(): Generates a MGRS grid coordinate text.
|
||||
--
|
||||
-- ## 10) Drawings on F10 map
|
||||
--
|
||||
@@ -887,9 +892,8 @@ do -- COORDINATE
|
||||
-- @param #COORDINATE TargetCoordinate The target COORDINATE. Can also be a DCS#Vec3.
|
||||
-- @return DCS#Distance Distance The distance in meters.
|
||||
function COORDINATE:Get2DDistance(TargetCoordinate)
|
||||
|
||||
if not TargetCoordinate then return 1000000 end
|
||||
local a={x=TargetCoordinate.x-self.x, y=0, z=TargetCoordinate.z-self.z}
|
||||
|
||||
local norm=UTILS.VecNorm(a)
|
||||
return norm
|
||||
end
|
||||
@@ -1112,25 +1116,28 @@ do -- COORDINATE
|
||||
-- @param #COORDINATE self
|
||||
-- @param #number Distance The distance in meters.
|
||||
-- @param Core.Settings#SETTINGS Settings
|
||||
-- @param #string Language (optional) "EN" or "RU"
|
||||
-- @param #number Precision (optional) round to this many decimal places
|
||||
-- @return #string The distance text expressed in the units of measurement.
|
||||
function COORDINATE:GetDistanceText( Distance, Settings, Language )
|
||||
function COORDINATE:GetDistanceText( Distance, Settings, Language, Precision )
|
||||
|
||||
local Settings = Settings or _SETTINGS -- Core.Settings#SETTINGS
|
||||
local Language = Language or "EN"
|
||||
|
||||
local Precision = Precision or 0
|
||||
|
||||
local DistanceText
|
||||
|
||||
if Settings:IsMetric() then
|
||||
if Language == "EN" then
|
||||
DistanceText = " for " .. UTILS.Round( Distance / 1000, 2 ) .. " km"
|
||||
DistanceText = " for " .. UTILS.Round( Distance / 1000, Precision ) .. " km"
|
||||
elseif Language == "RU" then
|
||||
DistanceText = " за " .. UTILS.Round( Distance / 1000, 2 ) .. " километров"
|
||||
DistanceText = " за " .. UTILS.Round( Distance / 1000, Precision ) .. " километров"
|
||||
end
|
||||
else
|
||||
if Language == "EN" then
|
||||
DistanceText = " for " .. UTILS.Round( UTILS.MetersToNM( Distance ), 2 ) .. " miles"
|
||||
DistanceText = " for " .. UTILS.Round( UTILS.MetersToNM( Distance ), Precision ) .. " miles"
|
||||
elseif Language == "RU" then
|
||||
DistanceText = " за " .. UTILS.Round( UTILS.MetersToNM( Distance ), 2 ) .. " миль"
|
||||
DistanceText = " за " .. UTILS.Round( UTILS.MetersToNM( Distance ), Precision ) .. " миль"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1208,7 +1215,7 @@ do -- COORDINATE
|
||||
local Settings = Settings or _SETTINGS -- Core.Settings#SETTINGS
|
||||
|
||||
local BearingText = self:GetBearingText( AngleRadians, 0, Settings, Language )
|
||||
local DistanceText = self:GetDistanceText( Distance, Settings, Language )
|
||||
local DistanceText = self:GetDistanceText( Distance, Settings, Language, 0 )
|
||||
|
||||
local BRText = BearingText .. DistanceText
|
||||
|
||||
@@ -1226,7 +1233,7 @@ do -- COORDINATE
|
||||
local Settings = Settings or _SETTINGS -- Core.Settings#SETTINGS
|
||||
|
||||
local BearingText = self:GetBearingText( AngleRadians, 0, Settings, Language )
|
||||
local DistanceText = self:GetDistanceText( Distance, Settings, Language )
|
||||
local DistanceText = self:GetDistanceText( Distance, Settings, Language, 0 )
|
||||
local AltitudeText = self:GetAltitudeText( Settings, Language )
|
||||
|
||||
local BRAText = BearingText .. DistanceText .. AltitudeText -- When the POINT is a VEC2, there will be no altitude shown.
|
||||
@@ -2164,14 +2171,21 @@ do -- COORDINATE
|
||||
if ReadOnly==nil then
|
||||
ReadOnly=false
|
||||
end
|
||||
|
||||
local vec3=self:GetVec3()
|
||||
|
||||
Radius=Radius or 1000
|
||||
|
||||
Coalition=Coalition or -1
|
||||
|
||||
Color=Color or {1,0,0}
|
||||
Color[4]=Alpha or 1.0
|
||||
|
||||
LineType=LineType or 1
|
||||
FillColor=FillColor or Color
|
||||
|
||||
FillColor=FillColor or UTILS.DeepCopy(Color)
|
||||
FillColor[4]=FillAlpha or 0.15
|
||||
|
||||
trigger.action.circleToAll(Coalition, MarkID, vec3, Radius, Color, FillColor, LineType, ReadOnly, Text or "")
|
||||
return MarkID
|
||||
end
|
||||
@@ -2196,13 +2210,19 @@ do -- COORDINATE
|
||||
if ReadOnly==nil then
|
||||
ReadOnly=false
|
||||
end
|
||||
|
||||
local vec3=Endpoint:GetVec3()
|
||||
|
||||
Coalition=Coalition or -1
|
||||
|
||||
Color=Color or {1,0,0}
|
||||
Color[4]=Alpha or 1.0
|
||||
|
||||
LineType=LineType or 1
|
||||
FillColor=FillColor or Color
|
||||
|
||||
FillColor=FillColor or UTILS.DeepCopy(Color)
|
||||
FillColor[4]=FillAlpha or 0.15
|
||||
|
||||
trigger.action.rectToAll(Coalition, MarkID, self:GetVec3(), vec3, Color, FillColor, LineType, ReadOnly, Text or "")
|
||||
return MarkID
|
||||
end
|
||||
@@ -2226,17 +2246,23 @@ do -- COORDINATE
|
||||
if ReadOnly==nil then
|
||||
ReadOnly=false
|
||||
end
|
||||
|
||||
local point1=self:GetVec3()
|
||||
local point2=Coord2:GetVec3()
|
||||
local point3=Coord3:GetVec3()
|
||||
local point4=Coord4:GetVec3()
|
||||
|
||||
Coalition=Coalition or -1
|
||||
|
||||
Color=Color or {1,0,0}
|
||||
Color[4]=Alpha or 1.0
|
||||
|
||||
LineType=LineType or 1
|
||||
FillColor=FillColor or Color
|
||||
|
||||
FillColor=FillColor or UTILS.DeepCopy(Color)
|
||||
FillColor[4]=FillAlpha or 0.15
|
||||
trigger.action.quadToAll(Coalition, MarkID, self:GetVec3(), point2, point3, point4, Color, FillColor, LineType, ReadOnly, Text or "")
|
||||
|
||||
trigger.action.quadToAll(Coalition, MarkID, point1, point2, point3, point4, Color, FillColor, LineType, ReadOnly, Text or "")
|
||||
return MarkID
|
||||
end
|
||||
|
||||
@@ -2320,11 +2346,15 @@ do -- COORDINATE
|
||||
ReadOnly=false
|
||||
end
|
||||
Coalition=Coalition or -1
|
||||
|
||||
Color=Color or {1,0,0}
|
||||
Color[4]=Alpha or 1.0
|
||||
FillColor=FillColor or Color
|
||||
|
||||
FillColor=FillColor or UTILS.DeepCopy(Color)
|
||||
FillColor[4]=FillAlpha or 0.3
|
||||
|
||||
FontSize=FontSize or 14
|
||||
|
||||
trigger.action.textToAll(Coalition, MarkID, self:GetVec3(), Color, FillColor, FontSize, ReadOnly, Text or "Hello World")
|
||||
return MarkID
|
||||
end
|
||||
@@ -2346,13 +2376,19 @@ do -- COORDINATE
|
||||
if ReadOnly==nil then
|
||||
ReadOnly=false
|
||||
end
|
||||
|
||||
local vec3=Endpoint:GetVec3()
|
||||
|
||||
Coalition=Coalition or -1
|
||||
|
||||
Color=Color or {1,0,0}
|
||||
Color[4]=Alpha or 1.0
|
||||
|
||||
LineType=LineType or 1
|
||||
FillColor=FillColor or Color
|
||||
|
||||
FillColor=FillColor or UTILS.DeepCopy(Color)
|
||||
FillColor[4]=FillAlpha or 0.15
|
||||
|
||||
--trigger.action.textToAll(Coalition, MarkID, self:GetVec3(), Color, FillColor, FontSize, ReadOnly, Text or "Hello World")
|
||||
trigger.action.arrowToAll(Coalition, MarkID, vec3, self:GetVec3(), Color, FillColor, LineType, ReadOnly, Text or "")
|
||||
return MarkID
|
||||
@@ -2715,7 +2751,7 @@ do -- COORDINATE
|
||||
return "BR, " .. self:GetBRText( AngleRadians, Distance, Settings )
|
||||
end
|
||||
|
||||
--- Return a BRAA string from a COORDINATE to the COORDINATE.
|
||||
--- Return a BRA string from a COORDINATE to the COORDINATE.
|
||||
-- @param #COORDINATE self
|
||||
-- @param #COORDINATE FromCoordinate The coordinate to measure the distance and the bearing from.
|
||||
-- @param Core.Settings#SETTINGS Settings (optional) The settings. Can be nil, and in this case the default settings are used. If you want to specify your own settings, use the _SETTINGS object.
|
||||
@@ -2727,7 +2763,53 @@ do -- COORDINATE
|
||||
local Altitude = self:GetAltitudeText()
|
||||
return "BRA, " .. self:GetBRAText( AngleRadians, Distance, Settings, Language )
|
||||
end
|
||||
|
||||
--- Create a BRAA NATO call string to this COORDINATE from the FromCOORDINATE. Note - BRA delivered if no aspect can be obtained and "Merged" if range < 3nm
|
||||
-- @param #COORDINATE self
|
||||
-- @param #COORDINATE FromCoordinate The coordinate to measure the distance and the bearing from.
|
||||
-- @param #boolean Bogey Add "Bogey" at the end if true (not yet declared hostile or friendly)
|
||||
-- @param #boolean Spades Add "Spades" at the end if true (no IFF/VID ID yet known)
|
||||
-- @return #string The BRAA text.
|
||||
function COORDINATE:ToStringBRAANATO(FromCoordinate,Bogey,Spades)
|
||||
|
||||
-- Thanks to @Pikey
|
||||
local BRAANATO = "Merged."
|
||||
|
||||
local currentCoord = FromCoordinate
|
||||
local DirectionVec3 = FromCoordinate:GetDirectionVec3( self )
|
||||
local AngleRadians = self:GetAngleRadians( DirectionVec3 )
|
||||
|
||||
local bearing = UTILS.Round( UTILS.ToDegree( AngleRadians ),0 )
|
||||
|
||||
local rangeMetres = self:Get2DDistance(currentCoord)
|
||||
local rangeNM = UTILS.Round( UTILS.MetersToNM(rangeMetres), 0)
|
||||
|
||||
local aspect = self:ToStringAspect(currentCoord)
|
||||
|
||||
local alt = UTILS.Round(UTILS.MetersToFeet(self.y)/1000,0)--*1000
|
||||
|
||||
local track = UTILS.BearingToCardinal(bearing) or "North"
|
||||
|
||||
if rangeNM > 3 then
|
||||
if aspect == "" then
|
||||
BRAANATO = string.format("BRA, %03d, %d miles, Angels %d, Track %s",bearing, rangeNM, alt, track)
|
||||
else
|
||||
BRAANATO = string.format("BRAA, %03d, %d miles, Angels %d, %s, Track %s",bearing, rangeNM, alt, aspect, track)
|
||||
end
|
||||
if Bogey and Spades then
|
||||
BRAANATO = BRAANATO..", Bogey, Spades."
|
||||
elseif Bogey then
|
||||
BRAANATO = BRAANATO..", Bogey."
|
||||
elseif Spades then
|
||||
BRAANATO = BRAANATO..", Spades."
|
||||
else
|
||||
BRAANATO = BRAANATO.."."
|
||||
end
|
||||
end
|
||||
|
||||
return BRAANATO
|
||||
end
|
||||
|
||||
--- Return a BULLS string out of the BULLS of the coalition to the COORDINATE.
|
||||
-- @param #COORDINATE self
|
||||
-- @param DCS#coalition.side Coalition The coalition.
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
--- **Core** - Provides a handy means to create messages and reports.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- ## Features:
|
||||
--
|
||||
--
|
||||
-- * Create text blocks that are formatted.
|
||||
-- * Create automatic indents.
|
||||
-- * Variate the delimiters between reporting lines.
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Authors: FlightControl : Design & Programming
|
||||
@@ -15,6 +15,7 @@
|
||||
-- @module Core.Report
|
||||
-- @image Core_Report.JPG
|
||||
|
||||
|
||||
--- @type REPORT
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
@@ -35,7 +36,7 @@ function REPORT:New( Title )
|
||||
|
||||
self.Report = {}
|
||||
|
||||
self:SetTitle( Title or "" )
|
||||
self:SetTitle( Title or "" )
|
||||
self:SetIndent( 3 )
|
||||
|
||||
return self
|
||||
@@ -44,26 +45,28 @@ end
|
||||
--- Has the REPORT Text?
|
||||
-- @param #REPORT self
|
||||
-- @return #boolean
|
||||
function REPORT:HasText() -- R2.1
|
||||
|
||||
function REPORT:HasText() --R2.1
|
||||
|
||||
return #self.Report > 0
|
||||
end
|
||||
|
||||
|
||||
--- Set indent of a REPORT.
|
||||
-- @param #REPORT self
|
||||
-- @param #number Indent
|
||||
-- @return #REPORT
|
||||
function REPORT:SetIndent( Indent ) -- R2.1
|
||||
function REPORT:SetIndent( Indent ) --R2.1
|
||||
self.Indent = Indent
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Add a new line to a REPORT.
|
||||
-- @param #REPORT self
|
||||
-- @param #string Text
|
||||
-- @return #REPORT
|
||||
function REPORT:Add( Text )
|
||||
self.Report[#self.Report + 1] = Text
|
||||
self.Report[#self.Report+1] = Text
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -73,17 +76,17 @@ end
|
||||
-- @param #string Separator (optional) The start of each report line can begin with an optional separator character. This can be a "-", or "#", or "*". You're free to choose what you find the best.
|
||||
-- @return #REPORT
|
||||
function REPORT:AddIndent( Text, Separator )
|
||||
self.Report[#self.Report + 1] = ((Separator and Separator .. string.rep( " ", self.Indent - 1 )) or string.rep( " ", self.Indent )) .. Text:gsub( "\n", "\n" .. string.rep( " ", self.Indent ) )
|
||||
self.Report[#self.Report+1] = ( ( Separator and Separator .. string.rep( " ", self.Indent - 1 ) ) or string.rep(" ", self.Indent ) ) .. Text:gsub("\n","\n"..string.rep( " ", self.Indent ) )
|
||||
return self
|
||||
end
|
||||
|
||||
--- Produces the text of the report, taking into account an optional delimiter, which is \n by default.
|
||||
--- Produces the text of the report, taking into account an optional delimeter, which is \n by default.
|
||||
-- @param #REPORT self
|
||||
-- @param #string Delimiter (optional) A delimiter text.
|
||||
-- @return #string The report text.
|
||||
function REPORT:Text( Delimiter )
|
||||
Delimiter = Delimiter or "\n"
|
||||
local ReportText = (self.Title ~= "" and self.Title .. Delimiter or self.Title) .. table.concat( self.Report, Delimiter ) or ""
|
||||
local ReportText = ( self.Title ~= "" and self.Title .. Delimiter or self.Title ) .. table.concat( self.Report, Delimiter ) or ""
|
||||
return ReportText
|
||||
end
|
||||
|
||||
@@ -92,7 +95,7 @@ end
|
||||
-- @param #string Title The title of the report.
|
||||
-- @return #REPORT
|
||||
function REPORT:SetTitle( Title )
|
||||
self.Title = Title
|
||||
self.Title = Title
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
@@ -1,36 +1,36 @@
|
||||
--- **Core** -- SCHEDULEDISPATCHER dispatches the different schedules.
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- Takes care of the creation and dispatching of scheduled functions for SCHEDULER objects.
|
||||
--
|
||||
--
|
||||
-- This class is tricky and needs some thorough explanation.
|
||||
-- SCHEDULE classes are used to schedule functions for objects, or as persistent objects.
|
||||
-- The SCHEDULEDISPATCHER class ensures that:
|
||||
--
|
||||
--
|
||||
-- - Scheduled functions are planned according the SCHEDULER object parameters.
|
||||
-- - Scheduled functions are repeated when requested, according the SCHEDULER object parameters.
|
||||
-- - Scheduled functions are automatically removed when the schedule is finished, according the SCHEDULER object parameters.
|
||||
--
|
||||
--
|
||||
-- The SCHEDULEDISPATCHER class will manage SCHEDULER object in memory during garbage collection:
|
||||
--
|
||||
--
|
||||
-- - When a SCHEDULER object is not attached to another object (that is, it's first :Schedule() parameter is nil), then the SCHEDULER object is _persistent_ within memory.
|
||||
-- - When a SCHEDULER object *is* attached to another object, then the SCHEDULER object is _not persistent_ within memory after a garbage collection!
|
||||
--
|
||||
-- The non-persistency of SCHEDULERS attached to objects is required to allow SCHEDULER objects to be garbage collected when the parent object is destroyed, or set to nil and garbage collected.
|
||||
-- Even when there are pending timer scheduled functions to be executed for the SCHEDULER object,
|
||||
--
|
||||
-- The none persistency of SCHEDULERS attached to objects is required to allow SCHEDULER objects to be garbage collectged, when the parent object is also desroyed or nillified and garbage collected.
|
||||
-- Even when there are pending timer scheduled functions to be executed for the SCHEDULER object,
|
||||
-- these will not be executed anymore when the SCHEDULER object has been destroyed.
|
||||
--
|
||||
--
|
||||
-- The SCHEDULEDISPATCHER allows multiple scheduled functions to be planned and executed for one SCHEDULER object.
|
||||
-- The SCHEDULER object therefore keeps a table of "CallID's", which are returned after each planning of a new scheduled function by the SCHEDULEDISPATCHER.
|
||||
-- The SCHEDULER object plans new scheduled functions through the @{Core.Scheduler#SCHEDULER.Schedule}() method.
|
||||
-- The SCHEDULER object plans new scheduled functions through the @{Core.Scheduler#SCHEDULER.Schedule}() method.
|
||||
-- The Schedule() method returns the CallID that is the reference ID for each planned schedule.
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- ### Contributions: -
|
||||
-- ### Authors: FlightControl : Design & Programming
|
||||
--
|
||||
--
|
||||
-- @module Core.ScheduleDispatcher
|
||||
-- @image Core_Schedule_Dispatcher.JPG
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
-- @type SCHEDULEDISPATCHER
|
||||
-- @field #string ClassName Name of the class.
|
||||
-- @field #number CallID Call ID counter.
|
||||
-- @field #table PersistentSchedulers Persistent schedulers.
|
||||
-- @field #table PersistentSchedulers Persistant schedulers.
|
||||
-- @field #table ObjectSchedulers Schedulers that only exist as long as the master object exists.
|
||||
-- @field #table Schedule Meta table setmetatable( {}, { __mode = "k" } ).
|
||||
-- @extends Core.Base#BASE
|
||||
@@ -46,11 +46,11 @@
|
||||
--- The SCHEDULEDISPATCHER structure
|
||||
-- @type SCHEDULEDISPATCHER
|
||||
SCHEDULEDISPATCHER = {
|
||||
ClassName = "SCHEDULEDISPATCHER",
|
||||
CallID = 0,
|
||||
PersistentSchedulers = {},
|
||||
ObjectSchedulers = {},
|
||||
Schedule = nil,
|
||||
ClassName = "SCHEDULEDISPATCHER",
|
||||
CallID = 0,
|
||||
PersistentSchedulers = {},
|
||||
ObjectSchedulers = {},
|
||||
Schedule = nil,
|
||||
}
|
||||
|
||||
--- Player data table holding all important parameters of each player.
|
||||
@@ -58,7 +58,7 @@ SCHEDULEDISPATCHER = {
|
||||
-- @field #function Function The schedule function to be called.
|
||||
-- @field #table Arguments Schedule function arguments.
|
||||
-- @field #number Start Start time in seconds.
|
||||
-- @field #number Repeat Repeat time interval in seconds.
|
||||
-- @field #number Repeat Repeat time intervall in seconds.
|
||||
-- @field #number Randomize Randomization factor [0,1].
|
||||
-- @field #number Stop Stop time in seconds.
|
||||
-- @field #number StartTime Time in seconds when the scheduler is created.
|
||||
@@ -77,7 +77,7 @@ end
|
||||
|
||||
--- Add a Schedule to the ScheduleDispatcher.
|
||||
-- The development of this method was really tidy.
|
||||
-- It is constructed as such that a garbage collection is executed on the weak tables, when the Scheduler is set to nil.
|
||||
-- It is constructed as such that a garbage collection is executed on the weak tables, when the Scheduler is nillified.
|
||||
-- Nothing of this code should be modified without testing it thoroughly.
|
||||
-- @param #SCHEDULEDISPATCHER self
|
||||
-- @param Core.Scheduler#SCHEDULER Scheduler Scheduler object.
|
||||
@@ -85,7 +85,7 @@ end
|
||||
-- @param #table ScheduleArguments Table of arguments passed to the ScheduleFunction.
|
||||
-- @param #number Start Start time in seconds.
|
||||
-- @param #number Repeat Repeat interval in seconds.
|
||||
-- @param #number Randomize Randomization factor [0,1].
|
||||
-- @param #number Randomize Radomization factor [0,1].
|
||||
-- @param #number Stop Stop time in seconds.
|
||||
-- @param #number TraceLevel Trace level [0,3].
|
||||
-- @param Core.Fsm#FSM Fsm Finite state model.
|
||||
@@ -95,38 +95,41 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr
|
||||
|
||||
-- Increase counter.
|
||||
self.CallID = self.CallID + 1
|
||||
|
||||
|
||||
-- Create ID.
|
||||
local CallID = self.CallID .. "#" .. (Scheduler.MasterObject and Scheduler.MasterObject.GetClassNameAndID and Scheduler.MasterObject:GetClassNameAndID() or "") or ""
|
||||
|
||||
self:T2( string.format( "Adding schedule #%d CallID=%s", self.CallID, CallID ) )
|
||||
local CallID = self.CallID .. "#" .. ( Scheduler.MasterObject and Scheduler.MasterObject.GetClassNameAndID and Scheduler.MasterObject:GetClassNameAndID() or "" ) or ""
|
||||
|
||||
self:T2(string.format("Adding schedule #%d CallID=%s", self.CallID, CallID))
|
||||
|
||||
-- Initialize PersistentSchedulers
|
||||
self.PersistentSchedulers = self.PersistentSchedulers or {}
|
||||
|
||||
-- Initialize the ObjectSchedulers array, which is a weakly coupled table.
|
||||
-- If the object used as the key is nil, then the garbage collector will remove the item from the Functions array.
|
||||
self.ObjectSchedulers = self.ObjectSchedulers or setmetatable( {}, { __mode = "v" } )
|
||||
|
||||
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 ) } )
|
||||
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
|
||||
|
||||
|
||||
self.Schedule = self.Schedule or setmetatable( {}, { __mode = "k" } )
|
||||
self.Schedule[Scheduler] = self.Schedule[Scheduler] or {}
|
||||
self.Schedule[Scheduler][CallID] = {} -- #SCHEDULEDISPATCHER.ScheduleData
|
||||
self.Schedule[Scheduler][CallID] = {} --#SCHEDULEDISPATCHER.ScheduleData
|
||||
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].StartTime = timer.getTime() + ( Start or 0 )
|
||||
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
|
||||
|
||||
|
||||
|
||||
-- This section handles the tracing of the scheduled calls.
|
||||
-- Because these calls will be executed with a delay, we inspect the place where these scheduled calls are initiated.
|
||||
-- The Info structure contains the output of the debug.getinfo() calls, which inspects the call stack for the function name, line number and source name.
|
||||
@@ -148,10 +151,10 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr
|
||||
-- Therefore, in the call stack, at the TraceLevel these functions are mentioned as "tail calls", and the Info.name field will be nil as a result.
|
||||
-- To obtain the correct function name for FSM object calls, the function is mentioned in the call stack at a higher stack level.
|
||||
-- So when function name stored in Info.name is nil, then I inspect the function name within the call stack one level higher.
|
||||
-- So this little piece of code does its magic wonderfully, performance overhead is negligible, as scheduled calls don't happen that often.
|
||||
-- So this little piece of code does its magic wonderfully, preformance overhead is neglectible, as scheduled calls don't happen that often.
|
||||
|
||||
local Info = {}
|
||||
|
||||
|
||||
if debug then
|
||||
TraceLevel = TraceLevel or 2
|
||||
Info = debug.getinfo( TraceLevel, "nlS" )
|
||||
@@ -165,7 +168,7 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr
|
||||
|
||||
--- Function passed to the DCS timer.scheduleFunction()
|
||||
self.Schedule[Scheduler][CallID].CallHandler = function( Params )
|
||||
|
||||
|
||||
local CallID = Params.CallID
|
||||
local Info = Params.Info or {}
|
||||
local Source = Info.source or "?"
|
||||
@@ -179,27 +182,27 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr
|
||||
end
|
||||
return errmsg
|
||||
end
|
||||
|
||||
-- Get object or persistent scheduler object.
|
||||
local Scheduler = self.ObjectSchedulers[CallID] -- Core.Scheduler#SCHEDULER
|
||||
|
||||
-- Get object or persistant scheduler object.
|
||||
local Scheduler = self.ObjectSchedulers[CallID] --Core.Scheduler#SCHEDULER
|
||||
if not Scheduler then
|
||||
Scheduler = self.PersistentSchedulers[CallID]
|
||||
end
|
||||
|
||||
-- self:T3( { Scheduler = Scheduler } )
|
||||
|
||||
|
||||
--self:T3( { Scheduler = Scheduler } )
|
||||
|
||||
if Scheduler then
|
||||
|
||||
local MasterObject = tostring( Scheduler.MasterObject )
|
||||
|
||||
local MasterObject = tostring(Scheduler.MasterObject)
|
||||
|
||||
-- Schedule object.
|
||||
local Schedule = self.Schedule[Scheduler][CallID] -- #SCHEDULEDISPATCHER.ScheduleData
|
||||
local Schedule = self.Schedule[Scheduler][CallID] --#SCHEDULEDISPATCHER.ScheduleData
|
||||
|
||||
--self:T3( { Schedule = Schedule } )
|
||||
|
||||
-- self:T3( { Schedule = Schedule } )
|
||||
|
||||
local SchedulerObject = Scheduler.MasterObject -- Scheduler.SchedulerObject Now is this the Master or Scheduler object?
|
||||
local SchedulerObject = Scheduler.MasterObject --Scheduler.SchedulerObject Now is this the Maste or Scheduler object?
|
||||
local ShowTrace = Scheduler.ShowTrace
|
||||
|
||||
|
||||
local ScheduleFunction = Schedule.Function
|
||||
local ScheduleArguments = Schedule.Arguments or {}
|
||||
local Start = Schedule.Start
|
||||
@@ -207,17 +210,19 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr
|
||||
local Randomize = Schedule.Randomize or 0
|
||||
local Stop = Schedule.Stop or 0
|
||||
local ScheduleID = Schedule.ScheduleID
|
||||
|
||||
local Prefix = (Repeat == 0) and "--->" or "+++>"
|
||||
|
||||
|
||||
|
||||
local Prefix = ( Repeat == 0 ) and "--->" or "+++>"
|
||||
|
||||
local Status, Result
|
||||
-- self:E( { SchedulerObject = SchedulerObject } )
|
||||
--self:E( { SchedulerObject = SchedulerObject } )
|
||||
if SchedulerObject then
|
||||
local function Timer()
|
||||
if ShowTrace then
|
||||
SchedulerObject:T( Prefix .. Name .. ":" .. Line .. " (" .. Source .. ")" )
|
||||
end
|
||||
return ScheduleFunction( SchedulerObject, unpack( ScheduleArguments ) )
|
||||
-- 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 )
|
||||
else
|
||||
@@ -225,39 +230,40 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr
|
||||
if ShowTrace then
|
||||
self:T( Prefix .. Name .. ":" .. Line .. " (" .. Source .. ")" )
|
||||
end
|
||||
return ScheduleFunction( unpack( ScheduleArguments ) )
|
||||
return ScheduleFunction( unpack( ScheduleArguments ) )
|
||||
end
|
||||
Status, Result = xpcall( Timer, ErrorHandler )
|
||||
end
|
||||
|
||||
|
||||
local CurrentTime = timer.getTime()
|
||||
local StartTime = Schedule.StartTime
|
||||
|
||||
-- Debug info.
|
||||
self:F3( { CallID = CallID, ScheduleID = ScheduleID, Master = MasterObject, CurrentTime = CurrentTime, StartTime = StartTime, Start = Start, Repeat = Repeat, Randomize = Randomize, Stop = Stop } )
|
||||
|
||||
if Status and ((Result == nil) or (Result and Result ~= false)) then
|
||||
|
||||
if Repeat ~= 0 and ((Stop == 0) or (Stop ~= 0 and CurrentTime <= StartTime + Stop)) then
|
||||
local ScheduleTime = CurrentTime + Repeat + math.random( -(Randomize * Repeat / 2), (Randomize * Repeat / 2) ) + 0.0001 -- Accuracy
|
||||
-- self:T3( { Repeat = CallID, CurrentTime, ScheduleTime, ScheduleArguments } )
|
||||
self:F3( { CallID=CallID, ScheduleID=ScheduleID, Master = MasterObject, CurrentTime = CurrentTime, StartTime = StartTime, Start = Start, Repeat = Repeat, Randomize = Randomize, Stop = Stop } )
|
||||
|
||||
|
||||
if Status and (( Result == nil ) or ( Result and Result ~= false ) ) then
|
||||
|
||||
if Repeat ~= 0 and ( ( Stop == 0 ) or ( Stop ~= 0 and CurrentTime <= StartTime + Stop ) ) then
|
||||
local ScheduleTime = CurrentTime + Repeat + math.random(- ( Randomize * Repeat / 2 ), ( Randomize * Repeat / 2 )) + 0.0001 -- Accuracy
|
||||
--self:T3( { Repeat = CallID, CurrentTime, ScheduleTime, ScheduleArguments } )
|
||||
return ScheduleTime -- returns the next time the function needs to be called.
|
||||
else
|
||||
self:Stop( Scheduler, CallID )
|
||||
end
|
||||
|
||||
|
||||
else
|
||||
self:Stop( Scheduler, CallID )
|
||||
end
|
||||
else
|
||||
self:I( "<<<>" .. Name .. ":" .. Line .. " (" .. Source .. ")" )
|
||||
end
|
||||
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
self:Start( Scheduler, CallID, Info )
|
||||
|
||||
|
||||
return CallID
|
||||
end
|
||||
|
||||
@@ -281,67 +287,67 @@ end
|
||||
-- @param #string Info (Optional) Debug info.
|
||||
function SCHEDULEDISPATCHER:Start( Scheduler, CallID, Info )
|
||||
self:F2( { Start = CallID, Scheduler = Scheduler } )
|
||||
|
||||
|
||||
if CallID then
|
||||
|
||||
local Schedule = self.Schedule[Scheduler][CallID] -- #SCHEDULEDISPATCHER.ScheduleData
|
||||
|
||||
|
||||
local Schedule = self.Schedule[Scheduler][CallID] --#SCHEDULEDISPATCHER.ScheduleData
|
||||
|
||||
-- Only start when there is no ScheduleID defined!
|
||||
-- This prevents to "Start" the scheduler twice with the same CallID...
|
||||
if not Schedule.ScheduleID then
|
||||
|
||||
|
||||
-- Current time in seconds.
|
||||
local Tnow = timer.getTime()
|
||||
|
||||
Schedule.StartTime = Tnow -- Set the StartTime field to indicate when the scheduler started.
|
||||
|
||||
local Tnow=timer.getTime()
|
||||
|
||||
Schedule.StartTime = Tnow -- Set the StartTime field to indicate when the scheduler started.
|
||||
|
||||
-- Start DCS schedule function https://wiki.hoggitworld.com/view/DCS_func_scheduleFunction
|
||||
Schedule.ScheduleID = timer.scheduleFunction( Schedule.CallHandler, { CallID = CallID, Info = Info }, Tnow + Schedule.Start )
|
||||
|
||||
self:T( string.format( "Starting SCHEDULEDISPATCHER Call ID=%s ==> Schedule ID=%s", tostring( CallID ), tostring( Schedule.ScheduleID ) ) )
|
||||
Schedule.ScheduleID = timer.scheduleFunction(Schedule.CallHandler, { CallID = CallID, Info = Info }, Tnow + Schedule.Start)
|
||||
|
||||
self:T(string.format("Starting scheduledispatcher Call ID=%s ==> Schedule ID=%s", tostring(CallID), tostring(Schedule.ScheduleID)))
|
||||
end
|
||||
|
||||
|
||||
else
|
||||
|
||||
|
||||
-- Recursive.
|
||||
for CallID, Schedule in pairs( self.Schedule[Scheduler] or {} ) do
|
||||
self:Start( Scheduler, CallID, Info ) -- Recursive
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
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 } )
|
||||
|
||||
if CallID then
|
||||
|
||||
local Schedule = self.Schedule[Scheduler][CallID] -- #SCHEDULEDISPATCHER.ScheduleData
|
||||
|
||||
|
||||
local Schedule = self.Schedule[Scheduler][CallID] --#SCHEDULEDISPATCHER.ScheduleData
|
||||
|
||||
-- Only stop when there is a ScheduleID defined for the CallID. So, when the scheduler was stopped before, do nothing.
|
||||
if Schedule.ScheduleID then
|
||||
|
||||
self:T( string.format( "SCHEDULEDISPATCHER stopping scheduler CallID=%s, ScheduleID=%s", tostring( CallID ), tostring( Schedule.ScheduleID ) ) )
|
||||
|
||||
|
||||
self:T(string.format("scheduledispatcher stopping scheduler CallID=%s, ScheduleID=%s", tostring(CallID), tostring(Schedule.ScheduleID)))
|
||||
|
||||
-- Remove schedule function https://wiki.hoggitworld.com/view/DCS_func_removeFunction
|
||||
timer.removeFunction( Schedule.ScheduleID )
|
||||
|
||||
timer.removeFunction(Schedule.ScheduleID)
|
||||
|
||||
Schedule.ScheduleID = nil
|
||||
|
||||
|
||||
else
|
||||
self:T( string.format( "Error no ScheduleID for CallID=%s", tostring( CallID ) ) )
|
||||
self:T(string.format("Error no ScheduleID for CallID=%s", tostring(CallID)))
|
||||
end
|
||||
|
||||
|
||||
else
|
||||
|
||||
|
||||
for CallID, Schedule in pairs( self.Schedule[Scheduler] or {} ) do
|
||||
self:Stop( Scheduler, CallID ) -- Recursive
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
@@ -356,7 +362,7 @@ function SCHEDULEDISPATCHER:Clear( Scheduler )
|
||||
end
|
||||
end
|
||||
|
||||
--- Show tracing info.
|
||||
--- Shopw tracing info.
|
||||
-- @param #SCHEDULEDISPATCHER self
|
||||
-- @param Core.Scheduler#SCHEDULER Scheduler Scheduler object.
|
||||
function SCHEDULEDISPATCHER:ShowTrace( Scheduler )
|
||||
|
||||
@@ -1,41 +1,41 @@
|
||||
--- **Core** - Prepares and handles the execution of functions over scheduled time (intervals).
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- ## Features:
|
||||
--
|
||||
--
|
||||
-- * Schedule functions over time,
|
||||
-- * optionally in an optional specified time interval,
|
||||
-- * optionally **repeating** with a specified time repeat interval,
|
||||
-- * optionally **randomizing** with a specified time interval randomization factor,
|
||||
-- * optionally **stop** the repeating after a specified time interval.
|
||||
-- * optionally in an optional specified time interval,
|
||||
-- * optionally **repeating** with a specified time repeat interval,
|
||||
-- * optionally **randomizing** with a specified time interval randomization factor,
|
||||
-- * optionally **stop** the repeating after a specified time interval.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- # Demo Missions
|
||||
--
|
||||
--
|
||||
-- ### [SCHEDULER Demo Missions source code](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release/SCH%20-%20Scheduler)
|
||||
--
|
||||
--
|
||||
-- ### [SCHEDULER Demo Missions, only for beta testers](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/SCH%20-%20Scheduler)
|
||||
--
|
||||
-- ### [ALL Demo Missions pack of the last release](https://github.com/FlightControl-Master/MOOSE_MISSIONS/releases)
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- # YouTube Channel
|
||||
--
|
||||
--
|
||||
-- ### [SCHEDULER YouTube Channel (none)]()
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Contributions:
|
||||
--
|
||||
-- ### Contributions:
|
||||
--
|
||||
-- * FlightControl : Concept & Testing
|
||||
--
|
||||
-- ### Authors:
|
||||
--
|
||||
--
|
||||
-- ### Authors:
|
||||
--
|
||||
-- * FlightControl : Design & Programming
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module Core.Scheduler
|
||||
@@ -48,61 +48,62 @@
|
||||
-- @field #boolean ShowTrace Trace info if true.
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
|
||||
--- Creates and handles schedules over time, which allow to execute code at specific time intervals with randomization.
|
||||
--
|
||||
--
|
||||
-- A SCHEDULER can manage **multiple** (repeating) schedules. Each planned or executing schedule has a unique **ScheduleID**.
|
||||
-- The ScheduleID is returned when the method @{#SCHEDULER.Schedule}() is called.
|
||||
-- It is recommended to store the ScheduleID in a variable, as it is used in the methods @{SCHEDULER.Start}() and @{SCHEDULER.Stop}(),
|
||||
-- which can start and stop specific repeating schedules respectively within a SCHEDULER object.
|
||||
--
|
||||
-- ## SCHEDULER constructor
|
||||
--
|
||||
--
|
||||
-- The SCHEDULER class is quite easy to use, but note that the New constructor has variable parameters:
|
||||
--
|
||||
--
|
||||
-- The @{#SCHEDULER.New}() method returns 2 variables:
|
||||
--
|
||||
--
|
||||
-- 1. The SCHEDULER object reference.
|
||||
-- 2. The first schedule planned in the SCHEDULER object.
|
||||
--
|
||||
--
|
||||
-- To clarify the different appliances, lets have a look at the following examples:
|
||||
--
|
||||
--
|
||||
-- ### Construct a SCHEDULER object without a persistent schedule.
|
||||
--
|
||||
--
|
||||
-- * @{#SCHEDULER.New}( nil ): Setup a new SCHEDULER object, which is persistently executed after garbage collection.
|
||||
--
|
||||
--
|
||||
-- MasterObject = SCHEDULER:New()
|
||||
-- SchedulerID = MasterObject:Schedule( nil, ScheduleFunction, {} )
|
||||
--
|
||||
--
|
||||
-- The above example creates a new MasterObject, but does not schedule anything.
|
||||
-- A separate schedule is created by using the MasterObject using the method :Schedule..., which returns a ScheduleID
|
||||
--
|
||||
--
|
||||
-- ### Construct a SCHEDULER object without a volatile schedule, but volatile to the Object existence...
|
||||
--
|
||||
-- * @{#SCHEDULER.New}( Object ): Setup a new SCHEDULER object, which is linked to the Object. When the Object is set to nil or destroyed, the SCHEDULER object will also be destroyed and stopped after garbage collection.
|
||||
--
|
||||
--
|
||||
-- * @{#SCHEDULER.New}( Object ): Setup a new SCHEDULER object, which is linked to the Object. When the Object is nillified or destroyed, the SCHEDULER object will also be destroyed and stopped after garbage collection.
|
||||
--
|
||||
-- ZoneObject = ZONE:New( "ZoneName" )
|
||||
-- MasterObject = SCHEDULER:New( ZoneObject )
|
||||
-- SchedulerID = MasterObject:Schedule( ZoneObject, ScheduleFunction, {} )
|
||||
-- ...
|
||||
-- ZoneObject = nil
|
||||
-- garbagecollect()
|
||||
--
|
||||
--
|
||||
-- The above example creates a new MasterObject, but does not schedule anything, and is bound to the existence of ZoneObject, which is a ZONE.
|
||||
-- A separate schedule is created by using the MasterObject using the method :Schedule()..., which returns a ScheduleID
|
||||
-- Later in the logic, the ZoneObject is put to nil, and garbage is collected.
|
||||
-- As a result, the MasterObject will cancel any planned schedule.
|
||||
--
|
||||
--
|
||||
-- ### Construct a SCHEDULER object with a persistent schedule.
|
||||
--
|
||||
--
|
||||
-- * @{#SCHEDULER.New}( nil, Function, FunctionArguments, Start, ... ): Setup a new persistent SCHEDULER object, and start a new schedule for the Function with the defined FunctionArguments according the Start and sequent parameters.
|
||||
--
|
||||
--
|
||||
-- MasterObject, SchedulerID = SCHEDULER:New( nil, ScheduleFunction, {} )
|
||||
--
|
||||
--
|
||||
-- The above example creates a new MasterObject, and does schedule the first schedule as part of the call.
|
||||
-- Note that 2 variables are returned here: MasterObject, ScheduleID...
|
||||
--
|
||||
--
|
||||
-- ### Construct a SCHEDULER object without a schedule, but volatile to the Object existence...
|
||||
--
|
||||
--
|
||||
-- * @{#SCHEDULER.New}( Object, Function, FunctionArguments, Start, ... ): Setup a new SCHEDULER object, linked to Object, and start a new schedule for the Function with the defined FunctionArguments according the Start and sequent parameters.
|
||||
--
|
||||
-- ZoneObject = ZONE:New( "ZoneName" )
|
||||
@@ -111,13 +112,13 @@
|
||||
-- ...
|
||||
-- ZoneObject = nil
|
||||
-- garbagecollect()
|
||||
--
|
||||
--
|
||||
-- The above example creates a new MasterObject, and schedules a method call (ScheduleFunction),
|
||||
-- and is bound to the existence of ZoneObject, which is a ZONE object (ZoneObject).
|
||||
-- Both a MasterObject and a SchedulerID variable are returned.
|
||||
-- Later in the logic, the ZoneObject is put to nil, and garbage is collected.
|
||||
-- As a result, the MasterObject will cancel the planned schedule.
|
||||
--
|
||||
--
|
||||
-- ## SCHEDULER timer stopping and (re-)starting.
|
||||
--
|
||||
-- The SCHEDULER can be stopped and restarted with the following methods:
|
||||
@@ -132,70 +133,70 @@
|
||||
-- MasterObject:Stop( SchedulerID )
|
||||
-- ...
|
||||
-- MasterObject:Start( SchedulerID )
|
||||
--
|
||||
--
|
||||
-- The above example creates a new MasterObject, and does schedule the first schedule as part of the call.
|
||||
-- Note that 2 variables are returned here: MasterObject, ScheduleID...
|
||||
-- Later in the logic, the repeating schedule with SchedulerID is stopped.
|
||||
-- A bit later, the repeating schedule with SchedulerId is (re)-started.
|
||||
--
|
||||
-- Note that 2 variables are returned here: MasterObject, ScheduleID...
|
||||
-- Later in the logic, the repeating schedule with SchedulerID is stopped.
|
||||
-- A bit later, the repeating schedule with SchedulerId is (re)-started.
|
||||
--
|
||||
-- ## Create a new schedule
|
||||
--
|
||||
-- With the method @{#SCHEDULER.Schedule}() a new time event can be scheduled.
|
||||
--
|
||||
-- With the method @{#SCHEDULER.Schedule}() a new time event can be scheduled.
|
||||
-- This method is used by the :New() constructor when a new schedule is planned.
|
||||
--
|
||||
--
|
||||
-- Consider the following code fragment of the SCHEDULER object creation.
|
||||
--
|
||||
--
|
||||
-- ZoneObject = ZONE:New( "ZoneName" )
|
||||
-- MasterObject = SCHEDULER:New( ZoneObject )
|
||||
--
|
||||
-- Several parameters can be specified that influence the behavior of a Schedule.
|
||||
--
|
||||
--
|
||||
-- Several parameters can be specified that influence the behaviour of a Schedule.
|
||||
--
|
||||
-- ### A single schedule, immediately executed
|
||||
--
|
||||
--
|
||||
-- SchedulerID = MasterObject:Schedule( ZoneObject, ScheduleFunction, {} )
|
||||
--
|
||||
-- The above example schedules a new ScheduleFunction call to be executed asynchronously, within milliseconds ...
|
||||
--
|
||||
--
|
||||
-- The above example schedules a new ScheduleFunction call to be executed asynchronously, within milleseconds ...
|
||||
--
|
||||
-- ### A single schedule, planned over time
|
||||
--
|
||||
--
|
||||
-- SchedulerID = MasterObject:Schedule( ZoneObject, ScheduleFunction, {}, 10 )
|
||||
--
|
||||
--
|
||||
-- The above example schedules a new ScheduleFunction call to be executed asynchronously, within 10 seconds ...
|
||||
--
|
||||
--
|
||||
-- ### A schedule with a repeating time interval, planned over time
|
||||
--
|
||||
--
|
||||
-- SchedulerID = MasterObject:Schedule( ZoneObject, ScheduleFunction, {}, 10, 60 )
|
||||
--
|
||||
-- The above example schedules a new ScheduleFunction call to be executed asynchronously, within 10 seconds,
|
||||
--
|
||||
-- The above example schedules a new ScheduleFunction call to be executed asynchronously, within 10 seconds,
|
||||
-- and repeating 60 every seconds ...
|
||||
--
|
||||
--
|
||||
-- ### A schedule with a repeating time interval, planned over time, with time interval randomization
|
||||
--
|
||||
--
|
||||
-- SchedulerID = MasterObject:Schedule( ZoneObject, ScheduleFunction, {}, 10, 60, 0.5 )
|
||||
--
|
||||
-- The above example schedules a new ScheduleFunction call to be executed asynchronously, within 10 seconds,
|
||||
--
|
||||
-- The above example schedules a new ScheduleFunction call to be executed asynchronously, within 10 seconds,
|
||||
-- and repeating 60 seconds, with a 50% time interval randomization ...
|
||||
-- So the repeating time interval will be randomized using the **0.5**,
|
||||
-- and will calculate between **60 - ( 60 * 0.5 )** and **60 + ( 60 * 0.5 )** for each repeat,
|
||||
-- So the repeating time interval will be randomized using the **0.5**,
|
||||
-- and will calculate between **60 - ( 60 * 0.5 )** and **60 + ( 60 * 0.5 )** for each repeat,
|
||||
-- which is in this example between **30** and **90** seconds.
|
||||
--
|
||||
--
|
||||
-- ### A schedule with a repeating time interval, planned over time, with time interval randomization, and stop after a time interval
|
||||
--
|
||||
--
|
||||
-- SchedulerID = MasterObject:Schedule( ZoneObject, ScheduleFunction, {}, 10, 60, 0.5, 300 )
|
||||
--
|
||||
-- The above example schedules a new ScheduleFunction call to be executed asynchronously, within 10 seconds,
|
||||
--
|
||||
-- The above example schedules a new ScheduleFunction call to be executed asynchronously, within 10 seconds,
|
||||
-- The schedule will repeat every 60 seconds.
|
||||
-- So the repeating time interval will be randomized using the **0.5**,
|
||||
-- and will calculate between **60 - ( 60 * 0.5 )** and **60 + ( 60 * 0.5 )** for each repeat,
|
||||
-- So the repeating time interval will be randomized using the **0.5**,
|
||||
-- and will calculate between **60 - ( 60 * 0.5 )** and **60 + ( 60 * 0.5 )** for each repeat,
|
||||
-- which is in this example between **30** and **90** seconds.
|
||||
-- The schedule will stop after **300** seconds.
|
||||
--
|
||||
--
|
||||
-- @field #SCHEDULER
|
||||
SCHEDULER = {
|
||||
ClassName = "SCHEDULER",
|
||||
Schedules = {},
|
||||
MasterObject = nil,
|
||||
ShowTrace = nil,
|
||||
ClassName = "SCHEDULER",
|
||||
Schedules = {},
|
||||
MasterObject = nil,
|
||||
ShowTrace = nil,
|
||||
}
|
||||
|
||||
--- SCHEDULER constructor.
|
||||
@@ -210,15 +211,15 @@ SCHEDULER = {
|
||||
-- @return #SCHEDULER self.
|
||||
-- @return #table The ScheduleID of the planned schedule.
|
||||
function SCHEDULER:New( MasterObject, SchedulerFunction, SchedulerArguments, Start, Repeat, RandomizeFactor, Stop )
|
||||
|
||||
|
||||
local self = BASE:Inherit( self, BASE:New() ) -- #SCHEDULER
|
||||
self:F2( { Start, Repeat, RandomizeFactor, Stop } )
|
||||
|
||||
local ScheduleID = nil
|
||||
|
||||
|
||||
self.MasterObject = MasterObject
|
||||
self.ShowTrace = false
|
||||
|
||||
|
||||
if SchedulerFunction then
|
||||
ScheduleID = self:Schedule( MasterObject, SchedulerFunction, SchedulerArguments, Start, Repeat, RandomizeFactor, Stop, 3 )
|
||||
end
|
||||
@@ -234,47 +235,48 @@ end
|
||||
-- @param #number Start Specifies the amount of seconds that will be waited before the scheduling is started, and the event function is called.
|
||||
-- @param #number Repeat Specifies the time interval in seconds when the scheduler will call the event function.
|
||||
-- @param #number RandomizeFactor Specifies a randomization factor between 0 and 1 to randomize the Repeat.
|
||||
-- @param #number Stop Time interval in seconds after which the scheduler will be stopped.
|
||||
-- @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 } )
|
||||
|
||||
-- Debug info.
|
||||
local ObjectName = "-"
|
||||
if MasterObject and MasterObject.ClassName and MasterObject.ClassID then
|
||||
if MasterObject and MasterObject.ClassName and MasterObject.ClassID then
|
||||
ObjectName = MasterObject.ClassName .. MasterObject.ClassID
|
||||
end
|
||||
self:F3( { "Schedule :", ObjectName, tostring( MasterObject ), Start, Repeat, RandomizeFactor, Stop } )
|
||||
|
||||
self:F3( { "Schedule :", ObjectName, tostring( MasterObject ), Start, Repeat, RandomizeFactor, Stop } )
|
||||
|
||||
-- Set master object.
|
||||
self.MasterObject = MasterObject
|
||||
|
||||
|
||||
-- Add schedule.
|
||||
local ScheduleID = _SCHEDULEDISPATCHER:AddSchedule( self,
|
||||
SchedulerFunction,
|
||||
SchedulerArguments,
|
||||
Start,
|
||||
Repeat,
|
||||
RandomizeFactor,
|
||||
Stop,
|
||||
TraceLevel or 3,
|
||||
Fsm
|
||||
)
|
||||
|
||||
self.Schedules[#self.Schedules + 1] = ScheduleID
|
||||
local ScheduleID = _SCHEDULEDISPATCHER:AddSchedule(
|
||||
self,
|
||||
SchedulerFunction,
|
||||
SchedulerArguments,
|
||||
Start,
|
||||
Repeat,
|
||||
RandomizeFactor,
|
||||
Stop,
|
||||
TraceLevel or 3,
|
||||
Fsm
|
||||
)
|
||||
|
||||
self.Schedules[#self.Schedules+1] = ScheduleID
|
||||
|
||||
return ScheduleID
|
||||
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 ) ) )
|
||||
self:T(string.format("Starting scheduler ID=%s", tostring(ScheduleID)))
|
||||
_SCHEDULEDISPATCHER:Start( self, ScheduleID )
|
||||
end
|
||||
|
||||
@@ -283,7 +285,7 @@ end
|
||||
-- @param #string ScheduleID (Optional) The ScheduleID of the planned (repeating) schedule.
|
||||
function SCHEDULER:Stop( ScheduleID )
|
||||
self:F3( { ScheduleID } )
|
||||
self:T( string.format( "Stopping scheduler ID=%s", tostring( ScheduleID ) ) )
|
||||
self:T(string.format("Stopping scheduler ID=%s", tostring(ScheduleID)))
|
||||
_SCHEDULEDISPATCHER:Stop( self, ScheduleID )
|
||||
end
|
||||
|
||||
@@ -292,15 +294,15 @@ end
|
||||
-- @param #string ScheduleID (optional) The ScheduleID of the planned (repeating) schedule.
|
||||
function SCHEDULER:Remove( ScheduleID )
|
||||
self:F3( { ScheduleID } )
|
||||
self:T( string.format( "Removing scheduler ID=%s", tostring( ScheduleID ) ) )
|
||||
self:T(string.format("Removing scheduler ID=%s", tostring(ScheduleID)))
|
||||
_SCHEDULEDISPATCHER:RemoveSchedule( self, ScheduleID )
|
||||
end
|
||||
|
||||
--- Clears all pending schedules.
|
||||
-- @param #SCHEDULER self
|
||||
function SCHEDULER:Clear()
|
||||
self:F3()
|
||||
self:T( string.format( "Clearing scheduler" ) )
|
||||
self:F3( )
|
||||
self:T(string.format("Clearing scheduler"))
|
||||
_SCHEDULEDISPATCHER:Clear( self )
|
||||
end
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -29,14 +29,15 @@
|
||||
-- @module Core.Settings
|
||||
-- @image Core_Settings.JPG
|
||||
|
||||
|
||||
--- @type SETTINGS
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
--- Takes care of various settings that influence the behavior of certain functionalities and classes within the MOOSE framework.
|
||||
--- Takes care of various settings that influence the behaviour of certain functionalities and classes within the MOOSE framework.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- The SETTINGS class takes care of various settings that influence the behavior of certain functionalities and classes within the MOOSE framework.
|
||||
-- The SETTINGS class takes care of various settings that influence the behaviour of certain functionalities and classes within the MOOSE framework.
|
||||
-- SETTINGS can work on 2 levels:
|
||||
--
|
||||
-- - **Default settings**: A running mission has **Default settings**.
|
||||
@@ -58,7 +59,7 @@
|
||||
--
|
||||
-- A menu is created automatically per Command Center that allows to modify the **Default** settings.
|
||||
-- So, when joining a CC unit, a menu will be available that allows to change the settings parameters **FOR ALL THE PLAYERS**!
|
||||
-- Note that the **Default settings** will only be used when a player has not chosen its own settings.
|
||||
-- Note that the **Default settings** will only be used when a player has not choosen its own settings.
|
||||
--
|
||||
-- ## 2.2) Player settings menu
|
||||
--
|
||||
@@ -68,7 +69,7 @@
|
||||
--
|
||||
-- ## 2.3) Show or Hide the Player Setting menus
|
||||
--
|
||||
-- Of course, it may be required not to show any setting menus. In this case, a method is available on the **\_SETTINGS object**.
|
||||
-- Of course, it may be requried not to show any setting menus. In this case, a method is available on the **\_SETTINGS object**.
|
||||
-- Use @{#SETTINGS.SetPlayerMenuOff}() to hide the player menus, and use @{#SETTINGS.SetPlayerMenuOn}() show the player menus.
|
||||
-- Note that when this method is used, any player already in a slot will not have its menus visibility changed.
|
||||
-- The option will only have effect when a player enters a new slot or changes a slot.
|
||||
@@ -93,8 +94,8 @@
|
||||
--
|
||||
-- - A2G BR: [Bearing Range](https://en.wikipedia.org/wiki/Bearing_(navigation)).
|
||||
-- - A2G MGRS: The [Military Grid Reference System](https://en.wikipedia.org/wiki/Military_Grid_Reference_System). The accuracy can also be adapted.
|
||||
-- - A2G LL DMS: Latitude Longitude [Degrees Minutes Seconds](https://en.wikipedia.org/wiki/Geographic_coordinate_conversion). The accuracy can also be adapted.
|
||||
-- - A2G LL DDM: Latitude Longitude [Decimal Degrees Minutes](https://en.wikipedia.org/wiki/Decimal_degrees). The accuracy can also be adapted.
|
||||
-- - A2G LL DMS: Lattitude Longitude [Degrees Minutes Seconds](https://en.wikipedia.org/wiki/Geographic_coordinate_conversion). The accuracy can also be adapted.
|
||||
-- - A2G LL DDM: Lattitude Longitude [Decimal Degrees Minutes](https://en.wikipedia.org/wiki/Decimal_degrees). The accuracy can also be adapted.
|
||||
--
|
||||
-- ### 3.1.2) A2G coordinates setting **menu**
|
||||
--
|
||||
@@ -182,7 +183,7 @@
|
||||
-- The settings can be changed by using the **Default settings menu** on the Command Center or the **Player settings menu** on the Player Slot.
|
||||
--
|
||||
-- Each Message Type has specific timings that will be applied when the message is displayed.
|
||||
-- The Settings Menu will provide for each Message Type a selection of proposed durations from which can be chosen.
|
||||
-- The Settings Menu will provide for each Message Type a selection of proposed durations from which can be choosen.
|
||||
-- So the player can choose its own amount of seconds how long a message should be displayed of a certain type.
|
||||
-- Note that **Update** messages can be chosen not to be displayed at all!
|
||||
--
|
||||
@@ -195,7 +196,7 @@
|
||||
--
|
||||
-- ## 3.5) **Era** of the battle
|
||||
--
|
||||
-- The threat level metric is scaled according the era of the battle. A target that is AAA, will pose a much greater threat in WWII than on modern warfare.
|
||||
-- The threat level metric is scaled according the era of the battle. A target that is AAA, will pose a much greather threat in WWII than on modern warfare.
|
||||
-- Therefore, there are 4 era that are defined within the settings:
|
||||
--
|
||||
-- - **WWII** era: Use for warfare with equipment during the world war II time.
|
||||
@@ -212,8 +213,8 @@
|
||||
SETTINGS = {
|
||||
ClassName = "SETTINGS",
|
||||
ShowPlayerMenu = true,
|
||||
MenuShort = false,
|
||||
MenuStatic = false,
|
||||
MenuShort = false,
|
||||
MenuStatic = false,
|
||||
}
|
||||
|
||||
SETTINGS.__Enum = {}
|
||||
@@ -230,6 +231,7 @@ SETTINGS.__Enum.Era = {
|
||||
Modern = 4,
|
||||
}
|
||||
|
||||
|
||||
do -- SETTINGS
|
||||
|
||||
--- SETTINGS constructor.
|
||||
@@ -251,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 )
|
||||
@@ -266,14 +269,14 @@ do -- SETTINGS
|
||||
-- Short text are better suited for, e.g., VR.
|
||||
-- @param #SETTINGS self
|
||||
-- @param #boolean onoff If *true* use short menu texts. If *false* long ones (default).
|
||||
function SETTINGS:SetMenutextShort( onoff )
|
||||
function SETTINGS:SetMenutextShort(onoff)
|
||||
_SETTINGS.MenuShort = onoff
|
||||
end
|
||||
|
||||
--- Set menu to be static.
|
||||
-- @param #SETTINGS self
|
||||
-- @param #boolean onoff If *true* menu is static. If *false* menu will be updated after changes (default).
|
||||
function SETTINGS:SetMenuStatic( onoff )
|
||||
function SETTINGS:SetMenuStatic(onoff)
|
||||
_SETTINGS.MenuStatic = onoff
|
||||
end
|
||||
|
||||
@@ -282,12 +285,26 @@ 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.
|
||||
function SETTINGS:IsMetric()
|
||||
return (self.Metric ~= nil and self.Metric == true) or (self.Metric == nil and _SETTINGS:IsMetric())
|
||||
return ( self.Metric ~= nil and self.Metric == true ) or ( self.Metric == nil and _SETTINGS:IsMetric() )
|
||||
end
|
||||
|
||||
--- Sets the SETTINGS imperial.
|
||||
@@ -300,7 +317,7 @@ do -- SETTINGS
|
||||
-- @param #SETTINGS self
|
||||
-- @return #boolean true if imperial.
|
||||
function SETTINGS:IsImperial()
|
||||
return (self.Metric ~= nil and self.Metric == false) or (self.Metric == nil and _SETTINGS:IsMetric())
|
||||
return ( self.Metric ~= nil and self.Metric == false ) or ( self.Metric == nil and _SETTINGS:IsMetric() )
|
||||
end
|
||||
|
||||
--- Sets the SETTINGS LL accuracy.
|
||||
@@ -342,12 +359,13 @@ do -- SETTINGS
|
||||
self.MessageTypeTimings[MessageType] = MessageTime
|
||||
end
|
||||
|
||||
|
||||
--- Gets the SETTINGS Message Display Timing of a MessageType
|
||||
-- @param #SETTINGS self
|
||||
-- @param Core.Message#MESSAGE MessageType The type of the message.
|
||||
-- @return #number
|
||||
function SETTINGS:GetMessageTime( MessageType )
|
||||
return (self.MessageTypeTimings and self.MessageTypeTimings[MessageType]) or _SETTINGS:GetMessageTime( MessageType )
|
||||
return ( self.MessageTypeTimings and self.MessageTypeTimings[MessageType] ) or _SETTINGS:GetMessageTime( MessageType )
|
||||
end
|
||||
|
||||
--- Sets A2G LL DMS
|
||||
@@ -368,14 +386,14 @@ do -- SETTINGS
|
||||
-- @param #SETTINGS self
|
||||
-- @return #boolean true if LL DMS
|
||||
function SETTINGS:IsA2G_LL_DMS()
|
||||
return (self.A2GSystem and self.A2GSystem == "LL DMS") or (not self.A2GSystem and _SETTINGS:IsA2G_LL_DMS())
|
||||
return ( self.A2GSystem and self.A2GSystem == "LL DMS" ) or ( not self.A2GSystem and _SETTINGS:IsA2G_LL_DMS() )
|
||||
end
|
||||
|
||||
--- Is LL DDM
|
||||
-- @param #SETTINGS self
|
||||
-- @return #boolean true if LL DDM
|
||||
function SETTINGS:IsA2G_LL_DDM()
|
||||
return (self.A2GSystem and self.A2GSystem == "LL DDM") or (not self.A2GSystem and _SETTINGS:IsA2G_LL_DDM())
|
||||
return ( self.A2GSystem and self.A2GSystem == "LL DDM" ) or ( not self.A2GSystem and _SETTINGS:IsA2G_LL_DDM() )
|
||||
end
|
||||
|
||||
--- Sets A2G MGRS
|
||||
@@ -389,7 +407,7 @@ do -- SETTINGS
|
||||
-- @param #SETTINGS self
|
||||
-- @return #boolean true if MGRS
|
||||
function SETTINGS:IsA2G_MGRS()
|
||||
return (self.A2GSystem and self.A2GSystem == "MGRS") or (not self.A2GSystem and _SETTINGS:IsA2G_MGRS())
|
||||
return ( self.A2GSystem and self.A2GSystem == "MGRS" ) or ( not self.A2GSystem and _SETTINGS:IsA2G_MGRS() )
|
||||
end
|
||||
|
||||
--- Sets A2G BRA
|
||||
@@ -403,7 +421,7 @@ do -- SETTINGS
|
||||
-- @param #SETTINGS self
|
||||
-- @return #boolean true if BRA
|
||||
function SETTINGS:IsA2G_BR()
|
||||
return (self.A2GSystem and self.A2GSystem == "BR") or (not self.A2GSystem and _SETTINGS:IsA2G_BR())
|
||||
return ( self.A2GSystem and self.A2GSystem == "BR" ) or ( not self.A2GSystem and _SETTINGS:IsA2G_BR() )
|
||||
end
|
||||
|
||||
--- Sets A2A BRA
|
||||
@@ -417,7 +435,7 @@ do -- SETTINGS
|
||||
-- @param #SETTINGS self
|
||||
-- @return #boolean true if BRA
|
||||
function SETTINGS:IsA2A_BRAA()
|
||||
return (self.A2ASystem and self.A2ASystem == "BRAA") or (not self.A2ASystem and _SETTINGS:IsA2A_BRAA())
|
||||
return ( self.A2ASystem and self.A2ASystem == "BRAA" ) or ( not self.A2ASystem and _SETTINGS:IsA2A_BRAA() )
|
||||
end
|
||||
|
||||
--- Sets A2A BULLS
|
||||
@@ -431,7 +449,7 @@ do -- SETTINGS
|
||||
-- @param #SETTINGS self
|
||||
-- @return #boolean true if BULLS
|
||||
function SETTINGS:IsA2A_BULLS()
|
||||
return (self.A2ASystem and self.A2ASystem == "BULLS") or (not self.A2ASystem and _SETTINGS:IsA2A_BULLS())
|
||||
return ( self.A2ASystem and self.A2ASystem == "BULLS" ) or ( not self.A2ASystem and _SETTINGS:IsA2A_BULLS() )
|
||||
end
|
||||
|
||||
--- Sets A2A LL DMS
|
||||
@@ -452,14 +470,14 @@ do -- SETTINGS
|
||||
-- @param #SETTINGS self
|
||||
-- @return #boolean true if LL DMS
|
||||
function SETTINGS:IsA2A_LL_DMS()
|
||||
return (self.A2ASystem and self.A2ASystem == "LL DMS") or (not self.A2ASystem and _SETTINGS:IsA2A_LL_DMS())
|
||||
return ( self.A2ASystem and self.A2ASystem == "LL DMS" ) or ( not self.A2ASystem and _SETTINGS:IsA2A_LL_DMS() )
|
||||
end
|
||||
|
||||
--- Is LL DDM
|
||||
-- @param #SETTINGS self
|
||||
-- @return #boolean true if LL DDM
|
||||
function SETTINGS:IsA2A_LL_DDM()
|
||||
return (self.A2ASystem and self.A2ASystem == "LL DDM") or (not self.A2ASystem and _SETTINGS:IsA2A_LL_DDM())
|
||||
return ( self.A2ASystem and self.A2ASystem == "LL DDM" ) or ( not self.A2ASystem and _SETTINGS:IsA2A_LL_DDM() )
|
||||
end
|
||||
|
||||
--- Sets A2A MGRS
|
||||
@@ -473,7 +491,7 @@ do -- SETTINGS
|
||||
-- @param #SETTINGS self
|
||||
-- @return #boolean true if MGRS
|
||||
function SETTINGS:IsA2A_MGRS()
|
||||
return (self.A2ASystem and self.A2ASystem == "MGRS") or (not self.A2ASystem and _SETTINGS:IsA2A_MGRS())
|
||||
return ( self.A2ASystem and self.A2ASystem == "MGRS" ) or ( not self.A2ASystem and _SETTINGS:IsA2A_MGRS() )
|
||||
end
|
||||
|
||||
--- @param #SETTINGS self
|
||||
@@ -492,37 +510,37 @@ do -- SETTINGS
|
||||
-- A2G Coordinate System
|
||||
-------
|
||||
|
||||
local text = "A2G Coordinate System"
|
||||
local text="A2G Coordinate System"
|
||||
if _SETTINGS.MenuShort then
|
||||
text = "A2G Coordinates"
|
||||
text="A2G Coordinates"
|
||||
end
|
||||
local A2GCoordinateMenu = MENU_GROUP:New( MenuGroup, text, SettingsMenu ):SetTime( MenuTime )
|
||||
|
||||
-- Set LL DMS
|
||||
if not self:IsA2G_LL_DMS() then
|
||||
local text = "Lat/Lon Degree Min Sec (LL DMS)"
|
||||
local text="Lat/Lon Degree Min Sec (LL DMS)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text = "LL DMS"
|
||||
text="LL DMS"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( MenuGroup, text, A2GCoordinateMenu, self.A2GMenuSystem, self, MenuGroup, RootMenu, "LL DMS" ):SetTime( MenuTime )
|
||||
end
|
||||
|
||||
-- Set LL DDM
|
||||
if not self:IsA2G_LL_DDM() then
|
||||
local text = "Lat/Lon Degree Dec Min (LL DDM)"
|
||||
local text="Lat/Lon Degree Dec Min (LL DDM)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text = "LL DDM"
|
||||
text="LL DDM"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( MenuGroup, "Lat/Lon Degree Dec Min (LL DDM)", A2GCoordinateMenu, self.A2GMenuSystem, self, MenuGroup, RootMenu, "LL DDM" ):SetTime( MenuTime )
|
||||
end
|
||||
|
||||
-- Set LL DMS accuracy.
|
||||
if self:IsA2G_LL_DDM() then
|
||||
local text1 = "LL DDM Accuracy 1"
|
||||
local text2 = "LL DDM Accuracy 2"
|
||||
local text3 = "LL DDM Accuracy 3"
|
||||
local text1="LL DDM Accuracy 1"
|
||||
local text2="LL DDM Accuracy 2"
|
||||
local text3="LL DDM Accuracy 3"
|
||||
if _SETTINGS.MenuShort then
|
||||
text1 = "LL DDM"
|
||||
text1="LL DDM"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( MenuGroup, "LL DDM Accuracy 1", A2GCoordinateMenu, self.MenuLL_DDM_Accuracy, self, MenuGroup, RootMenu, 1 ):SetTime( MenuTime )
|
||||
MENU_GROUP_COMMAND:New( MenuGroup, "LL DDM Accuracy 2", A2GCoordinateMenu, self.MenuLL_DDM_Accuracy, self, MenuGroup, RootMenu, 2 ):SetTime( MenuTime )
|
||||
@@ -531,18 +549,18 @@ do -- SETTINGS
|
||||
|
||||
-- Set BR.
|
||||
if not self:IsA2G_BR() then
|
||||
local text = "Bearing, Range (BR)"
|
||||
local text="Bearing, Range (BR)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text = "BR"
|
||||
text="BR"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( MenuGroup, text, A2GCoordinateMenu, self.A2GMenuSystem, self, MenuGroup, RootMenu, "BR" ):SetTime( MenuTime )
|
||||
MENU_GROUP_COMMAND:New( MenuGroup, text , A2GCoordinateMenu, self.A2GMenuSystem, self, MenuGroup, RootMenu, "BR" ):SetTime( MenuTime )
|
||||
end
|
||||
|
||||
-- Set MGRS.
|
||||
if not self:IsA2G_MGRS() then
|
||||
local text = "Military Grid (MGRS)"
|
||||
local text="Military Grid (MGRS)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text = "MGRS"
|
||||
text="MGRS"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( MenuGroup, text, A2GCoordinateMenu, self.A2GMenuSystem, self, MenuGroup, RootMenu, "MGRS" ):SetTime( MenuTime )
|
||||
end
|
||||
@@ -560,24 +578,24 @@ do -- SETTINGS
|
||||
-- A2A Coordinate System
|
||||
-------
|
||||
|
||||
local text = "A2A Coordinate System"
|
||||
local text="A2A Coordinate System"
|
||||
if _SETTINGS.MenuShort then
|
||||
text = "A2A Coordinates"
|
||||
text="A2A Coordinates"
|
||||
end
|
||||
local A2ACoordinateMenu = MENU_GROUP:New( MenuGroup, text, SettingsMenu ):SetTime( MenuTime )
|
||||
|
||||
if not self:IsA2A_LL_DMS() then
|
||||
local text = "Lat/Lon Degree Min Sec (LL DMS)"
|
||||
local text="Lat/Lon Degree Min Sec (LL DMS)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text = "LL DMS"
|
||||
text="LL DMS"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( MenuGroup, text, A2ACoordinateMenu, self.A2AMenuSystem, self, MenuGroup, RootMenu, "LL DMS" ):SetTime( MenuTime )
|
||||
end
|
||||
|
||||
if not self:IsA2A_LL_DDM() then
|
||||
local text = "Lat/Lon Degree Dec Min (LL DDM)"
|
||||
local text="Lat/Lon Degree Dec Min (LL DDM)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text = "LL DDM"
|
||||
text="LL DDM"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( MenuGroup, text, A2ACoordinateMenu, self.A2AMenuSystem, self, MenuGroup, RootMenu, "LL DDM" ):SetTime( MenuTime )
|
||||
end
|
||||
@@ -590,25 +608,25 @@ do -- SETTINGS
|
||||
end
|
||||
|
||||
if not self:IsA2A_BULLS() then
|
||||
local text = "Bullseye (BULLS)"
|
||||
local text="Bullseye (BULLS)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text = "Bulls"
|
||||
text="Bulls"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( MenuGroup, text, A2ACoordinateMenu, self.A2AMenuSystem, self, MenuGroup, RootMenu, "BULLS" ):SetTime( MenuTime )
|
||||
end
|
||||
|
||||
if not self:IsA2A_BRAA() then
|
||||
local text = "Bearing Range Altitude Aspect (BRAA)"
|
||||
local text="Bearing Range Altitude Aspect (BRAA)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text = "BRAA"
|
||||
text="BRAA"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( MenuGroup, text, A2ACoordinateMenu, self.A2AMenuSystem, self, MenuGroup, RootMenu, "BRAA" ):SetTime( MenuTime )
|
||||
end
|
||||
|
||||
if not self:IsA2A_MGRS() then
|
||||
local text = "Military Grid (MGRS)"
|
||||
local text="Military Grid (MGRS)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text = "MGRS"
|
||||
text="MGRS"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( MenuGroup, text, A2ACoordinateMenu, self.A2AMenuSystem, self, MenuGroup, RootMenu, "MGRS" ):SetTime( MenuTime )
|
||||
end
|
||||
@@ -621,31 +639,31 @@ do -- SETTINGS
|
||||
MENU_GROUP_COMMAND:New( MenuGroup, "MGRS Accuracy 5", A2ACoordinateMenu, self.MenuMGRS_Accuracy, self, MenuGroup, RootMenu, 5 ):SetTime( MenuTime )
|
||||
end
|
||||
|
||||
local text = "Measures and Weights System"
|
||||
local text="Measures and Weights System"
|
||||
if _SETTINGS.MenuShort then
|
||||
text = "Unit System"
|
||||
text="Unit System"
|
||||
end
|
||||
local MetricsMenu = MENU_GROUP:New( MenuGroup, text, SettingsMenu ):SetTime( MenuTime )
|
||||
|
||||
if self:IsMetric() then
|
||||
local text = "Imperial (Miles,Feet)"
|
||||
local text="Imperial (Miles,Feet)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text = "Imperial"
|
||||
text="Imperial"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( MenuGroup, text, MetricsMenu, self.MenuMWSystem, self, MenuGroup, RootMenu, false ):SetTime( MenuTime )
|
||||
end
|
||||
|
||||
if self:IsImperial() then
|
||||
local text = "Metric (Kilometers,Meters)"
|
||||
local text="Metric (Kilometers,Meters)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text = "Metric"
|
||||
text="Metric"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( MenuGroup, text, MetricsMenu, self.MenuMWSystem, self, MenuGroup, RootMenu, true ):SetTime( MenuTime )
|
||||
end
|
||||
|
||||
local text = "Messages and Reports"
|
||||
local text="Messages and Reports"
|
||||
if _SETTINGS.MenuShort then
|
||||
text = "Messages & Reports"
|
||||
text="Messages & Reports"
|
||||
end
|
||||
local MessagesMenu = MENU_GROUP:New( MenuGroup, text, SettingsMenu ):SetTime( MenuTime )
|
||||
|
||||
@@ -686,6 +704,7 @@ do -- SETTINGS
|
||||
MENU_GROUP_COMMAND:New( MenuGroup, "2 minutes", DetailedReportsMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.DetailedReportsMenu, 120 ):SetTime( MenuTime )
|
||||
MENU_GROUP_COMMAND:New( MenuGroup, "3 minutes", DetailedReportsMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.DetailedReportsMenu, 180 ):SetTime( MenuTime )
|
||||
|
||||
|
||||
SettingsMenu:Remove( MenuTime )
|
||||
|
||||
return self
|
||||
@@ -729,11 +748,11 @@ do -- SETTINGS
|
||||
|
||||
self.PlayerMenu = PlayerMenu
|
||||
|
||||
self:I( string.format( "Setting menu for player %s", tostring( PlayerName ) ) )
|
||||
self:I(string.format("Setting menu for player %s", tostring(PlayerName)))
|
||||
|
||||
local submenu = MENU_GROUP:New( PlayerGroup, "LL Accuracy", PlayerMenu )
|
||||
MENU_GROUP_COMMAND:New( PlayerGroup, "LL 0 Decimals", submenu, self.MenuGroupLL_DDM_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 0 )
|
||||
MENU_GROUP_COMMAND:New( PlayerGroup, "LL 1 Decimal", submenu, self.MenuGroupLL_DDM_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 1 )
|
||||
MENU_GROUP_COMMAND:New( PlayerGroup, "LL 1 Decimal", submenu, self.MenuGroupLL_DDM_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 1 )
|
||||
MENU_GROUP_COMMAND:New( PlayerGroup, "LL 2 Decimals", submenu, self.MenuGroupLL_DDM_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 2 )
|
||||
MENU_GROUP_COMMAND:New( PlayerGroup, "LL 3 Decimals", submenu, self.MenuGroupLL_DDM_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 3 )
|
||||
MENU_GROUP_COMMAND:New( PlayerGroup, "LL 4 Decimals", submenu, self.MenuGroupLL_DDM_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 4 )
|
||||
@@ -750,40 +769,40 @@ do -- SETTINGS
|
||||
-- A2G Coordinate System
|
||||
------
|
||||
|
||||
local text = "A2G Coordinate System"
|
||||
local text="A2G Coordinate System"
|
||||
if _SETTINGS.MenuShort then
|
||||
text = "A2G Coordinates"
|
||||
text="A2G Coordinates"
|
||||
end
|
||||
local A2GCoordinateMenu = MENU_GROUP:New( PlayerGroup, text, PlayerMenu )
|
||||
|
||||
if not self:IsA2G_LL_DMS() or _SETTINGS.MenuStatic then
|
||||
local text = "Lat/Lon Degree Min Sec (LL DMS)"
|
||||
local text="Lat/Lon Degree Min Sec (LL DMS)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text = "A2G LL DMS"
|
||||
text="A2G LL DMS"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( PlayerGroup, text, A2GCoordinateMenu, self.MenuGroupA2GSystem, self, PlayerUnit, PlayerGroup, PlayerName, "LL DMS" )
|
||||
end
|
||||
|
||||
if not self:IsA2G_LL_DDM() or _SETTINGS.MenuStatic then
|
||||
local text = "Lat/Lon Degree Dec Min (LL DDM)"
|
||||
local text="Lat/Lon Degree Dec Min (LL DDM)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text = "A2G LL DDM"
|
||||
text="A2G LL DDM"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( PlayerGroup, text, A2GCoordinateMenu, self.MenuGroupA2GSystem, self, PlayerUnit, PlayerGroup, PlayerName, "LL DDM" )
|
||||
end
|
||||
|
||||
if not self:IsA2G_BR() or _SETTINGS.MenuStatic then
|
||||
local text = "Bearing, Range (BR)"
|
||||
local text="Bearing, Range (BR)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text = "A2G BR"
|
||||
text="A2G BR"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( PlayerGroup, text, A2GCoordinateMenu, self.MenuGroupA2GSystem, self, PlayerUnit, PlayerGroup, PlayerName, "BR" )
|
||||
end
|
||||
|
||||
if not self:IsA2G_MGRS() or _SETTINGS.MenuStatic then
|
||||
local text = "Military Grid (MGRS)"
|
||||
local text="Military Grid (MGRS)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text = "A2G MGRS"
|
||||
text="A2G MGRS"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( PlayerGroup, text, A2GCoordinateMenu, self.MenuGroupA2GSystem, self, PlayerUnit, PlayerGroup, PlayerName, "MGRS" )
|
||||
end
|
||||
@@ -792,48 +811,49 @@ do -- SETTINGS
|
||||
-- A2A Coordinates Menu
|
||||
------
|
||||
|
||||
local text = "A2A Coordinate System"
|
||||
local text="A2A Coordinate System"
|
||||
if _SETTINGS.MenuShort then
|
||||
text = "A2A Coordinates"
|
||||
text="A2A Coordinates"
|
||||
end
|
||||
local A2ACoordinateMenu = MENU_GROUP:New( PlayerGroup, text, PlayerMenu )
|
||||
|
||||
|
||||
if not self:IsA2A_LL_DMS() or _SETTINGS.MenuStatic then
|
||||
local text = "Lat/Lon Degree Min Sec (LL DMS)"
|
||||
local text="Lat/Lon Degree Min Sec (LL DMS)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text = "A2A LL DMS"
|
||||
text="A2A LL DMS"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( PlayerGroup, text, A2ACoordinateMenu, self.MenuGroupA2GSystem, self, PlayerUnit, PlayerGroup, PlayerName, "LL DMS" )
|
||||
end
|
||||
|
||||
if not self:IsA2A_LL_DDM() or _SETTINGS.MenuStatic then
|
||||
local text = "Lat/Lon Degree Dec Min (LL DDM)"
|
||||
local text="Lat/Lon Degree Dec Min (LL DDM)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text = "A2A LL DDM"
|
||||
text="A2A LL DDM"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( PlayerGroup, text, A2ACoordinateMenu, self.MenuGroupA2ASystem, self, PlayerUnit, PlayerGroup, PlayerName, "LL DDM" )
|
||||
end
|
||||
|
||||
if not self:IsA2A_BULLS() or _SETTINGS.MenuStatic then
|
||||
local text = "Bullseye (BULLS)"
|
||||
local text="Bullseye (BULLS)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text = "A2A BULLS"
|
||||
text="A2A BULLS"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( PlayerGroup, text, A2ACoordinateMenu, self.MenuGroupA2ASystem, self, PlayerUnit, PlayerGroup, PlayerName, "BULLS" )
|
||||
end
|
||||
|
||||
if not self:IsA2A_BRAA() or _SETTINGS.MenuStatic then
|
||||
local text = "Bearing Range Altitude Aspect (BRAA)"
|
||||
local text="Bearing Range Altitude Aspect (BRAA)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text = "A2A BRAA"
|
||||
text="A2A BRAA"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( PlayerGroup, text, A2ACoordinateMenu, self.MenuGroupA2ASystem, self, PlayerUnit, PlayerGroup, PlayerName, "BRAA" )
|
||||
end
|
||||
|
||||
if not self:IsA2A_MGRS() or _SETTINGS.MenuStatic then
|
||||
local text = "Military Grid (MGRS)"
|
||||
local text="Military Grid (MGRS)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text = "A2A MGRS"
|
||||
text="A2A MGRS"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( PlayerGroup, text, A2ACoordinateMenu, self.MenuGroupA2ASystem, self, PlayerUnit, PlayerGroup, PlayerName, "MGRS" )
|
||||
end
|
||||
@@ -842,24 +862,24 @@ do -- SETTINGS
|
||||
-- Unit system
|
||||
---
|
||||
|
||||
local text = "Measures and Weights System"
|
||||
local text="Measures and Weights System"
|
||||
if _SETTINGS.MenuShort then
|
||||
text = "Unit System"
|
||||
text="Unit System"
|
||||
end
|
||||
local MetricsMenu = MENU_GROUP:New( PlayerGroup, text, PlayerMenu )
|
||||
|
||||
if self:IsMetric() or _SETTINGS.MenuStatic then
|
||||
local text = "Imperial (Miles,Feet)"
|
||||
local text="Imperial (Miles,Feet)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text = "Imperial"
|
||||
text="Imperial"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( PlayerGroup, text, MetricsMenu, self.MenuGroupMWSystem, self, PlayerUnit, PlayerGroup, PlayerName, false )
|
||||
end
|
||||
|
||||
if self:IsImperial() or _SETTINGS.MenuStatic then
|
||||
local text = "Metric (Kilometers,Meters)"
|
||||
local text="Metric (Kilometers,Meters)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text = "Metric"
|
||||
text="Metric"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( PlayerGroup, text, MetricsMenu, self.MenuGroupMWSystem, self, PlayerUnit, PlayerGroup, PlayerName, true )
|
||||
end
|
||||
@@ -868,9 +888,9 @@ do -- SETTINGS
|
||||
-- Messages and Reports
|
||||
---
|
||||
|
||||
local text = "Messages and Reports"
|
||||
local text="Messages and Reports"
|
||||
if _SETTINGS.MenuShort then
|
||||
text = "Messages & Reports"
|
||||
text="Messages & Reports"
|
||||
end
|
||||
local MessagesMenu = MENU_GROUP:New( PlayerGroup, text, PlayerMenu )
|
||||
|
||||
@@ -930,38 +950,39 @@ do -- SETTINGS
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- @param #SETTINGS self
|
||||
function SETTINGS:A2GMenuSystem( MenuGroup, RootMenu, A2GSystem )
|
||||
self.A2GSystem = A2GSystem
|
||||
MESSAGE:New( string.format( "Settings: Default A2G coordinate system set to %s for all players!", A2GSystem ), 5 ):ToAll()
|
||||
MESSAGE:New( string.format("Settings: Default A2G coordinate system set to %s for all players!", A2GSystem ), 5 ):ToAll()
|
||||
self:SetSystemMenu( MenuGroup, RootMenu )
|
||||
end
|
||||
|
||||
--- @param #SETTINGS self
|
||||
function SETTINGS:A2AMenuSystem( MenuGroup, RootMenu, A2ASystem )
|
||||
self.A2ASystem = A2ASystem
|
||||
MESSAGE:New( string.format( "Settings: Default A2A coordinate system set to %s for all players!", A2ASystem ), 5 ):ToAll()
|
||||
MESSAGE:New( string.format("Settings: Default A2A coordinate system set to %s for all players!", A2ASystem ), 5 ):ToAll()
|
||||
self:SetSystemMenu( MenuGroup, RootMenu )
|
||||
end
|
||||
|
||||
--- @param #SETTINGS self
|
||||
function SETTINGS:MenuLL_DDM_Accuracy( MenuGroup, RootMenu, LL_Accuracy )
|
||||
self.LL_Accuracy = LL_Accuracy
|
||||
MESSAGE:New( string.format( "Settings: Default LL accuracy set to %s for all players!", LL_Accuracy ), 5 ):ToAll()
|
||||
MESSAGE:New( string.format("Settings: Default LL accuracy set to %s for all players!", LL_Accuracy ), 5 ):ToAll()
|
||||
self:SetSystemMenu( MenuGroup, RootMenu )
|
||||
end
|
||||
|
||||
--- @param #SETTINGS self
|
||||
function SETTINGS:MenuMGRS_Accuracy( MenuGroup, RootMenu, MGRS_Accuracy )
|
||||
self.MGRS_Accuracy = MGRS_Accuracy
|
||||
MESSAGE:New( string.format( "Settings: Default MGRS accuracy set to %s for all players!", MGRS_Accuracy ), 5 ):ToAll()
|
||||
MESSAGE:New( string.format("Settings: Default MGRS accuracy set to %s for all players!", MGRS_Accuracy ), 5 ):ToAll()
|
||||
self:SetSystemMenu( MenuGroup, RootMenu )
|
||||
end
|
||||
|
||||
--- @param #SETTINGS self
|
||||
function SETTINGS:MenuMWSystem( MenuGroup, RootMenu, MW )
|
||||
self.Metric = MW
|
||||
MESSAGE:New( string.format( "Settings: Default measurement format set to %s for all players!", MW and "Metric" or "Imperial" ), 5 ):ToAll()
|
||||
MESSAGE:New( string.format("Settings: Default measurement format set to %s for all players!", MW and "Metric" or "Imperial" ), 5 ):ToAll()
|
||||
self:SetSystemMenu( MenuGroup, RootMenu )
|
||||
end
|
||||
|
||||
@@ -974,12 +995,12 @@ do -- SETTINGS
|
||||
do
|
||||
--- @param #SETTINGS self
|
||||
function SETTINGS:MenuGroupA2GSystem( PlayerUnit, PlayerGroup, PlayerName, A2GSystem )
|
||||
BASE:E( { self, PlayerUnit:GetName(), A2GSystem } )
|
||||
BASE:E( {self, PlayerUnit:GetName(), A2GSystem} )
|
||||
self.A2GSystem = A2GSystem
|
||||
MESSAGE:New( string.format( "Settings: A2G format set to %s for player %s.", A2GSystem, PlayerName ), 5 ):ToGroup( PlayerGroup )
|
||||
if _SETTINGS.MenuStatic == false then
|
||||
self:RemovePlayerMenu( PlayerUnit )
|
||||
self:SetPlayerMenu( PlayerUnit )
|
||||
if _SETTINGS.MenuStatic==false then
|
||||
self:RemovePlayerMenu(PlayerUnit)
|
||||
self:SetPlayerMenu(PlayerUnit)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -987,9 +1008,9 @@ do -- SETTINGS
|
||||
function SETTINGS:MenuGroupA2ASystem( PlayerUnit, PlayerGroup, PlayerName, A2ASystem )
|
||||
self.A2ASystem = A2ASystem
|
||||
MESSAGE:New( string.format( "Settings: A2A format set to %s for player %s.", A2ASystem, PlayerName ), 5 ):ToGroup( PlayerGroup )
|
||||
if _SETTINGS.MenuStatic == false then
|
||||
self:RemovePlayerMenu( PlayerUnit )
|
||||
self:SetPlayerMenu( PlayerUnit )
|
||||
if _SETTINGS.MenuStatic==false then
|
||||
self:RemovePlayerMenu(PlayerUnit)
|
||||
self:SetPlayerMenu(PlayerUnit)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -997,9 +1018,9 @@ do -- SETTINGS
|
||||
function SETTINGS:MenuGroupLL_DDM_AccuracySystem( PlayerUnit, PlayerGroup, PlayerName, LL_Accuracy )
|
||||
self.LL_Accuracy = LL_Accuracy
|
||||
MESSAGE:New( string.format( "Settings: LL format accuracy set to %d decimal places for player %s.", LL_Accuracy, PlayerName ), 5 ):ToGroup( PlayerGroup )
|
||||
if _SETTINGS.MenuStatic == false then
|
||||
self:RemovePlayerMenu( PlayerUnit )
|
||||
self:SetPlayerMenu( PlayerUnit )
|
||||
if _SETTINGS.MenuStatic==false then
|
||||
self:RemovePlayerMenu(PlayerUnit)
|
||||
self:SetPlayerMenu(PlayerUnit)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1007,9 +1028,9 @@ do -- SETTINGS
|
||||
function SETTINGS:MenuGroupMGRS_AccuracySystem( PlayerUnit, PlayerGroup, PlayerName, MGRS_Accuracy )
|
||||
self.MGRS_Accuracy = MGRS_Accuracy
|
||||
MESSAGE:New( string.format( "Settings: MGRS format accuracy set to %d for player %s.", MGRS_Accuracy, PlayerName ), 5 ):ToGroup( PlayerGroup )
|
||||
if _SETTINGS.MenuStatic == false then
|
||||
self:RemovePlayerMenu( PlayerUnit )
|
||||
self:SetPlayerMenu( PlayerUnit )
|
||||
if _SETTINGS.MenuStatic==false then
|
||||
self:RemovePlayerMenu(PlayerUnit)
|
||||
self:SetPlayerMenu(PlayerUnit)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1017,9 +1038,9 @@ do -- SETTINGS
|
||||
function SETTINGS:MenuGroupMWSystem( PlayerUnit, PlayerGroup, PlayerName, MW )
|
||||
self.Metric = MW
|
||||
MESSAGE:New( string.format( "Settings: Measurement format set to %s for player %s.", MW and "Metric" or "Imperial", PlayerName ), 5 ):ToGroup( PlayerGroup )
|
||||
if _SETTINGS.MenuStatic == false then
|
||||
self:RemovePlayerMenu( PlayerUnit )
|
||||
self:SetPlayerMenu( PlayerUnit )
|
||||
if _SETTINGS.MenuStatic==false then
|
||||
self:RemovePlayerMenu(PlayerUnit)
|
||||
self:SetPlayerMenu(PlayerUnit)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1049,6 +1070,7 @@ do -- SETTINGS
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- Configures the era of the mission to be Cold war.
|
||||
-- @param #SETTINGS self
|
||||
-- @return #SETTINGS self
|
||||
@@ -1058,6 +1080,7 @@ do -- SETTINGS
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- Configures the era of the mission to be Modern war.
|
||||
-- @param #SETTINGS self
|
||||
-- @return #SETTINGS self
|
||||
@@ -1067,4 +1090,7 @@ do -- SETTINGS
|
||||
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
end
|
||||
|
||||
+1535
-1479
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
|
||||
|
||||
@@ -260,7 +260,7 @@ end
|
||||
-- @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
|
||||
|
||||
@@ -269,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
|
||||
|
||||
@@ -417,16 +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.canCargo=self.InitCargo
|
||||
if self.InitStaticCargo~=nil then
|
||||
Template.canCargo=self.InitStaticCargo
|
||||
end
|
||||
|
||||
if self.InitCargoMass~=nil then
|
||||
Template.mass=self.InitCargoMass
|
||||
if self.InitStaticCargoMass~=nil then
|
||||
Template.mass=self.InitStaticCargoMass
|
||||
end
|
||||
|
||||
if self.InitLinkUnit then
|
||||
@@ -479,6 +488,8 @@ function SPAWNSTATIC:_SpawnStatic(Template, CountryID)
|
||||
-- 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
|
||||
|
||||
|
||||
@@ -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
|
||||
----------------------------------------------------------------
|
||||
@@ -34,8 +34,6 @@
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- # The TIMER Concept
|
||||
--
|
||||
-- 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.
|
||||
@@ -107,19 +105,17 @@ TIMER = {
|
||||
--- Timer ID.
|
||||
_TIMERID=0
|
||||
|
||||
--- Timer data base.
|
||||
--_TIMERDB={}
|
||||
|
||||
--- TIMER class version.
|
||||
-- @field #string version
|
||||
TIMER.version="0.1.1"
|
||||
TIMER.version="0.1.2"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- TODO list
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
-- TODO: A lot.
|
||||
-- TODO: Write docs.
|
||||
-- TODO: Randomization.
|
||||
-- TODO: Pause/unpause.
|
||||
-- DONE: Write docs.
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Constructor
|
||||
@@ -156,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
|
||||
|
||||
@@ -219,10 +212,7 @@ function TIMER:Stop(Delay)
|
||||
|
||||
-- Not running any more.
|
||||
self.isrunning=false
|
||||
|
||||
-- Remove DB entry.
|
||||
--_TIMERDB[self.uid]=nil
|
||||
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
@@ -239,6 +229,15 @@ 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.
|
||||
|
||||
@@ -59,6 +59,8 @@
|
||||
-- @field #number ZoneProbability A value between 0 and 1. 0 = 0% and 1 = 100% probability.
|
||||
-- @field #number DrawID Unique ID of the drawn zone on the F10 map.
|
||||
-- @field #table Color Table with four entries, e.g. {1, 0, 0, 0.15}. First three are RGB color code. Fourth is the transparency Alpha value.
|
||||
-- @field #number ZoneID ID of zone. Only zones defined in the ME have an ID!
|
||||
-- @field #number Surface Type of surface. Only determined at the center of the zone!
|
||||
-- @extends Core.Fsm#FSM
|
||||
|
||||
|
||||
@@ -108,7 +110,9 @@ ZONE_BASE = {
|
||||
ZoneName = "",
|
||||
ZoneProbability = 1,
|
||||
DrawID=nil,
|
||||
Color={}
|
||||
Color={},
|
||||
ZoneID=nil,
|
||||
Sureface=nil,
|
||||
}
|
||||
|
||||
|
||||
@@ -172,6 +176,7 @@ end
|
||||
-- @param DCS#Vec3 Vec3 The point to test.
|
||||
-- @return #boolean true if the Vec3 is within the zone.
|
||||
function ZONE_BASE:IsVec3InZone( Vec3 )
|
||||
if not Vec3 then return false end
|
||||
local InZone = self:IsVec2InZone( { x = Vec3.x, y = Vec3.z } )
|
||||
return InZone
|
||||
end
|
||||
@@ -333,15 +338,22 @@ end
|
||||
-- @param #ZONE_BASE self
|
||||
-- @return #nil The bounding square.
|
||||
function ZONE_BASE:GetBoundingSquare()
|
||||
--return { x1 = 0, y1 = 0, x2 = 0, y2 = 0 }
|
||||
return nil
|
||||
end
|
||||
|
||||
--- Get surface type of the zone.
|
||||
-- @param #ZONE_BASE self
|
||||
-- @return DCS#SurfaceType Type of surface.
|
||||
function ZONE_BASE:GetSurfaceType()
|
||||
local coord=self:GetCoordinate()
|
||||
local surface=coord:GetSurfaceType()
|
||||
return surface
|
||||
end
|
||||
|
||||
--- Bound the zone boundaries with a tires.
|
||||
-- @param #ZONE_BASE self
|
||||
function ZONE_BASE:BoundZone()
|
||||
self:F2()
|
||||
|
||||
end
|
||||
|
||||
|
||||
@@ -620,7 +632,7 @@ function ZONE_RADIUS:DrawZone(Coalition, Color, Alpha, FillColor, FillAlpha, Lin
|
||||
|
||||
Color=Color or self:GetColorRGB()
|
||||
Alpha=Alpha or 1
|
||||
FillColor=FillColor or Color
|
||||
FillColor=FillColor or UTILS.DeepCopy(Color)
|
||||
FillAlpha=FillAlpha or self:GetColorAlpha()
|
||||
|
||||
self.DrawID=coordinate:CircleToAll(Radius, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly)
|
||||
@@ -808,7 +820,7 @@ end
|
||||
|
||||
|
||||
--- Scan the zone for the presence of units of the given ObjectCategories.
|
||||
-- Note that after a zone has been scanned, the zone can be evaluated by:
|
||||
-- Note that **only after** a zone has been scanned, the zone can be evaluated by:
|
||||
--
|
||||
-- * @{ZONE_RADIUS.IsAllInZoneOfCoalition}(): Scan the presence of units in the zone of a coalition.
|
||||
-- * @{ZONE_RADIUS.IsAllInZoneOfOtherCoalition}(): Scan the presence of units in the zone of an other coalition.
|
||||
@@ -817,10 +829,10 @@ end
|
||||
-- * @{ZONE_RADIUS.IsNoneInZone}(): Scan if the zone is empty.
|
||||
-- @{#ZONE_RADIUS.
|
||||
-- @param #ZONE_RADIUS self
|
||||
-- @param ObjectCategories An array of categories of the objects to find in the zone.
|
||||
-- @param UnitCategories An array of unit categories of the objects to find in the zone.
|
||||
-- @param ObjectCategories An array of categories of the objects to find in the zone. E.g. `{Object.Category.UNIT}`
|
||||
-- @param UnitCategories An array of unit categories of the objects to find in the zone. E.g. `{Unit.Category.GROUND_UNIT,Unit.Category.SHIP}`
|
||||
-- @usage
|
||||
-- self.Zone:Scan()
|
||||
-- self.Zone:Scan({Object.Category.UNIT},{Unit.Category.GROUND_UNIT})
|
||||
-- local IsAttacked = self.Zone:IsSomeInZoneOfCoalition( self.Coalition )
|
||||
function ZONE_RADIUS:Scan( ObjectCategories, UnitCategories )
|
||||
|
||||
@@ -1154,7 +1166,9 @@ end
|
||||
-- @return #boolean true if the location is within the zone.
|
||||
function ZONE_RADIUS:IsVec2InZone( Vec2 )
|
||||
self:F2( Vec2 )
|
||||
|
||||
|
||||
if not Vec2 then return false end
|
||||
|
||||
local ZoneVec2 = self:GetVec2()
|
||||
|
||||
if ZoneVec2 then
|
||||
@@ -1172,7 +1186,7 @@ end
|
||||
-- @return #boolean true if the point is within the zone.
|
||||
function ZONE_RADIUS:IsVec3InZone( Vec3 )
|
||||
self:F2( Vec3 )
|
||||
|
||||
if not Vec3 then return false end
|
||||
local InZone = self:IsVec2InZone( { x = Vec3.x, y = Vec3.z } )
|
||||
|
||||
return InZone
|
||||
@@ -1186,13 +1200,13 @@ end
|
||||
-- @return DCS#Vec2 The random location within the zone.
|
||||
function ZONE_RADIUS:GetRandomVec2(inner, outer, surfacetypes)
|
||||
|
||||
local Vec2 = self:GetVec2()
|
||||
local _inner = inner or 0
|
||||
local _outer = outer or self:GetRadius()
|
||||
local Vec2 = self:GetVec2()
|
||||
local _inner = inner or 0
|
||||
local _outer = outer or self:GetRadius()
|
||||
|
||||
if surfacetypes and type(surfacetypes)~="table" then
|
||||
if surfacetypes and type(surfacetypes)~="table" then
|
||||
surfacetypes={surfacetypes}
|
||||
end
|
||||
end
|
||||
|
||||
local function _getpoint()
|
||||
local point = {}
|
||||
@@ -1212,10 +1226,10 @@ function ZONE_RADIUS:GetRandomVec2(inner, outer, surfacetypes)
|
||||
return false
|
||||
end
|
||||
|
||||
local point=_getpoint()
|
||||
local point=_getpoint()
|
||||
|
||||
if surfacetypes then
|
||||
local N=1 ; local Nmax=100 ; local gotit=false
|
||||
if surfacetypes then
|
||||
local N=1 ; local Nmax=100 ; local gotit=false
|
||||
while gotit==false and N<=Nmax do
|
||||
gotit=_checkSurface(point)
|
||||
if gotit then
|
||||
@@ -1227,7 +1241,7 @@ function ZONE_RADIUS:GetRandomVec2(inner, outer, surfacetypes)
|
||||
end
|
||||
end
|
||||
|
||||
return point
|
||||
return point
|
||||
end
|
||||
|
||||
--- Returns a @{Core.Point#POINT_VEC2} object reflecting a random 2D location within the zone.
|
||||
@@ -1279,8 +1293,8 @@ end
|
||||
|
||||
--- Returns a @{Core.Point#COORDINATE} object reflecting a random 3D location within the zone.
|
||||
-- @param #ZONE_RADIUS self
|
||||
-- @param #number inner (Optional) Minimal distance from the center of the zone. Default is 0.
|
||||
-- @param #number outer (Optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone.
|
||||
-- @param #number inner (Optional) Minimal distance from the center of the zone in meters. Default is 0 m.
|
||||
-- @param #number outer (Optional) Maximal distance from the outer edge of the zone in meters. Default is the radius of the zone.
|
||||
-- @param #table surfacetypes (Optional) Table of surface types. Can also be a single surface type. We will try max 1000 times to find the right type!
|
||||
-- @return Core.Point#COORDINATE The random coordinate.
|
||||
function ZONE_RADIUS:GetRandomCoordinate(inner, outer, surfacetypes)
|
||||
@@ -1860,7 +1874,8 @@ function ZONE_POLYGON_BASE:DrawZone(Coalition, Color, Alpha, FillColor, FillAlph
|
||||
|
||||
Color=Color or self:GetColorRGB()
|
||||
Alpha=Alpha or 1
|
||||
FillColor=FillColor or Color
|
||||
|
||||
FillColor=FillColor or UTILS.DeepCopy(Color)
|
||||
FillAlpha=FillAlpha or self:GetColorAlpha()
|
||||
|
||||
|
||||
@@ -1962,7 +1977,7 @@ end
|
||||
-- @return #boolean true if the location is within the zone.
|
||||
function ZONE_POLYGON_BASE:IsVec2InZone( Vec2 )
|
||||
self:F2( Vec2 )
|
||||
|
||||
if not Vec2 then return false end
|
||||
local Next
|
||||
local Prev
|
||||
local InPolygon = false
|
||||
@@ -1986,6 +2001,20 @@ function ZONE_POLYGON_BASE:IsVec2InZone( Vec2 )
|
||||
return InPolygon
|
||||
end
|
||||
|
||||
--- Returns if a point is within the zone.
|
||||
-- @param #ZONE_POLYGON_BASE self
|
||||
-- @param DCS#Vec3 Vec3 The point to test.
|
||||
-- @return #boolean true if the point is within the zone.
|
||||
function ZONE_POLYGON_BASE:IsVec3InZone( Vec3 )
|
||||
self:F2( Vec3 )
|
||||
|
||||
if not Vec3 then return false end
|
||||
|
||||
local InZone = self:IsVec2InZone( { x = Vec3.x, y = Vec3.z } )
|
||||
|
||||
return InZone
|
||||
end
|
||||
|
||||
--- Define a random @{DCS#Vec2} within the zone.
|
||||
-- @param #ZONE_POLYGON_BASE self
|
||||
-- @return DCS#Vec2 The Vec2 coordinate.
|
||||
|
||||
@@ -471,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 --
|
||||
|
||||
|
||||
@@ -487,8 +503,9 @@ do -- Object
|
||||
-- @field UNIT
|
||||
-- @field WEAPON
|
||||
-- @field STATIC
|
||||
-- @field SCENERY
|
||||
-- @field BASE
|
||||
-- @field SCENERY
|
||||
-- @field CARGO
|
||||
|
||||
--- @type Object.Desc
|
||||
-- @extends #Desc
|
||||
@@ -498,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
|
||||
@@ -1130,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
|
||||
|
||||
@@ -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 )
|
||||
@@ -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
|
||||
|
||||
@@ -73,7 +73,7 @@
|
||||
-- @field Core.Point#COORDINATE RearmingPlaceCoord Coordinates of the rearming place. If the place is more than 100 m away from the ARTY group, the group will go there.
|
||||
-- @field #boolean RearmingArtyOnRoad If true, ARTY group will move to rearming place using mainly roads. Default false.
|
||||
-- @field Core.Point#COORDINATE InitialCoord Initial coordinates of the ARTY group.
|
||||
-- @field #boolean report Arty group sends messages about their current state or target to its coalition.
|
||||
-- @field #boolean report Arty group sends messages about their current state or target to its coaliton.
|
||||
-- @field #table ammoshells Table holding names of the shell types which are included when counting the ammo. Default is {"weapons.shells"} which include most shells.
|
||||
-- @field #table ammorockets Table holding names of the rocket types which are included when counting the ammo. Default is {"weapons.nurs"} which includes most unguided rockets.
|
||||
-- @field #table ammomissiles Table holding names of the missile types which are included when counting the ammo. Default is {"weapons.missiles"} which includes some guided missiles.
|
||||
|
||||
@@ -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
|
||||
-------------------------------------------------------------------
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
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 )
|
||||
|
||||
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
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -233,6 +233,16 @@
|
||||
-- The next time you start the mission, these results are also automatically loaded.
|
||||
--
|
||||
-- Strafing results are currently **not** saved.
|
||||
--
|
||||
-- # FSM Events
|
||||
--
|
||||
-- This class creates additional events that can be used by mission designers for custom reactions
|
||||
--
|
||||
-- * `EnterRange` when a player enters a range zone. See @{#RANGE.OnAfterEnterRange}
|
||||
-- * `ExitRange` when a player leaves a range zone. See @{#RANGE.OnAfterExitRange}
|
||||
-- * `Impact` on impact of a player's weapon on a bombing target. See @{#RANGE.OnAfterImpact}
|
||||
-- * `RollingIn` when a player rolls in on a strafing target. See @{#RANGE.OnAfterRollingIn}
|
||||
-- * `StrafeResult` when a player finishes a strafing run. See @{#RANGE.OnAfterStrafeResult}
|
||||
--
|
||||
-- # Examples
|
||||
--
|
||||
@@ -366,14 +376,6 @@ RANGE.TargetType = {
|
||||
COORD = "Coordinate"
|
||||
}
|
||||
|
||||
--- Default range variables for RangeBoss/Hypeman tie in.
|
||||
hypemanStrafeRollIn = "nil"
|
||||
StrafeAircraftType = "strafeAircraftTypeNotSet"
|
||||
Straferesult = {}
|
||||
clientRollingIn = false
|
||||
clientStrafed = false
|
||||
invalidStrafe = false
|
||||
|
||||
--- Player settings.
|
||||
-- @type RANGE.PlayerData
|
||||
-- @field #boolean smokebombimpact Smoke bomb impact points.
|
||||
@@ -408,6 +410,14 @@ invalidStrafe = false
|
||||
-- @field #number smokepoints Number of smoke points.
|
||||
-- @field #number heading Heading of pit.
|
||||
|
||||
--- Strafe status for player.
|
||||
-- @type RANGE.StrafeStatus
|
||||
-- @field #number hits Number of hits on target.
|
||||
-- @field #number time Number of times.
|
||||
-- @field #number ammo Amount of ammo.
|
||||
-- @field #boolean pastfoulline If `true`, player passed foul line. Invalid pass.
|
||||
-- @field #RANGE.StrafeTarget zone Strafe target.
|
||||
|
||||
--- Bomb target result.
|
||||
-- @type RANGE.BombResult
|
||||
-- @field #string name Name of closest target.
|
||||
@@ -420,6 +430,13 @@ invalidStrafe = false
|
||||
-- @field #number time Time via timer.getAbsTime() in seconds of impact.
|
||||
-- @field #string date OS date.
|
||||
|
||||
--- Strafe result.
|
||||
-- @type RANGE.StrafeResult
|
||||
-- @field #string player Player name.
|
||||
-- @field #string airframe Aircraft type of player.
|
||||
-- @field #number time Time via timer.getAbsTime() in seconds of impact.
|
||||
-- @field #string date OS date.
|
||||
|
||||
--- Sound file data.
|
||||
-- @type RANGE.Soundfile
|
||||
-- @field #string filename Name of the file
|
||||
@@ -532,7 +549,7 @@ RANGE.MenuF10Root = nil
|
||||
|
||||
--- Range script version.
|
||||
-- @field #string version
|
||||
RANGE.version = "2.3.0"
|
||||
RANGE.version = "2.4.0"
|
||||
|
||||
-- TODO list:
|
||||
-- TODO: Verbosity level for messages.
|
||||
@@ -583,6 +600,8 @@ function RANGE:New( rangename )
|
||||
self:AddTransition("Stopped", "Start", "Running") -- Start RANGE script.
|
||||
self:AddTransition("*", "Status", "*") -- Status of RANGE script.
|
||||
self:AddTransition("*", "Impact", "*") -- Impact of bomb/rocket/missile.
|
||||
self:AddTransition("*", "RollingIn", "*") -- Player rolling in on strafe target.
|
||||
self:AddTransition("*", "StrafeResult", "*") -- Strafe result of player.
|
||||
self:AddTransition("*", "EnterRange", "*") -- Player enters the range.
|
||||
self:AddTransition("*", "ExitRange", "*") -- Player leaves the range.
|
||||
self:AddTransition("*", "Save", "*") -- Save player results.
|
||||
@@ -640,6 +659,37 @@ function RANGE:New( rangename )
|
||||
-- @param #RANGE.BombResult result Data of the bombing run.
|
||||
-- @param #RANGE.PlayerData player Data of player settings etc.
|
||||
|
||||
|
||||
--- Triggers the FSM event "RollingIn".
|
||||
-- @function [parent=#RANGE] RollingIn
|
||||
-- @param #RANGE self
|
||||
-- @param #RANGE.PlayerData player Data of player settings etc.
|
||||
-- @param #RANGE.StrafeTarget target Strafe target.
|
||||
|
||||
--- On after "RollingIn" event user function. Called when a player rolls in to a strafe taret.
|
||||
-- @function [parent=#RANGE] OnAfterRollingIn
|
||||
-- @param #RANGE self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param #RANGE.PlayerData player Data of player settings etc.
|
||||
-- @param #RANGE.StrafeTarget target Strafe target.
|
||||
|
||||
--- Triggers the FSM event "StrafeResult".
|
||||
-- @function [parent=#RANGE] StrafeResult
|
||||
-- @param #RANGE self
|
||||
-- @param #RANGE.PlayerData player Data of player settings etc.
|
||||
-- @param #RANGE.StrafeResult result Data of the strafing run.
|
||||
|
||||
--- On after "StrafeResult" event user function. Called when a player finished a strafing run.
|
||||
-- @function [parent=#RANGE] OnAfterStrafeResult
|
||||
-- @param #RANGE self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param #RANGE.PlayerData player Data of player settings etc.
|
||||
-- @param #RANGE.StrafeResult result Data of the strafing run.
|
||||
|
||||
--- Triggers the FSM event "EnterRange".
|
||||
-- @function [parent=#RANGE] EnterRange
|
||||
-- @param #RANGE self
|
||||
@@ -1594,7 +1644,6 @@ function RANGE:OnEventBirth( EventData )
|
||||
self.strafeStatus[_uid] = nil
|
||||
|
||||
-- Add Menu commands after a delay of 0.1 seconds.
|
||||
-- SCHEDULER:New(nil, self._AddF10Commands, {self,_unitName}, 0.1)
|
||||
self:ScheduleOnce( 0.1, self._AddF10Commands, self, _unitName )
|
||||
|
||||
-- By default, some bomb impact points and do not flare each hit on target.
|
||||
@@ -1613,7 +1662,6 @@ function RANGE:OnEventBirth( EventData )
|
||||
|
||||
-- Start check in zone timer.
|
||||
if self.planes[_uid] ~= true then
|
||||
-- SCHEDULER:New(nil, self._CheckInZone, {self, EventData.IniUnitName}, 1, 1)
|
||||
self.timerCheckZone = TIMER:New( self._CheckInZone, self, EventData.IniUnitName ):Start( 1, 1 )
|
||||
self.planes[_uid] = true
|
||||
end
|
||||
@@ -1647,7 +1695,7 @@ function RANGE:OnEventHit( EventData )
|
||||
local targetname = EventData.TgtUnitName
|
||||
|
||||
-- Current strafe target of player.
|
||||
local _currentTarget = self.strafeStatus[_unitID]
|
||||
local _currentTarget = self.strafeStatus[_unitID] --#RANGE.StrafeStatus
|
||||
|
||||
-- Player has rolled in on a strafing target.
|
||||
if _currentTarget and target:IsAlive() then
|
||||
@@ -1931,74 +1979,6 @@ end
|
||||
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- FSM Functions
|
||||
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
function RANGE:_SaveTargetSheet( _playername, result ) -- RangeBoss Specific Function
|
||||
|
||||
--- 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()
|
||||
else
|
||||
env.info( "RANGEBOSS EDIT - could not save target sheet to file" )
|
||||
-- self:E(self.lid..string.format("ERROR: could not save target sheet to file %s.\nFile may contain invalid characters.", tostring(filename)))
|
||||
end
|
||||
end
|
||||
|
||||
-- Set path or default.
|
||||
local path = self.targetpath
|
||||
if lfs then
|
||||
path = path or lfs.writedir() .. [[Logs\]]
|
||||
end
|
||||
|
||||
-- Create unused file name.
|
||||
local filename = nil
|
||||
for i = 1, 9999 do
|
||||
|
||||
-- Create file name
|
||||
if self.targetprefix then
|
||||
filename = string.format( "%s_%s-%04d.csv", self.targetprefix, playerData.actype, i )
|
||||
else
|
||||
local name = UTILS.ReplaceIllegalCharacters( _playername, "_" )
|
||||
filename = string.format( "RANGERESULTS-%s_Targetsheet-%s-%04d.csv", self.rangename, name, i )
|
||||
end
|
||||
|
||||
-- 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
|
||||
|
||||
-- Header line
|
||||
local data = "Name,Target,Distance,Radial,Quality,Rounds Fired,Rounds Hit,Rounds Quality,Attack Heading,Weapon,Airframe,Mission Time,OS Time\n"
|
||||
|
||||
-- local result=_result --#RANGE.BombResult
|
||||
local distance = result.distance
|
||||
local weapon = result.weapon
|
||||
local target = result.name
|
||||
local radial = result.radial
|
||||
local quality = result.quality
|
||||
local time = UTILS.SecondsToClock( result.time )
|
||||
local airframe = result.airframe
|
||||
local date = "n/a"
|
||||
local roundsFired = result.roundsFired
|
||||
local roundsHit = result.roundsHit
|
||||
local strafeResult = result.roundsQuality
|
||||
local attackHeading = result.heading
|
||||
if os then
|
||||
date = os.date()
|
||||
end
|
||||
data = data .. string.format( "%s,%s,%.2f,%03d,%s,%03d,%03d,%s,%03d,%s,%s,%s,%s", _playername, target, distance, radial, quality, roundsFired, roundsHit, strafeResult, attackHeading, weapon, airframe, time, date )
|
||||
|
||||
-- Save file.
|
||||
_savefile( filename, data )
|
||||
end
|
||||
|
||||
--- Check spawn queue and spawn aircraft if necessary.
|
||||
-- @param #RANGE self
|
||||
@@ -2299,6 +2279,73 @@ function RANGE:onafterLoad( From, Event, To )
|
||||
end
|
||||
end
|
||||
|
||||
--- Save target sheet.
|
||||
-- @param #RANGE self
|
||||
-- @param #string _playername Player name.
|
||||
-- @param #RANGE.StrafeResult result Results table.
|
||||
function RANGE:_SaveTargetSheet( _playername, result ) -- RangeBoss Specific Function
|
||||
|
||||
--- 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()
|
||||
else
|
||||
env.info( "RANGEBOSS EDIT - could not save target sheet to file" )
|
||||
-- self:E(self.lid..string.format("ERROR: could not save target sheet to file %s.\nFile may contain invalid characters.", tostring(filename)))
|
||||
end
|
||||
end
|
||||
|
||||
-- Set path or default.
|
||||
local path = self.targetpath
|
||||
if lfs then
|
||||
path = path or lfs.writedir() .. [[Logs\]]
|
||||
end
|
||||
|
||||
-- Create unused file name.
|
||||
local filename = nil
|
||||
for i = 1, 9999 do
|
||||
|
||||
-- Create file name
|
||||
if self.targetprefix then
|
||||
filename = string.format( "%s_%s-%04d.csv", self.targetprefix, result.airframe, i )
|
||||
else
|
||||
local name = UTILS.ReplaceIllegalCharacters( _playername, "_" )
|
||||
filename = string.format( "RANGERESULTS-%s_Targetsheet-%s-%04d.csv", self.rangename, name, i )
|
||||
end
|
||||
|
||||
-- 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
|
||||
|
||||
-- Header line
|
||||
local data = "Name,Target,Rounds Fired,Rounds Hit,Rounds Quality,Airframe,Mission Time,OS Time\n"
|
||||
|
||||
local target = result.name
|
||||
local airframe = result.airframe
|
||||
local roundsFired = result.roundsFired
|
||||
local roundsHit = result.roundsHit
|
||||
local strafeResult = result.roundsQuality
|
||||
local time = UTILS.SecondsToClock( result.time )
|
||||
local date = "n/a"
|
||||
if os then
|
||||
date = os.date()
|
||||
end
|
||||
data = data .. string.format( "%s,%s,%d,%d,%s,%s,%s,%s", _playername, target, roundsFired, roundsHit, strafeResult, airframe, time, date )
|
||||
|
||||
-- Save file.
|
||||
_savefile( filename, data )
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Display Messages
|
||||
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
@@ -2324,7 +2371,7 @@ function RANGE:_DisplayMyStrafePitResults( _unitName )
|
||||
local _message = string.format( "My Top %d Strafe Pit Results:\n", self.ndisplayresult )
|
||||
|
||||
-- Get player results.
|
||||
local _results = self.strafePlayerResults[_playername]
|
||||
local _results = self.strafePlayerResults[_playername]
|
||||
|
||||
-- Create message.
|
||||
if _results == nil then
|
||||
@@ -2344,9 +2391,10 @@ function RANGE:_DisplayMyStrafePitResults( _unitName )
|
||||
|
||||
-- Loop over results
|
||||
for _, _result in pairs( _results ) do
|
||||
local result=_result --#RANGE.StrafeResult
|
||||
|
||||
-- Message text.
|
||||
_message = _message .. string.format( "\n[%d] Hits %d - %s - %s", _count, _result.hits, _result.zone.name, _result.text )
|
||||
_message = _message .. string.format( "\n[%d] Hits %d - %s - %s", _count, result.roundsHit, result.name, result.roundsQuality )
|
||||
|
||||
-- Best result.
|
||||
if _bestMsg == "" then
|
||||
@@ -2835,14 +2883,16 @@ function RANGE:_CheckInZone( _unitName )
|
||||
local unitheading = 0 -- RangeBoss
|
||||
|
||||
if _unit and _playername then
|
||||
|
||||
|
||||
-- Player data.
|
||||
local playerData=self.PlayerSettings[_playername] -- #RANGE.PlayerData
|
||||
|
||||
--- Function to check if unit is in zone and facing in the right direction and is below the max alt.
|
||||
local function checkme( targetheading, _zone )
|
||||
local zone = _zone -- Core.Zone#ZONE
|
||||
|
||||
-- Heading check.
|
||||
local unitheading = _unit:GetHeading()
|
||||
unitheadingStrafe = _unit:GetHeading() -- RangeBoss
|
||||
local pitheading = targetheading - 180
|
||||
local deltaheading = unitheading - pitheading
|
||||
local towardspit = math.abs( deltaheading ) <= 90 or math.abs( deltaheading - 360 ) <= 90
|
||||
@@ -2867,7 +2917,7 @@ function RANGE:_CheckInZone( _unitName )
|
||||
local _unitID = _unit:GetID()
|
||||
|
||||
-- Currently strafing? (strafeStatus is nil if not)
|
||||
local _currentStrafeRun = self.strafeStatus[_unitID]
|
||||
local _currentStrafeRun = self.strafeStatus[_unitID] --#RANGE.StrafeStatus
|
||||
|
||||
if _currentStrafeRun then -- player has already registered for a strafing run.
|
||||
|
||||
@@ -2879,7 +2929,6 @@ function RANGE:_CheckInZone( _unitName )
|
||||
|
||||
-- Check if player is in strafe zone and below max alt.
|
||||
if unitinzone then
|
||||
StrafeAircraftType = _unit:GetTypeName() -- RangeBoss
|
||||
-- Still in zone, keep counting hits. Increase counter.
|
||||
_currentStrafeRun.time = _currentStrafeRun.time + 1
|
||||
|
||||
@@ -2909,8 +2958,10 @@ function RANGE:_CheckInZone( _unitName )
|
||||
local _ammo = self:_GetAmmo( _unitName )
|
||||
|
||||
-- Result.
|
||||
local _result = self.strafeStatus[_unitID]
|
||||
local _result = self.strafeStatus[_unitID] --#RANGE.StrafeStatus
|
||||
|
||||
local _sound = nil -- #RANGE.Soundfile
|
||||
|
||||
--[[ --RangeBoss commented out in order to implement strafe quality based on accuracy percentage, not the number of rounds on target
|
||||
-- Judge this pass. Text is displayed on summary.
|
||||
if _result.hits >= _result.zone.goodPass*2 then
|
||||
@@ -2927,6 +2978,7 @@ function RANGE:_CheckInZone( _unitName )
|
||||
_sound=RANGE.Sound.RCPoorPass
|
||||
end
|
||||
]]
|
||||
|
||||
-- Calculate accuracy of run. Number of hits wrt number of rounds fired.
|
||||
local shots = _result.ammo - _ammo
|
||||
local accur = 0
|
||||
@@ -2936,29 +2988,30 @@ function RANGE:_CheckInZone( _unitName )
|
||||
accur = 100
|
||||
end
|
||||
end
|
||||
|
||||
if invalidStrafe == true then --
|
||||
_result.text = "* INVALID - PASSED FOUL LINE *"
|
||||
|
||||
-- Results text and sound message.
|
||||
local resulttext=""
|
||||
if _result.pastfoulline == true then --
|
||||
resulttext = "* INVALID - PASSED FOUL LINE *"
|
||||
_sound = RANGE.Sound.RCPoorPass --
|
||||
else
|
||||
if accur >= 90 then
|
||||
_result.text = "DEADEYE PASS"
|
||||
resulttext = "DEADEYE PASS"
|
||||
_sound = RANGE.Sound.RCExcellentPass
|
||||
elseif accur >= 75 then
|
||||
_result.text = "EXCELLENT PASS"
|
||||
resulttext = "EXCELLENT PASS"
|
||||
_sound = RANGE.Sound.RCExcellentPass
|
||||
elseif accur >= 50 then
|
||||
_result.text = "GOOD PASS"
|
||||
resulttext = "GOOD PASS"
|
||||
_sound = RANGE.Sound.RCGoodPass
|
||||
elseif accur >= 25 then
|
||||
_result.text = "INEFFECTIVE PASS"
|
||||
resulttext = "INEFFECTIVE PASS"
|
||||
_sound = RANGE.Sound.RCIneffectivePass
|
||||
else
|
||||
_result.text = "POOR PASS"
|
||||
resulttext = "POOR PASS"
|
||||
_sound = RANGE.Sound.RCPoorPass
|
||||
end
|
||||
end
|
||||
clientStrafed = true -- RANGEBOSS
|
||||
|
||||
-- Message text.
|
||||
local _text = string.format( "%s, hits on target %s: %d", self:_myname( _unitName ), _result.zone.name, _result.hits )
|
||||
@@ -2969,45 +3022,27 @@ function RANGE:_CheckInZone( _unitName )
|
||||
|
||||
-- Send message.
|
||||
self:_DisplayMessageToGroup( _unit, _text )
|
||||
|
||||
-- RangeBoss Edit for strafe table insert
|
||||
|
||||
-- Local results.
|
||||
|
||||
local result = {} -- #RANGE.BombResult
|
||||
result.name = _result.zone.name or "unknown"
|
||||
result.distance = 0
|
||||
result.radial = 0
|
||||
result.weapon = "N/A"
|
||||
result.quality = "N/A"
|
||||
result.player = _playernamee
|
||||
|
||||
-- Strafe result.
|
||||
local result = {} -- #RANGE.StrafeResult
|
||||
result.player=_playername
|
||||
result.name=_result.zone.name or "unknown"
|
||||
result.time = timer.getAbsTime()
|
||||
result.airframe = StrafeAircraftType
|
||||
result.roundsFired = shots -- RANGEBOSS
|
||||
result.roundsHit = _result.hits -- RANGEBOSS
|
||||
result.roundsQuality = _result.text -- RANGEBOSS
|
||||
result.roundsFired = shots
|
||||
result.roundsHit = _result.hits
|
||||
result.roundsQuality = resulttext
|
||||
result.strafeAccuracy = accur
|
||||
result.heading = unitheadingStrafe -- RANGEBOSS
|
||||
|
||||
Straferesult.name = _result.zone.name or "unknown"
|
||||
Straferesult.distance = 0
|
||||
Straferesult.radial = 0
|
||||
Straferesult.weapon = "N/A"
|
||||
Straferesult.quality = "N/A"
|
||||
Straferesult.player = _playername
|
||||
Straferesult.time = timer.getAbsTime()
|
||||
Straferesult.airframe = StrafeAircraftType
|
||||
Straferesult.roundsFired = shots
|
||||
Straferesult.roundsHit = _result.hits
|
||||
Straferesult.roundsQuality = _result.text
|
||||
Straferesult.strafeAccuracy = accur
|
||||
Straferesult.rangename = self.rangename
|
||||
|
||||
result.rangename = self.rangename
|
||||
result.airframe=playerData.airframe
|
||||
result.invalid = _result.pastfoulline
|
||||
|
||||
-- Griger Results.
|
||||
self:StrafeResult(playerData, result)
|
||||
|
||||
-- Save trap sheet.
|
||||
if playerData.targeton and self.targetsheet then
|
||||
if playerData and playerData.targeton and self.targetsheet then
|
||||
self:_SaveTargetSheet( _playername, result )
|
||||
end
|
||||
-- RangeBoss edit for strafe data saved to file
|
||||
end
|
||||
|
||||
-- Voice over.
|
||||
if self.rangecontrol then
|
||||
@@ -3028,7 +3063,7 @@ function RANGE:_CheckInZone( _unitName )
|
||||
|
||||
-- Save stats so the player can retrieve them.
|
||||
local _stats = self.strafePlayerResults[_playername] or {}
|
||||
table.insert( _stats, _result )
|
||||
table.insert( _stats, result )
|
||||
self.strafePlayerResults[_playername] = _stats
|
||||
end
|
||||
|
||||
@@ -3038,12 +3073,13 @@ function RANGE:_CheckInZone( _unitName )
|
||||
|
||||
-- Check to see if we're in any of the strafing zones (first time).
|
||||
for _, _targetZone in pairs( self.strafeTargets ) do
|
||||
local target=_targetZone --#RANGE.StrafeTarget
|
||||
|
||||
-- Get the current approach zone and check if player is inside.
|
||||
local zone = _targetZone.polygon -- Core.Zone#ZONE_POLYGON_BASE
|
||||
local zone = target.polygon -- Core.Zone#ZONE_POLYGON_BASE
|
||||
|
||||
-- Check if unit in zone and facing the right direction.
|
||||
local unitinzone = checkme( _targetZone.heading, zone )
|
||||
local unitinzone = checkme( target.heading, zone )
|
||||
|
||||
-- Player is inside zone.
|
||||
if unitinzone then
|
||||
@@ -3052,19 +3088,20 @@ function RANGE:_CheckInZone( _unitName )
|
||||
local _ammo = self:_GetAmmo( _unitName )
|
||||
|
||||
-- Init strafe status for this player.
|
||||
self.strafeStatus[_unitID] = { hits = 0, zone = _targetZone, time = 1, ammo = _ammo, pastfoulline = false }
|
||||
self.strafeStatus[_unitID] = { hits = 0, zone = target, time = 1, ammo = _ammo, pastfoulline = false }
|
||||
|
||||
-- Rolling in!
|
||||
local _msg = string.format( "%s, rolling in on strafe pit %s.", self:_myname( _unitName ), _targetZone.name )
|
||||
local _msg = string.format( "%s, rolling in on strafe pit %s.", self:_myname( _unitName ), target.name )
|
||||
|
||||
if self.rangecontrol then
|
||||
self.rangecontrol:NewTransmission( RANGE.Sound.RCRollingInOnStrafeTarget.filename, RANGE.Sound.RCRollingInOnStrafeTarget.duration, self.soundpath )
|
||||
end
|
||||
clientRollingIn = true -- RANGEBOSS
|
||||
|
||||
-- Send message.
|
||||
self:_DisplayMessageToGroup( _unit, _msg, 10, true )
|
||||
hypemanStrafeRollIn = _msg -- RANGEBOSS
|
||||
|
||||
-- Trigger event that player is rolling in.
|
||||
self:RollingIn(playerData, target)
|
||||
|
||||
-- We found our player. Skip remaining checks.
|
||||
break
|
||||
@@ -3524,7 +3561,7 @@ function RANGE:_TargetsheetOnOff( _unitname )
|
||||
playerData.targeton = not playerData.targeton
|
||||
|
||||
-- Inform player.
|
||||
if playerData.targeton == true then
|
||||
if playerData and playerData.targeton == true then
|
||||
text = string.format( "roger, your targetsheets are now SAVED." )
|
||||
else
|
||||
text = string.format( "affirm, your targetsheets are NOT SAVED." )
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -79,6 +79,7 @@ SEAD = {
|
||||
["Kh25"] = "Kh25",
|
||||
["BGM_109"] = "BGM_109",
|
||||
["AGM_154"] = "AGM_154",
|
||||
["HY-2"] = "HY-2",
|
||||
}
|
||||
|
||||
--- Missile enumerators - from DCS ME and Wikipedia
|
||||
@@ -98,6 +99,7 @@ SEAD = {
|
||||
["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.
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
-- @module Functional.Shorad
|
||||
-- @image Functional.Shorad.jpg
|
||||
--
|
||||
-- Date: July 2021
|
||||
-- Date: Nov 2021
|
||||
|
||||
-------------------------------------------------------------------------
|
||||
--- **SHORAD** class, extends Core.Base#BASE
|
||||
@@ -41,6 +41,7 @@
|
||||
-- @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
|
||||
@@ -131,6 +132,7 @@ do
|
||||
["Kh29"] = "Kh29",
|
||||
["Kh31"] = "Kh31",
|
||||
["Kh66"] = "Kh66",
|
||||
--["BGM_109"] = "BGM_109",
|
||||
}
|
||||
|
||||
--- Instantiates a new SHORAD object
|
||||
@@ -138,13 +140,13 @@ do
|
||||
-- @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 groups
|
||||
-- @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, BASE:New() )
|
||||
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()
|
||||
@@ -162,11 +164,17 @@ do
|
||||
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.2.8")
|
||||
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
|
||||
|
||||
@@ -310,7 +318,7 @@ do
|
||||
local hit = false
|
||||
if self.DefendHarms then
|
||||
for _,_name in pairs (SHORAD.Harms) do
|
||||
if string.find(WeaponName,_name,1) then hit = true end
|
||||
if string.find(WeaponName,_name,1,true) then hit = true end
|
||||
end
|
||||
end
|
||||
return hit
|
||||
@@ -326,7 +334,7 @@ do
|
||||
local hit = false
|
||||
if self.DefendMavs then
|
||||
for _,_name in pairs (SHORAD.Mavs) do
|
||||
if string.find(WeaponName,_name,1) then hit = true end
|
||||
if string.find(WeaponName,_name,1,true) then hit = true end
|
||||
end
|
||||
end
|
||||
return hit
|
||||
@@ -367,7 +375,7 @@ do
|
||||
local returnname = false
|
||||
for _,_groups in pairs (shoradset) do
|
||||
local groupname = _groups:GetName()
|
||||
if string.find(groupname, tgtgrp, 1) then
|
||||
if string.find(groupname, tgtgrp, 1, true) then
|
||||
returnname = true
|
||||
--_groups:RelocateGroundRandomInRadius(7,100,false,false) -- be a bit evasive
|
||||
end
|
||||
@@ -388,7 +396,7 @@ do
|
||||
local returnname = false
|
||||
for _,_groups in pairs (shoradset) do
|
||||
local groupname = _groups:GetName()
|
||||
if string.find(groupname, tgtgrp, 1) then
|
||||
if string.find(groupname, tgtgrp, 1, true) then
|
||||
returnname = true
|
||||
end
|
||||
end
|
||||
@@ -400,6 +408,7 @@ do
|
||||
-- @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
|
||||
@@ -423,7 +432,7 @@ do
|
||||
-- mymantis = MANTIS:New("BlueMantis","Blue SAM","Blue EWR",nil,"blue",false,"Blue Awacs")
|
||||
-- mymantis:AddShorad(myshorad,720)
|
||||
-- mymantis:Start()
|
||||
function SHORAD:WakeUpShorad(TargetGroup, Radius, ActiveTimer, TargetCat)
|
||||
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
|
||||
@@ -477,6 +486,76 @@ do
|
||||
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
|
||||
@@ -504,13 +583,48 @@ do
|
||||
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
|
||||
targetunit = STATIC:Find(targetdata)
|
||||
--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
|
||||
@@ -519,7 +633,11 @@ do
|
||||
local targetgroup = nil
|
||||
local targetgroupname = "none"
|
||||
if targetcat == Object.Category.UNIT then
|
||||
targetgroup = targetunit:GetGroup()
|
||||
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
|
||||
@@ -540,6 +658,7 @@ do
|
||||
end
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
--
|
||||
end
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -804,7 +804,7 @@ do -- ZONE_CAPTURE_COALITION
|
||||
return IsEmpty
|
||||
end
|
||||
|
||||
--- Check if zone is "Guarded", i.e. only one (the defending) coalition is present inside the zone.
|
||||
--- Check if zone is "Guarded", i.e. only one (the defending) coaliton is present inside the zone.
|
||||
-- @param #ZONE_CAPTURE_COALITION self
|
||||
-- @return #boolean self:IsAllInZoneOfCoalition( self.Coalition )
|
||||
function ZONE_CAPTURE_COALITION:IsGuarded()
|
||||
@@ -826,7 +826,7 @@ do -- ZONE_CAPTURE_COALITION
|
||||
return IsCaptured
|
||||
end
|
||||
|
||||
--- Check if zone is "Attacked", i.e. another coalition entered the zone.
|
||||
--- Check if zone is "Attacked", i.e. another coaliton entered the zone.
|
||||
-- @param #ZONE_CAPTURE_COALITION self
|
||||
-- @return #boolean self:IsSomeInZoneOfCoalition( self.Coalition )
|
||||
function ZONE_CAPTURE_COALITION:IsAttacked()
|
||||
@@ -899,23 +899,24 @@ do -- ZONE_CAPTURE_COALITION
|
||||
end
|
||||
self:I(text)
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Misc Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
--- Update Mark on F10 map.
|
||||
-- @param #ZONE_CAPTURE_COALITION self
|
||||
function ZONE_CAPTURE_COALITION:Mark()
|
||||
|
||||
|
||||
if self.MarkOn then
|
||||
|
||||
|
||||
local Coord = self:GetCoordinate()
|
||||
local ZoneName = self:GetZoneName()
|
||||
local State = self:GetState()
|
||||
|
||||
|
||||
-- Remove marks.
|
||||
if self.MarkRed then
|
||||
Coord:RemoveMark(self.MarkRed)
|
||||
@@ -923,21 +924,21 @@ do -- ZONE_CAPTURE_COALITION
|
||||
if self.MarkBlue then
|
||||
Coord:RemoveMark(self.MarkBlue)
|
||||
end
|
||||
|
||||
-- Create new marks for each coalition.
|
||||
|
||||
-- Create new marks for each coaliton.
|
||||
if self.Coalition == coalition.side.BLUE then
|
||||
self.MarkBlue = Coord:MarkToCoalitionBlue( "Coalition: Blue\nGuard Zone: " .. ZoneName .. "\nStatus: " .. State )
|
||||
self.MarkBlue = Coord:MarkToCoalitionBlue( "Coalition: Blue\nGuard Zone: " .. ZoneName .. "\nStatus: " .. State )
|
||||
self.MarkRed = Coord:MarkToCoalitionRed( "Coalition: Blue\nCapture Zone: " .. ZoneName .. "\nStatus: " .. State )
|
||||
elseif self.Coalition == coalition.side.RED then
|
||||
self.MarkRed = Coord:MarkToCoalitionRed( "Coalition: Red\nGuard Zone: " .. ZoneName .. "\nStatus: " .. State )
|
||||
self.MarkRed = Coord:MarkToCoalitionRed( "Coalition: Red\nGuard Zone: " .. ZoneName .. "\nStatus: " .. State )
|
||||
self.MarkBlue = Coord:MarkToCoalitionBlue( "Coalition: Red\nCapture Zone: " .. ZoneName .. "\nStatus: " .. State )
|
||||
else
|
||||
self.MarkRed = Coord:MarkToCoalitionRed( "Coalition: Neutral\nCapture Zone: " .. ZoneName .. "\nStatus: " .. State )
|
||||
self.MarkRed = Coord:MarkToCoalitionRed( "Coalition: Neutral\nCapture Zone: " .. ZoneName .. "\nStatus: " .. State )
|
||||
self.MarkBlue = Coord:MarkToCoalitionBlue( "Coalition: Neutral\nCapture Zone: " .. ZoneName .. "\nStatus: " .. State )
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
--- **Functional (WIP)** -- Base class that models processes to achieve goals involving a Zone for a Coalition.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ZONE_GOAL_COALITION models processes that have a Goal with a defined achievement involving a Zone for a Coalition.
|
||||
--
|
||||
-- ZONE_GOAL_COALITION models processes that have a Goal with a defined achievement involving a Zone for a Coalition.
|
||||
-- Derived classes implement the ways how the achievements can be realized.
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- @module Functional.ZoneGoalCoalition
|
||||
-- @image MOOSE.JPG
|
||||
|
||||
@@ -24,33 +24,34 @@ do -- ZoneGoal
|
||||
-- @field #table ObjectCategories Table of object categories that are able to hold a zone. Default is UNITS and STATICS.
|
||||
-- @extends Functional.ZoneGoal#ZONE_GOAL
|
||||
|
||||
--- ZONE_GOAL_COALITION models processes that have a Goal with a defined achievement involving a Zone for a Coalition.
|
||||
|
||||
--- ZONE_GOAL_COALITION models processes that have a Goal with a defined achievement involving a Zone for a Coalition.
|
||||
-- Derived classes implement the ways how the achievements can be realized.
|
||||
--
|
||||
--
|
||||
-- ## 1. ZONE_GOAL_COALITION constructor
|
||||
--
|
||||
--
|
||||
-- * @{#ZONE_GOAL_COALITION.New}(): Creates a new ZONE_GOAL_COALITION object.
|
||||
--
|
||||
--
|
||||
-- ## 2. ZONE_GOAL_COALITION is a finite state machine (FSM).
|
||||
--
|
||||
--
|
||||
-- ### 2.1 ZONE_GOAL_COALITION States
|
||||
--
|
||||
--
|
||||
-- ### 2.2 ZONE_GOAL_COALITION Events
|
||||
--
|
||||
--
|
||||
-- ### 2.3 ZONE_GOAL_COALITION State Machine
|
||||
--
|
||||
--
|
||||
-- @field #ZONE_GOAL_COALITION
|
||||
ZONE_GOAL_COALITION = {
|
||||
ClassName = "ZONE_GOAL_COALITION",
|
||||
Coalition = nil,
|
||||
PreviousCoalition = nil,
|
||||
UnitCategories = nil,
|
||||
ClassName = "ZONE_GOAL_COALITION",
|
||||
Coalition = nil,
|
||||
PreviousCoaliton = nil,
|
||||
UnitCategories = nil,
|
||||
ObjectCategories = nil,
|
||||
}
|
||||
|
||||
|
||||
--- @field #table ZONE_GOAL_COALITION.States
|
||||
ZONE_GOAL_COALITION.States = {}
|
||||
|
||||
|
||||
--- ZONE_GOAL_COALITION Constructor.
|
||||
-- @param #ZONE_GOAL_COALITION self
|
||||
-- @param Core.Zone#ZONE Zone A @{Zone} object with the goal to be achieved.
|
||||
@@ -58,32 +59,33 @@ do -- ZoneGoal
|
||||
-- @param #table UnitCategories Table of unit categories. See [DCS Class Unit](https://wiki.hoggitworld.com/view/DCS_Class_Unit). Default {Unit.Category.GROUND_UNIT}.
|
||||
-- @return #ZONE_GOAL_COALITION
|
||||
function ZONE_GOAL_COALITION:New( Zone, Coalition, UnitCategories )
|
||||
|
||||
|
||||
if not Zone then
|
||||
BASE:E( "ERROR: No Zone specified in ZONE_GOAL_COALITION!" )
|
||||
BASE:E("ERROR: No Zone specified in ZONE_GOAL_COALITON!")
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
-- Inherit ZONE_GOAL.
|
||||
local self = BASE:Inherit( self, ZONE_GOAL:New( Zone ) ) -- #ZONE_GOAL_COALITION
|
||||
self:F( { Zone = Zone, Coalition = Coalition } )
|
||||
self:F( { Zone = Zone, Coalition = Coalition } )
|
||||
|
||||
-- Set initial owner.
|
||||
self:SetCoalition( Coalition or coalition.side.NEUTRAL )
|
||||
|
||||
self:SetCoalition( Coalition or coalition.side.NEUTRAL)
|
||||
|
||||
-- Set default unit and object categories for the zone scan.
|
||||
self:SetUnitCategories( UnitCategories )
|
||||
self:SetUnitCategories(UnitCategories)
|
||||
self:SetObjectCategories()
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Set the owning coalition of the zone.
|
||||
-- @param #ZONE_GOAL_COALITION self
|
||||
-- @param DCSCoalition.DCSCoalition#coalition Coalition The coalition ID, e.g. *coalition.side.RED*.
|
||||
-- @return #ZONE_GOAL_COALITION
|
||||
function ZONE_GOAL_COALITION:SetCoalition( Coalition )
|
||||
self.PreviousCoalition = self.Coalition or Coalition
|
||||
self.PreviousCoalition=self.Coalition or Coalition
|
||||
self.Coalition = Coalition
|
||||
return self
|
||||
end
|
||||
@@ -93,31 +95,31 @@ do -- ZoneGoal
|
||||
-- @param #table UnitCategories Table of unit categories. See [DCS Class Unit](https://wiki.hoggitworld.com/view/DCS_Class_Unit). Default {Unit.Category.GROUND_UNIT}.
|
||||
-- @return #ZONE_GOAL_COALITION
|
||||
function ZONE_GOAL_COALITION:SetUnitCategories( UnitCategories )
|
||||
|
||||
if UnitCategories and type( UnitCategories ) ~= "table" then
|
||||
UnitCategories = { UnitCategories }
|
||||
|
||||
if UnitCategories and type(UnitCategories)~="table" then
|
||||
UnitCategories={UnitCategories}
|
||||
end
|
||||
|
||||
self.UnitCategories = UnitCategories or { Unit.Category.GROUND_UNIT }
|
||||
|
||||
|
||||
self.UnitCategories=UnitCategories or {Unit.Category.GROUND_UNIT}
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Set the owning coalition of the zone.
|
||||
-- @param #ZONE_GOAL_COALITION self
|
||||
-- @param #table ObjectCategories Table of unit categories. See [DCS Class Object](https://wiki.hoggitworld.com/view/DCS_Class_Object). Default {Object.Category.UNIT, Object.Category.STATIC}, i.e. all UNITS and STATICS.
|
||||
-- @return #ZONE_GOAL_COALITION
|
||||
function ZONE_GOAL_COALITION:SetObjectCategories( ObjectCategories )
|
||||
|
||||
if ObjectCategories and type( ObjectCategories ) ~= "table" then
|
||||
ObjectCategories = { ObjectCategories }
|
||||
|
||||
if ObjectCategories and type(ObjectCategories)~="table" then
|
||||
ObjectCategories={ObjectCategories}
|
||||
end
|
||||
|
||||
self.ObjectCategories = ObjectCategories or { Object.Category.UNIT, Object.Category.STATIC }
|
||||
|
||||
|
||||
self.ObjectCategories=ObjectCategories or {Object.Category.UNIT, Object.Category.STATIC}
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Get the owning coalition of the zone.
|
||||
-- @param #ZONE_GOAL_COALITION self
|
||||
-- @return DCSCoalition.DCSCoalition#coalition Coalition.
|
||||
@@ -125,37 +127,39 @@ do -- ZoneGoal
|
||||
return self.Coalition
|
||||
end
|
||||
|
||||
--- Get the previous coalition, i.e. the one owning the zone before the current one.
|
||||
--- Get the previous coaliton, i.e. the one owning the zone before the current one.
|
||||
-- @param #ZONE_GOAL_COALITION self
|
||||
-- @return DCSCoalition.DCSCoalition#coalition Coalition.
|
||||
function ZONE_GOAL_COALITION:GetPreviousCoalition()
|
||||
return self.PreviousCoalition
|
||||
end
|
||||
|
||||
|
||||
--- Get the owning coalition name of the zone.
|
||||
-- @param #ZONE_GOAL_COALITION self
|
||||
-- @return #string Coalition name.
|
||||
function ZONE_GOAL_COALITION:GetCoalitionName()
|
||||
return UTILS.GetCoalitionName( self.Coalition )
|
||||
return UTILS.GetCoalitionName(self.Coalition)
|
||||
end
|
||||
|
||||
|
||||
--- Check status Coalition ownership.
|
||||
-- @param #ZONE_GOAL_COALITION self
|
||||
-- @return #ZONE_GOAL_COALITION
|
||||
function ZONE_GOAL_COALITION:StatusZone()
|
||||
|
||||
|
||||
-- Get current state.
|
||||
local State = self:GetState()
|
||||
|
||||
|
||||
-- Debug text.
|
||||
local text = string.format( "Zone state=%s, Owner=%s, Scanning...", State, self:GetCoalitionName() )
|
||||
self:F( text )
|
||||
|
||||
local text=string.format("Zone state=%s, Owner=%s, Scanning...", State, self:GetCoalitionName())
|
||||
self:F(text)
|
||||
|
||||
-- Scan zone.
|
||||
self:Scan( self.ObjectCategories, self.UnitCategories )
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
|
||||
@@ -33,13 +33,13 @@ if io then
|
||||
BASE:I("- io available")
|
||||
else
|
||||
BASE:I("- io NOT available! Some functions may not work.")
|
||||
__na=true
|
||||
__na=true
|
||||
end
|
||||
if lfs then
|
||||
BASE:I("- lfs available")
|
||||
else
|
||||
BASE:I("- lfs NOT available! Some functions may not work.")
|
||||
__na=true
|
||||
__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!)")
|
||||
|
||||
@@ -4,6 +4,7 @@ __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' )
|
||||
@@ -27,6 +28,9 @@ __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' )
|
||||
@@ -39,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' )
|
||||
@@ -68,13 +73,35 @@ __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/CTLD.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' )
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -32,9 +32,11 @@
|
||||
-- * [USS George Washington](https://en.wikipedia.org/wiki/USS_George_Washington_(CVN-73)) (CVN-73) [Super Carrier Module]
|
||||
-- * [USS Harry S. Truman](https://en.wikipedia.org/wiki/USS_Harry_S._Truman) (CVN-75) [Super Carrier Module]
|
||||
-- * [USS Forrestal](https://en.wikipedia.org/wiki/USS_Forrestal_(CV-59)) (CV-59) [Heatblur Carrier Module]
|
||||
-- * [HMS Hermes](https://en.wikipedia.org/wiki/HMS_Hermes_(R12)) (R12) [**WIP**]
|
||||
-- * [USS Tarawa](https://en.wikipedia.org/wiki/USS_Tarawa_(LHA-1)) (LHA-1) [**WIP**]
|
||||
-- * [USS America](https://en.wikipedia.org/wiki/USS_America_(LHA-6)) (LHA-6) [**WIP**]
|
||||
-- * [Juan Carlos I](https://en.wikipedia.org/wiki/Spanish_amphibious_assault_ship_Juan_Carlos_I) (L61) [**WIP**]
|
||||
-- * [HMAS Canberra](https://en.wikipedia.org/wiki/HMAS_Canberra_(L02)) (L02) [**WIP**]
|
||||
--
|
||||
-- **Supported Aircraft:**
|
||||
--
|
||||
@@ -51,9 +53,9 @@
|
||||
--
|
||||
-- At the moment, optimized parameters are available for the F/A-18C Hornet (Lot 20) and A-4E community mod as aircraft and the USS John C. Stennis as carrier.
|
||||
--
|
||||
-- The AV-8B Harrier, the USS Tarawa, USS America, HMAS Canberra and Juan Carlos I are WIP. The AV-8B harrier and the LHA's and LHD can only be used together, i.e. these ships are the only carriers the harrier is supposed to land on and
|
||||
-- The AV-8B Harrier, HMS Hermes, the USS Tarawa, USS America, HMAS Canberra, and Juan Carlos I are WIP. The AV-8B harrier and the LHA's and LHD can only be used together, i.e. these ships are the only carriers the harrier is supposed to land on and
|
||||
-- no other fixed wing aircraft (human or AI controlled) are supposed to land on these ships. Currently only Case I is supported. Case II/III take slightly different steps from the CVN carrier.
|
||||
-- However, the two Case II/III pattern are very similar so this is not a big drawback.
|
||||
-- However, if no offset is used for the holding radial this provides a very close representation of the V/STOL Case III, allowing for an approach to over the deck and a vertical landing.
|
||||
--
|
||||
-- Heatblur's mighty F-14B Tomcat has been added (March 13th 2019) as well. Same goes for the A version.
|
||||
--
|
||||
@@ -113,10 +115,11 @@
|
||||
-- * [Harrier Ship Landing Mission with Auto LSO!](https://www.youtube.com/watch?v=lqmVvpunk2c)
|
||||
-- * [Updated Airboss V/STOL Features USS Tarawa](https://youtu.be/K7I4pU6j718)
|
||||
-- * [Harrier Practice pattern USS America](https://youtu.be/99NigITYmcI)
|
||||
-- * [Harrier CASE III TACAN Approach USS Tarawa](https://www.youtube.com/watch?v=wWHag5WpNZ0)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **funkyfranky**
|
||||
-- ### Author: **funkyfranky** LHA and LHD V/STOL additions by **Pene**
|
||||
-- ### Special Thanks To **Bankler**
|
||||
-- For his great [Recovery Trainer](https://forums.eagle.ru/showthread.php?t=221412) mission and script!
|
||||
-- His work was the initial inspiration for this class. Also note that this implementation uses some routines for determining the player position in Case I recoveries he developed.
|
||||
@@ -1198,6 +1201,8 @@ AIRBOSS = {
|
||||
NmaxSection = nil,
|
||||
NmaxStack = nil,
|
||||
handleai = nil,
|
||||
xtVoiceOvers = nil,
|
||||
xtVoiceOversAI = nil,
|
||||
tanker = nil,
|
||||
Corientation = nil,
|
||||
Corientlast = nil,
|
||||
@@ -1260,7 +1265,7 @@ AIRBOSS = {
|
||||
|
||||
--- Aircraft types capable of landing on carrier (human+AI).
|
||||
-- @type AIRBOSS.AircraftCarrier
|
||||
-- @field #string AV8B AV-8B Night Harrier. Works only with the USS Tarawa, USS America and Juan Carlos I.
|
||||
-- @field #string AV8B AV-8B Night Harrier. Works only with the HMS Hermes, USS Tarawa, USS America, and Juan Carlos I.
|
||||
-- @field #string A4EC A-4E Community mod.
|
||||
-- @field #string HORNET F/A-18C Lot 20 Hornet by Eagle Dynamics.
|
||||
-- @field #string F14A F-14A by Heatblur.
|
||||
@@ -1296,6 +1301,7 @@ AIRBOSS.AircraftCarrier={
|
||||
-- @field #string TRUMAN USS Harry S. Truman (CVN-75) [Super Carrier Module]
|
||||
-- @field #string FORRESTAL USS Forrestal (CV-59) [Heatblur Carrier Module]
|
||||
-- @field #string VINSON USS Carl Vinson (CVN-70) [Obsolete]
|
||||
-- @field #string HERMES HMS Hermes (R12) [V/STOL Carrier]
|
||||
-- @field #string TARAWA USS Tarawa (LHA-1) [V/STOL Carrier]
|
||||
-- @field #string AMERICA USS America (LHA-6) [V/STOL Carrier]
|
||||
-- @field #string JCARLOS Juan Carlos I (L61) [V/STOL Carrier]
|
||||
@@ -1309,6 +1315,7 @@ AIRBOSS.CarrierType = {
|
||||
STENNIS = "Stennis",
|
||||
FORRESTAL = "Forrestal",
|
||||
VINSON = "VINSON",
|
||||
HERMES = "HERMES81",
|
||||
TARAWA = "LHA_Tarawa",
|
||||
AMERICA = "USS America LHA-6",
|
||||
JCARLOS = "L61",
|
||||
@@ -1725,7 +1732,6 @@ AIRBOSS.MenuF10Root = nil
|
||||
--- Airboss class version.
|
||||
-- @field #string version
|
||||
AIRBOSS.version = "1.2.1"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- TODO list
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
@@ -1895,6 +1901,12 @@ function AIRBOSS:New( carriername, alias )
|
||||
-- Set AI handling On.
|
||||
self:SetHandleAION()
|
||||
|
||||
-- No extra voiceover/calls from player by default
|
||||
self:SetExtraVoiceOvers(false)
|
||||
|
||||
-- No extra voiceover/calls from AI by default
|
||||
self:SetExtraVoiceOversAI(false)
|
||||
|
||||
-- Airboss is a nice guy.
|
||||
self:SetAirbossNiceGuy()
|
||||
|
||||
@@ -1980,6 +1992,9 @@ function AIRBOSS:New( carriername, alias )
|
||||
elseif self.carriertype == AIRBOSS.CarrierType.VINSON then
|
||||
-- TODO: Carl Vinson parameters.
|
||||
self:_InitStennis()
|
||||
elseif self.carriertype == AIRBOSS.CarrierType.HERMES then
|
||||
-- Hermes parameters.
|
||||
self:_InitHermes()
|
||||
elseif self.carriertype == AIRBOSS.CarrierType.TARAWA then
|
||||
-- Tarawa parameters.
|
||||
self:_InitTarawa()
|
||||
@@ -2082,7 +2097,7 @@ function AIRBOSS:New( carriername, alias )
|
||||
-- cL:FlareYellow()
|
||||
|
||||
-- Carrier specific.
|
||||
if self.carrier:GetTypeName() ~= AIRBOSS.CarrierType.TARAWA or self.carrier:GetTypeName() ~= AIRBOSS.CarrierType.AMERICA or self.carrier:GetTypeName() ~= AIRBOSS.CarrierType.JCARLOS or self.carrier:GetTypeName() ~= AIRBOSS.CarrierType.CANBERRA then
|
||||
if self.carrier:GetTypeName() ~= AIRBOSS.CarrierType.HERMES or self.carrier:GetTypeName() ~= AIRBOSS.CarrierType.TARAWA or self.carrier:GetTypeName() ~= AIRBOSS.CarrierType.AMERICA or self.carrier:GetTypeName() ~= AIRBOSS.CarrierType.JCARLOS or self.carrier:GetTypeName() ~= AIRBOSS.CarrierType.CANBERRA then
|
||||
|
||||
-- Flare wires.
|
||||
local w1 = stern:Translate( self.carrierparam.wire1, FB, true )
|
||||
@@ -2811,13 +2826,29 @@ end
|
||||
-- @param #number Low
|
||||
-- @param #number LOW
|
||||
-- @return #AIRBOSS self
|
||||
function AIRBOSS:SetGlideslopeErrorThresholds( _max, _min, High, HIGH, Low, LOW )
|
||||
self.gle._max = _max or 0.4
|
||||
self.gle.High = High or 0.8
|
||||
self.gle.HIGH = HIGH or 1.5
|
||||
self.gle._min = _min or -0.3
|
||||
self.gle.Low = Low or -0.6
|
||||
self.gle.LOW = LOW or -0.9
|
||||
|
||||
function AIRBOSS:SetGlideslopeErrorThresholds(_max,_min, High, HIGH, Low, LOW)
|
||||
|
||||
--Check if V/STOL Carrier
|
||||
if self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then
|
||||
|
||||
-- allow a larger GSE for V/STOL operations --Pene Testing
|
||||
self.gle._max=_max or 0.7
|
||||
self.gle.High=High or 1.4
|
||||
self.gle.HIGH=HIGH or 1.9
|
||||
self.gle._min=_min or -0.5
|
||||
self.gle.Low=Low or -1.2
|
||||
self.gle.LOW=LOW or -1.5
|
||||
-- CVN values
|
||||
else
|
||||
self.gle._max=_max or 0.4
|
||||
self.gle.High=High or 0.8
|
||||
self.gle.HIGH=HIGH or 1.5
|
||||
self.gle._min=_min or -0.3
|
||||
self.gle.Low=Low or -0.6
|
||||
self.gle.LOW=LOW or -0.9
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -2832,15 +2863,33 @@ end
|
||||
-- @param #number RightMed
|
||||
-- @param #number RIGHT
|
||||
-- @return #AIRBOSS self
|
||||
function AIRBOSS:SetLineupErrorThresholds( _max, _min, Left, LeftMed, LEFT, Right, RightMed, RIGHT )
|
||||
self.lue._max = _max or 0.5
|
||||
self.lue._min = _min or -0.5
|
||||
self.lue.Left = Left or -1.0
|
||||
self.lue.LeftMed = LeftMed or -2.0
|
||||
self.lue.LEFT = LEFT or -3.0
|
||||
self.lue.Right = Right or 1.0
|
||||
self.lue.RightMed = RightMed or 2.0
|
||||
self.lue.RIGHT = RIGHT or 3.0
|
||||
|
||||
function AIRBOSS:SetLineupErrorThresholds(_max,_min, Left, LeftMed, LEFT, Right, RightMed, RIGHT)
|
||||
|
||||
--Check if V/STOL Carrier -- Pene testing
|
||||
if self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then
|
||||
|
||||
-- V/STOL Values -- allow a larger LUE for V/STOL operations
|
||||
self.lue._max=_max or 1.8
|
||||
self.lue._min=_min or -1.8
|
||||
self.lue.Left=Left or -2.8
|
||||
self.lue.LeftMed=LeftMed or -3.8
|
||||
self.lue.LEFT=LEFT or -4.5
|
||||
self.lue.Right=Right or 2.8
|
||||
self.lue.RightMed=RightMed or 3.8
|
||||
self.lue.RIGHT=RIGHT or 4.5
|
||||
-- CVN Values
|
||||
else
|
||||
self.lue._max=_max or 0.5
|
||||
self.lue._min=_min or -0.5
|
||||
self.lue.Left=Left or -1.0
|
||||
self.lue.LeftMed=LeftMed or -2.0
|
||||
self.lue.LEFT=LEFT or -3.0
|
||||
self.lue.Right=Right or 1.0
|
||||
self.lue.RightMed=RightMed or 2.0
|
||||
self.lue.RIGHT=RIGHT or 3.0
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -2973,6 +3022,7 @@ end
|
||||
-- @param #AIRBOSS self
|
||||
-- @param #number TimeInterval (Optional) Time interval in seconds. Default 1200 sec = 20 min.
|
||||
-- @return #AIRBOSS self
|
||||
|
||||
function AIRBOSS:SetBeaconRefresh( TimeInterval )
|
||||
self.dTbeacon = TimeInterval or (20 * 60)
|
||||
return self
|
||||
@@ -3186,6 +3236,24 @@ function AIRBOSS:SetHandleAION()
|
||||
return self
|
||||
end
|
||||
|
||||
--- Will play the inbound calls, commencing, initial, etc. from the player when requesteing marshal
|
||||
-- @param #AIRBOSS self
|
||||
-- @param #AIRBOSS status Boolean to activate (true) / deactivate (false) the radio inbound calls (default is ON)
|
||||
-- @return #AIRBOSS self
|
||||
function AIRBOSS:SetExtraVoiceOvers(status)
|
||||
self.xtVoiceOvers=status
|
||||
return self
|
||||
end
|
||||
|
||||
--- Will simulate the inbound call, commencing, initial, etc from the AI when requested by Airboss
|
||||
-- @param #AIRBOSS self
|
||||
-- @param #AIRBOSS status Boolean to activate (true) / deactivate (false) the radio inbound calls (default is ON)
|
||||
-- @return #AIRBOSS self
|
||||
function AIRBOSS:SetExtraVoiceOversAI(status)
|
||||
self.xtVoiceOversAI=status
|
||||
return self
|
||||
end
|
||||
|
||||
--- Do not handle AI aircraft.
|
||||
-- @param #AIRBOSS self
|
||||
-- @return #AIRBOSS self
|
||||
@@ -4384,6 +4452,46 @@ function AIRBOSS:_InitForrestal()
|
||||
|
||||
end
|
||||
|
||||
--- Init parameters for R12 HMS Hermes carrier.
|
||||
-- @param #AIRBOSS self
|
||||
function AIRBOSS:_InitHermes()
|
||||
|
||||
-- Init Stennis as default.
|
||||
self:_InitStennis()
|
||||
|
||||
-- Carrier Parameters.
|
||||
self.carrierparam.sterndist = -105
|
||||
self.carrierparam.deckheight = 12 -- From model viewer WL0.
|
||||
|
||||
-- Total size of the carrier (approx as rectangle).
|
||||
self.carrierparam.totlength = 228.19
|
||||
self.carrierparam.totwidthport = 20.5
|
||||
self.carrierparam.totwidthstarboard = 24.5
|
||||
|
||||
-- Landing runway.
|
||||
self.carrierparam.rwyangle = 0
|
||||
self.carrierparam.rwylength = 215
|
||||
self.carrierparam.rwywidth = 13
|
||||
|
||||
-- Wires.
|
||||
self.carrierparam.wire1 = nil
|
||||
self.carrierparam.wire2 = nil
|
||||
self.carrierparam.wire3 = nil
|
||||
self.carrierparam.wire4 = nil
|
||||
|
||||
-- Late break.
|
||||
self.BreakLate.name = "Late Break"
|
||||
self.BreakLate.Xmin = -UTILS.NMToMeters( 1 ) -- Not more than 1 NM behind the boat. Last check was at 0.
|
||||
self.BreakLate.Xmax = UTILS.NMToMeters( 5 ) -- Not more than 5 NM in front of the boat. Enough for late breaks?
|
||||
self.BreakLate.Zmin = -UTILS.NMToMeters( 0.25 ) -- Not more than 0.25 NM port.
|
||||
self.BreakLate.Zmax = UTILS.NMToMeters( 0.5 ) -- Not more than 0.5 NM starboard.
|
||||
self.BreakLate.LimitXmin = 0 -- Check and next step 0.8 NM port and in front of boat.
|
||||
self.BreakLate.LimitXmax = nil
|
||||
self.BreakLate.LimitZmin = -UTILS.NMToMeters( 0.5 ) -- 926 m port, closer than the stennis as abeam is 0.8-1.0 rather than 1.2
|
||||
self.BreakLate.LimitZmax = nil
|
||||
|
||||
end
|
||||
|
||||
--- Init parameters for LHA-1 Tarawa carrier.
|
||||
-- @param #AIRBOSS self
|
||||
function AIRBOSS:_InitTarawa()
|
||||
@@ -5007,14 +5115,16 @@ function AIRBOSS:_GetAircraftAoA( playerData )
|
||||
aoa.Fast = 8.25 -- =17.5/2
|
||||
aoa.FAST = 8.00 -- =16.5/2
|
||||
elseif harrier then
|
||||
-- AV-8B Harrier parameters. Tuning done on the Fast AoA to allow for abeam and ninety at Nozzles 60 - 73.
|
||||
aoa.SLOW = 14.0
|
||||
aoa.Slow = 13.0
|
||||
aoa.OnSpeedMax = 12.0
|
||||
aoa.OnSpeed = 11.0
|
||||
aoa.OnSpeedMin = 10.0
|
||||
aoa.Fast = 8.0
|
||||
aoa.FAST = 7.5
|
||||
|
||||
-- AV-8B Harrier parameters. Tuning done on the Fast AoA to allow for abeam and ninety at Nozzles 55. Pene testing
|
||||
aoa.SLOW = 16.0
|
||||
aoa.Slow = 13.5
|
||||
aoa.OnSpeedMax = 12.5
|
||||
aoa.OnSpeed = 10.0
|
||||
aoa.OnSpeedMin = 9.5
|
||||
aoa.Fast = 8.0
|
||||
aoa.FAST = 7.5
|
||||
|
||||
end
|
||||
|
||||
return aoa
|
||||
@@ -5177,6 +5287,7 @@ function AIRBOSS:_GetAircraftParameters( playerData, step )
|
||||
alt = UTILS.FeetToMeters( 800 )
|
||||
speed = UTILS.KnotsToMps( 350 )
|
||||
elseif skyhawk then
|
||||
|
||||
alt = UTILS.FeetToMeters( 600 )
|
||||
speed = UTILS.KnotsToMps( 250 )
|
||||
elseif goshawk then
|
||||
@@ -5273,8 +5384,8 @@ function AIRBOSS:_GetAircraftParameters( playerData, step )
|
||||
elseif skyhawk then
|
||||
alt = UTILS.FeetToMeters( 300 ) -- ?
|
||||
elseif harrier then
|
||||
-- 300-325 ft
|
||||
alt = UTILS.FeetToMeters( 300 ) -- Need to verify
|
||||
alt=UTILS.FeetToMeters(312)-- 300-325 ft
|
||||
|
||||
end
|
||||
|
||||
aoa = aoaac.OnSpeed
|
||||
@@ -5492,6 +5603,12 @@ function AIRBOSS:_ClearForLanding( flight )
|
||||
-- Cleared for Case X recovery.
|
||||
self:_MarshalCallClearedForRecovery( flight.onboard, flight.case )
|
||||
|
||||
-- Voice over of the commencing simulated call from AI
|
||||
if self.xtVoiceOversAI then
|
||||
local leader = flight.group:GetUnits()[1]
|
||||
self:_CommencingCall(leader, flight.onboard)
|
||||
end
|
||||
|
||||
else
|
||||
|
||||
-- Cleared for Case X recovery.
|
||||
@@ -5596,7 +5713,7 @@ function AIRBOSS:_ScanCarrierZone()
|
||||
local putintomarshal = false
|
||||
|
||||
-- Get flight group.
|
||||
local flight = _DATABASE:GetFlightGroup( groupname )
|
||||
local flight = _DATABASE:GetOpsGroup( groupname )
|
||||
|
||||
if flight and flight:IsInbound() and flight.destbase:GetName() == self.carrier:GetName() then
|
||||
if flight.ishelo then
|
||||
@@ -5642,7 +5759,6 @@ function AIRBOSS:_ScanCarrierZone()
|
||||
if not self:_IsHuman( group ) then
|
||||
self:_CreateFlightGroup( group )
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
@@ -5856,7 +5972,12 @@ function AIRBOSS:_MarshalAI( flight, nstack, respawn )
|
||||
end
|
||||
|
||||
-- Check if flight is already in Marshal queue.
|
||||
if not self:_InQueue( self.Qmarshal, flight.group ) then
|
||||
if not self:_InQueue(self.Qmarshal,flight.group) then
|
||||
-- Simulate inbound call
|
||||
if self.xtVoiceOversAI then
|
||||
local leader = flight.group:GetUnits()[1]
|
||||
self:_MarshallInboundCall(leader, flight.onboard)
|
||||
end
|
||||
-- Add group to marshal stack queue.
|
||||
self:_AddMarshalGroup( flight, nstack )
|
||||
end
|
||||
@@ -6209,7 +6330,7 @@ function AIRBOSS:_GetMarshalAltitude( stack, case )
|
||||
p2 = Carrier:Translate( UTILS.NMToMeters( 1.5 ), hdg )
|
||||
|
||||
-- Tarawa,LHA,LHD Delta patterns.
|
||||
if self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then
|
||||
if self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then
|
||||
|
||||
-- Pattern is directly overhead the carrier.
|
||||
p1 = Carrier:Translate( UTILS.NMToMeters( 1.0 ), hdg + 90 )
|
||||
@@ -8048,7 +8169,7 @@ function AIRBOSS:OnEventLand( EventData )
|
||||
self:T( self.lid .. text )
|
||||
|
||||
-- Check carrier type.
|
||||
if self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then
|
||||
if self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then
|
||||
|
||||
-- Power "Idle".
|
||||
self:RadioTransmission( self.LSORadio, self.LSOCall.IDLE, false, 1, nil, true )
|
||||
@@ -8083,7 +8204,8 @@ function AIRBOSS:OnEventLand( EventData )
|
||||
-- AI unit landed --
|
||||
--------------------
|
||||
|
||||
if self.carriertype ~= AIRBOSS.CarrierType.TARAWA or self.carriertype ~= AIRBOSS.CarrierType.AMERICA or self.carriertype ~= AIRBOSS.CarrierType.JCARLOS or self.carriertype ~= AIRBOSS.CarrierType.CANBERRA then
|
||||
if self.carriertype ~= AIRBOSS.CarrierType.HERMES or self.carriertype ~= AIRBOSS.CarrierType.TARAWA or self.carriertype ~= AIRBOSS.CarrierType.AMERICA or self.carriertype ~= AIRBOSS.CarrierType.JCARLOS or self.carriertype ~= AIRBOSS.CarrierType.CANBERRA then
|
||||
|
||||
|
||||
-- Coordinate at landing event
|
||||
local coord = EventData.IniUnit:GetCoordinate()
|
||||
@@ -9121,7 +9243,7 @@ function AIRBOSS:_CheckForLongDownwind( playerData )
|
||||
local limit = UTILS.NMToMeters( -1.6 )
|
||||
|
||||
-- For the tarawa, other LHA and LHD we give a bit more space.
|
||||
if self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then
|
||||
if self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then
|
||||
limit = UTILS.NMToMeters( -2.0 )
|
||||
end
|
||||
|
||||
@@ -9208,7 +9330,8 @@ function AIRBOSS:_Ninety( playerData )
|
||||
self:_PlayerHint( playerData )
|
||||
|
||||
-- Next step: wake.
|
||||
if self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then
|
||||
if self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then
|
||||
|
||||
-- Harrier has no wake stop. It stays port of the boat.
|
||||
self:_SetPlayerStep( playerData, AIRBOSS.PatternStep.FINAL )
|
||||
else
|
||||
@@ -9464,8 +9587,9 @@ function AIRBOSS:_Groove( playerData )
|
||||
-- Speed difference.
|
||||
local dv = math.abs( vplayer - vcarrier )
|
||||
|
||||
-- Stable when speed difference < 20 km/h.
|
||||
local stable = dv < 20
|
||||
|
||||
-- Stable when speed difference < 30 km/h.(16 Kts)Pene Testing
|
||||
local stable=dv<30
|
||||
|
||||
-- Check if player is inside the zone.
|
||||
if playerData.unit:IsInZone( ZoneALS ) and stable then
|
||||
@@ -9497,8 +9621,8 @@ function AIRBOSS:_Groove( playerData )
|
||||
-- Speed difference.
|
||||
local dv = math.abs( vplayer - vcarrier )
|
||||
|
||||
-- Stable when v<10 km/h.
|
||||
local stable = dv < 10
|
||||
-- Stable when v<15 km/h.
|
||||
local stable=dv<15
|
||||
|
||||
-- Radio Transmission "Stabilized" once the aircraft has been cleared to cross and is over the Landing Spot and stable.
|
||||
if playerData.unit:IsInZone( ZoneLS ) and stable and playerData.stable == true then
|
||||
@@ -9537,25 +9661,26 @@ function AIRBOSS:_Groove( playerData )
|
||||
-- Nothing else necessary.
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
-- Long V/STOL groove time Wave Off over 75 seconds to IC - TOPGUN level Only. --pene testing (WIP)
|
||||
|
||||
-- if rho>=RAR and rho<=RIC and not playerData.waveoff and playerData.difficulty==AIRBOSS.Difficulty.HARD and playerData.actype== AIRBOSS.AircraftCarrier.AV8B then
|
||||
-- Get groove time
|
||||
-- local vSlow=groovedata.time
|
||||
-- If too slow wave off.
|
||||
-- if vSlow >75 then
|
||||
|
||||
-- LSO Wave off!
|
||||
-- self:RadioTransmission(self.LSORadio, self.LSOCall.WAVEOFF, nil, nil, nil, true)
|
||||
-- playerData.Tlso=timer.getTime()
|
||||
|
||||
-- Player was waved Off
|
||||
-- playerData.waveoff=true
|
||||
-- return
|
||||
-- end
|
||||
-- end
|
||||
end
|
||||
|
||||
-- Long V/STOL groove time Wave Off over 75 seconds to IC - TOPGUN level Only. --pene testing (WIP)--- Need to think more about this.
|
||||
|
||||
--if rho>=RAR and rho<=RIC and not playerData.waveoff and playerData.difficulty==AIRBOSS.Difficulty.HARD and playerData.actype== AIRBOSS.AircraftCarrier.AV8B then
|
||||
-- Get groove time
|
||||
--local vSlow=groovedata.time
|
||||
-- If too slow wave off.
|
||||
--if vSlow >75 then
|
||||
|
||||
-- LSO Wave off!
|
||||
--self:RadioTransmission(self.LSORadio, self.LSOCall.WAVEOFF, nil, nil, nil, true)
|
||||
--playerData.Tlso=timer.getTime()
|
||||
|
||||
-- Player was waved Off
|
||||
--playerData.waveoff=true
|
||||
--return
|
||||
--end
|
||||
--end
|
||||
|
||||
-- Groovedata step.
|
||||
groovedata.Step = playerData.step
|
||||
@@ -9726,8 +9851,8 @@ function AIRBOSS:_CheckWaveOff( glideslopeError, lineupError, AoA, playerData )
|
||||
-- For the harrier, we allow a bit more room.
|
||||
if playerData.actype == AIRBOSS.AircraftCarrier.AV8B then
|
||||
glMax = 2.6
|
||||
glMin = -2.0
|
||||
luAbs = 4.1 -- Testing Pene (WIP) needs feedback to tighten up tolerences.
|
||||
glMin = -2.2 -- Testing, @Engines may be just dragging it in on Hermes, or the carrier parameters need adjusting.
|
||||
luAbs = 4.1 -- Testing Pene.
|
||||
|
||||
end
|
||||
|
||||
@@ -9893,17 +10018,23 @@ function AIRBOSS:_GetSternCoord()
|
||||
local hdg = self.carrier:GetHeading()
|
||||
|
||||
-- Final bearing (true).
|
||||
local FB = self:GetFinalBearing()
|
||||
local FB=self:GetFinalBearing()
|
||||
local case=self.case
|
||||
|
||||
-- Stern coordinate (sterndist<0). Also translate 10 meters starboard wrt Final bearing.
|
||||
self.sterncoord:UpdateFromCoordinate( self:GetCoordinate() )
|
||||
-- local stern=self:GetCoordinate()
|
||||
|
||||
-- Stern coordinate (sterndist<0).
|
||||
if self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then
|
||||
-- Tarawa: Translate 8 meters port.
|
||||
self.sterncoord:Translate( self.carrierparam.sterndist, hdg, true, true ):Translate( 8, FB - 90, true, true )
|
||||
elseif self.carriertype == AIRBOSS.CarrierType.STENNIS then
|
||||
-- Stern coordinate (sterndist<0). --Pene testing Case III
|
||||
if self.carriertype==AIRBOSS.CarrierType.HERMES or self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS or self.carriertype==AIRBOSS.CarrierType.CANBERRA then
|
||||
if case==3 then
|
||||
-- CASE III V/STOL translation Due over deck approach if needed.
|
||||
self.sterncoord:Translate(self.carrierparam.sterndist, hdg, true, true):Translate(8, FB-90, true, true)
|
||||
elseif case==2 or case==1 then
|
||||
-- V/Stol: Translate 8 meters port.
|
||||
self.sterncoord:Translate(self.carrierparam.sterndist, hdg, true, true):Translate(8, FB-90, true, true)
|
||||
end
|
||||
elseif self.carriertype==AIRBOSS.CarrierType.STENNIS then
|
||||
-- Stennis: translate 7 meters starboard wrt Final bearing.
|
||||
self.sterncoord:Translate( self.carrierparam.sterndist, hdg, true, true ):Translate( 7, FB + 90, true, true )
|
||||
elseif self.carriertype == AIRBOSS.CarrierType.FORRESTAL then
|
||||
@@ -9945,6 +10076,7 @@ function AIRBOSS:_GetWire( Lcoord, dc )
|
||||
-- Multiplayer wire correction.
|
||||
if self.mpWireCorrection then
|
||||
d = d - self.mpWireCorrection
|
||||
|
||||
end
|
||||
|
||||
-- Shift wires from stern to their correct position.
|
||||
@@ -10536,7 +10668,8 @@ function AIRBOSS:_GetZoneRunwayBox()
|
||||
return self.zoneRunwaybox
|
||||
end
|
||||
|
||||
--- Get zone of primary abeam landing position of USS Tarawa, USS America and Juan Carlos. Box length 50 meters and width 30 meters.
|
||||
--- Get zone of primary abeam landing position of HMS Hermes, USS Tarawa, USS America and Juan Carlos. Box length 50 meters and width 30 meters.
|
||||
|
||||
--- Allow for Clear to land call from LSO approaching abeam the landing spot if stable as per NATOPS 00-80T
|
||||
-- @param #AIRBOSS self
|
||||
-- @return Core.Zone#ZONE_POLYGON Zone surrounding landing runway.
|
||||
@@ -10548,9 +10681,9 @@ function AIRBOSS:_GetZoneAbeamLandingSpot()
|
||||
-- Current carrier heading.
|
||||
local FB = self:GetFinalBearing( false )
|
||||
|
||||
-- Coordinate array.
|
||||
local p = {}
|
||||
|
||||
-- Coordinate array. Pene Testing extended Abeam landing spot V/STOL.
|
||||
local p={}
|
||||
|
||||
-- Points.
|
||||
p[1] = S:Translate( 15, FB ):Translate( 15, FB + 90 ) -- Top-Right
|
||||
p[2] = S:Translate( -45, FB ):Translate( 15, FB + 90 ) -- Bottom-Right
|
||||
@@ -10640,7 +10773,7 @@ function AIRBOSS:_GetZoneHolding( case, stack )
|
||||
self.zoneHolding = ZONE_RADIUS:New( "CASE I Holding Zone", Post:GetVec2(), self.marshalradius )
|
||||
|
||||
-- Delta pattern.
|
||||
if self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then
|
||||
if self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then
|
||||
self.zoneHolding = ZONE_RADIUS:New( "CASE I Holding Zone", self.carrier:GetVec2(), UTILS.NMToMeters( 5 ) )
|
||||
end
|
||||
|
||||
@@ -10692,8 +10825,7 @@ function AIRBOSS:_GetZoneCommence( case, stack )
|
||||
-- Three position
|
||||
local Three = self:GetCoordinate():Translate( D, hdg + 275 )
|
||||
|
||||
if self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then
|
||||
|
||||
if self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then
|
||||
local Dx = UTILS.NMToMeters( 2.25 )
|
||||
|
||||
local Dz = UTILS.NMToMeters( 2.25 )
|
||||
@@ -10983,7 +11115,7 @@ function AIRBOSS:_GetAltCarrier( unit )
|
||||
return h
|
||||
end
|
||||
|
||||
--- Get optimal landing position of the aircraft. Usually between second and third wire. In case of Tarawa and America we take the abeam landing spot 120 ft abeam the 7.5 position, for the Juan Carlos I it is 120 ft and abeam the 5 position.
|
||||
--- Get optimal landing position of the aircraft. Usually between second and third wire. In case of Tarawa, Canberrra, Juan Carlos and America we take the abeam landing spot 120 ft above and 21 ft abeam the 7.5 position, for the Juan Carlos I and HMS Hermes it is 120 ft above and 21 ft abeam the 5 position. For CASE III it is 120ft directly above the landing spot.
|
||||
-- @param #AIRBOSS self
|
||||
-- @return Core.Point#COORDINATE Optimal landing coordinate.
|
||||
function AIRBOSS:_GetOptLandingCoordinate()
|
||||
@@ -10993,45 +11125,28 @@ function AIRBOSS:_GetOptLandingCoordinate()
|
||||
|
||||
-- Stern coordinate.
|
||||
-- local stern=self:_GetSternCoord()
|
||||
|
||||
-- Final bearing.
|
||||
local FB = self:GetFinalBearing( false )
|
||||
|
||||
if self.carriertype == AIRBOSS.CarrierType.TARAWA then
|
||||
local FB=self:GetFinalBearing(false)
|
||||
local case=self.case
|
||||
-- set Case III V/STOL abeam landing spot over deck -- Pene Testing
|
||||
if self.carriertype==AIRBOSS.CarrierType.HERMES or self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS or self.carriertype==AIRBOSS.CarrierType.CANBERRA then
|
||||
|
||||
if case==3 then
|
||||
self.landingcoord:UpdateFromCoordinate(self:_GetLandingSpotCoordinate())
|
||||
-- Altitude 120ft -- is this corect for Case III?
|
||||
self.landingcoord:SetAltitude(UTILS.FeetToMeters(120))
|
||||
|
||||
elseif case==2 or case==1 then
|
||||
|
||||
-- Landing 100 ft abeam, 120 ft alt.
|
||||
self.landingcoord:UpdateFromCoordinate( self:_GetLandingSpotCoordinate() ):Translate( 35, FB - 90, true, true )
|
||||
-- stern=self:_GetLandingSpotCoordinate():Translate(35, FB-90)
|
||||
self.landingcoord:UpdateFromCoordinate(self:_GetLandingSpotCoordinate()):Translate(35, FB-90, true, true)
|
||||
--stern=self:_GetLandingSpotCoordinate():Translate(35, FB-90)
|
||||
|
||||
-- Alitude 120 ft.
|
||||
self.landingcoord:SetAltitude( UTILS.FeetToMeters( 120 ) )
|
||||
elseif self.carriertype == AIRBOSS.CarrierType.AMERICA then
|
||||
|
||||
-- Landing 100 ft abeam, 120 ft alt. To allow adjustments to match different deck configurations.
|
||||
self.landingcoord:UpdateFromCoordinate( self:_GetLandingSpotCoordinate() ):Translate( 35, FB - 90, true, true )
|
||||
-- stern=self:_GetLandingSpotCoordinate():Translate(35, FB-90)
|
||||
|
||||
-- Alitude 120 ft.
|
||||
self.landingcoord:SetAltitude( UTILS.FeetToMeters( 120 ) )
|
||||
|
||||
elseif self.carriertype == AIRBOSS.CarrierType.JCARLOS then
|
||||
|
||||
-- Landing 100 ft abeam, 120 ft alt.
|
||||
self.landingcoord:UpdateFromCoordinate( self:_GetLandingSpotCoordinate() ):Translate( 35, FB - 90, true, true )
|
||||
-- stern=self:_GetLandingSpotCoordinate():Translate(35, FB-90)
|
||||
|
||||
-- Alitude 120 ft.
|
||||
self.landingcoord:SetAltitude( UTILS.FeetToMeters( 120 ) )
|
||||
|
||||
elseif self.carriertype == AIRBOSS.CarrierType.CANBERRA then
|
||||
|
||||
-- Landing 100 ft abeam, 120 ft alt.
|
||||
self.landingcoord:UpdateFromCoordinate( self:_GetLandingSpotCoordinate() ):Translate( 35, FB - 90, true, true )
|
||||
-- stern=self:_GetLandingSpotCoordinate():Translate(35, FB-90)
|
||||
|
||||
-- Alitude 120 ft.
|
||||
self.landingcoord:SetAltitude( UTILS.FeetToMeters( 120 ) )
|
||||
|
||||
self.landingcoord:SetAltitude(UTILS.FeetToMeters(120))
|
||||
end
|
||||
|
||||
else
|
||||
|
||||
-- Ideally we want to land between 2nd and 3rd wire.
|
||||
@@ -11059,7 +11174,14 @@ function AIRBOSS:_GetLandingSpotCoordinate()
|
||||
-- Stern coordinate.
|
||||
-- local stern=self:_GetSternCoord()
|
||||
|
||||
if self.carriertype == AIRBOSS.CarrierType.TARAWA then
|
||||
if self.carriertype==AIRBOSS.CarrierType.HERMES then
|
||||
|
||||
-- Landing 100 ft abeam, 100 alt.
|
||||
local hdg = self:GetHeading()
|
||||
|
||||
-- Primary landing spot 5
|
||||
self.landingspotcoord:Translate( 69, hdg, true, true ):SetAltitude( self.carrierparam.deckheight )
|
||||
elseif self.carriertype == AIRBOSS.CarrierType.TARAWA then
|
||||
|
||||
-- Landing 100 ft abeam, 120 alt.
|
||||
local hdg = self:GetHeading()
|
||||
@@ -11649,15 +11771,17 @@ function AIRBOSS:_LSOgrade( playerData )
|
||||
local G = GXX .. " " .. GIM .. " " .. " " .. GIC .. " " .. GAR
|
||||
|
||||
-- Count number of minor, normal and major deviations.
|
||||
local N = nXX + nIM + nIC + nAR
|
||||
local nL = count( G, '_' ) / 2
|
||||
local nS = count( G, '%(' )
|
||||
local nN = N - nS - nL
|
||||
|
||||
-- Groove time 15-18.99 sec for a unicorn. Or 65-70 for V/STOL unicorn.
|
||||
local Tgroove = playerData.Tgroove
|
||||
local TgrooveUnicorn = Tgroove and (Tgroove >= 15.0 and Tgroove <= 18.99) or false
|
||||
local TgrooveVstolUnicorn = Tgroove and (Tgroove >= 60.0 and Tgroove <= 65.0) and playerData.actype == AIRBOSS.AircraftCarrier.AV8B or false
|
||||
local N=nXX+nIM+nIC+nAR
|
||||
local Nv=nXX+nIM
|
||||
local nL=count(G, '_')/2
|
||||
local nS=count(G, '%(')
|
||||
local nN=N-nS-nL
|
||||
local nNv=Nv-nS-nL
|
||||
|
||||
-- Groove time 15-18.99 sec for a unicorn. Or 60-65 for V/STOL unicorn.
|
||||
local Tgroove=playerData.Tgroove
|
||||
local TgrooveUnicorn=Tgroove and (Tgroove>=15.0 and Tgroove<=18.99) or false
|
||||
local TgrooveVstolUnicorn=Tgroove and (Tgroove>=60.0 and Tgroove<=65.0)and playerData.actype==AIRBOSS.AircraftCarrier.AV8B or false
|
||||
|
||||
local grade
|
||||
local points
|
||||
@@ -11668,29 +11792,33 @@ function AIRBOSS:_LSOgrade( playerData )
|
||||
G = "Unicorn"
|
||||
else
|
||||
|
||||
-- Add AV-8B Harrier devation allowances due to lower groundspeed and 3x conventional groove time, this allows to maintain LSO tolerances while respecting the deviations are not unsafe. (WIP requires feedback)
|
||||
-- Large devaitions still result in a No Grade, A Unicorn still requires a clean pass with no deviation.
|
||||
if nL > 3 and playerData.actype == AIRBOSS.AircraftCarrier.AV8B then
|
||||
-- Add AV-8B Harrier devation allowances due to lower groundspeed and 3x conventional groove time, this allows to maintain LSO tolerances while respecting the deviations are not unsafe.--Pene testing
|
||||
-- Large devaitions still result in a No Grade, A Unicorn still requires a clean pass with no deviation.
|
||||
if nL > 1 and playerData.actype==AIRBOSS.AircraftCarrier.AV8B then
|
||||
-- Larger deviations ==> "No grade" 2.0 points.
|
||||
grade = "--"
|
||||
points = 2.0
|
||||
elseif nN > 2 and playerData.actype == AIRBOSS.AircraftCarrier.AV8B then
|
||||
grade="--"
|
||||
points=2.0
|
||||
elseif nNv >= 1 and playerData.actype==AIRBOSS.AircraftCarrier.AV8B then
|
||||
-- Only average deviations ==> "Fair Pass" Pass with average deviations and corrections.
|
||||
grade = "(OK)"
|
||||
points = 3.0
|
||||
elseif nL > 0 then
|
||||
grade="(OK)"
|
||||
points=3.0
|
||||
elseif nNv < 1 and playerData.actype==AIRBOSS.AircraftCarrier.AV8B then
|
||||
-- Only minor average deviations ==> "OK" Pass with minor deviations and corrections. (test nNv<=1 and)
|
||||
grade="OK"
|
||||
points=4.0
|
||||
elseif nL > 0 then
|
||||
-- Larger deviations ==> "No grade" 2.0 points.
|
||||
grade = "--"
|
||||
points = 2.0
|
||||
elseif nN > 0 then
|
||||
grade="--"
|
||||
points=2.0
|
||||
elseif nN> 0 then
|
||||
-- No larger but average deviations ==> "Fair Pass" Pass with average deviations and corrections.
|
||||
grade = "(OK)"
|
||||
points = 3.0
|
||||
else
|
||||
grade="(OK)"
|
||||
points=3.0
|
||||
else
|
||||
-- Only minor corrections
|
||||
grade = "OK"
|
||||
points = 4.0
|
||||
end
|
||||
grade="OK"
|
||||
points=4.0
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -11994,7 +12122,7 @@ function AIRBOSS:_GS( step, n )
|
||||
if n == -1 then
|
||||
gp = AIRBOSS.GroovePos.IC
|
||||
elseif n == 1 then
|
||||
if self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then
|
||||
if self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then
|
||||
gp = AIRBOSS.GroovePos.AL
|
||||
else
|
||||
gp = AIRBOSS.GroovePos.IW
|
||||
@@ -13873,7 +14001,7 @@ function AIRBOSS:_IsCarrierAircraft( unit )
|
||||
|
||||
-- Special case for Harrier which can only land on Tarawa, LHA and LHD.
|
||||
if aircrafttype == AIRBOSS.AircraftCarrier.AV8B then
|
||||
if self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then
|
||||
if self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then
|
||||
return true
|
||||
else
|
||||
return false
|
||||
@@ -13881,7 +14009,7 @@ function AIRBOSS:_IsCarrierAircraft( unit )
|
||||
end
|
||||
|
||||
-- Also only Harriers can land on the Tarawa, LHA and LHD.
|
||||
if self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then
|
||||
if self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then
|
||||
if aircrafttype ~= AIRBOSS.AircraftCarrier.AV8B then
|
||||
return false
|
||||
end
|
||||
@@ -14979,6 +15107,86 @@ function AIRBOSS:_Number2Radio( radio, number, delay, interval, pilotcall )
|
||||
return wait
|
||||
end
|
||||
|
||||
--- Aircraft request marshal (Inbound call both for players and AI).
|
||||
-- @param #AIRBOSS self
|
||||
-- @return Wrapper.Unit#UNIT Unit of player or nil.
|
||||
-- @param #string modex Tail number.
|
||||
function AIRBOSS:_MarshallInboundCall(unit, modex)
|
||||
|
||||
-- Calculate
|
||||
local vectorCarrier = self:GetCoordinate():GetDirectionVec3(unit:GetCoordinate())
|
||||
local bearing = UTILS.Round(unit:GetCoordinate():GetAngleDegrees( vectorCarrier ), 0)
|
||||
local distance = UTILS.Round(UTILS.MetersToNM(unit:GetCoordinate():Get2DDistance(self:GetCoordinate())),0)
|
||||
local angels = UTILS.Round(UTILS.MetersToFeet(unit:GetHeight()/1000),0)
|
||||
local state = UTILS.Round(self:_GetFuelState(unit)/1000,1)
|
||||
|
||||
-- Pilot: "Marshall, [modex], marking mom's [bearing] for [distance], angels [XX], state [X.X]"
|
||||
local text=string.format("Marshal, %s, marking mom's %d for %d, angels %d, state %.1f", modex, bearing, distance, angels, state)
|
||||
-- Debug message.
|
||||
self:T(self.lid..text)
|
||||
|
||||
-- Fuel state.
|
||||
local FS=UTILS.Split(string.format("%.1f", state), ".")
|
||||
|
||||
-- Create new call to display complete subtitle.
|
||||
local inboundcall=self:_NewRadioCall(self.MarshalCall.CLICK, unit.UnitName:upper() , text, self.Tmessage, nil, unit.UnitName:upper())
|
||||
|
||||
-- CLICK!
|
||||
self:RadioTransmission(self.MarshalRadio, inboundcall)
|
||||
-- Marshal ..
|
||||
self:RadioTransmission(self.MarshalRadio, self.PilotCall.MARSHAL, nil, nil, nil, nil, true)
|
||||
-- Modex..
|
||||
self:_Number2Radio(self.MarshalRadio, modex, nil, nil, true)
|
||||
-- Marking Mom's,
|
||||
self:RadioTransmission(self.MarshalRadio, self.PilotCall.MARKINGMOMS, nil, nil, nil, nil, true)
|
||||
-- Bearing ..
|
||||
self:_Number2Radio(self.MarshalRadio, tostring(bearing), nil, nil, true)
|
||||
-- For ..
|
||||
self:RadioTransmission(self.MarshalRadio, self.PilotCall.FOR, nil, nil, nil, nil, true)
|
||||
-- Distance ..
|
||||
self:_Number2Radio(self.MarshalRadio, tostring(distance), nil, nil, true)
|
||||
-- Angels ..
|
||||
self:RadioTransmission(self.MarshalRadio, self.PilotCall.ANGELS, nil, nil, nil, nil, true)
|
||||
-- Angels Number ..
|
||||
self:_Number2Radio(self.MarshalRadio, tostring(angels), nil, nil, true)
|
||||
-- State ..
|
||||
self:RadioTransmission(self.MarshalRadio, self.PilotCall.STATE, nil, nil, nil, nil, true)
|
||||
-- X..
|
||||
self:_Number2Radio(self.MarshalRadio, FS[1], nil, nil, true)
|
||||
-- Point..
|
||||
self:RadioTransmission(self.MarshalRadio, self.PilotCall.POINT, nil, nil, nil, nil, true)
|
||||
-- Y.
|
||||
self:_Number2Radio(self.MarshalRadio, FS[2], nil, nil, true)
|
||||
-- CLICK!
|
||||
self:RadioTransmission(self.MarshalRadio, self.MarshalRadio.CLICK, nil, nil, nil, nil, true)
|
||||
|
||||
end
|
||||
|
||||
--- Aircraft commencing call (both for players and AI).
|
||||
-- @param #AIRBOSS self
|
||||
-- @return Wrapper.Unit#UNIT Unit of player or nil.
|
||||
-- @param #string modex Tail number.
|
||||
function AIRBOSS:_CommencingCall(unit, modex)
|
||||
|
||||
-- Pilot: "[modex], commencing"
|
||||
local text=string.format("%s, commencing", modex)
|
||||
-- Debug message.
|
||||
self:T(self.lid..text)
|
||||
|
||||
-- Create new call to display complete subtitle.
|
||||
local commencingCall=self:_NewRadioCall(self.MarshalCall.CLICK, unit.UnitName:upper() , text, self.Tmessage, nil, unit.UnitName:upper())
|
||||
|
||||
-- Click
|
||||
self:RadioTransmission(self.MarshalRadio, commencingCall)
|
||||
-- Modex..
|
||||
self:_Number2Radio(self.MarshalRadio, modex, nil, nil, true)
|
||||
-- Commencing
|
||||
self:RadioTransmission(self.MarshalRadio, self.PilotCall.COMMENCING, nil, nil, nil, nil, true)
|
||||
-- CLICK!
|
||||
self:RadioTransmission(self.MarshalRadio, self.MarshalRadio.CLICK, nil, nil, nil, nil, true)
|
||||
|
||||
end
|
||||
|
||||
--- AI aircraft calls the ball.
|
||||
-- @param #AIRBOSS self
|
||||
-- @param #string modex Tail number.
|
||||
@@ -15028,6 +15236,7 @@ function AIRBOSS:_MarshalCallGasAtTanker( modex )
|
||||
-- Debug message.
|
||||
self:I( self.lid .. text )
|
||||
|
||||
|
||||
-- Create new call to display complete subtitle.
|
||||
local call = self:_NewRadioCall( self.PilotCall.BINGOFUEL, modex, text, self.Tmessage, nil, modex )
|
||||
|
||||
@@ -15734,6 +15943,11 @@ function AIRBOSS:_RequestMarshal( _unitName )
|
||||
|
||||
if playerData then
|
||||
|
||||
-- Voice over of inbound call (regardless of airboss rejecting it or not)
|
||||
if self.xtVoiceOvers then
|
||||
self:_MarshallInboundCall(_unit, playerData.onboard)
|
||||
end
|
||||
|
||||
-- Check if player is in CCA
|
||||
local inCCA = playerData.unit:IsInZone( self.zoneCCA )
|
||||
|
||||
@@ -15981,7 +16195,12 @@ function AIRBOSS:_RequestCommence( _unitName )
|
||||
local playerData = self.players[_playername] -- #AIRBOSS.PlayerData
|
||||
|
||||
if playerData then
|
||||
|
||||
|
||||
-- Voice over of Commencing call (regardless of Airboss will rejected or not)
|
||||
if self.xtVoiceOvers then
|
||||
self:_CommencingCall(_unit, playerData.onboard)
|
||||
end
|
||||
|
||||
-- Check if unit is in CCA.
|
||||
local text = ""
|
||||
local cleared = false
|
||||
@@ -17238,7 +17457,7 @@ function AIRBOSS:_MarkCaseZones( _unitName, flare )
|
||||
end
|
||||
|
||||
-- Tarawa, LHA and LHD landing spots.
|
||||
if self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then
|
||||
if self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then
|
||||
text = text .. "\n* abeam landing stop with RED flares"
|
||||
-- Abeam landing spot zone.
|
||||
local ALSPT = self:_GetZoneAbeamLandingSpot()
|
||||
|
||||
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
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
@@ -124,6 +124,7 @@
|
||||
-- self.SRSPath = "E:\\Progra~1\\DCS-SimpleRadio-Standalone\\" -- adjust your own path in your SRS installation -- server(!)
|
||||
-- self.SRSchannel = 300 -- radio channel
|
||||
-- self.SRSModulation = radio.modulation.AM -- modulation
|
||||
-- self.SRSport = 5002 -- and SRS port
|
||||
-- --
|
||||
-- self.csarUsePara = false -- If set to true, will use the LandingAfterEjection Event instead of Ejection --shagrat
|
||||
-- self.wetfeettemplate = "man in floating thingy" -- if you use a mod to have a pilot in a rescue float, put the template name in here for wet feet spawns. Note: in conjunction with csarUsePara this might create dual ejected pilots in edge cases.
|
||||
@@ -254,12 +255,12 @@ CSAR.AircraftType["Mi-8MT"] = 12
|
||||
CSAR.AircraftType["Mi-24P"] = 8
|
||||
CSAR.AircraftType["Mi-24V"] = 8
|
||||
CSAR.AircraftType["Bell-47"] = 2
|
||||
CSAR.AircraftType["UH-60L"] = 10
|
||||
CSAR.AircraftType["AH-64D_BLK_II"] = 2
|
||||
CSAR.AircraftType["UH-60L"] = 10
|
||||
CSAR.AircraftType["AH-64D_BLK_II"] = 2
|
||||
|
||||
--- CSAR class version.
|
||||
-- @field #string version
|
||||
CSAR.version="1.0.4d"
|
||||
CSAR.version="1.0.4e"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- ToDo list
|
||||
@@ -413,6 +414,7 @@ function CSAR:New(Coalition, Template, Alias)
|
||||
self.SRSPath = "E:\\Progra~1\\DCS-SimpleRadio-Standalone\\" -- adjust your own path in your server(!)
|
||||
self.SRSchannel = 300 -- radio channel
|
||||
self.SRSModulation = radio.modulation.AM -- modulation
|
||||
self.SRSport = 5002 -- port
|
||||
|
||||
------------------------
|
||||
--- Pseudo Functions ---
|
||||
@@ -1523,6 +1525,7 @@ function CSAR:_DisplayMessageToSAR(_unit, _text, _time, _clear, _speak, _overrid
|
||||
local modulation = self.SRSModulation
|
||||
local channel = self.SRSchannel
|
||||
local msrs = MSRS:New(path,channel,modulation)
|
||||
msrs:SetPort(self.SRSport)
|
||||
msrs:PlaySoundText(srstext, 2)
|
||||
end
|
||||
return self
|
||||
|
||||
@@ -28,7 +28,7 @@ do
|
||||
|
||||
------------------------------------------------------
|
||||
--- **CTLD_ENGINEERING** class, extends Core.Base#BASE
|
||||
--- @type CTLD_ENGINEERING
|
||||
-- @type CTLD_ENGINEERING
|
||||
-- @field #string ClassName
|
||||
-- @field #string lid
|
||||
-- @field #string Name
|
||||
@@ -870,8 +870,12 @@ do
|
||||
--
|
||||
-- ## 5. Support for Hercules mod by Anubis
|
||||
--
|
||||
-- Basic support for the Hercules mod By Anubis has been build into CTLD. Currently this does **not** cover objects and troops which can
|
||||
-- be loaded from the Rearm/Refuel menu, i.e. you can drop them into the field, but you cannot use them in functions scripted with this class.
|
||||
-- Basic support for the Hercules mod By Anubis has been build into CTLD - that is you can load/drop/build the same objects as the helicopters.
|
||||
-- To also cover objects and troops which can be loaded from the groud crew Rearm/Refuel menu, you need to use @{#CTLD_HERCULES.New}() and link
|
||||
-- this object to your CTLD setup. In this case, do **not** use the `Hercules_Cargo.lua` or `Hercules_Cargo_CTLD.lua` which are part of the mod
|
||||
-- in your mission!
|
||||
--
|
||||
-- ### 5.1 Create an own CTLD instance and allow the usage of the Hercules mod:
|
||||
--
|
||||
-- local my_ctld = CTLD:New(coalition.side.BLUE,{"Helicargo", "Hercules"},"Lufttransportbrigade I")
|
||||
--
|
||||
@@ -882,10 +886,33 @@ do
|
||||
-- my_ctld.HercMaxAngels = 2000 -- for troop/cargo drop via chute in meters, ca 6000 ft
|
||||
-- my_ctld.HercMaxSpeed = 77 -- 77mps or 270kph or 150kn
|
||||
--
|
||||
-- Hint: you can **only** airdrop from the Hercules if you are "in parameters", i.e. at or below `HercMaxSpeed` and in the AGL bracket between
|
||||
-- `HercMinAngels` and `HercMaxAngels`!
|
||||
--
|
||||
-- Also, the following options need to be set to `true`:
|
||||
--
|
||||
-- my_ctld.useprefix = true -- this is true by default and MUST BE ON.
|
||||
--
|
||||
-- ### 5.2 Integrate Hercules ground crew loadable objects
|
||||
--
|
||||
-- Add ground crew loadable objects to your CTLD instance like so, where `my_ctld` is the previously created CTLD instance:
|
||||
--
|
||||
-- local herccargo = CTLD_HERCULES:New("blue", "Hercules Test", my_ctld)
|
||||
--
|
||||
-- You also need:
|
||||
--
|
||||
-- * A template called "Infantry" for 10 Paratroopers (as set via herccargo.infantrytemplate).
|
||||
-- * Depending on what you are loading with the help of the ground crew, there are 42 more templates for the various vehicles that are loadable.
|
||||
--
|
||||
-- There's a **quick check output in the `dcs.log`** which tells you what's there and what not.
|
||||
-- E.g.:
|
||||
-- ...Checking template for APC BTR-82A Air [24998lb] (BTR-82A) ... MISSING)
|
||||
-- ...Checking template for ART 2S9 NONA Skid [19030lb] (SAU 2-C9) ... MISSING)
|
||||
-- ...Checking template for EWR SBORKA Air [21624lb] (Dog Ear radar) ... MISSING)
|
||||
-- ...Checking template for Transport Tigr Air [15900lb] (Tigr_233036) ... OK)
|
||||
--
|
||||
-- Expected template names are the ones in the rounded brackets.
|
||||
--
|
||||
-- Standard transport capabilities as per the real Hercules are:
|
||||
--
|
||||
-- ["Hercules"] = {type="Hercules", crates=true, troops=true, cratelimit = 7, trooplimit = 64}, -- 19t cargo, 64 paratroopers
|
||||
@@ -924,7 +951,6 @@ CTLD = {
|
||||
FreeUHFFrequencies = {}, -- Table of UHF
|
||||
FreeFMFrequencies = {}, -- Table of FM
|
||||
CargoCounter = 0,
|
||||
wpZones = {},
|
||||
Cargo_Troops = {}, -- generic troops objects
|
||||
Cargo_Crates = {}, -- generic crate objects
|
||||
Loaded_Cargo = {}, -- cargo aboard units
|
||||
@@ -1823,6 +1849,8 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop)
|
||||
local drop = drop or false
|
||||
local ship = nil
|
||||
local width = 20
|
||||
local distance = nil
|
||||
local zone = nil
|
||||
if not drop then
|
||||
inzone = self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD)
|
||||
if not inzone then
|
||||
@@ -2687,6 +2715,8 @@ function CTLD:_BuildCrates(Group, Unit,Engineering)
|
||||
local required = Crate:GetCratesNeeded()
|
||||
local template = Crate:GetTemplates()
|
||||
local ctype = Crate:GetType()
|
||||
local ccoord = Crate:GetPositionable():GetCoordinate() -- Core.Point#COORDINATE
|
||||
--local testmarker = ccoord:MarkToAll("Crate found",true,"Build Position")
|
||||
if not buildables[name] then
|
||||
local object = {} -- #CTLD.Buildable
|
||||
object.Name = name
|
||||
@@ -2695,6 +2725,7 @@ function CTLD:_BuildCrates(Group, Unit,Engineering)
|
||||
object.Template = template
|
||||
object.CanBuild = false
|
||||
object.Type = ctype -- #CTLD_CARGO.Enum
|
||||
object.Coord = ccoord:GetVec2()
|
||||
buildables[name] = object
|
||||
foundbuilds = true
|
||||
else
|
||||
@@ -2856,7 +2887,8 @@ function CTLD:_BuildObjectFromCrates(Group,Unit,Build,Repair,RepairLocation)
|
||||
temptable = {temptable}
|
||||
end
|
||||
local zone = ZONE_GROUP:New(string.format("Unload zone-%s",unitname),Group,100)
|
||||
local randomcoord = zone:GetRandomCoordinate(35):GetVec2()
|
||||
--local randomcoord = zone:GetRandomCoordinate(35):GetVec2()
|
||||
local randomcoord = Build.Coord or zone:GetRandomCoordinate(35):GetVec2()
|
||||
if Repair then
|
||||
randomcoord = RepairLocation:GetVec2()
|
||||
end
|
||||
@@ -2865,7 +2897,7 @@ function CTLD:_BuildObjectFromCrates(Group,Unit,Build,Repair,RepairLocation)
|
||||
local alias = string.format("%s-%d", _template, math.random(1,100000))
|
||||
if canmove then
|
||||
self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias)
|
||||
:InitRandomizeUnits(true,20,2)
|
||||
--:InitRandomizeUnits(true,20,2)
|
||||
:InitDelayOff()
|
||||
:SpawnFromVec2(randomcoord)
|
||||
else -- don't random position of e.g. SAM units build as FOB
|
||||
@@ -3787,8 +3819,8 @@ end
|
||||
local ucoord = Unit:GetCoordinate()
|
||||
local gheight = ucoord:GetLandHeight()
|
||||
local aheight = uheight - gheight -- height above ground
|
||||
local maxh = self.HercMinAngels-- 1500m
|
||||
local minh = self.HercMaxAngels -- 5000m
|
||||
local minh = self.HercMinAngels-- 1500m
|
||||
local maxh = self.HercMaxAngels -- 5000m
|
||||
local maxspeed = self.HercMaxSpeed -- 77 mps
|
||||
-- DONE: TEST - Speed test for Herc, should not be above 280kph/150kn
|
||||
local kmspeed = uspeed * 3.6
|
||||
@@ -3816,7 +3848,7 @@ end
|
||||
else
|
||||
local minheight = UTILS.MetersToFeet(self.minimumHoverHeight)
|
||||
local maxheight = UTILS.MetersToFeet(self.maximumHoverHeight)
|
||||
text = string.format("Hover parameters (autoload/drop):\n - Min height %dm \n - Max height %dm \n - Max speed 6fts \n - In parameter: %s", minheight, maxheight, htxt)
|
||||
text = string.format("Hover parameters (autoload/drop):\n - Min height %dft \n - Max height %dft \n - Max speed 6ftps \n - In parameter: %s", minheight, maxheight, htxt)
|
||||
end
|
||||
self:_SendMessage(text, 10, false, Group)
|
||||
return self
|
||||
@@ -5097,7 +5129,7 @@ function CTLD_HERCULES:Cargo_SpawnObjects(Cargo_Drop_initiator,Cargo_Drop_Direct
|
||||
self:Soldier_SpawnGroup(Cargo_Drop_initiator,Cargo_Content_position, Cargo_Type_name, CargoHeading, Cargo_Country, 5)
|
||||
self:Soldier_SpawnGroup(Cargo_Drop_initiator,Cargo_Content_position, Cargo_Type_name, CargoHeading, Cargo_Country, 10)
|
||||
else
|
||||
self:Cargo_SpawnGroup(Cargo_Drop_initiator,Cargo_Content_position, Cargo_Type_name, CargoHeading, Cargo_Country, 0)
|
||||
self:Cargo_SpawnGroup(Cargo_Drop_initiator,Cargo_Content_position, Cargo_Type_name, CargoHeading, Cargo_Country)
|
||||
end
|
||||
else
|
||||
if all_cargo_gets_destroyed == true or Cargo_over_water == true then
|
||||
|
||||
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
|
||||
|
||||
@@ -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
@@ -0,0 +1,755 @@
|
||||
--- **Ops** - Commander Air Wing.
|
||||
--
|
||||
-- **Main Features:**
|
||||
--
|
||||
-- * Stuff
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **funkyfranky**
|
||||
-- @module Ops.WingCommander
|
||||
-- @image OPS_WingCommander.png
|
||||
|
||||
|
||||
--- WINGCOMMANDER class.
|
||||
-- @type WINGCOMMANDER
|
||||
-- @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 airwings Table of airwings.
|
||||
-- @field #table missionqueue Mission queue.
|
||||
-- @field Core.Set#SET_ZONE borderzoneset Set of zones defining the border of our territory.
|
||||
-- @field Core.Set#SET_ZONE yellowzoneset Set of zones defining the extended border. Defcon is set to YELLOW if enemy activity is detected.
|
||||
-- @field Core.Set#SET_ZONE engagezoneset Set of zones where enemies are actively engaged.
|
||||
-- @field #string Defcon Defence condition.
|
||||
-- @extends Ops.Intelligence#INTEL
|
||||
|
||||
--- Be surprised!
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- # The WINGCOMMANDER Concept
|
||||
--
|
||||
--
|
||||
--
|
||||
-- @field #WINGCOMMANDER
|
||||
WINGCOMMANDER = {
|
||||
ClassName = "WINGCOMMANDER",
|
||||
Debug = nil,
|
||||
lid = nil,
|
||||
airwings = {},
|
||||
missionqueue = {},
|
||||
borderzoneset = nil,
|
||||
yellowzoneset = nil,
|
||||
engagezoneset = nil,
|
||||
}
|
||||
|
||||
--- Contact details.
|
||||
-- @type WINGCOMMANDER.Contact
|
||||
-- @field Ops.Auftrag#AUFTRAG mission The assigned mission.
|
||||
-- @extends Ops.Intelligence#INTEL.DetectedItem
|
||||
|
||||
--- Defence condition.
|
||||
-- @type WINGCOMMANDER.DEFCON
|
||||
-- @field #string GREEN No enemy activities detected.
|
||||
-- @field #string YELLOW Enemy near our border.
|
||||
-- @field #string RED Enemy within our border.
|
||||
WINGCOMMANDER.DEFCON = {
|
||||
GREEN="Green",
|
||||
YELLOW="Yellow",
|
||||
RED="Red",
|
||||
}
|
||||
|
||||
--- WINGCOMMANDER class version.
|
||||
-- @field #string version
|
||||
WINGCOMMANDER.version="0.1.0"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- TODO list
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
-- TODO: Define A2A and A2G parameters.
|
||||
-- TODO: Improve airwing selection. Mostly done!
|
||||
-- DONE: Add/remove spawned flightgroups to detection set.
|
||||
-- DONE: Borderzones.
|
||||
-- NOGO: Maybe it's possible to preselect the assets for the mission.
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Constructor
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Create a new WINGCOMMANDER object and start the FSM.
|
||||
-- @param #WINGCOMMANDER self
|
||||
-- @param Core.Set#SET_GROUP AgentSet Set of agents (groups) providing intel. Default is an empty set.
|
||||
-- @param #number Coalition Coalition side, e.g. `coaliton.side.BLUE`. Can also be passed as a string "red", "blue" or "neutral".
|
||||
-- @return #WINGCOMMANDER self
|
||||
function WINGCOMMANDER:New(AgentSet, Coalition)
|
||||
|
||||
AgentSet=AgentSet or SET_GROUP:New()
|
||||
|
||||
-- Inherit everything from INTEL class.
|
||||
local self=BASE:Inherit(self, INTEL:New(AgentSet, Coalition)) --#WINGCOMMANDER
|
||||
|
||||
-- Set some string id for output to DCS.log file.
|
||||
--self.lid=string.format("WINGCOMMANDER | ")
|
||||
|
||||
self:SetBorderZones()
|
||||
self:SetYellowZones()
|
||||
|
||||
self:SetThreatLevelRange()
|
||||
|
||||
self.Defcon=WINGCOMMANDER.DEFCON.GREEN
|
||||
|
||||
-- Add FSM transitions.
|
||||
-- From State --> Event --> To State
|
||||
self:AddTransition("*", "MissionAssign", "*") -- Mission was assigned to an AIRWING.
|
||||
self:AddTransition("*", "CancelMission", "*") -- Cancel mission.
|
||||
self:AddTransition("*", "Defcon", "*") -- Cancel mission.
|
||||
|
||||
------------------------
|
||||
--- Pseudo Functions ---
|
||||
------------------------
|
||||
|
||||
--- Triggers the FSM event "Start". Starts the WINGCOMMANDER. Initializes parameters and starts event handlers.
|
||||
-- @function [parent=#WINGCOMMANDER] Start
|
||||
-- @param #WINGCOMMANDER self
|
||||
|
||||
--- Triggers the FSM event "Start" after a delay. Starts the WINGCOMMANDER. Initializes parameters and starts event handlers.
|
||||
-- @function [parent=#WINGCOMMANDER] __Start
|
||||
-- @param #WINGCOMMANDER self
|
||||
-- @param #number delay Delay in seconds.
|
||||
|
||||
--- Triggers the FSM event "Stop". Stops the WINGCOMMANDER and all its event handlers.
|
||||
-- @param #WINGCOMMANDER self
|
||||
|
||||
--- Triggers the FSM event "Stop" after a delay. Stops the WINGCOMMANDER and all its event handlers.
|
||||
-- @function [parent=#WINGCOMMANDER] __Stop
|
||||
-- @param #WINGCOMMANDER self
|
||||
-- @param #number delay Delay in seconds.
|
||||
|
||||
--- Triggers the FSM event "Status".
|
||||
-- @function [parent=#WINGCOMMANDER] Status
|
||||
-- @param #WINGCOMMANDER self
|
||||
|
||||
--- Triggers the FSM event "SkipperStatus" after a delay.
|
||||
-- @function [parent=#WINGCOMMANDER] __Status
|
||||
-- @param #WINGCOMMANDER self
|
||||
-- @param #number delay Delay in seconds.
|
||||
|
||||
|
||||
-- Debug trace.
|
||||
if false then
|
||||
self.Debug=true
|
||||
BASE:TraceOnOff(true)
|
||||
BASE:TraceClass(self.ClassName)
|
||||
BASE:TraceLevel(1)
|
||||
end
|
||||
self.Debug=true
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- User functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Set this to be an air-to-any dispatcher, i.e. engaging air, ground and naval targets. This is the default anyway.
|
||||
-- @param #WINGCOMMANDER self
|
||||
-- @return #WINGCOMMANDER self
|
||||
function WINGCOMMANDER:SetAirToAny()
|
||||
|
||||
self:SetFilterCategory({})
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set this to be an air-to-air dispatcher.
|
||||
-- @param #WINGCOMMANDER self
|
||||
-- @return #WINGCOMMANDER self
|
||||
function WINGCOMMANDER:SetAirToAir()
|
||||
|
||||
self:SetFilterCategory({Unit.Category.AIRPLANE, Unit.Category.HELICOPTER})
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set this to be an air-to-ground dispatcher, i.e. engage only ground units
|
||||
-- @param #WINGCOMMANDER self
|
||||
-- @return #WINGCOMMANDER self
|
||||
function WINGCOMMANDER:SetAirToGround()
|
||||
|
||||
self:SetFilterCategory({Unit.Category.GROUND_UNIT})
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set this to be an air-to-sea dispatcher, i.e. engage only naval units.
|
||||
-- @param #WINGCOMMANDER self
|
||||
-- @return #WINGCOMMANDER self
|
||||
function WINGCOMMANDER:SetAirToSea()
|
||||
|
||||
self:SetFilterCategory({Unit.Category.SHIP})
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set this to be an air-to-surface dispatcher, i.e. engaging ground and naval groups.
|
||||
-- @param #WINGCOMMANDER self
|
||||
-- @return #WINGCOMMANDER self
|
||||
function WINGCOMMANDER:SetAirToSurface()
|
||||
|
||||
self:SetFilterCategory({Unit.Category.GROUND_UNIT, Unit.Category.SHIP})
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set a threat level range that will be engaged. Threat level is a number between 0 and 10, where 10 is a very dangerous threat.
|
||||
-- Targets with threat level 0 are usually harmless.
|
||||
-- @param #WINGCOMMANDER self
|
||||
-- @param #number ThreatLevelMin Min threat level. Default 1.
|
||||
-- @param #number ThreatLevelMax Max threat level. Default 10.
|
||||
-- @return #WINGCOMMANDER self
|
||||
function WINGCOMMANDER:SetThreatLevelRange(ThreatLevelMin, ThreatLevelMax)
|
||||
|
||||
self.threatLevelMin=ThreatLevelMin or 1
|
||||
self.threatLevelMax=ThreatLevelMax or 10
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set defence condition.
|
||||
-- @param #WINGCOMMANDER self
|
||||
-- @param #string Defcon Defence condition. See @{#WINGCOMMANDER.DEFCON}, e.g. `WINGCOMMANDER.DEFCON.RED`.
|
||||
-- @return #WINGCOMMANDER self
|
||||
function WINGCOMMANDER:SetDefcon(Defcon)
|
||||
|
||||
self.Defcon=Defcon
|
||||
--self:Defcon(Defcon)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Add an airwing to the wingcommander.
|
||||
-- @param #WINGCOMMANDER self
|
||||
-- @param Ops.AirWing#AIRWING Airwing The airwing to add.
|
||||
-- @return #WINGCOMMANDER self
|
||||
function WINGCOMMANDER:AddAirwing(Airwing)
|
||||
|
||||
-- This airwing is managed by this wing commander.
|
||||
Airwing.wingcommander=self
|
||||
|
||||
table.insert(self.airwings, Airwing)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Add mission to mission queue.
|
||||
-- @param #WINGCOMMANDER self
|
||||
-- @param Ops.Auftrag#AUFTRAG Mission Mission to be added.
|
||||
-- @return #WINGCOMMANDER self
|
||||
function WINGCOMMANDER:AddMission(Mission)
|
||||
|
||||
Mission.wingcommander=self
|
||||
|
||||
table.insert(self.missionqueue, Mission)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Remove mission from queue.
|
||||
-- @param #WINGCOMMANDER self
|
||||
-- @param Ops.Auftrag#AUFTRAG Mission Mission to be removed.
|
||||
-- @return #WINGCOMMANDER self
|
||||
function WINGCOMMANDER:RemoveMission(Mission)
|
||||
|
||||
for i,_mission in pairs(self.missionqueue) do
|
||||
local mission=_mission --Ops.Auftrag#AUFTRAG
|
||||
|
||||
if mission.auftragsnummer==Mission.auftragsnummer then
|
||||
self:I(self.lid..string.format("Removing mission %s (%s) status=%s from queue", Mission.name, Mission.type, Mission.status))
|
||||
table.remove(self.missionqueue, i)
|
||||
break
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set border zone set.
|
||||
-- @param #WINGCOMMANDER self
|
||||
-- @param Core.Set#SET_ZONE BorderZoneSet Set of zones, defining our borders.
|
||||
-- @return #WINGCOMMANDER self
|
||||
function WINGCOMMANDER:SetBorderZones(BorderZoneSet)
|
||||
|
||||
-- Border zones.
|
||||
self.borderzoneset=BorderZoneSet or SET_ZONE:New()
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Add a zone defining your territory.
|
||||
-- @param #WINGCOMMANDER self
|
||||
-- @param Core.Zone#ZONE BorderZone The zone defining the border of your territory.
|
||||
-- @return #WINGCOMMANDER self
|
||||
function WINGCOMMANDER:AddBorderZone(BorderZone)
|
||||
|
||||
-- Add a border zone.
|
||||
self.borderzoneset:AddZone(BorderZone)
|
||||
|
||||
-- Set accept zone.
|
||||
--self:AddAcceptZone(BorderZone)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set yellow zone set. Detected enemy troops in this zone will trigger defence condition YELLOW.
|
||||
-- @param #WINGCOMMANDER self
|
||||
-- @param Core.Set#SET_ZONE YellowZoneSet Set of zones, defining our borders.
|
||||
-- @return #WINGCOMMANDER self
|
||||
function WINGCOMMANDER:SetYellowZones(YellowZoneSet)
|
||||
|
||||
-- Border zones.
|
||||
self.yellowzoneset=YellowZoneSet or SET_ZONE:New()
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Add a zone defining an area outside your territory that is monitored for enemy activity.
|
||||
-- @param #WINGCOMMANDER self
|
||||
-- @param Core.Zone#ZONE YellowZone The zone defining the border of your territory.
|
||||
-- @return #WINGCOMMANDER self
|
||||
function WINGCOMMANDER:AddYellowZone(YellowZone)
|
||||
|
||||
-- Add a border zone.
|
||||
self.yellowzoneset:AddZone(YellowZone)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Start & Status
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- On after Start event. Starts the FLIGHTGROUP FSM and event handlers.
|
||||
-- @param #WINGCOMMANDER self
|
||||
-- @param Wrapper.Group#GROUP Group Flight group.
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
function WINGCOMMANDER:onafterStart(From, Event, To)
|
||||
|
||||
-- Short info.
|
||||
local text=string.format("Starting Wing Commander")
|
||||
self:I(self.lid..text)
|
||||
|
||||
-- Start parent INTEL.
|
||||
self:GetParent(self).onafterStart(self, From, Event, To)
|
||||
|
||||
-- Start attached airwings.
|
||||
for _,_airwing in pairs(self.airwings) do
|
||||
local airwing=_airwing --Ops.AirWing#AIRWING
|
||||
if airwing:GetState()=="NotReadyYet" then
|
||||
airwing:Start()
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- On after "Status" event.
|
||||
-- @param #WINGCOMMANDER self
|
||||
-- @param Wrapper.Group#GROUP Group Flight group.
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
function WINGCOMMANDER:onafterStatus(From, Event, To)
|
||||
|
||||
-- Start parent INTEL.
|
||||
self:GetParent(self).onafterStatus(self, From, Event, To)
|
||||
|
||||
-- FSM state.
|
||||
local fsmstate=self:GetState()
|
||||
|
||||
|
||||
-- Clean up missions where the contact was lost.
|
||||
for _,_contact in pairs(self.ContactsLost) do
|
||||
local contact=_contact --#WINGCOMMANDER.Contact
|
||||
|
||||
if contact.mission and contact.mission:IsNotOver() then
|
||||
|
||||
local text=string.format("Lost contact to target %s! %s mission %s will be cancelled.", contact.groupname, contact.mission.type:upper(), contact.mission.name)
|
||||
MESSAGE:New(text, 120, "WINGCOMMANDER"):ToAll()
|
||||
self:I(self.lid..text)
|
||||
|
||||
-- Cancel this mission.
|
||||
contact.mission:Cancel()
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-- Create missions for all new contacts.
|
||||
local Nred=0
|
||||
local Nyellow=0
|
||||
local Nengage=0
|
||||
for _,_contact in pairs(self.Contacts) do
|
||||
local contact=_contact --#WINGCOMMANDER.Contact
|
||||
local group=contact.group --Wrapper.Group#GROUP
|
||||
|
||||
local inred=self:CheckGroupInBorder(group)
|
||||
if inred then
|
||||
Nred=Nred+1
|
||||
end
|
||||
|
||||
local inyellow=self:CheckGroupInYellow(group)
|
||||
if inyellow then
|
||||
Nyellow=Nyellow+1
|
||||
end
|
||||
|
||||
-- Is this a threat?
|
||||
local threat=contact.threatlevel>=self.threatLevelMin and contact.threatlevel<=self.threatLevelMax
|
||||
|
||||
local redalert=true
|
||||
if self.borderzoneset:Count()>0 then
|
||||
redalert=inred
|
||||
end
|
||||
|
||||
if redalert and threat and not contact.mission then
|
||||
|
||||
-- Create a mission based on group category.
|
||||
local mission=AUFTRAG:NewAUTO(group)
|
||||
|
||||
|
||||
-- Add mission to queue.
|
||||
if mission then
|
||||
|
||||
--TODO: Better amount of necessary assets. Count units in asset and in contact. Might need nassetMin/Max.
|
||||
mission.nassets=1
|
||||
|
||||
-- Missons are repeated max 3 times on failure.
|
||||
mission.missionRepeatMax=3
|
||||
|
||||
-- Set mission contact.
|
||||
contact.mission=mission
|
||||
|
||||
-- Add mission to queue.
|
||||
self:AddMission(mission)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-- Set defcon.
|
||||
-- TODO: Need to introduce time check to avoid fast oscillation between different defcon states in case groups move in and out of the zones.
|
||||
if Nred>0 then
|
||||
self:SetDefcon(WINGCOMMANDER.DEFCON.RED)
|
||||
elseif Nyellow>0 then
|
||||
self:SetDefcon(WINGCOMMANDER.DEFCON.YELLOW)
|
||||
else
|
||||
self:SetDefcon(WINGCOMMANDER.DEFCON.GREEN)
|
||||
end
|
||||
|
||||
|
||||
-- Check mission queue and assign one PLANNED mission.
|
||||
self:CheckMissionQueue()
|
||||
|
||||
local text=string.format("Defcon=%s Missions=%d Contacts: Total=%d Yellow=%d Red=%d", self.Defcon, #self.missionqueue, #self.Contacts, Nyellow, Nred)
|
||||
self:I(self.lid..text)
|
||||
|
||||
-- Infor about contacts.
|
||||
if #self.Contacts>0 then
|
||||
local text="Contacts:"
|
||||
for i,_contact in pairs(self.Contacts) do
|
||||
local contact=_contact --#WINGCOMMANDER.Contact
|
||||
local mtext="N/A"
|
||||
if contact.mission then
|
||||
mtext=string.format("Mission %s (%s) %s", contact.mission.name, contact.mission.type, contact.mission.status:upper())
|
||||
end
|
||||
text=text..string.format("\n[%d] %s Type=%s (%s): Threat=%d Mission=%s", i, contact.groupname, contact.categoryname, contact.typename, contact.threatlevel, mtext)
|
||||
end
|
||||
self:I(self.lid..text)
|
||||
end
|
||||
|
||||
-- Mission queue.
|
||||
if #self.missionqueue>0 then
|
||||
local text="Mission queue:"
|
||||
for i,_mission in pairs(self.missionqueue) do
|
||||
local mission=_mission --Ops.Auftrag#AUFTRAG
|
||||
|
||||
local target=mission:GetTargetName() or "unknown"
|
||||
|
||||
text=text..string.format("\n[%d] %s (%s): status=%s, target=%s", i, mission.name, mission.type, mission.status, target)
|
||||
end
|
||||
self:I(self.lid..text)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- FSM Events
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- On after "MissionAssign" event. Mission is added to the AIRWING mission queue.
|
||||
-- @param #WINGCOMMANDER self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param Ops.AirWing#AIRWING Airwing The AIRWING.
|
||||
-- @param Ops.Auftrag#AUFTRAG Mission The mission.
|
||||
function WINGCOMMANDER:onafterMissionAssign(From, Event, To, Airwing, Mission)
|
||||
|
||||
self:I(self.lid..string.format("Assigning mission %s (%s) to airwing %s", Mission.name, Mission.type, Airwing.alias))
|
||||
Airwing:AddMission(Mission)
|
||||
|
||||
end
|
||||
|
||||
--- On after "CancelMission" event.
|
||||
-- @param #WINGCOMMANDER self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param Ops.Auftrag#AUFTRAG Mission The mission.
|
||||
function WINGCOMMANDER:onafterCancelMission(From, Event, To, Mission)
|
||||
|
||||
self:I(self.lid..string.format("Cancelling mission %s (%s) in status %s", Mission.name, Mission.type, Mission.status))
|
||||
|
||||
if Mission.status==AUFTRAG.Status.PLANNED then
|
||||
|
||||
-- Mission is still in planning stage. Should not have an airbase assigned ==> Just remove it form the queue.
|
||||
self:RemoveMission(Mission)
|
||||
|
||||
else
|
||||
|
||||
-- Airwing will cancel mission.
|
||||
if Mission.airwing then
|
||||
Mission.airwing:MissionCancel(Mission)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- On before "Defcon" event.
|
||||
-- @param #WINGCOMMANDER self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param #string Defcon New defence condition.
|
||||
-- @param Ops.Auftrag#AUFTRAG Mission The mission.
|
||||
function WINGCOMMANDER:onbeforeDefcon(From, Event, To, Defcon)
|
||||
|
||||
local gotit=false
|
||||
for _,defcon in pairs(WINGCOMMANDER.DEFCON) do
|
||||
if defcon==Defcon then
|
||||
gotit=true
|
||||
end
|
||||
end
|
||||
|
||||
if not gotit then
|
||||
self:E(self.lid..string.format("ERROR: Unknown DEFCON specified! Dont know defcon=%s", tostring(Defcon)))
|
||||
return false
|
||||
end
|
||||
|
||||
-- Defcon did not change.
|
||||
if Defcon==self.Defcon then
|
||||
self:I(self.lid..string.format("Defcon %s unchanged. No processing transition.", tostring(Defcon)))
|
||||
return false
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
--- On after "Defcon" event.
|
||||
-- @param #WINGCOMMANDER self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param #string Defcon New defence condition.
|
||||
-- @param Ops.Auftrag#AUFTRAG Mission The mission.
|
||||
function WINGCOMMANDER:onafterDefcon(From, Event, To, Defcon)
|
||||
self:I(self.lid..string.format("Changing Defcon from %s --> %s", self.Defcon, Defcon))
|
||||
|
||||
-- Set new defcon.
|
||||
self.Defcon=Defcon
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Resources
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Check mission queue and assign ONE planned mission.
|
||||
-- @param #WINGCOMMANDER self
|
||||
function WINGCOMMANDER:CheckMissionQueue()
|
||||
|
||||
-- TODO: Sort mission queue. wrt what? Threat level?
|
||||
|
||||
for _,_mission in pairs(self.missionqueue) do
|
||||
local mission=_mission --Ops.Auftrag#AUFTRAG
|
||||
|
||||
-- We look for PLANNED missions.
|
||||
if mission.status==AUFTRAG.Status.PLANNED then
|
||||
|
||||
---
|
||||
-- PLANNNED Mission
|
||||
---
|
||||
|
||||
local airwing=self:GetAirwingForMission(mission)
|
||||
|
||||
if airwing then
|
||||
|
||||
-- Add mission to airwing.
|
||||
self:MissionAssign(airwing, mission)
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
else
|
||||
|
||||
---
|
||||
-- Missions NOT in PLANNED state
|
||||
---
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Check all airwings if they are able to do a specific mission type at a certain location with a given number of assets.
|
||||
-- @param #WINGCOMMANDER self
|
||||
-- @param Ops.Auftrag#AUFTRAG Mission The mission.
|
||||
-- @return Ops.AirWing#AIRWING The airwing best for this mission.
|
||||
function WINGCOMMANDER:GetAirwingForMission(Mission)
|
||||
|
||||
-- Table of airwings that can do the mission.
|
||||
local airwings={}
|
||||
|
||||
-- Loop over all airwings.
|
||||
for _,_airwing in pairs(self.airwings) do
|
||||
local airwing=_airwing --Ops.AirWing#AIRWING
|
||||
|
||||
-- Check if airwing can do this mission.
|
||||
local can,assets=airwing:CanMission(Mission)
|
||||
|
||||
-- Can it?
|
||||
if can then
|
||||
|
||||
-- Get coordinate of the target.
|
||||
local coord=Mission:GetTargetCoordinate()
|
||||
|
||||
if coord then
|
||||
|
||||
-- Distance from airwing to target.
|
||||
local dist=UTILS.MetersToNM(coord:Get2DDistance(airwing:GetCoordinate()))
|
||||
|
||||
-- Add airwing to table of airwings that can.
|
||||
table.insert(airwings, {airwing=airwing, dist=dist, targetcoord=coord, nassets=#assets})
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-- Can anyone?
|
||||
if #airwings>0 then
|
||||
|
||||
--- Something like:
|
||||
-- * Closest airwing that can should be first prio.
|
||||
-- * However, there should be a certain "quantization". if wing is 50 or 60 NM way should not really matter. In that case, the airwing with more resources should get the job.
|
||||
local function score(a)
|
||||
local d=math.round(a.dist/10)
|
||||
end
|
||||
|
||||
-- Sort table wrt distance and number of assets.
|
||||
-- Distances within 10 NM are equal and the airwing with more assets is preferred.
|
||||
local function sortdist(a,b)
|
||||
local ad=math.round(a.dist/10) -- dist 55 NM ==> 5.5 ==> 6
|
||||
local bd=math.round(b.dist/10) -- dist 63 NM ==> 6.3 ==> 6
|
||||
return ad<bd or (ad==bd and a.nassets>b.nassets)
|
||||
end
|
||||
table.sort(airwings, sortdist)
|
||||
|
||||
-- This is the closest airwing to the target.
|
||||
local airwing=airwings[1].airwing --Ops.AirWing#AIRWING
|
||||
|
||||
return airwing
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
--- Check if group is inside our border.
|
||||
-- @param #WINGCOMMANDER self
|
||||
-- @param Wrapper.Group#GROUP group The group.
|
||||
-- @return #boolean If true, group is in any zone.
|
||||
function WINGCOMMANDER:CheckGroupInBorder(group)
|
||||
|
||||
local inside=self:CheckGroupInZones(group, self.borderzoneset)
|
||||
|
||||
return inside
|
||||
end
|
||||
|
||||
--- Check if group is near our border (yellow zone).
|
||||
-- @param #WINGCOMMANDER self
|
||||
-- @param Wrapper.Group#GROUP group The group.
|
||||
-- @return #boolean If true, group is in any zone.
|
||||
function WINGCOMMANDER:CheckGroupInYellow(group)
|
||||
|
||||
-- Check inside yellow but not inside our border.
|
||||
local inside=self:CheckGroupInZones(group, self.yellowzoneset) and not self:CheckGroupInZones(group, self.borderzoneset)
|
||||
|
||||
return inside
|
||||
end
|
||||
|
||||
--- Check if group is inside a zone.
|
||||
-- @param #WINGCOMMANDER self
|
||||
-- @param Wrapper.Group#GROUP group The group.
|
||||
-- @param Core.Set#SET_ZONE zoneset Set of zones.
|
||||
-- @return #boolean If true, group is in any zone.
|
||||
function WINGCOMMANDER:CheckGroupInZones(group, zoneset)
|
||||
|
||||
for _,_zone in pairs(zoneset.Set or {}) do
|
||||
local zone=_zone --Core.Zone#ZONE
|
||||
|
||||
if group:IsPartlyOrCompletelyInZone(zone) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
--- Check resources.
|
||||
-- @param #WINGCOMMANDER self
|
||||
-- @return #table
|
||||
function WINGCOMMANDER:CheckResources()
|
||||
|
||||
local capabilities={}
|
||||
|
||||
for _,MissionType in pairs(AUFTRAG.Type) do
|
||||
capabilities[MissionType]=0
|
||||
|
||||
for _,_airwing in pairs(self.airwings) do
|
||||
local airwing=_airwing --Ops.AirWing#AIRWING
|
||||
|
||||
-- Get Number of assets that can do this type of missions.
|
||||
local _,assets=airwing:CanMission(MissionType)
|
||||
|
||||
-- Add up airwing resources.
|
||||
capabilities[MissionType]=capabilities[MissionType]+#assets
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
return capabilities
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
@@ -1,28 +1,28 @@
|
||||
--- **Sound** - Radio transmissions.
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- ## Features:
|
||||
--
|
||||
--
|
||||
-- * Provide radio functionality to broadcast radio transmissions.
|
||||
--
|
||||
--
|
||||
-- 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,
|
||||
@@ -37,41 +37,41 @@
|
||||
|
||||
|
||||
--- *It's not true I had nothing on, I had the radio on.* -- Marilyn Monroe
|
||||
--
|
||||
--
|
||||
-- # 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.
|
||||
--
|
||||
-- * 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.
|
||||
@@ -105,12 +105,12 @@ 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
|
||||
@@ -137,19 +137,19 @@ end
|
||||
-- @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
|
||||
@@ -161,34 +161,34 @@ end
|
||||
-- @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
|
||||
|
||||
--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
|
||||
end
|
||||
|
||||
|
||||
self:E({"Frequency is not a number. Frequency unchanged.", Frequency})
|
||||
return self
|
||||
end
|
||||
@@ -215,13 +215,13 @@ end
|
||||
-- @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
|
||||
|
||||
@@ -249,7 +249,7 @@ end
|
||||
-- -- 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)
|
||||
@@ -264,14 +264,14 @@ function RADIO:SetSubtitle(Subtitle, SubtitleDuration)
|
||||
self.SubtitleDuration = SubtitleDuration
|
||||
else
|
||||
self.SubtitleDuration = 0
|
||||
self:E({"SubtitleDuration is invalid. SubtitleDuration reset.", self.SubtitleDuration})
|
||||
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.
|
||||
-- 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.
|
||||
@@ -281,20 +281,20 @@ end
|
||||
-- @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}.
|
||||
-- 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.
|
||||
@@ -316,20 +316,20 @@ function RADIO:NewUnitTransmission(FileName, Subtitle, SubtitleDuration, Frequen
|
||||
end
|
||||
|
||||
-- Set frequency.
|
||||
if Frequency then
|
||||
if Frequency then
|
||||
self:SetFrequency(Frequency)
|
||||
end
|
||||
|
||||
|
||||
-- Set subtitle.
|
||||
if Subtitle then
|
||||
self:SetSubtitle(Subtitle, SubtitleDuration or 0)
|
||||
end
|
||||
|
||||
|
||||
-- Set Looping.
|
||||
if Loop then
|
||||
if Loop then
|
||||
self:SetLoop(Loop)
|
||||
end
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -346,7 +346,7 @@ end
|
||||
-- @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")
|
||||
@@ -359,7 +359,7 @@ function RADIO:Broadcast(viatrigger)
|
||||
subtitle = self.Subtitle,
|
||||
loop = self.Loop,
|
||||
}}
|
||||
|
||||
|
||||
self:T3(commandTransmitMessage)
|
||||
self.Positionable:SetCommand(commandTransmitMessage)
|
||||
else
|
||||
@@ -368,7 +368,7 @@ function RADIO:Broadcast(viatrigger)
|
||||
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
|
||||
|
||||
@@ -380,11 +380,11 @@ end
|
||||
-- @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 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
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
--- **Core** - Makes the radio talk.
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- ## Features:
|
||||
--
|
||||
--
|
||||
-- * Send text strings using a vocabulary that is converted in spoken language.
|
||||
-- * Possiblity to implement multiple language.
|
||||
--
|
||||
@@ -15,10 +15,10 @@
|
||||
-- @image Core_Radio.JPG
|
||||
|
||||
--- Makes the radio speak.
|
||||
--
|
||||
--
|
||||
-- # RADIOSPEECH usage
|
||||
--
|
||||
--
|
||||
--
|
||||
--
|
||||
-- @type RADIOSPEECH
|
||||
-- @extends Core.RadioQueue#RADIOQUEUE
|
||||
RADIOSPEECH = {
|
||||
@@ -59,24 +59,24 @@ RADIOSPEECH.Vocabulary.EN = {
|
||||
["70"] = { "70", 0.48 },
|
||||
["80"] = { "80", 0.26 },
|
||||
["90"] = { "90", 0.36 },
|
||||
["100"] = { "100", 0.55 },
|
||||
["200"] = { "200", 0.55 },
|
||||
["300"] = { "300", 0.61 },
|
||||
["400"] = { "400", 0.60 },
|
||||
["500"] = { "500", 0.61 },
|
||||
["600"] = { "600", 0.65 },
|
||||
["700"] = { "700", 0.70 },
|
||||
["800"] = { "800", 0.54 },
|
||||
["900"] = { "900", 0.60 },
|
||||
["1000"] = { "1000", 0.60 },
|
||||
["2000"] = { "2000", 0.61 },
|
||||
["3000"] = { "3000", 0.64 },
|
||||
["4000"] = { "4000", 0.62 },
|
||||
["5000"] = { "5000", 0.69 },
|
||||
["6000"] = { "6000", 0.69 },
|
||||
["7000"] = { "7000", 0.75 },
|
||||
["8000"] = { "8000", 0.59 },
|
||||
["9000"] = { "9000", 0.65 },
|
||||
["100"] = { "100", 0.55 },
|
||||
["200"] = { "200", 0.55 },
|
||||
["300"] = { "300", 0.61 },
|
||||
["400"] = { "400", 0.60 },
|
||||
["500"] = { "500", 0.61 },
|
||||
["600"] = { "600", 0.65 },
|
||||
["700"] = { "700", 0.70 },
|
||||
["800"] = { "800", 0.54 },
|
||||
["900"] = { "900", 0.60 },
|
||||
["1000"] = { "1000", 0.60 },
|
||||
["2000"] = { "2000", 0.61 },
|
||||
["3000"] = { "3000", 0.64 },
|
||||
["4000"] = { "4000", 0.62 },
|
||||
["5000"] = { "5000", 0.69 },
|
||||
["6000"] = { "6000", 0.69 },
|
||||
["7000"] = { "7000", 0.75 },
|
||||
["8000"] = { "8000", 0.59 },
|
||||
["9000"] = { "9000", 0.65 },
|
||||
|
||||
["chevy"] = { "chevy", 0.35 },
|
||||
["colt"] = { "colt", 0.35 },
|
||||
@@ -94,10 +94,10 @@ RADIOSPEECH.Vocabulary.EN = {
|
||||
["meters"] = { "meters", 0.41 },
|
||||
["mi"] = { "miles", 0.45 },
|
||||
["feet"] = { "feet", 0.29 },
|
||||
|
||||
|
||||
["br"] = { "br", 1.1 },
|
||||
["bra"] = { "bra", 0.3 },
|
||||
|
||||
|
||||
|
||||
["returning to base"] = { "returning_to_base", 0.85 },
|
||||
["on route to ground target"] = { "on_route_to_ground_target", 1.05 },
|
||||
@@ -143,24 +143,24 @@ RADIOSPEECH.Vocabulary.RU = {
|
||||
["70"] = { "70", 0.68 },
|
||||
["80"] = { "80", 0.84 },
|
||||
["90"] = { "90", 0.71 },
|
||||
["100"] = { "100", 0.35 },
|
||||
["200"] = { "200", 0.59 },
|
||||
["300"] = { "300", 0.53 },
|
||||
["400"] = { "400", 0.70 },
|
||||
["500"] = { "500", 0.50 },
|
||||
["600"] = { "600", 0.58 },
|
||||
["700"] = { "700", 0.64 },
|
||||
["800"] = { "800", 0.77 },
|
||||
["900"] = { "900", 0.75 },
|
||||
["1000"] = { "1000", 0.87 },
|
||||
["2000"] = { "2000", 0.83 },
|
||||
["3000"] = { "3000", 0.84 },
|
||||
["4000"] = { "4000", 1.00 },
|
||||
["5000"] = { "5000", 0.77 },
|
||||
["6000"] = { "6000", 0.90 },
|
||||
["7000"] = { "7000", 0.77 },
|
||||
["8000"] = { "8000", 0.92 },
|
||||
["9000"] = { "9000", 0.87 },
|
||||
["100"] = { "100", 0.35 },
|
||||
["200"] = { "200", 0.59 },
|
||||
["300"] = { "300", 0.53 },
|
||||
["400"] = { "400", 0.70 },
|
||||
["500"] = { "500", 0.50 },
|
||||
["600"] = { "600", 0.58 },
|
||||
["700"] = { "700", 0.64 },
|
||||
["800"] = { "800", 0.77 },
|
||||
["900"] = { "900", 0.75 },
|
||||
["1000"] = { "1000", 0.87 },
|
||||
["2000"] = { "2000", 0.83 },
|
||||
["3000"] = { "3000", 0.84 },
|
||||
["4000"] = { "4000", 1.00 },
|
||||
["5000"] = { "5000", 0.77 },
|
||||
["6000"] = { "6000", 0.90 },
|
||||
["7000"] = { "7000", 0.77 },
|
||||
["8000"] = { "8000", 0.92 },
|
||||
["9000"] = { "9000", 0.87 },
|
||||
|
||||
["градусы"] = { "degrees", 0.5 },
|
||||
["километры"] = { "kilometers", 0.65 },
|
||||
@@ -170,10 +170,11 @@ RADIOSPEECH.Vocabulary.RU = {
|
||||
["метров"] = { "meters", 0.41 },
|
||||
["m"] = { "meters", 0.41 },
|
||||
["ноги"] = { "feet", 0.37 },
|
||||
|
||||
|
||||
["br"] = { "br", 1.1 },
|
||||
["bra"] = { "bra", 0.3 },
|
||||
|
||||
|
||||
|
||||
["возвращение на базу"] = { "returning_to_base", 1.40 },
|
||||
["на пути к наземной цели"] = { "on_route_to_ground_target", 1.45 },
|
||||
["перехват боги"] = { "intercepting_bogeys", 1.22 },
|
||||
@@ -199,11 +200,11 @@ function RADIOSPEECH:New(frequency, modulation)
|
||||
|
||||
-- Inherit base
|
||||
local self = BASE:Inherit( self, RADIOQUEUE:New( frequency, modulation ) ) -- #RADIOSPEECH
|
||||
|
||||
|
||||
self.Language = "EN"
|
||||
|
||||
|
||||
self:BuildTree()
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -261,7 +262,7 @@ end
|
||||
function RADIOSPEECH:BuildTree()
|
||||
|
||||
self.Speech = {}
|
||||
|
||||
|
||||
for Language, Sentences in pairs( self.Vocabulary ) do
|
||||
self:I( { Language = Language, Sentences = Sentences })
|
||||
self.Speech[Language] = {}
|
||||
@@ -270,7 +271,7 @@ function RADIOSPEECH:BuildTree()
|
||||
self:AddSentenceToSpeech( Sentence, self.Speech[Language], Sentence, Data )
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
self:I( { Speech = self.Speech } )
|
||||
|
||||
return self
|
||||
@@ -289,7 +290,7 @@ function RADIOSPEECH:SpeakWords( Sentence, Speech, Language )
|
||||
local Word, RemainderSentence = Sentence:match( "^[., ]*([^ .,]+)(.*)" )
|
||||
|
||||
self:I( { Word = Word, Speech = Speech[Word], RemainderSentence = RemainderSentence } )
|
||||
|
||||
|
||||
|
||||
if Word then
|
||||
if Word ~= "" and tonumber(Word) == nil then
|
||||
@@ -301,7 +302,7 @@ function RADIOSPEECH:SpeakWords( Sentence, Speech, Language )
|
||||
if Speech[Word].Next == nil then
|
||||
self:I( { Sentence = Speech[Word].Sentence, Data = Speech[Word].Data } )
|
||||
self:NewTransmission( Speech[Word].Data[1] .. ".wav", Speech[Word].Data[2], Language .. "/" )
|
||||
else
|
||||
else
|
||||
if RemainderSentence and RemainderSentence ~= "" then
|
||||
return self:SpeakWords( RemainderSentence, Speech[Word].Next, Language )
|
||||
end
|
||||
@@ -309,11 +310,11 @@ function RADIOSPEECH:SpeakWords( Sentence, Speech, Language )
|
||||
end
|
||||
return RemainderSentence
|
||||
end
|
||||
return OriginalSentence
|
||||
return OriginalSentence
|
||||
else
|
||||
return ""
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Speak a sentence.
|
||||
@@ -332,7 +333,7 @@ function RADIOSPEECH:SpeakDigits( Sentence, Speech, Langauge )
|
||||
|
||||
if Digits then
|
||||
if Digits ~= "" and tonumber( Digits ) ~= nil then
|
||||
|
||||
|
||||
-- Construct numbers
|
||||
local Number = tonumber( Digits )
|
||||
local Multiple = nil
|
||||
@@ -356,7 +357,7 @@ function RADIOSPEECH:SpeakDigits( Sentence, Speech, Langauge )
|
||||
end
|
||||
return RemainderSentence
|
||||
end
|
||||
return OriginalSentence
|
||||
return OriginalSentence
|
||||
else
|
||||
return ""
|
||||
end
|
||||
@@ -373,26 +374,26 @@ function RADIOSPEECH:Speak( Sentence, Language )
|
||||
self:I( { Sentence, Language } )
|
||||
|
||||
local Language = Language or "EN"
|
||||
|
||||
|
||||
self:I( { Language = Language } )
|
||||
|
||||
|
||||
-- If there is no node for Speech, then we start at the first nodes of the language.
|
||||
local Speech = self.Speech[Language]
|
||||
|
||||
|
||||
self:I( { Speech = Speech, Language = Language } )
|
||||
|
||||
|
||||
self:NewTransmission( "_In.wav", 0.52, Language .. "/" )
|
||||
|
||||
|
||||
repeat
|
||||
|
||||
Sentence = self:SpeakWords( Sentence, Speech, Language )
|
||||
|
||||
|
||||
self:I( { Sentence = Sentence } )
|
||||
|
||||
Sentence = self:SpeakDigits( Sentence, Speech, Language )
|
||||
|
||||
self:I( { Sentence = Sentence } )
|
||||
|
||||
|
||||
-- Sentence = self:SpeakSymbols( Sentence, Speech )
|
||||
--
|
||||
-- self:I( { Sentence = Sentence } )
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
-- @field Core.Point#COORDINATE coordinate Coordinate from where the transmission is send.
|
||||
-- @field #string path Path to the SRS exe. This includes the final slash "/".
|
||||
-- @field #string google Full path google credentials JSON file, e.g. "C:\Users\username\Downloads\service-account-file.json".
|
||||
-- @field #string Label Label showing up on the SRS radio overlay. Default is "ROBOT". No spaces allowed.
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
--- *It is a very sad thing that nowadays there is so little useless information.* - Oscar Wilde
|
||||
@@ -106,6 +107,14 @@
|
||||
--
|
||||
-- Use @{#MSRS.SetCoordinate} to define the origin from where the transmission is broadcasted.
|
||||
--
|
||||
-- ## Set SRS Port
|
||||
--
|
||||
-- Use @{#MSRS.SetPort} to define the SRS port. Defaults to 5002.
|
||||
--
|
||||
-- ## Set SRS Volume
|
||||
--
|
||||
-- Use @{#MSRS.SetVolume} to define the SRS volume. Defaults to 1.0. Allowed values are between 0.0 and 1.0, from silent to loudest.
|
||||
--
|
||||
-- @field #MSRS
|
||||
MSRS = {
|
||||
ClassName = "MSRS",
|
||||
@@ -121,11 +130,12 @@ MSRS = {
|
||||
volume = 1,
|
||||
speed = 1,
|
||||
coordinate = nil,
|
||||
Label = "ROBOT",
|
||||
}
|
||||
|
||||
--- MSRS class version.
|
||||
-- @field #string version
|
||||
MSRS.version="0.0.3"
|
||||
MSRS.version="0.0.5"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- TODO list
|
||||
@@ -144,8 +154,9 @@ MSRS.version="0.0.3"
|
||||
-- @param #string PathToSRS Path to the directory, where SRS is located.
|
||||
-- @param #number Frequency Radio frequency in MHz. Default 143.00 MHz. Can also be given as a #table of multiple frequencies.
|
||||
-- @param #number Modulation Radio modulation: 0=AM (default), 1=FM. See `radio.modulation.AM` and `radio.modulation.FM` enumerators. Can also be given as a #table of multiple modulations.
|
||||
-- @param #number Volume Volume - 1.0 is max, 0.0 is silence
|
||||
-- @return #MSRS self
|
||||
function MSRS:New(PathToSRS, Frequency, Modulation)
|
||||
function MSRS:New(PathToSRS, Frequency, Modulation, Volume)
|
||||
|
||||
-- Defaults.
|
||||
Frequency =Frequency or 143
|
||||
@@ -160,6 +171,13 @@ function MSRS:New(PathToSRS, Frequency, Modulation)
|
||||
self:SetModulations(Modulation)
|
||||
self:SetGender()
|
||||
self:SetCoalition()
|
||||
self:SetLabel()
|
||||
self:SetVolume()
|
||||
self.lid = string.format("%s-%s | ",self.name,self.version)
|
||||
|
||||
if not io or not os then
|
||||
self:E(self.lid.."***** ERROR - io or os NOT desanitized! MSRS will not work!")
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
@@ -202,12 +220,47 @@ function MSRS:GetPath()
|
||||
return self.path
|
||||
end
|
||||
|
||||
--- Set SRS volume.
|
||||
-- @param #MSRS self
|
||||
-- @param #number Volume Volume - 1.0 is max, 0.0 is silence
|
||||
-- @return #MSRS self
|
||||
function MSRS:SetVolume(Volume)
|
||||
local volume = Volume or 1
|
||||
if volume > 1 then volume = 1 elseif volume < 0 then volume = 0 end
|
||||
self.volume = volume
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get SRS volume.
|
||||
-- @param #MSRS self
|
||||
-- @return #number Volume Volume - 1.0 is max, 0.0 is silence
|
||||
function MSRS:GetVolume()
|
||||
return self.volume
|
||||
end
|
||||
|
||||
--- Set label.
|
||||
-- @param #MSRS self
|
||||
-- @param #number Label. Default "ROBOT"
|
||||
-- @return #MSRS self
|
||||
function MSRS:SetLabel(Label)
|
||||
self.Label=Label or "ROBOT"
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get label.
|
||||
-- @param #MSRS self
|
||||
-- @return #number Label.
|
||||
function MSRS:GetLabel()
|
||||
return self.Label
|
||||
end
|
||||
|
||||
--- Set port.
|
||||
-- @param #MSRS self
|
||||
-- @param #number Port Port. Default 5002.
|
||||
-- @return #MSRS self
|
||||
function MSRS:SetPort(Port)
|
||||
self.port=Port or 5002
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get port.
|
||||
@@ -223,6 +276,7 @@ end
|
||||
-- @return #MSRS self
|
||||
function MSRS:SetCoalition(Coalition)
|
||||
self.coalition=Coalition or 0
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get coalition.
|
||||
@@ -391,7 +445,7 @@ function MSRS:PlaySoundFile(Soundfile, Delay)
|
||||
local command=self:_GetCommand()
|
||||
|
||||
-- Append file.
|
||||
command=command.." --file="..tostring(soundfile)
|
||||
command=command..' --file="'..tostring(soundfile)..'"'
|
||||
|
||||
self:_ExecCommand(command)
|
||||
|
||||
@@ -634,8 +688,9 @@ end
|
||||
-- @param #number volume Volume.
|
||||
-- @param #number speed Speed.
|
||||
-- @param #number port Port.
|
||||
-- @param #string label Label, defaults to "ROBOT" (displayed sender name in the radio overlay of SRS) - No spaces allowed!
|
||||
-- @return #string Command.
|
||||
function MSRS:_GetCommand(freqs, modus, coal, gender, voice, culture, volume, speed, port)
|
||||
function MSRS:_GetCommand(freqs, modus, coal, gender, voice, culture, volume, speed, port,label)
|
||||
|
||||
local path=self:GetPath() or STTS.DIRECTORY
|
||||
local exe=STTS.EXECUTABLE or "DCS-SR-ExternalAudio.exe"
|
||||
@@ -648,6 +703,7 @@ function MSRS:_GetCommand(freqs, modus, coal, gender, voice, culture, volume, sp
|
||||
volume=volume or self.volume
|
||||
speed=speed or self.speed
|
||||
port=port or self.port
|
||||
label=label or self.Label
|
||||
|
||||
-- Replace modulation
|
||||
modus=modus:gsub("0", "AM")
|
||||
@@ -657,12 +713,12 @@ function MSRS:_GetCommand(freqs, modus, coal, gender, voice, culture, volume, sp
|
||||
--local command=string.format("%s --freqs=%s --modulations=%s --coalition=%d --port=%d --volume=%.2f --speed=%d", exe, freqs, modus, coal, port, volume, speed)
|
||||
|
||||
-- Command from orig STTS script. Works better for some unknown reason!
|
||||
local command=string.format("start /min \"\" /d \"%s\" /b \"%s\" -f %s -m %s -c %s -p %s -n \"%s\" -h", path, exe, freqs, modus, coal, port, "ROBOT")
|
||||
--local command=string.format("start /min \"\" /d \"%s\" /b \"%s\" -f %s -m %s -c %s -p %s -n \"%s\" -h", path, exe, freqs, modus, coal, port, "ROBOT")
|
||||
|
||||
--local command=string.format('start /b "" /d "%s" "%s" -f %s -m %s -c %s -p %s -n "%s" > bla.txt', path, exe, freqs, modus, coal, port, "ROBOT")
|
||||
|
||||
-- Command.
|
||||
local command=string.format('%s/%s -f %s -m %s -c %s -p %s -n "%s"', path, exe, freqs, modus, coal, port, "ROBOT")
|
||||
local command=string.format('"%s\\%s" -f %s -m %s -c %s -p %s -n "%s"', path, exe, freqs, modus, coal, port, label)
|
||||
|
||||
-- Set voice or gender/culture.
|
||||
if voice then
|
||||
@@ -671,7 +727,7 @@ function MSRS:_GetCommand(freqs, modus, coal, gender, voice, culture, volume, sp
|
||||
else
|
||||
-- Add gender.
|
||||
if gender and gender~="female" then
|
||||
command=command..string.format(" --gender=%s", tostring(gender))
|
||||
command=command..string.format(" -g %s", tostring(gender))
|
||||
end
|
||||
-- Add culture.
|
||||
if culture and culture~="en-GB" then
|
||||
|
||||
@@ -183,13 +183,17 @@ do -- Sound File
|
||||
|
||||
--- Set path, where the sound file is located.
|
||||
-- @param #SOUNDFILE self
|
||||
-- @param #string Path Path to the directory, where the sound file is located.
|
||||
-- @param #string Path Path to the directory, where the sound file is located. In case this is nil, it defaults to the DCS mission temp directory.
|
||||
-- @return #SOUNDFILE self
|
||||
function SOUNDFILE:SetPath(Path)
|
||||
|
||||
-- Init path.
|
||||
self.path=Path or "l10n/DEFAULT/"
|
||||
|
||||
|
||||
if not Path and self.useSRS then -- use path to mission temp dir
|
||||
self.path = os.getenv('TMP') .. "\\DCS\\Mission\\l10n\\DEFAULT"
|
||||
end
|
||||
|
||||
-- Remove (back)slashes.
|
||||
local nmax=1000 ; local n=1
|
||||
while (self.path:sub(-1)=="/" or self.path:sub(-1)==[[\]]) and n<=nmax do
|
||||
|
||||
@@ -40,7 +40,7 @@ do -- UserSound
|
||||
-- @param #USERSOUND self
|
||||
-- @param #string UserSoundFileName The filename of the usersound.
|
||||
-- @return #USERSOUND
|
||||
function USERSOUND:New( UserSoundFileName ) --R2.3
|
||||
function USERSOUND:New( UserSoundFileName )
|
||||
|
||||
local self = BASE:Inherit( self, BASE:New() ) -- #USERSOUND
|
||||
|
||||
@@ -58,7 +58,7 @@ do -- UserSound
|
||||
-- local BlueVictory = USERSOUND:New( "BlueVictory.ogg" )
|
||||
-- BlueVictory:SetFileName( "BlueVictoryLoud.ogg" ) -- Set the BlueVictory to change the file name to play a louder sound.
|
||||
--
|
||||
function USERSOUND:SetFileName( UserSoundFileName ) --R2.3
|
||||
function USERSOUND:SetFileName( UserSoundFileName )
|
||||
|
||||
self.UserSoundFileName = UserSoundFileName
|
||||
|
||||
@@ -75,7 +75,7 @@ do -- UserSound
|
||||
-- local BlueVictory = USERSOUND:New( "BlueVictory.ogg" )
|
||||
-- BlueVictory:ToAll() -- Play the sound that Blue has won.
|
||||
--
|
||||
function USERSOUND:ToAll() --R2.3
|
||||
function USERSOUND:ToAll()
|
||||
|
||||
trigger.action.outSound( self.UserSoundFileName )
|
||||
|
||||
@@ -91,7 +91,7 @@ do -- UserSound
|
||||
-- local BlueVictory = USERSOUND:New( "BlueVictory.ogg" )
|
||||
-- BlueVictory:ToCoalition( coalition.side.BLUE ) -- Play the sound that Blue has won to the blue coalition.
|
||||
--
|
||||
function USERSOUND:ToCoalition( Coalition ) --R2.3
|
||||
function USERSOUND:ToCoalition( Coalition )
|
||||
|
||||
trigger.action.outSoundForCoalition(Coalition, self.UserSoundFileName )
|
||||
|
||||
@@ -107,7 +107,7 @@ do -- UserSound
|
||||
-- local BlueVictory = USERSOUND:New( "BlueVictory.ogg" )
|
||||
-- BlueVictory:ToCountry( country.id.USA ) -- Play the sound that Blue has won to the USA country.
|
||||
--
|
||||
function USERSOUND:ToCountry( Country ) --R2.3
|
||||
function USERSOUND:ToCountry( Country )
|
||||
|
||||
trigger.action.outSoundForCountry( Country, self.UserSoundFileName )
|
||||
|
||||
@@ -123,9 +123,9 @@ do -- UserSound
|
||||
-- @usage
|
||||
-- local BlueVictory = USERSOUND:New( "BlueVictory.ogg" )
|
||||
-- local PlayerGroup = GROUP:FindByName( "PlayerGroup" ) -- Search for the active group named "PlayerGroup", that contains a human player.
|
||||
-- BlueVictory:ToGroup( PlayerGroup ) -- Play the sound that Blue has won to the player group.
|
||||
-- BlueVictory:ToGroup( PlayerGroup ) -- Play the victory sound to the player group.
|
||||
--
|
||||
function USERSOUND:ToGroup( Group, Delay ) --R2.3
|
||||
function USERSOUND:ToGroup( Group, Delay )
|
||||
|
||||
Delay=Delay or 0
|
||||
if Delay>0 then
|
||||
@@ -136,5 +136,27 @@ do -- UserSound
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Play the usersound to the given @{Wrapper.Unit}.
|
||||
-- @param #USERSOUND self
|
||||
-- @param Wrapper.Unit#UNIT Unit The @{Wrapper.Unit} to play the usersound to.
|
||||
-- @param #number Delay (Optional) Delay in seconds, before the sound is played. Default 0.
|
||||
-- @return #USERSOUND The usersound instance.
|
||||
-- @usage
|
||||
-- local BlueVictory = USERSOUND:New( "BlueVictory.ogg" )
|
||||
-- local PlayerUnit = UNIT:FindByName( "PlayerUnit" ) -- Search for the active unit named "PlayerUnit", a human player.
|
||||
-- BlueVictory:ToUnit( PlayerUnit ) -- Play the victory sound to the player unit.
|
||||
--
|
||||
function USERSOUND:ToUnit( Unit, Delay )
|
||||
|
||||
Delay=Delay or 0
|
||||
if Delay>0 then
|
||||
SCHEDULER:New(nil, USERSOUND.ToUnit,{self, Unit}, Delay)
|
||||
else
|
||||
trigger.action.outSoundForUnit( Unit:GetID(), self.UserSoundFileName )
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
end
|
||||
@@ -216,7 +216,7 @@ function COMMANDCENTER:New( CommandCenterPositionable, CommandCenterName )
|
||||
local MenuReporting = MENU_GROUP:New( EventGroup, "Missions Reports", CommandCenterMenu )
|
||||
local MenuMissionsSummary = MENU_GROUP_COMMAND:New( EventGroup, "Missions Status Report", MenuReporting, self.ReportSummary, self, EventGroup )
|
||||
local MenuMissionsDetails = MENU_GROUP_COMMAND:New( EventGroup, "Missions Players Report", MenuReporting, self.ReportMissionsPlayers, self, EventGroup )
|
||||
self:ReportSummary( EventGroup )
|
||||
--self:ReportSummary( EventGroup )
|
||||
local PlayerUnit = EventData.IniUnit
|
||||
for MissionID, Mission in pairs( self:GetMissions() ) do
|
||||
local Mission = Mission -- Tasking.Mission#MISSION
|
||||
@@ -560,9 +560,11 @@ function COMMANDCENTER:SetAutoAssignTasks( AutoAssign )
|
||||
self.AutoAssignTasks = AutoAssign or false
|
||||
|
||||
if self.AutoAssignTasks == true then
|
||||
self:ScheduleRepeat( 10, 30, 0, nil, self.AssignTasks, self )
|
||||
self.autoAssignTasksScheduleID=self:ScheduleRepeat( 10, 30, 0, nil, self.AssignTasks, self )
|
||||
else
|
||||
self:ScheduleStop( self.AssignTasks )
|
||||
self:ScheduleStop()
|
||||
-- FF this is not the schedule ID
|
||||
--self:ScheduleStop( self.AssignTasks )
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user