mirror of
https://github.com/FlightControl-Master/MOOSE.git
synced 2025-08-15 10:47:21 +00:00
Compare commits
1297 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a37d4214c0 | ||
|
|
636d6ce324 | ||
|
|
eef8b362d2 | ||
|
|
4ae586ebaa | ||
|
|
f6e673c2bb | ||
|
|
183a60159c | ||
|
|
1fdf4f371d | ||
|
|
f50c374d04 | ||
|
|
d59fc331f6 | ||
|
|
b83f478294 | ||
|
|
d5636f4a19 | ||
|
|
196bcf39cf | ||
|
|
afec1c3a5b | ||
|
|
6025339b46 | ||
|
|
40c6cc59d3 | ||
|
|
514e568e04 | ||
|
|
2f34b0a5ed | ||
|
|
708c076885 | ||
|
|
f0e0b918af | ||
|
|
e45f5e1122 | ||
|
|
932015668b | ||
|
|
4011bc3fe6 | ||
|
|
1dcccdc434 | ||
|
|
3380ed9360 | ||
|
|
ed9c14e63d | ||
|
|
4762793adc | ||
|
|
7df3946189 | ||
|
|
d5fb75fe43 | ||
|
|
c0b32a5584 | ||
|
|
e2b1276d7b | ||
|
|
f6aea13fae | ||
|
|
646b113c55 | ||
|
|
58074f499f | ||
|
|
1483ffd7ff | ||
|
|
cdaef851a0 | ||
|
|
04068d7117 | ||
|
|
41e8ddea8c | ||
|
|
cc49791997 | ||
|
|
ba4a8050ba | ||
|
|
7c5067a59a | ||
|
|
69eb920173 | ||
|
|
07d761941a | ||
|
|
ca52585759 | ||
|
|
decc9d09f8 | ||
|
|
466a18447c | ||
|
|
27902ee107 | ||
|
|
8a8b806362 | ||
|
|
40bb181c78 | ||
|
|
8099847e29 | ||
|
|
6e8edd95ec | ||
|
|
5112c9598b | ||
|
|
749158c086 | ||
|
|
b3d4024f21 | ||
|
|
c283b66c1d | ||
|
|
3209843318 | ||
|
|
d35e5cc0f7 | ||
|
|
e5eeb592a2 | ||
|
|
3d38f4d17a | ||
|
|
2d91647e0b | ||
|
|
cac0f30673 | ||
|
|
c5ecba3389 | ||
|
|
e0397dff47 | ||
|
|
e08fb2e972 | ||
|
|
c02ae82003 | ||
|
|
e6fc301b0d | ||
|
|
a385ed57fb | ||
|
|
09dafe4b1d | ||
|
|
ba8505c983 | ||
|
|
fba359d389 | ||
|
|
c56763b68f | ||
|
|
03c2943545 | ||
|
|
98039b9048 | ||
|
|
5065d3b068 | ||
|
|
6828f7e262 | ||
|
|
a685f3ffbd | ||
|
|
e84156d2e9 | ||
|
|
0ce1c31e1c | ||
|
|
f7e14bb60c | ||
|
|
7b907df816 | ||
|
|
7f5be2829c | ||
|
|
fa5afae783 | ||
|
|
b17507d0fa | ||
|
|
5ed43a3190 | ||
|
|
d2a5144a23 | ||
|
|
fb2031d7ca | ||
|
|
4a42571925 | ||
|
|
38413625c2 | ||
|
|
2d544b7a98 | ||
|
|
b1e5e1840e | ||
|
|
e6f9b4a125 | ||
|
|
5260b2b430 | ||
|
|
0213bc7aef | ||
|
|
ca8b0899d0 | ||
|
|
a1f5c0ab9b | ||
|
|
b0e3f82d27 | ||
|
|
327ab4766b | ||
|
|
3aee8a49c1 | ||
|
|
57de0b7351 | ||
|
|
4df1e310a3 | ||
|
|
802a77238a | ||
|
|
85a7e18fae | ||
|
|
26b1fd3487 | ||
|
|
ae7a363012 | ||
|
|
473362af45 | ||
|
|
cde0d09f0a | ||
|
|
94f093826b | ||
|
|
84f231ea08 | ||
|
|
3d9bb14713 | ||
|
|
6c6cdcf763 | ||
|
|
00c8690e61 | ||
|
|
a0d492cd2d | ||
|
|
ba5ccc1021 | ||
|
|
a4163017d5 | ||
|
|
7f4a5c48ec | ||
|
|
9f7588b245 | ||
|
|
63cbc0c55b | ||
|
|
28eb7a678c | ||
|
|
a95c49915a | ||
|
|
b7adc6add6 | ||
|
|
2aeebf280b | ||
|
|
8ac06979f0 | ||
|
|
2d4f90d5eb | ||
|
|
d7a44a639d | ||
|
|
7bfa05f47d | ||
|
|
c7bbb09195 | ||
|
|
41c9c15ae5 | ||
|
|
964831becf | ||
|
|
e847b92cce | ||
|
|
c2ecd86bb4 | ||
|
|
70d922fad6 | ||
|
|
854bee0519 | ||
|
|
d54d991bdd | ||
|
|
a4b600b97d | ||
|
|
d6cfaa5050 | ||
|
|
00d1aec210 | ||
|
|
f62e3391e1 | ||
|
|
4a406604bd | ||
|
|
55cee46a8d | ||
|
|
607c52c0b7 | ||
|
|
2694321256 | ||
|
|
e8e790102a | ||
|
|
9c5561921b | ||
|
|
78fab9ab0c | ||
|
|
058c750bc6 | ||
|
|
f29da39dff | ||
|
|
624a7c70c9 | ||
|
|
0447ee2d9e | ||
|
|
456fcd38d0 | ||
|
|
a3cab7097a | ||
|
|
848e2f1294 | ||
|
|
ef4dc48ea1 | ||
|
|
2138a33292 | ||
|
|
a59343b987 | ||
|
|
a4ca4bdc99 | ||
|
|
a57b9a9081 | ||
|
|
249a6af456 | ||
|
|
18685d1a94 | ||
|
|
493b090534 | ||
|
|
389adab9b8 | ||
|
|
6360b8c58f | ||
|
|
b0818977cf | ||
|
|
32deb160ef | ||
|
|
2ba5215036 | ||
|
|
73ea4c7b32 | ||
|
|
46c37ff06a | ||
|
|
ff4708b624 | ||
|
|
82b2c84f13 | ||
|
|
48e8b1a9b3 | ||
|
|
1ad7c54ace | ||
|
|
9998c86c1f | ||
|
|
01a707ae0a | ||
|
|
570e8388fc | ||
|
|
9c5b5d4633 | ||
|
|
822bf13626 | ||
|
|
c520de0087 | ||
|
|
0e9076efa3 | ||
|
|
74cd5e3387 | ||
|
|
65c92be09e | ||
|
|
8e776cb3ab | ||
|
|
ab6cd2b751 | ||
|
|
18c3d990fc | ||
|
|
19d5cb8ecb | ||
|
|
2b56a78255 | ||
|
|
176d9df476 | ||
|
|
c94275cb8b | ||
|
|
45dbce3677 | ||
|
|
18745158a3 | ||
|
|
98c6c88391 | ||
|
|
2e4fd72781 | ||
|
|
80ced88ef1 | ||
|
|
8af3f89c14 | ||
|
|
fe3079caad | ||
|
|
61ac6b4131 | ||
|
|
36cb189512 | ||
|
|
15f9843878 | ||
|
|
67f847dd16 | ||
|
|
8b9143d3f1 | ||
|
|
0388d47f23 | ||
|
|
de9b173d9b | ||
|
|
2cecc526fb | ||
|
|
968d178317 | ||
|
|
3c477b872a | ||
|
|
77e6088114 | ||
|
|
edd6594953 | ||
|
|
f8c05c99d0 | ||
|
|
50f6d98b49 | ||
|
|
147eeb05f6 | ||
|
|
d8cb15a577 | ||
|
|
0daac876ea | ||
|
|
1832125022 | ||
|
|
c311c40b72 | ||
|
|
db516a2077 | ||
|
|
ff8766669c | ||
|
|
06dc9a732e | ||
|
|
50c74d0852 | ||
|
|
1c97eb6f3c | ||
|
|
69449430d1 | ||
|
|
663cd34aa3 | ||
|
|
cfed6f5153 | ||
|
|
2b22d5288c | ||
|
|
a64424ecc8 | ||
|
|
fd1b2ecb86 | ||
|
|
6cae3e62cf | ||
|
|
05ce7e4513 | ||
|
|
136bd19f19 | ||
|
|
8873504daf | ||
|
|
a844a5d697 | ||
|
|
a49f4eaa21 | ||
|
|
e6e2651f8c | ||
|
|
b93ba13644 | ||
|
|
e4a51951b0 | ||
|
|
ad56e39942 | ||
|
|
ea09dc5a6e | ||
|
|
8ecfd913a3 | ||
|
|
53367c786e | ||
|
|
db5797bb4e | ||
|
|
5e8fe97752 | ||
|
|
393fa0bfbb | ||
|
|
4f51884b9d | ||
|
|
4c5c320073 | ||
|
|
9098590568 | ||
|
|
555bb7e68b | ||
|
|
c0a18957f0 | ||
|
|
2cf939560e | ||
|
|
9d3a7aae78 | ||
|
|
f6ed592f92 | ||
|
|
c98757d13c | ||
|
|
17378f509e | ||
|
|
7f18ea0e7a | ||
|
|
5172619cb1 | ||
|
|
3962529698 | ||
|
|
6481d5d41e | ||
|
|
6cc3d73c04 | ||
|
|
e541e39403 | ||
|
|
c7ea45e5fd | ||
|
|
20f28b3d2c | ||
|
|
f3f63ab8aa | ||
|
|
e91090cfff | ||
|
|
1a7fb3c13e | ||
|
|
59857ed79d | ||
|
|
4797665939 | ||
|
|
b89749036d | ||
|
|
c6268488de | ||
|
|
de04369703 | ||
|
|
05b6f19a87 | ||
|
|
2753df8216 | ||
|
|
33d761503d | ||
|
|
b52272e18e | ||
|
|
70fa1cf19f | ||
|
|
7ca7caea75 | ||
|
|
277c26821e | ||
|
|
22826b4cd1 | ||
|
|
8cc1c24b64 | ||
|
|
0db35a0e9f | ||
|
|
b4707bb3eb | ||
|
|
bbfc2e9ed7 | ||
|
|
2fb0ab1aed | ||
|
|
6fbf050b72 | ||
|
|
29210f670c | ||
|
|
18b45c9621 | ||
|
|
9356d67d74 | ||
|
|
5bb2b05f71 | ||
|
|
89308f7d06 | ||
|
|
353d6dfec0 | ||
|
|
3f5e322948 | ||
|
|
0e8732fd44 | ||
|
|
77a3c7369d | ||
|
|
8d8070bbd7 | ||
|
|
72df687ce7 | ||
|
|
246935622c | ||
|
|
078573629d | ||
|
|
973b8323c9 | ||
|
|
6d61c5ee94 | ||
|
|
3e8db6a1fa | ||
|
|
832d6b1c08 | ||
|
|
e7936950f4 | ||
|
|
831e986ee8 | ||
|
|
8a44fae3d4 | ||
|
|
0c9390914a | ||
|
|
4fa525a4ae | ||
|
|
858b00336b | ||
|
|
82d78c98bb | ||
|
|
06c3f7998b | ||
|
|
e03e87f501 | ||
|
|
cf83abfe90 | ||
|
|
5a00f461e9 | ||
|
|
bb43b08190 | ||
|
|
05f95796f6 | ||
|
|
cfcd7d7588 | ||
|
|
4c3d44a63b | ||
|
|
fcd75a34eb | ||
|
|
4827b73bb1 | ||
|
|
2d7e7d55a9 | ||
|
|
3129c8c8ea | ||
|
|
6e37300d9b | ||
|
|
6b747e924b | ||
|
|
85fef96d00 | ||
|
|
e55bb21401 | ||
|
|
7a3fb95851 | ||
|
|
d39074bf3e | ||
|
|
47d814e409 | ||
|
|
41b01a508d | ||
|
|
0410ae6877 | ||
|
|
1ae41319fa | ||
|
|
0265152c12 | ||
|
|
a893d46cb9 | ||
|
|
b68f271fb7 | ||
|
|
057e231a9d | ||
|
|
b6fedbd97d | ||
|
|
7cd29501a9 | ||
|
|
59e4f48726 | ||
|
|
20fe2ee505 | ||
|
|
8b4ba430fb | ||
|
|
3b0949de72 | ||
|
|
ebb4623bb5 | ||
|
|
2309487429 | ||
|
|
a3decc33c9 | ||
|
|
b2d87a8780 | ||
|
|
5034005f6b | ||
|
|
c045ba0c6d | ||
|
|
d0fb4d44ef | ||
|
|
d1f3e3f4bb | ||
|
|
45f578b5a3 | ||
|
|
620f4b96b9 | ||
|
|
39d3f85b71 | ||
|
|
9a7df65faa | ||
|
|
596eb4c7e4 | ||
|
|
2bce305451 | ||
|
|
2bcc531e73 | ||
|
|
2e66a854b1 | ||
|
|
57f6096aa1 | ||
|
|
dc64b98c59 | ||
|
|
e6dde7335a | ||
|
|
bbb2174957 | ||
|
|
b37b50a0c6 | ||
|
|
459ff8038b | ||
|
|
f2da862442 | ||
|
|
9c6c2ec5ca | ||
|
|
aa13583aa7 | ||
|
|
002f1e09ef | ||
|
|
5091ed7d6d | ||
|
|
2475f22937 | ||
|
|
f4f12f786c | ||
|
|
eb659446df | ||
|
|
62c42adebc | ||
|
|
0fc91595b8 | ||
|
|
18fca6461b | ||
|
|
869b819a4f | ||
|
|
8ae6192a5f | ||
|
|
3b2af6ea07 | ||
|
|
82da7a8f24 | ||
|
|
3749686882 | ||
|
|
b2eba4911a | ||
|
|
40700a387c | ||
|
|
0577940d58 | ||
|
|
1d6be07cff | ||
|
|
2d6545429a | ||
|
|
9f095a1d28 | ||
|
|
91b6da70af | ||
|
|
235e40cb26 | ||
|
|
8ba3a30278 | ||
|
|
3675ddaf04 | ||
|
|
62a59e7b2a | ||
|
|
1f949463aa | ||
|
|
6d53a30b1f | ||
|
|
5aeb480069 | ||
|
|
4b6aa3b56f | ||
|
|
4209569f29 | ||
|
|
7106efbc04 | ||
|
|
4e878e49a7 | ||
|
|
3e470e557d | ||
|
|
5f567fcf66 | ||
|
|
2289e16d91 | ||
|
|
40415ef933 | ||
|
|
6519c30b3a | ||
|
|
42d253a9ca | ||
|
|
38b40e2931 | ||
|
|
f7a7ea14a3 | ||
|
|
41e0f92cc0 | ||
|
|
2c5e9789db | ||
|
|
e198aae1dd | ||
|
|
d040cc0d03 | ||
|
|
f078e94a7a | ||
|
|
9973099f57 | ||
|
|
9ca7785a64 | ||
|
|
e7d5fd6c1b | ||
|
|
9eed35ace7 | ||
|
|
c7ec1b4e5e | ||
|
|
34ffebb46a | ||
|
|
e8a2167934 | ||
|
|
fa342a5b6b | ||
|
|
4ebcaf913c | ||
|
|
7a4c9806a4 | ||
|
|
bddc1d7fdc | ||
|
|
cb813b70e1 | ||
|
|
f161f08fc1 | ||
|
|
3e22411328 | ||
|
|
b00778bb86 | ||
|
|
564f95781e | ||
|
|
6c2f8aa6ff | ||
|
|
2e342e4341 | ||
|
|
3c6089884e | ||
|
|
884ea866e2 | ||
|
|
1af1ada7dc | ||
|
|
c72c3fd091 | ||
|
|
0848718313 | ||
|
|
832abd7860 | ||
|
|
12e745af54 | ||
|
|
f18c818b8e | ||
|
|
4deca4f816 | ||
|
|
b023ecfb99 | ||
|
|
cdb491bb5f | ||
|
|
3ac47c967e | ||
|
|
e577062720 | ||
|
|
95dc7ad2b4 | ||
|
|
fccf58cd6c | ||
|
|
6791e4d4ba | ||
|
|
11f8ac4820 | ||
|
|
8f2672f928 | ||
|
|
fb66d74ab7 | ||
|
|
37c061bcbd | ||
|
|
9491b18ff4 | ||
|
|
8cff2eaac0 | ||
|
|
f64550f7c8 | ||
|
|
0addafa627 | ||
|
|
b643d73f7f | ||
|
|
798c5b292a | ||
|
|
688c57d91d | ||
|
|
d4c8d5d91b | ||
|
|
154cc9fbeb | ||
|
|
327bac3058 | ||
|
|
c0e58e9c92 | ||
|
|
e8bfab515c | ||
|
|
695f204493 | ||
|
|
a7202ccd0d | ||
|
|
c71c6c5178 | ||
|
|
155b06edc8 | ||
|
|
64b8f2fb46 | ||
|
|
7a07e15032 | ||
|
|
3d74312d43 | ||
|
|
6baf85d429 | ||
|
|
311ab19389 | ||
|
|
e9f92d2250 | ||
|
|
d469374ed5 | ||
|
|
a6ff84c09a | ||
|
|
3ed8ca63a8 | ||
|
|
89aa08829e | ||
|
|
5146106885 | ||
|
|
f05fc956db | ||
|
|
d10adb5de8 | ||
|
|
45aac21b63 | ||
|
|
b52643bb36 | ||
|
|
036010c39d | ||
|
|
9440d2b2c3 | ||
|
|
29727ec9a6 | ||
|
|
eb0b43cea8 | ||
|
|
a6e16fb7ad | ||
|
|
616b80710a | ||
|
|
94f206af68 | ||
|
|
ae6613fae1 | ||
|
|
b61cfc7390 | ||
|
|
e2c5688670 | ||
|
|
1d016d9fce | ||
|
|
cbc5b5bb89 | ||
|
|
218c7736e0 | ||
|
|
8e46e41b34 | ||
|
|
bcf639d076 | ||
|
|
697d12aefc | ||
|
|
89afd2b4c0 | ||
|
|
3358f98bc4 | ||
|
|
417af6a93c | ||
|
|
9a2f6c7faa | ||
|
|
e52b0edc4e | ||
|
|
c51a65f058 | ||
|
|
5b2e67df19 | ||
|
|
7b7605e501 | ||
|
|
49d7d9ee2e | ||
|
|
774c8971c8 | ||
|
|
545c0886fa | ||
|
|
721d027fb3 | ||
|
|
5185ea35fe | ||
|
|
ec039f2999 | ||
|
|
c0fc649f8b | ||
|
|
c49497e68c | ||
|
|
57bc6de12b | ||
|
|
1b80d68f50 | ||
|
|
787151597c | ||
|
|
f725039da5 | ||
|
|
a4f15a0830 | ||
|
|
084f00afad | ||
|
|
87f4ebee64 | ||
|
|
a0276821d9 | ||
|
|
01a5b523da | ||
|
|
13477e17ec | ||
|
|
c32cec4c5b | ||
|
|
e08df3f9ce | ||
|
|
f958130b92 | ||
|
|
9a19dd254e | ||
|
|
4869ae4baa | ||
|
|
1fd41eaa55 | ||
|
|
25a7260d7f | ||
|
|
478934ddd1 | ||
|
|
3d7234de19 | ||
|
|
fed1fb2839 | ||
|
|
08ba001b45 | ||
|
|
ce5aaf0b48 | ||
|
|
ee6954a4b9 | ||
|
|
6b6e845a18 | ||
|
|
c01b94518f | ||
|
|
9cf5f74e5e | ||
|
|
4fda0414a0 | ||
|
|
effa4af3a1 | ||
|
|
80b7e49eed | ||
|
|
63748c3841 | ||
|
|
1c68b66047 | ||
|
|
6c9dce70e7 | ||
|
|
5515aa43d6 | ||
|
|
47c461e504 | ||
|
|
65174ceaae | ||
|
|
2500cfb3c7 | ||
|
|
7e673271ee | ||
|
|
e165cb156c | ||
|
|
82bfb0cc55 | ||
|
|
b0fe0663f1 | ||
|
|
1bd5193786 | ||
|
|
64c3fb985a | ||
|
|
1536c94319 | ||
|
|
e7efd89d7a | ||
|
|
c486167b01 | ||
|
|
e5201f3363 | ||
|
|
ff4927dbb7 | ||
|
|
5842562ce1 | ||
|
|
5f545fd3c5 | ||
|
|
aab2f20280 | ||
|
|
76ac68ce51 | ||
|
|
f034e3680c | ||
|
|
e5c57269bb | ||
|
|
ab5167717d | ||
|
|
176abf74a1 | ||
|
|
da4f13d0d7 | ||
|
|
7ddec7609a | ||
|
|
53c3996aef | ||
|
|
bf61f22dd1 | ||
|
|
5e8a36802c | ||
|
|
b34b537a26 | ||
|
|
812fb99e66 | ||
|
|
9cb3111dd5 | ||
|
|
1312555690 | ||
|
|
aa4f8452fa | ||
|
|
e2c1097ec5 | ||
|
|
4b8b13dd68 | ||
|
|
59f795fc17 | ||
|
|
26a935c29c | ||
|
|
848336f8ac | ||
|
|
8e6e9cbb4d | ||
|
|
15cb9bec40 | ||
|
|
38f5fd8249 | ||
|
|
6a04f83280 | ||
|
|
ec4d12a0d5 | ||
|
|
999aba5d77 | ||
|
|
652f29b782 | ||
|
|
0c6aae45c1 | ||
|
|
25b8cff442 | ||
|
|
9f7471178b | ||
|
|
f3f3406d64 | ||
|
|
4eb886d0f3 | ||
|
|
5c6e50e7f9 | ||
|
|
18fd9cdc3d | ||
|
|
fb3115a0f1 | ||
|
|
fdcdf52d9a | ||
|
|
5dcefff28d | ||
|
|
833d4f7b65 | ||
|
|
61bb59d8b3 | ||
|
|
21652de804 | ||
|
|
e677e2acf4 | ||
|
|
2658230331 | ||
|
|
bb0827bb8c | ||
|
|
be1f8cde34 | ||
|
|
29028bf128 | ||
|
|
c30ca2b18a | ||
|
|
d1b40b63c2 | ||
|
|
0b7ac754c5 | ||
|
|
5b514d223b | ||
|
|
7d8e27c83e | ||
|
|
dcd1060496 | ||
|
|
533e2abf17 | ||
|
|
6b318d0309 | ||
|
|
bee44b97fd | ||
|
|
77de1dda2b | ||
|
|
5b03bc5066 | ||
|
|
0d6f32581c | ||
|
|
2620370890 | ||
|
|
f866af0d4a | ||
|
|
b86eac33b6 | ||
|
|
c78bd2652a | ||
|
|
392d465b30 | ||
|
|
056cc1630d | ||
|
|
364ce927e7 | ||
|
|
517d5860fb | ||
|
|
4e49184da2 | ||
|
|
68ece29ab5 | ||
|
|
c3cc76c15b | ||
|
|
0a570da986 | ||
|
|
b25697e261 | ||
|
|
863e239204 | ||
|
|
457b30ed07 | ||
|
|
7360c51a8f | ||
|
|
df512f7a58 | ||
|
|
da05c41c27 | ||
|
|
3c428723f8 | ||
|
|
4c3fd1d867 | ||
|
|
909c028e48 | ||
|
|
14e12dc36b | ||
|
|
0502e0a020 | ||
|
|
36aebd3c04 | ||
|
|
007831bb9a | ||
|
|
def44bde89 | ||
|
|
4b369fae95 | ||
|
|
cb4a44b512 | ||
|
|
53fbec9c15 | ||
|
|
d476922af2 | ||
|
|
861f2da4d6 | ||
|
|
6d75b25f57 | ||
|
|
bfb3ae33f4 | ||
|
|
a796c7a594 | ||
|
|
af63c1a219 | ||
|
|
72da7a2c04 | ||
|
|
f9bdbf4e3f | ||
|
|
850951bcb4 | ||
|
|
3d0f002f73 | ||
|
|
ff8c454752 | ||
|
|
6882c4b42f | ||
|
|
9b0f474edf | ||
|
|
ceaae5421c | ||
|
|
36666df76f | ||
|
|
3616e4264a | ||
|
|
a704693abf | ||
|
|
4dc303a166 | ||
|
|
71b5140e69 | ||
|
|
00e5499f81 | ||
|
|
7cf73af707 | ||
|
|
4ef827836c | ||
|
|
e3d6337481 | ||
|
|
f258e9263e | ||
|
|
48d99a19c5 | ||
|
|
f34511bd8c | ||
|
|
63a5d2e3ac | ||
|
|
3fb52d4fa1 | ||
|
|
463403cef4 | ||
|
|
fedee49eb1 | ||
|
|
042cc7a85f | ||
|
|
9583168c4d | ||
|
|
0c32c35c27 | ||
|
|
6ab85072d2 | ||
|
|
04da941c36 | ||
|
|
d6c919a097 | ||
|
|
1f9ffcd92d | ||
|
|
12ef34acaa | ||
|
|
ce60e7ce74 | ||
|
|
aa5d36db88 | ||
|
|
a2759b1cc0 | ||
|
|
0da0b025b5 | ||
|
|
f685064c0c | ||
|
|
b68e254bc0 | ||
|
|
d43f77706e | ||
|
|
5e1fb36d01 | ||
|
|
6d3bbe88e6 | ||
|
|
e4906eb083 | ||
|
|
19eb81866a | ||
|
|
cd34755036 | ||
|
|
c72625c60f | ||
|
|
1e1154d190 | ||
|
|
ff0f62c01b | ||
|
|
e6e5787fc6 | ||
|
|
431e8a05f9 | ||
|
|
b91a8cf4c8 | ||
|
|
3e8455410e | ||
|
|
3a9ad1b7cc | ||
|
|
6000421957 | ||
|
|
231ceef41f | ||
|
|
4661f6981e | ||
|
|
fcfcfeae00 | ||
|
|
09ed562f61 | ||
|
|
b1f2bab87e | ||
|
|
aa6515e1ca | ||
|
|
d5b8ed62ae | ||
|
|
6208c79d1f | ||
|
|
740ee74f61 | ||
|
|
0bb0c70667 | ||
|
|
9fb311bf7d | ||
|
|
8915657e91 | ||
|
|
8b506a2f20 | ||
|
|
6dd73dba44 | ||
|
|
d3a3d96436 | ||
|
|
8337058306 | ||
|
|
389931fbf0 | ||
|
|
b9fd6c0e31 | ||
|
|
f48f53fb37 | ||
|
|
07edcd7e73 | ||
|
|
a193d342d7 | ||
|
|
5b9978e390 | ||
|
|
c3c984381e | ||
|
|
ca49e8f8ae | ||
|
|
59c46f9b4b | ||
|
|
b9384831fb | ||
|
|
0d57ad5584 | ||
|
|
cf34ec0905 | ||
|
|
1c99e474b2 | ||
|
|
e45134f086 | ||
|
|
1b5e9df586 | ||
|
|
4b6612e114 | ||
|
|
e4473010eb | ||
|
|
bb259b2572 | ||
|
|
e16e2851c1 | ||
|
|
169c5a674c | ||
|
|
60042e14dc | ||
|
|
14de37f390 | ||
|
|
e67b819df9 | ||
|
|
efccefecad | ||
|
|
faa2debbae | ||
|
|
ddf61d1bf3 | ||
|
|
c5d514efdf | ||
|
|
f1c16def9a | ||
|
|
626db45a7e | ||
|
|
21a7594134 | ||
|
|
5fd20477d9 | ||
|
|
01dd3c93d2 | ||
|
|
b733e3b6d8 | ||
|
|
fe2ef11f8d | ||
|
|
9b6994749b | ||
|
|
9b209d6ce0 | ||
|
|
ed649747bd | ||
|
|
f5f1c9043b | ||
|
|
c7c371dc34 | ||
|
|
1cea10f03a | ||
|
|
3e9f54aaa5 | ||
|
|
ab1cd41d79 | ||
|
|
dc9f730d7d | ||
|
|
c0e48288de | ||
|
|
a13b79ced1 | ||
|
|
cd845d385f | ||
|
|
dea7a5674c | ||
|
|
8d1d2179f9 | ||
|
|
9b96ff5978 | ||
|
|
6cc233cac7 | ||
|
|
ad2ce8df78 | ||
|
|
4ea44308bc | ||
|
|
5cdaf53727 | ||
|
|
fb875077d7 | ||
|
|
c579aad606 | ||
|
|
8c54b7298d | ||
|
|
b309c25619 | ||
|
|
04a699f990 | ||
|
|
d51fc5de8a | ||
|
|
e458c70a7b | ||
|
|
2d4a4fb2ca | ||
|
|
e23ebd622a | ||
|
|
98e2f2c09b | ||
|
|
1fd9cbec1f | ||
|
|
1aedcf1ae4 | ||
|
|
e0075cc9ac | ||
|
|
372dd704d2 | ||
|
|
14b35cb069 | ||
|
|
c1b240857f | ||
|
|
0f5eb30a0f | ||
|
|
9e0f2c22b6 | ||
|
|
c0771c0096 | ||
|
|
c716ba16b3 | ||
|
|
96a904c077 | ||
|
|
f951aae3ee | ||
|
|
7a859f20bb | ||
|
|
3bfa8830a5 | ||
|
|
ec85b42a38 | ||
|
|
a216eb4e74 | ||
|
|
94476e418e | ||
|
|
fd1eb5f601 | ||
|
|
db7ec60c86 | ||
|
|
97e1a65b52 | ||
|
|
23497dddd4 | ||
|
|
e8a7def26e | ||
|
|
28a23c21f4 | ||
|
|
f32094aa43 | ||
|
|
c215255ae2 | ||
|
|
53b998cfab | ||
|
|
0a0a7d0fc3 | ||
|
|
d84ae236eb | ||
|
|
6dff84f435 | ||
|
|
1bfa5b5ee0 | ||
|
|
c3618bdc26 | ||
|
|
eebc5d90d1 | ||
|
|
596b3ddb4a | ||
|
|
e770af9fc9 | ||
|
|
d3c76da7a2 | ||
|
|
5f9880f17d | ||
|
|
d6b1018700 | ||
|
|
f934c8cb77 | ||
|
|
db4b5383fa | ||
|
|
4982cabcfb | ||
|
|
5594cd8fdf | ||
|
|
fbb32a9e45 | ||
|
|
1c559110a2 | ||
|
|
6f170db344 | ||
|
|
27a1401ae1 | ||
|
|
debf784d0c | ||
|
|
5a97f68778 | ||
|
|
d2ef75b22c | ||
|
|
115743263b | ||
|
|
59447999f0 | ||
|
|
4554ac1f3e | ||
|
|
d08d8db298 | ||
|
|
099f95f3cc | ||
|
|
20cf69c182 | ||
|
|
f931af2ee9 | ||
|
|
96fe9d51d6 | ||
|
|
d264c6f6a5 | ||
|
|
e17e635710 | ||
|
|
d4b9fc9e40 | ||
|
|
d66672a29c | ||
|
|
345b0055f3 | ||
|
|
7fdc049079 | ||
|
|
5caab9c6f3 | ||
|
|
03042c8282 | ||
|
|
c46028ff2d | ||
|
|
bd83344c21 | ||
|
|
183c05bcf5 | ||
|
|
a94591d811 | ||
|
|
6973e8c028 | ||
|
|
ab0b3f9b91 | ||
|
|
6f140be710 | ||
|
|
8780ec3687 | ||
|
|
1afe1c48b4 | ||
|
|
14b834e8ad | ||
|
|
0b53bd8f7e | ||
|
|
376a3553fa | ||
|
|
35653898cd | ||
|
|
ab1ba9595d | ||
|
|
2a7b9cf898 | ||
|
|
1224fb2d5a | ||
|
|
ac1d07ec66 | ||
|
|
1a534108bf | ||
|
|
c88f7bede5 | ||
|
|
598ef58849 | ||
|
|
2a485ef85a | ||
|
|
98ce42e9aa | ||
|
|
3612d5d171 | ||
|
|
83f8e76584 | ||
|
|
a108751e0c | ||
|
|
6f4d6c3daf | ||
|
|
a907369082 | ||
|
|
bc9060adfc | ||
|
|
4296dbade7 | ||
|
|
b1cee9969f | ||
|
|
9c0c4a11b1 | ||
|
|
b4324cb057 | ||
|
|
1d4dad0180 | ||
|
|
1e0e67c13d | ||
|
|
9c523cad52 | ||
|
|
6a1bf50700 | ||
|
|
d34cff1675 | ||
|
|
0a027caa47 | ||
|
|
10bfefdd06 | ||
|
|
ead1aed8d5 | ||
|
|
bc229f1a86 | ||
|
|
94f9cb4a79 | ||
|
|
acd5b1bdca | ||
|
|
9fed91b16f | ||
|
|
1627c92460 | ||
|
|
7bae22248a | ||
|
|
7b5b57c087 | ||
|
|
271217cf04 | ||
|
|
c5236d337e | ||
|
|
747bb949da | ||
|
|
a53bb1aac6 | ||
|
|
d3ecbac40a | ||
|
|
28fe8c5a0e | ||
|
|
6d79bbfb45 | ||
|
|
944c76c250 | ||
|
|
256ef2c7b4 | ||
|
|
2a0ae95e3e | ||
|
|
604ff07933 | ||
|
|
be8fc4e2e6 | ||
|
|
e92a625c5c | ||
|
|
e141823556 | ||
|
|
fbd0e9839d | ||
|
|
889ce8f64b | ||
|
|
6be56b1b86 | ||
|
|
78a5e89928 | ||
|
|
e09a02ec14 | ||
|
|
32ff4db36a | ||
|
|
9bb1c83999 | ||
|
|
93951dd4b4 | ||
|
|
1440f6c626 | ||
|
|
aacf83fd11 | ||
|
|
6c35892f05 | ||
|
|
3657a19645 | ||
|
|
ab423b3ba4 | ||
|
|
b4d6f78e42 | ||
|
|
49217291ad | ||
|
|
315d6bf82a | ||
|
|
486fc83323 | ||
|
|
3dbb013e16 | ||
|
|
b21ff707ba | ||
|
|
d985d9a41d | ||
|
|
178ee09614 | ||
|
|
95b67550eb | ||
|
|
48e0f8add6 | ||
|
|
d4f6ba2bdb | ||
|
|
e26b2f4919 | ||
|
|
208761fa46 | ||
|
|
03c47cc9f9 | ||
|
|
eb62810911 | ||
|
|
bf449492b5 | ||
|
|
4c08002d3e | ||
|
|
5e67861ea9 | ||
|
|
c657ba9218 | ||
|
|
ddfc22bb50 | ||
|
|
f9f4b3a9fd | ||
|
|
0e14624d39 | ||
|
|
f1d217b6d7 | ||
|
|
8126b198ba | ||
|
|
c2b46ceea1 | ||
|
|
fcceb278ec | ||
|
|
7a89960d21 | ||
|
|
4abdad5f35 | ||
|
|
6593a262bb | ||
|
|
b2f6b141d4 | ||
|
|
4bcf43122f | ||
|
|
70f30d2c53 | ||
|
|
a29a8d7d81 | ||
|
|
77f1bb4fdd | ||
|
|
53166b555f | ||
|
|
5f58fd3550 | ||
|
|
b7b6034d76 | ||
|
|
59974ea33e | ||
|
|
e4935d8717 | ||
|
|
de0c8e3789 | ||
|
|
bd290ffdab | ||
|
|
bfb11b6f79 | ||
|
|
8667c98449 | ||
|
|
4c72496599 | ||
|
|
aa8498ce0b | ||
|
|
ec155ed80c | ||
|
|
f849732f55 | ||
|
|
9856a8625d | ||
|
|
b1e99dc317 | ||
|
|
310ba9bb8a | ||
|
|
565a3062cb | ||
|
|
e8b74f0fc7 | ||
|
|
ca65154ecd | ||
|
|
e996a30333 | ||
|
|
e33a38556e | ||
|
|
3f2b3e0e1a | ||
|
|
dab0aef1e3 | ||
|
|
d9fb9c4928 | ||
|
|
964a6d0708 | ||
|
|
985282ba55 | ||
|
|
8534afe0e3 | ||
|
|
a70fa066fe | ||
|
|
3f8468dcc7 | ||
|
|
d9d9235fba | ||
|
|
5e20b244c5 | ||
|
|
40ebc4e990 | ||
|
|
70e7857b62 | ||
|
|
02a486e457 | ||
|
|
9e09a5bac9 | ||
|
|
ece7fc3ea9 | ||
|
|
0ea5c7fa48 | ||
|
|
3f64a1b33f | ||
|
|
02ee12402a | ||
|
|
837361e899 | ||
|
|
cff4f60923 | ||
|
|
fb23ac1d55 | ||
|
|
87a44f7f7f | ||
|
|
3b520ab0c4 | ||
|
|
044b5cba7c | ||
|
|
0f9591464a | ||
|
|
6a301060bf | ||
|
|
1e0b51a0a9 | ||
|
|
e19f0402da | ||
|
|
925ce3ad63 | ||
|
|
afc918c5e5 | ||
|
|
10b96f6cce | ||
|
|
d72e89d52b | ||
|
|
ef1a9330a4 | ||
|
|
eee1aca14f | ||
|
|
c8c95e063d | ||
|
|
0af9e57b82 | ||
|
|
e0899a8a9d | ||
|
|
093dadc0f6 | ||
|
|
23f92e49dd | ||
|
|
d1c909e9ed | ||
|
|
5dbc3b1ba1 | ||
|
|
85febea3d9 | ||
|
|
dca772cac6 | ||
|
|
26347ce779 | ||
|
|
b49f3c5b0c | ||
|
|
79be7cbd2c | ||
|
|
6fe8667885 | ||
|
|
b56d62c777 | ||
|
|
6f13aa2c5b | ||
|
|
d4510af249 | ||
|
|
fb784a9a96 | ||
|
|
25c7b1ddac | ||
|
|
71c7e15fc3 | ||
|
|
e6e0cf5977 | ||
|
|
775757e657 | ||
|
|
1a303ec7c2 | ||
|
|
c16edd65b0 | ||
|
|
1ff41ec7ec | ||
|
|
d874456361 | ||
|
|
6853eecffa | ||
|
|
71001adc5e | ||
|
|
b6a19d34c6 | ||
|
|
1364115b07 | ||
|
|
48c7254c84 | ||
|
|
60b6d2ade0 | ||
|
|
df43ff0841 | ||
|
|
5ee0ae44cd | ||
|
|
9df35840fe | ||
|
|
cff9557217 | ||
|
|
b5b6d398ae | ||
|
|
aba0c19215 | ||
|
|
5050371e0d | ||
|
|
81b36ef37d | ||
|
|
59e219e3d6 | ||
|
|
412d9e7a82 | ||
|
|
d01b55c790 | ||
|
|
5ed768065c | ||
|
|
e64703de03 | ||
|
|
71126eb34d | ||
|
|
6992e47a66 | ||
|
|
70d1f96681 | ||
|
|
83ed456c29 | ||
|
|
26607776a2 | ||
|
|
7a075e73ca | ||
|
|
6c15091df2 | ||
|
|
e063379cd8 | ||
|
|
3a49f2258b | ||
|
|
dddf056b20 | ||
|
|
bdbfed13ff | ||
|
|
9eb0800b66 | ||
|
|
713ec61031 | ||
|
|
264e84649e | ||
|
|
c4016cfa4d | ||
|
|
1cb248692b | ||
|
|
331315a83e | ||
|
|
e325b2192b | ||
|
|
86094bc6d2 | ||
|
|
f2b2b1974d | ||
|
|
080c496cc3 | ||
|
|
bcfa0f9ac5 | ||
|
|
746c0d25f7 | ||
|
|
9e450ce76b | ||
|
|
77bee89ea5 | ||
|
|
dbc80183e6 | ||
|
|
ff0371faa5 | ||
|
|
ebaddb3fe4 | ||
|
|
8f3aeeb182 | ||
|
|
3157e4f986 | ||
|
|
0fe18ee5b4 | ||
|
|
265d6da331 | ||
|
|
1c063ca308 | ||
|
|
5a5340431e | ||
|
|
869a8a57e1 | ||
|
|
dd15da28b2 | ||
|
|
47778c6fe7 | ||
|
|
0d90eed270 | ||
|
|
d8147348c6 | ||
|
|
6b85141a39 | ||
|
|
0fcc379ede | ||
|
|
f22f62339f | ||
|
|
934b9d9cf1 | ||
|
|
47dd068655 | ||
|
|
382dfd797c | ||
|
|
8e66031afe | ||
|
|
eea1d56468 | ||
|
|
6edcc58b9a | ||
|
|
7a8197208c | ||
|
|
acd6e0f423 | ||
|
|
e741e0d1d6 | ||
|
|
09f28a555f | ||
|
|
a9590561d2 | ||
|
|
9700776468 | ||
|
|
cec6940aeb | ||
|
|
e082ee17e4 | ||
|
|
2327fa7e4b | ||
|
|
bd25a8ccb4 | ||
|
|
fff0c8ccd3 | ||
|
|
df6a10862e | ||
|
|
a33b7b2769 | ||
|
|
5e01db8809 | ||
|
|
32c064a1be | ||
|
|
610938b6e8 | ||
|
|
ea93e3863b | ||
|
|
ab38efd69e | ||
|
|
8dc5642599 | ||
|
|
d8c5ab7eae | ||
|
|
49e7a24e28 | ||
|
|
6f2de65b64 | ||
|
|
34603d69ab | ||
|
|
afb79391dd | ||
|
|
7f1dcf93d9 | ||
|
|
04722a7d83 | ||
|
|
fd6a319928 | ||
|
|
e91a744bd9 | ||
|
|
876b369c0d | ||
|
|
75ac76a8e5 | ||
|
|
6c4bde4ceb | ||
|
|
cfc9c655c5 | ||
|
|
d576573cf8 | ||
|
|
1559f14f11 | ||
|
|
242151eedf | ||
|
|
7cf10e90f8 | ||
|
|
5201c73d35 | ||
|
|
07f313a4a6 | ||
|
|
fa08434690 | ||
|
|
8969c58ca5 | ||
|
|
810cebfe0a | ||
|
|
d607b96021 | ||
|
|
d098930351 | ||
|
|
143e729a5e | ||
|
|
5ef4b593a2 | ||
|
|
7eb98c05eb | ||
|
|
43ec58fe85 | ||
|
|
0a5f042555 | ||
|
|
44f8c2a933 | ||
|
|
ecbdbedd96 | ||
|
|
c6a4e4c533 | ||
|
|
afb0a8af33 | ||
|
|
8d222fd0c3 | ||
|
|
9795d5655f | ||
|
|
db6c7f1a2c | ||
|
|
cfdce853a3 | ||
|
|
01b2f238c5 | ||
|
|
5a327b1d6b | ||
|
|
96b60d8ac0 | ||
|
|
e7f1d98b64 | ||
|
|
c2ddf17aa2 | ||
|
|
302d9dfad4 | ||
|
|
a83008aad3 | ||
|
|
047df5917a | ||
|
|
6b53e9f9a8 | ||
|
|
d66028a1b9 | ||
|
|
51de6756a7 | ||
|
|
74d97cc220 | ||
|
|
cf2ad6f277 | ||
|
|
b05d3fbde2 | ||
|
|
7b0db24e00 | ||
|
|
21f0094d7c | ||
|
|
99c2e76e53 | ||
|
|
50c40cb4e9 | ||
|
|
5e8b461478 | ||
|
|
73db4b365b | ||
|
|
0cf5bee09a | ||
|
|
3d54d78be8 | ||
|
|
bd537ece00 | ||
|
|
1f282693b3 | ||
|
|
e16f306e1d | ||
|
|
6b24673b6f | ||
|
|
58886dc42d | ||
|
|
8cea501748 | ||
|
|
c5171e8722 | ||
|
|
5af235a345 | ||
|
|
8133ef2036 | ||
|
|
9269e1e943 | ||
|
|
b4c82d0aac | ||
|
|
39ffc28cb4 | ||
|
|
e23488a723 | ||
|
|
ba1cb61f4d | ||
|
|
921ec8732c | ||
|
|
51a1f56011 | ||
|
|
2feb293c12 | ||
|
|
9d9f233c15 | ||
|
|
9ad948632c | ||
|
|
9b40ebe00c | ||
|
|
dab3ed5fbe | ||
|
|
34d7b18c26 | ||
|
|
dc4115d4e3 | ||
|
|
5cfd0aac1c | ||
|
|
9481cbf7e8 | ||
|
|
748f9b52aa | ||
|
|
c5ce3342e2 | ||
|
|
6c4ca046a9 | ||
|
|
256697c39e | ||
|
|
4017876fe2 | ||
|
|
9651614894 | ||
|
|
3bc2baaf9d | ||
|
|
fa05352882 | ||
|
|
849ca24ac8 | ||
|
|
84e9d225e9 | ||
|
|
7b53a43c5c | ||
|
|
e3121781d0 | ||
|
|
2c788b1b38 | ||
|
|
369606f0f1 | ||
|
|
8d4ce5e486 | ||
|
|
1b691e5887 | ||
|
|
2d7d19880f | ||
|
|
8b06af01b0 | ||
|
|
ea3899f469 | ||
|
|
adb8b6cbca | ||
|
|
252950af73 | ||
|
|
14189e1bca | ||
|
|
ed60c5dca4 | ||
|
|
c4984a7576 | ||
|
|
6a1052d95f | ||
|
|
caa3bcb02b | ||
|
|
74123d8ff3 | ||
|
|
70b858d200 | ||
|
|
3ca712a223 | ||
|
|
cf4be99093 | ||
|
|
900a55614b | ||
|
|
f76b0dc7d3 | ||
|
|
4542c96eae | ||
|
|
dd242bc1fe | ||
|
|
456a38022f | ||
|
|
34d73af8bf | ||
|
|
238b1cb925 | ||
|
|
604255bfba | ||
|
|
cfc0367c55 | ||
|
|
488804308b | ||
|
|
6fac8fe940 | ||
|
|
b53b1edd29 | ||
|
|
5a2bac9979 | ||
|
|
e7181c255b | ||
|
|
ac074ca23d | ||
|
|
b21bb3d433 | ||
|
|
0e656c8520 | ||
|
|
708ff794b0 | ||
|
|
6e34f12ac8 | ||
|
|
f4a89d62e3 | ||
|
|
a36e39205b | ||
|
|
6d45c09ea2 | ||
|
|
9fac65f31f | ||
|
|
3789674666 | ||
|
|
a22774b278 | ||
|
|
81bb8dd65f | ||
|
|
9d3796b605 | ||
|
|
ba4caf179a | ||
|
|
1febe765bf | ||
|
|
087ac992a2 | ||
|
|
3e3dfc83aa | ||
|
|
27bf6069d7 | ||
|
|
3153d196a2 | ||
|
|
8c320f729a | ||
|
|
c7fdc77ec8 | ||
|
|
dfd20ced59 | ||
|
|
5f40feb1af | ||
|
|
ae754fe334 | ||
|
|
e062db9411 | ||
|
|
023eae825d | ||
|
|
1a4baeafb6 | ||
|
|
b754972490 | ||
|
|
0c36e4e40d | ||
|
|
1f97495fdd | ||
|
|
c164c0f9e3 | ||
|
|
f0b5ec1025 | ||
|
|
b72ea2d02c | ||
|
|
238fcf1176 | ||
|
|
c354fecc6d | ||
|
|
4434d1da21 | ||
|
|
17ffc7cef9 | ||
|
|
006fd25e56 | ||
|
|
f2eafe0302 | ||
|
|
52069cc1d0 | ||
|
|
d9374f0389 | ||
|
|
19520f9688 | ||
|
|
1eda5af7df | ||
|
|
2ea8487e04 | ||
|
|
645ca570a8 | ||
|
|
07e690caf0 | ||
|
|
223de9d1aa | ||
|
|
7d7c521bce | ||
|
|
baaee9f92e | ||
|
|
3a9be7a890 | ||
|
|
74d2ee9430 | ||
|
|
856bd56cde | ||
|
|
4c91880a85 |
@@ -26,24 +26,25 @@ init:
|
||||
# - ps: iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
|
||||
|
||||
install:
|
||||
- cmd:
|
||||
# Outcomment if lua environment invalidates and needs to be reinstalled, otherwise all will run from the cache.
|
||||
# - call choco install 7zip.commandline
|
||||
# - call choco install lua51
|
||||
# - call choco install luarocks
|
||||
# - call refreshenv
|
||||
# - call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat"
|
||||
# - cmd: PATH = %PATH%;C:\ProgramData\chocolatey\lib\luarocks\luarocks-2.4.3-win32\systree\bin
|
||||
# - cmd: set LUA_PATH = %LUA_PATH%;C:\ProgramData\chocolatey\lib\luarocks\luarocks-2.4.3-win32\systree\share\lua\5.1\?.lua;C:\ProgramData\chocolatey\lib\luarocks\luarocks-2.4.3-win32\systree\share\lua\5.1\?\init.lua
|
||||
# - cmd: set LUA_CPATH = %LUA_CPATH%;C:\ProgramData\chocolatey\lib\luarocks\luarocks-2.4.3-win32\systree\lib\lua\5.1\?.dll
|
||||
# - call luarocks install luasrcdiet
|
||||
# - call luarocks install checks
|
||||
# - call luarocks install luadocumentor
|
||||
# - call luarocks install luacheck
|
||||
call choco install 7zip.commandline
|
||||
call choco install lua51
|
||||
call choco install luarocks
|
||||
call refreshenv
|
||||
call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat"
|
||||
cmd: PATH = %PATH%;C:\ProgramData\chocolatey\lib\luarocks\luarocks-2.4.3-win32\systree\bin
|
||||
cmd: set LUA_PATH = %LUA_PATH%;C:\ProgramData\chocolatey\lib\luarocks\luarocks-2.4.3-win32\systree\share\lua\5.1\?.lua;C:\ProgramData\chocolatey\lib\luarocks\luarocks-2.4.3-win32\systree\share\lua\5.1\?\init.lua
|
||||
cmd: set LUA_CPATH = %LUA_CPATH%;C:\ProgramData\chocolatey\lib\luarocks\luarocks-2.4.3-win32\systree\lib\lua\5.1\?.dll
|
||||
call luarocks install luasrcdiet
|
||||
call luarocks install checks
|
||||
call luarocks install luadocumentor
|
||||
call luarocks install luacheck
|
||||
|
||||
|
||||
#cache:
|
||||
# - C:\ProgramData\chocolatey\lib
|
||||
# - C:\ProgramData\chocolatey\bin
|
||||
cache:
|
||||
C:\ProgramData\chocolatey\lib
|
||||
C:\ProgramData\chocolatey\bin
|
||||
|
||||
|
||||
|
||||
@@ -51,8 +52,9 @@ build_script:
|
||||
- ps: |
|
||||
if( $env:appveyor_repo_branch -eq 'master' -or $env:appveyor_repo_branch -eq 'develop' )
|
||||
{
|
||||
echo "Hello World!"
|
||||
$apiUrl = 'https://ci.appveyor.com/api'
|
||||
$token = 'qts80b5kpq0ooj4x6vvw'
|
||||
$token = 'v2.6hcv3ige78kg3yvg4ge8'
|
||||
$headers = @{
|
||||
"Authorization" = "Bearer $token"
|
||||
"Content-type" = "application/json"
|
||||
@@ -65,7 +67,7 @@ build_script:
|
||||
if( $env:appveyor_repo_branch -eq 'master' -or $env:appveyor_repo_branch -eq 'develop' )
|
||||
{
|
||||
$apiUrl = 'https://ci.appveyor.com/api'
|
||||
$token = 'qts80b5kpq0ooj4x6vvw'
|
||||
$token = 'v2.6hcv3ige78kg3yvg4ge8'
|
||||
$headers = @{
|
||||
"Authorization" = "Bearer $token"
|
||||
"Content-type" = "application/json"
|
||||
|
||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -18,6 +18,8 @@ local.properties
|
||||
# External tool builders
|
||||
.externalToolBuilders/
|
||||
|
||||
# AppVeyor
|
||||
.appveyor/
|
||||
|
||||
# CDT-specific
|
||||
.cproject
|
||||
@@ -221,3 +223,8 @@ _gsdata_/
|
||||
.gitattributes
|
||||
.gitignore
|
||||
Moose Test Missions/MOOSE_Test_Template.miz
|
||||
Moose Development/Moose/.vscode/launch.json
|
||||
MooseCodeWS.code-workspace
|
||||
.gitignore
|
||||
.gitignore
|
||||
/.gitignore
|
||||
|
||||
54
.luacheckrc
Normal file
54
.luacheckrc
Normal file
@@ -0,0 +1,54 @@
|
||||
ignore = {
|
||||
"011", -- A syntax error.
|
||||
"021", -- An invalid inline option.
|
||||
"022", -- An unpaired inline push directive.
|
||||
"023", -- An unpaired inline pop directive.
|
||||
"111", -- Setting an undefined global variable.
|
||||
"112", -- Mutating an undefined global variable.
|
||||
"113", -- Accessing an undefined global variable.
|
||||
"121", -- Setting a read-only global variable.
|
||||
"122", -- Setting a read-only field of a global variable.
|
||||
"131", -- Unused implicitly defined global variable.
|
||||
"142", -- Setting an undefined field of a global variable.
|
||||
"143", -- Accessing an undefined field of a global variable.
|
||||
"211", -- Unused local variable.
|
||||
"212", -- Unused argument.
|
||||
"213", -- Unused loop variable.
|
||||
"221", -- Local variable is accessed but never set.
|
||||
"231", -- Local variable is set but never accessed.
|
||||
"232", -- An argument is set but never accessed.
|
||||
"233", -- Loop variable is set but never accessed.
|
||||
"241", -- Local variable is mutated but never accessed.
|
||||
"311", -- Value assigned to a local variable is unused.
|
||||
"312", -- Value of an argument is unused.
|
||||
"313", -- Value of a loop variable is unused.
|
||||
"314", -- Value of a field in a table literal is unused.
|
||||
"321", -- Accessing uninitialized local variable.
|
||||
"331", -- Value assigned to a local variable is mutated but never accessed.
|
||||
"341", -- Mutating uninitialized local variable.
|
||||
"411", -- Redefining a local variable.
|
||||
"412", -- Redefining an argument.
|
||||
"413", -- Redefining a loop variable.
|
||||
"421", -- Shadowing a local variable.
|
||||
"422", -- Shadowing an argument.
|
||||
"423", -- Shadowing a loop variable.
|
||||
"431", -- Shadowing an upvalue.
|
||||
"432", -- Shadowing an upvalue argument.
|
||||
"433", -- Shadowing an upvalue loop variable.
|
||||
"511", -- Unreachable code.
|
||||
"512", -- Loop can be executed at most once.
|
||||
"521", -- Unused label.
|
||||
"531", -- Left-hand side of an assignment is too short.
|
||||
"532", -- Left-hand side of an assignment is too long.
|
||||
"541", -- An empty do end block.
|
||||
"542", -- An empty if branch.
|
||||
"551", -- An empty statement.
|
||||
"561", -- Cyclomatic complexity of a function is too high.
|
||||
"571", -- A numeric for loop goes from #(expr) down to 1 or less without negative step.
|
||||
"611", -- A line consists of nothing but whitespace.
|
||||
"612", -- A line contains trailing whitespace.
|
||||
"613", -- Trailing whitespace in a string.
|
||||
"614", -- Trailing whitespace in a comment.
|
||||
"621", -- Inconsistent indentation (SPACE followed by TAB).
|
||||
"631", -- Line is too long.
|
||||
}
|
||||
0
.scannerwork/.sonar_lock
Normal file
0
.scannerwork/.sonar_lock
Normal file
6
.scannerwork/report-task.txt
Normal file
6
.scannerwork/report-task.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
projectKey=Test
|
||||
serverUrl=http://localhost:9000
|
||||
serverVersion=8.1.0.31237
|
||||
dashboardUrl=http://localhost:9000/dashboard?id=Test
|
||||
ceTaskId=AXAlUJO97YLjwz1VUDXR
|
||||
ceTaskUrl=http://localhost:9000/api/ce/task?id=AXAlUJO97YLjwz1VUDXR
|
||||
10
.vs/VSWorkspaceState.json
Normal file
10
.vs/VSWorkspaceState.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"ExpandedNodes": [
|
||||
"",
|
||||
"\\Moose Development",
|
||||
"\\Moose Development\\Moose",
|
||||
"\\Moose Development\\Moose\\Ops"
|
||||
],
|
||||
"SelectedNode": "\\Moose Development\\Moose\\Ops\\Airboss.lua",
|
||||
"PreviewInSolutionExplorer": false
|
||||
}
|
||||
BIN
.vs/slnx.sqlite
Normal file
BIN
.vs/slnx.sqlite
Normal file
Binary file not shown.
33
Moose Development/Moose/.lua-format
Normal file
33
Moose Development/Moose/.lua-format
Normal file
@@ -0,0 +1,33 @@
|
||||
# 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
|
||||
@@ -1,66 +1,67 @@
|
||||
--- **AI** -- (R2.2) - Models the process of Combat Air Patrol (CAP) for airplanes.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module AI.AI_A2A_Cap
|
||||
-- @image AI_Combat_Air_Patrol.JPG
|
||||
|
||||
--- @type AI_A2A_CAP
|
||||
-- @extends AI.AI_A2A_Patrol#AI_A2A_PATROL
|
||||
-- @extends AI.AI_Air_Patrol#AI_AIR_PATROL
|
||||
-- @extends AI.AI_Air_Engage#AI_AIR_ENGAGE
|
||||
|
||||
|
||||
--- The AI_A2A_CAP class implements the core functions to patrol a @{Zone} by an AI @{Wrapper.Group} or @{Wrapper.Group}
|
||||
--- The AI_A2A_CAP class implements the core functions to patrol a @{Zone} by an AI @{Wrapper.Group} or @{Wrapper.Group}
|
||||
-- and automatically engage any airborne enemies that are within a certain range or within a certain zone.
|
||||
--
|
||||
--
|
||||
-- 
|
||||
--
|
||||
--
|
||||
-- The AI_A2A_CAP is assigned a @{Wrapper.Group} and this must be done before the AI_A2A_CAP process can be started using the **Start** event.
|
||||
--
|
||||
--
|
||||
-- 
|
||||
--
|
||||
--
|
||||
-- The AI will fly towards the random 3D point within the 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.
|
||||
--
|
||||
--
|
||||
-- 
|
||||
--
|
||||
--
|
||||
-- During the patrol, the AI will detect enemy targets, which are reported through the **Detected** event.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
--
|
||||
-- When enemies are detected, the AI will automatically engage the enemy.
|
||||
--
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- 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.
|
||||
--
|
||||
--
|
||||
-- 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.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
--
|
||||
-- ## 1. AI_A2A_CAP constructor
|
||||
--
|
||||
--
|
||||
-- * @{#AI_A2A_CAP.New}(): Creates a new AI_A2A_CAP object.
|
||||
--
|
||||
--
|
||||
-- ## 2. AI_A2A_CAP is a FSM
|
||||
--
|
||||
--
|
||||
-- 
|
||||
--
|
||||
--
|
||||
-- ### 2.1 AI_A2A_CAP States
|
||||
--
|
||||
--
|
||||
-- * **None** ( Group ): The process is not started yet.
|
||||
-- * **Patrolling** ( Group ): The AI is patrolling the Patrol Zone.
|
||||
-- * **Engaging** ( Group ): The AI is engaging the bogeys.
|
||||
-- * **Returning** ( Group ): The AI is returning to Base..
|
||||
--
|
||||
--
|
||||
-- ### 2.2 AI_A2A_CAP Events
|
||||
--
|
||||
--
|
||||
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Start}**: Start the process.
|
||||
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Route}**: Route the AI to a new random 3D point within the Patrol Zone.
|
||||
-- * **@{#AI_A2A_CAP.Engage}**: Let the AI engage the bogeys.
|
||||
@@ -70,33 +71,64 @@
|
||||
-- * **@{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 tresholds have been reached, the AI will RTB.
|
||||
-- * **Status** ( Group ): The AI is checking status (fuel and damage). When the thresholds have been reached, the AI will RTB.
|
||||
--
|
||||
-- ## 3. Set the Range of Engagement
|
||||
--
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- An optional range can be set in meters,
|
||||
--
|
||||
-- An optional range can be set in meters,
|
||||
-- that will define when the AI will engage with the detected airborne enemy targets.
|
||||
-- The range can be beyond or smaller than the range of the Patrol Zone.
|
||||
-- The range is applied at the position of the AI.
|
||||
-- Use the method @{AI.AI_CAP#AI_A2A_CAP.SetEngageRange}() to define that range.
|
||||
--
|
||||
-- ## 4. Set the Zone of Engagement
|
||||
--
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- An optional @{Zone} can be set,
|
||||
--
|
||||
-- An optional @{Zone} can be set,
|
||||
-- that will define when the AI will engage with the detected airborne enemy targets.
|
||||
-- Use the method @{AI.AI_Cap#AI_A2A_CAP.SetEngageZone}() to define that Zone.
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- @field #AI_A2A_CAP
|
||||
AI_A2A_CAP = {
|
||||
ClassName = "AI_A2A_CAP",
|
||||
}
|
||||
|
||||
--- Creates a new AI_A2A_CAP object
|
||||
-- @param #AI_A2A_CAP self
|
||||
-- @param Wrapper.Group#GROUP AICap
|
||||
-- @param DCS#Speed EngageMinSpeed The minimum speed of the @{Wrapper.Group} in km/h when engaging a target.
|
||||
-- @param DCS#Speed EngageMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h when engaging a target.
|
||||
-- @param DCS#Altitude EngageFloorAltitude The lowest altitude in meters where to execute the engagement.
|
||||
-- @param DCS#Altitude EngageCeilingAltitude The highest altitude in meters where to execute the engagement.
|
||||
-- @param DCS#AltitudeType EngageAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to "RADIO".
|
||||
-- @param Core.Zone#ZONE_BASE PatrolZone The @{Zone} where the patrol needs to be executed.
|
||||
-- @param DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Group} in km/h.
|
||||
-- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h.
|
||||
-- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol.
|
||||
-- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol.
|
||||
-- @param DCS#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to "RADIO".
|
||||
-- @return #AI_A2A_CAP
|
||||
function AI_A2A_CAP:New2( AICap, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType, PatrolZone, PatrolMinSpeed, PatrolMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolAltType )
|
||||
|
||||
-- Multiple inheritance ... :-)
|
||||
local AI_Air = AI_AIR:New( AICap )
|
||||
local AI_Air_Patrol = AI_AIR_PATROL:New( AI_Air, AICap, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) -- #AI_AIR_PATROL
|
||||
local AI_Air_Engage = AI_AIR_ENGAGE:New( AI_Air_Patrol, AICap, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType )
|
||||
local self = BASE:Inherit( self, AI_Air_Engage ) --#AI_A2A_CAP
|
||||
|
||||
self:SetFuelThreshold( .2, 60 )
|
||||
self:SetDamageThreshold( 0.4 )
|
||||
self:SetDisengageRadius( 70000 )
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Creates a new AI_A2A_CAP object
|
||||
-- @param #AI_A2A_CAP self
|
||||
-- @param Wrapper.Group#GROUP AICap
|
||||
@@ -111,174 +143,8 @@ AI_A2A_CAP = {
|
||||
-- @return #AI_A2A_CAP
|
||||
function AI_A2A_CAP:New( AICap, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, EngageMinSpeed, EngageMaxSpeed, PatrolAltType )
|
||||
|
||||
-- Inherits from BASE
|
||||
local self = BASE:Inherit( self, AI_A2A_PATROL:New( AICap, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) ) -- #AI_A2A_CAP
|
||||
return self:New2( AICap, EngageMinSpeed, EngageMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolAltType, PatrolZone, PatrolMinSpeed, PatrolMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolAltType )
|
||||
|
||||
self.Accomplished = false
|
||||
self.Engaging = false
|
||||
|
||||
self.EngageMinSpeed = EngageMinSpeed
|
||||
self.EngageMaxSpeed = EngageMaxSpeed
|
||||
|
||||
self:AddTransition( { "Patrolling", "Engaging", "Returning", "Airborne" }, "Engage", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_A2A_CAP.
|
||||
|
||||
--- OnBefore Transition Handler for Event Engage.
|
||||
-- @function [parent=#AI_A2A_CAP] OnBeforeEngage
|
||||
-- @param #AI_A2A_CAP self
|
||||
-- @param Wrapper.Group#GROUP AICap The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event Engage.
|
||||
-- @function [parent=#AI_A2A_CAP] OnAfterEngage
|
||||
-- @param #AI_A2A_CAP self
|
||||
-- @param Wrapper.Group#GROUP AICap The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event Engage.
|
||||
-- @function [parent=#AI_A2A_CAP] Engage
|
||||
-- @param #AI_A2A_CAP self
|
||||
|
||||
--- Asynchronous Event Trigger for Event Engage.
|
||||
-- @function [parent=#AI_A2A_CAP] __Engage
|
||||
-- @param #AI_A2A_CAP self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
|
||||
--- OnLeave Transition Handler for State Engaging.
|
||||
-- @function [parent=#AI_A2A_CAP] OnLeaveEngaging
|
||||
-- @param #AI_A2A_CAP self
|
||||
-- @param Wrapper.Group#GROUP AICap The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnEnter Transition Handler for State Engaging.
|
||||
-- @function [parent=#AI_A2A_CAP] OnEnterEngaging
|
||||
-- @param #AI_A2A_CAP self
|
||||
-- @param Wrapper.Group#GROUP AICap The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
self:AddTransition( "Engaging", "Fired", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_A2A_CAP.
|
||||
|
||||
--- OnBefore Transition Handler for Event Fired.
|
||||
-- @function [parent=#AI_A2A_CAP] OnBeforeFired
|
||||
-- @param #AI_A2A_CAP self
|
||||
-- @param Wrapper.Group#GROUP AICap The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event Fired.
|
||||
-- @function [parent=#AI_A2A_CAP] OnAfterFired
|
||||
-- @param #AI_A2A_CAP self
|
||||
-- @param Wrapper.Group#GROUP AICap The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event Fired.
|
||||
-- @function [parent=#AI_A2A_CAP] Fired
|
||||
-- @param #AI_A2A_CAP self
|
||||
|
||||
--- Asynchronous Event Trigger for Event Fired.
|
||||
-- @function [parent=#AI_A2A_CAP] __Fired
|
||||
-- @param #AI_A2A_CAP self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
|
||||
self:AddTransition( "*", "Destroy", "*" ) -- FSM_CONTROLLABLE Transition for type #AI_A2A_CAP.
|
||||
|
||||
--- OnBefore Transition Handler for Event Destroy.
|
||||
-- @function [parent=#AI_A2A_CAP] OnBeforeDestroy
|
||||
-- @param #AI_A2A_CAP self
|
||||
-- @param Wrapper.Group#GROUP AICap The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event Destroy.
|
||||
-- @function [parent=#AI_A2A_CAP] OnAfterDestroy
|
||||
-- @param #AI_A2A_CAP self
|
||||
-- @param Wrapper.Group#GROUP AICap The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event Destroy.
|
||||
-- @function [parent=#AI_A2A_CAP] Destroy
|
||||
-- @param #AI_A2A_CAP self
|
||||
|
||||
--- Asynchronous Event Trigger for Event Destroy.
|
||||
-- @function [parent=#AI_A2A_CAP] __Destroy
|
||||
-- @param #AI_A2A_CAP self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
|
||||
|
||||
self:AddTransition( "Engaging", "Abort", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_A2A_CAP.
|
||||
|
||||
--- OnBefore Transition Handler for Event Abort.
|
||||
-- @function [parent=#AI_A2A_CAP] OnBeforeAbort
|
||||
-- @param #AI_A2A_CAP self
|
||||
-- @param Wrapper.Group#GROUP AICap The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event Abort.
|
||||
-- @function [parent=#AI_A2A_CAP] OnAfterAbort
|
||||
-- @param #AI_A2A_CAP self
|
||||
-- @param Wrapper.Group#GROUP AICap The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event Abort.
|
||||
-- @function [parent=#AI_A2A_CAP] Abort
|
||||
-- @param #AI_A2A_CAP self
|
||||
|
||||
--- Asynchronous Event Trigger for Event Abort.
|
||||
-- @function [parent=#AI_A2A_CAP] __Abort
|
||||
-- @param #AI_A2A_CAP self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
|
||||
self:AddTransition( "Engaging", "Accomplish", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_A2A_CAP.
|
||||
|
||||
--- OnBefore Transition Handler for Event Accomplish.
|
||||
-- @function [parent=#AI_A2A_CAP] OnBeforeAccomplish
|
||||
-- @param #AI_A2A_CAP self
|
||||
-- @param Wrapper.Group#GROUP AICap The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event Accomplish.
|
||||
-- @function [parent=#AI_A2A_CAP] OnAfterAccomplish
|
||||
-- @param #AI_A2A_CAP self
|
||||
-- @param Wrapper.Group#GROUP AICap The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event Accomplish.
|
||||
-- @function [parent=#AI_A2A_CAP] Accomplish
|
||||
-- @param #AI_A2A_CAP self
|
||||
|
||||
--- Asynchronous Event Trigger for Event Accomplish.
|
||||
-- @function [parent=#AI_A2A_CAP] __Accomplish
|
||||
-- @param #AI_A2A_CAP self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- onafter State Transition for Event Patrol.
|
||||
@@ -289,199 +155,58 @@ end
|
||||
-- @param #string To The To State string.
|
||||
function AI_A2A_CAP:onafterStart( AICap, From, Event, To )
|
||||
|
||||
self:GetParent( self ).onafterStart( self, AICap, From, Event, To )
|
||||
self:GetParent( self, AI_A2A_CAP ).onafterStart( self, AICap, From, Event, To )
|
||||
AICap:HandleEvent( EVENTS.Takeoff, nil, self )
|
||||
|
||||
end
|
||||
|
||||
--- Set the Engage Zone which defines where the AI will engage bogies.
|
||||
--- Set the Engage Zone which defines where the AI will engage bogies.
|
||||
-- @param #AI_A2A_CAP self
|
||||
-- @param Core.Zone#ZONE EngageZone The zone where the AI is performing CAP.
|
||||
-- @return #AI_A2A_CAP self
|
||||
function AI_A2A_CAP:SetEngageZone( EngageZone )
|
||||
self:F2()
|
||||
|
||||
if EngageZone then
|
||||
if EngageZone then
|
||||
self.EngageZone = EngageZone
|
||||
else
|
||||
self.EngageZone = nil
|
||||
end
|
||||
end
|
||||
|
||||
--- Set the Engage Range when the AI will engage with airborne enemies.
|
||||
--- Set the Engage Range when the AI will engage with airborne enemies.
|
||||
-- @param #AI_A2A_CAP self
|
||||
-- @param #number EngageRange The Engage Range.
|
||||
-- @return #AI_A2A_CAP self
|
||||
function AI_A2A_CAP:SetEngageRange( EngageRange )
|
||||
self:F2()
|
||||
|
||||
if EngageRange then
|
||||
if EngageRange then
|
||||
self.EngageRange = EngageRange
|
||||
else
|
||||
self.EngageRange = nil
|
||||
end
|
||||
end
|
||||
|
||||
--- onafter State Transition for Event Patrol.
|
||||
--- Evaluate the attack and create an AttackUnitTask list.
|
||||
-- @param #AI_A2A_CAP self
|
||||
-- @param Wrapper.Group#GROUP AICap The AI Group managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
function AI_A2A_CAP:onafterPatrol( AICap, From, Event, To )
|
||||
-- @param Core.Set#SET_UNIT AttackSetUnit The set of units to attack.
|
||||
-- @param Wrappper.Group#GROUP DefenderGroup The group of defenders.
|
||||
-- @param #number EngageAltitude The altitude to engage the targets.
|
||||
-- @return #AI_A2A_CAP self
|
||||
function AI_A2A_CAP:CreateAttackUnitTasks( AttackSetUnit, DefenderGroup, EngageAltitude )
|
||||
|
||||
-- Call the parent Start event handler
|
||||
self:GetParent(self).onafterPatrol( self, AICap, From, Event, To )
|
||||
self:HandleEvent( EVENTS.Dead )
|
||||
local AttackUnitTasks = {}
|
||||
|
||||
end
|
||||
|
||||
-- todo: need to fix this global function
|
||||
|
||||
--- @param Wrapper.Group#GROUP AICap
|
||||
function AI_A2A_CAP.AttackRoute( AICap, Fsm )
|
||||
|
||||
AICap:F( { "AI_A2A_CAP.AttackRoute:", AICap:GetName() } )
|
||||
|
||||
if AICap:IsAlive() then
|
||||
Fsm:__Engage( 0.5 )
|
||||
end
|
||||
end
|
||||
|
||||
--- @param #AI_A2A_CAP self
|
||||
-- @param Wrapper.Group#GROUP AICap The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
function AI_A2A_CAP:onbeforeEngage( AICap, From, Event, To )
|
||||
|
||||
if self.Accomplished == true then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
--- @param #AI_A2A_CAP self
|
||||
-- @param Wrapper.Group#GROUP AICap The AI Group managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
function AI_A2A_CAP:onafterAbort( AICap, From, Event, To )
|
||||
AICap:ClearTasks()
|
||||
self:__Route( 0.5 )
|
||||
end
|
||||
|
||||
|
||||
--- @param #AI_A2A_CAP self
|
||||
-- @param Wrapper.Group#GROUP AICap The AICap Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
function AI_A2A_CAP:onafterEngage( AICap, From, Event, To, AttackSetUnit )
|
||||
|
||||
self:F( { AICap, From, Event, To, AttackSetUnit} )
|
||||
|
||||
self.AttackSetUnit = AttackSetUnit or self.AttackSetUnit -- Core.Set#SET_UNIT
|
||||
|
||||
local FirstAttackUnit = self.AttackSetUnit:GetFirst() -- Wrapper.Unit#UNIT
|
||||
|
||||
if FirstAttackUnit and FirstAttackUnit:IsAlive() then -- If there is no attacker anymore, stop the engagement.
|
||||
|
||||
if AICap:IsAlive() then
|
||||
|
||||
local EngageRoute = {}
|
||||
|
||||
--- Calculate the target route point.
|
||||
local CurrentCoord = AICap:GetCoordinate()
|
||||
local ToTargetCoord = self.AttackSetUnit:GetFirst():GetCoordinate()
|
||||
local ToTargetSpeed = math.random( self.EngageMinSpeed, self.EngageMaxSpeed )
|
||||
local ToInterceptAngle = CurrentCoord:GetAngleDegrees( CurrentCoord:GetDirectionVec3( ToTargetCoord ) )
|
||||
|
||||
--- Create a route point of type air.
|
||||
local ToPatrolRoutePoint = CurrentCoord:Translate( 5000, ToInterceptAngle ):WaypointAir(
|
||||
self.PatrolAltType,
|
||||
POINT_VEC3.RoutePointType.TurningPoint,
|
||||
POINT_VEC3.RoutePointAction.TurningPoint,
|
||||
ToTargetSpeed,
|
||||
true
|
||||
)
|
||||
|
||||
self:F( { Angle = ToInterceptAngle, ToTargetSpeed = ToTargetSpeed } )
|
||||
self:T2( { self.MinSpeed, self.MaxSpeed, ToTargetSpeed } )
|
||||
|
||||
EngageRoute[#EngageRoute+1] = ToPatrolRoutePoint
|
||||
EngageRoute[#EngageRoute+1] = ToPatrolRoutePoint
|
||||
|
||||
local AttackTasks = {}
|
||||
|
||||
for AttackUnitID, AttackUnit in pairs( self.AttackSetUnit:GetSet() ) do
|
||||
local AttackUnit = AttackUnit -- Wrapper.Unit#UNIT
|
||||
self:T( { "Attacking Unit:", AttackUnit:GetName(), AttackUnit:IsAlive(), AttackUnit:IsAir() } )
|
||||
if AttackUnit:IsAlive() and AttackUnit:IsAir() then
|
||||
AttackTasks[#AttackTasks+1] = AICap:TaskAttackUnit( AttackUnit )
|
||||
end
|
||||
end
|
||||
|
||||
if #AttackTasks == 0 then
|
||||
self:E("No targets found -> Going back to Patrolling")
|
||||
self:__Abort( 0.5 )
|
||||
else
|
||||
AICap:OptionROEOpenFire()
|
||||
AICap:OptionROTEvadeFire()
|
||||
|
||||
AttackTasks[#AttackTasks+1] = AICap:TaskFunction( "AI_A2A_CAP.AttackRoute", self )
|
||||
EngageRoute[#EngageRoute].task = AICap:TaskCombo( AttackTasks )
|
||||
end
|
||||
|
||||
AICap:Route( EngageRoute, 0.5 )
|
||||
for AttackUnitID, AttackUnit in pairs( self.AttackSetUnit:GetSet() ) do
|
||||
local AttackUnit = AttackUnit -- Wrapper.Unit#UNIT
|
||||
if AttackUnit and AttackUnit:IsAlive() and AttackUnit:IsAir() then
|
||||
-- TODO: Add coalition check? Only attack units of if AttackUnit:GetCoalition()~=AICap:GetCoalition()
|
||||
-- Maybe the detected set also contains
|
||||
self:T( { "Attacking Task:", AttackUnit:GetName(), AttackUnit:IsAlive(), AttackUnit:IsAir() } )
|
||||
AttackUnitTasks[#AttackUnitTasks+1] = DefenderGroup:TaskAttackUnit( AttackUnit )
|
||||
end
|
||||
else
|
||||
self:E("No targets found -> Going back to Patrolling")
|
||||
self:__Abort( 0.5 )
|
||||
end
|
||||
end
|
||||
|
||||
--- @param #AI_A2A_CAP self
|
||||
-- @param Wrapper.Group#GROUP AICap The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
function AI_A2A_CAP:onafterAccomplish( AICap, From, Event, To )
|
||||
self.Accomplished = true
|
||||
self:SetDetectionOff()
|
||||
end
|
||||
|
||||
--- @param #AI_A2A_CAP self
|
||||
-- @param Wrapper.Group#GROUP AICap The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function AI_A2A_CAP:onafterDestroy( AICap, From, Event, To, EventData )
|
||||
|
||||
if EventData.IniUnit then
|
||||
self.AttackUnits[EventData.IniUnit] = nil
|
||||
end
|
||||
end
|
||||
|
||||
--- @param #AI_A2A_CAP self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function AI_A2A_CAP:OnEventDead( EventData )
|
||||
self:F( { "EventDead", EventData } )
|
||||
|
||||
if EventData.IniDCSUnit then
|
||||
if self.AttackUnits and self.AttackUnits[EventData.IniUnit] then
|
||||
self:__Destroy( 1, EventData )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- @param Wrapper.Group#GROUP AICap
|
||||
function AI_A2A_CAP.Resume( AICap, Fsm )
|
||||
|
||||
AICap:I( { "AI_A2A_CAP.Resume:", AICap:GetName() } )
|
||||
if AICap:IsAlive() then
|
||||
Fsm:__Reset( 1 )
|
||||
Fsm:__Route( 5 )
|
||||
end
|
||||
|
||||
|
||||
return AttackUnitTasks
|
||||
end
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,12 +1,12 @@
|
||||
--- **AI** -- (R2.2) - Models the process of Ground Controlled Interception (GCI) for airplanes.
|
||||
--
|
||||
-- This is a class used in the @{AI_A2A_Dispatcher}.
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module AI.AI_A2A_GCI
|
||||
-- @image AI_Ground_Control_Intercept.JPG
|
||||
@@ -18,52 +18,52 @@
|
||||
|
||||
|
||||
--- Implements the core functions to intercept intruders. Use the Engage trigger to intercept intruders.
|
||||
--
|
||||
--
|
||||
-- 
|
||||
--
|
||||
--
|
||||
-- The AI_A2A_GCI is assigned a @{Wrapper.Group} and this must be done before the AI_A2A_GCI process can be started using the **Start** event.
|
||||
--
|
||||
--
|
||||
-- 
|
||||
--
|
||||
--
|
||||
-- The AI will fly towards the random 3D point within the 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.
|
||||
--
|
||||
--
|
||||
-- 
|
||||
--
|
||||
--
|
||||
-- During the patrol, the AI will detect enemy targets, which are reported through the **Detected** event.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
--
|
||||
-- When enemies are detected, the AI will automatically engage the enemy.
|
||||
--
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- 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.
|
||||
--
|
||||
--
|
||||
-- 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.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
--
|
||||
-- ## 1. AI_A2A_GCI constructor
|
||||
--
|
||||
--
|
||||
-- * @{#AI_A2A_GCI.New}(): Creates a new AI_A2A_GCI object.
|
||||
--
|
||||
--
|
||||
-- ## 2. AI_A2A_GCI is a FSM
|
||||
--
|
||||
--
|
||||
-- 
|
||||
--
|
||||
--
|
||||
-- ### 2.1 AI_A2A_GCI States
|
||||
--
|
||||
--
|
||||
-- * **None** ( Group ): The process is not started yet.
|
||||
-- * **Patrolling** ( Group ): The AI is patrolling the Patrol Zone.
|
||||
-- * **Engaging** ( Group ): The AI is engaging the bogeys.
|
||||
-- * **Returning** ( Group ): The AI is returning to Base..
|
||||
--
|
||||
--
|
||||
-- ### 2.2 AI_A2A_GCI Events
|
||||
--
|
||||
--
|
||||
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Start}**: Start the process.
|
||||
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Route}**: Route the AI to a new random 3D point within the Patrol Zone.
|
||||
-- * **@{#AI_A2A_GCI.Engage}**: Let the AI engage the bogeys.
|
||||
@@ -73,28 +73,28 @@
|
||||
-- * **@{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 tresholds have been reached, the AI will RTB.
|
||||
-- * **Status** ( Group ): The AI is checking status (fuel and damage). When the thresholds have been reached, the AI will RTB.
|
||||
--
|
||||
-- ## 3. Set the Range of Engagement
|
||||
--
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- An optional range can be set in meters,
|
||||
--
|
||||
-- An optional range can be set in meters,
|
||||
-- that will define when the AI will engage with the detected airborne enemy targets.
|
||||
-- The range can be beyond or smaller than the range of the Patrol Zone.
|
||||
-- The range is applied at the position of the AI.
|
||||
-- Use the method @{AI.AI_GCI#AI_A2A_GCI.SetEngageRange}() to define that range.
|
||||
--
|
||||
-- ## 4. Set the Zone of Engagement
|
||||
--
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- An optional @{Zone} can be set,
|
||||
--
|
||||
-- An optional @{Zone} can be set,
|
||||
-- that will define when the AI will engage with the detected airborne enemy targets.
|
||||
-- Use the method @{AI.AI_Cap#AI_A2A_GCI.SetEngageZone}() to define that Zone.
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- @field #AI_A2A_GCI
|
||||
AI_A2A_GCI = {
|
||||
ClassName = "AI_A2A_GCI",
|
||||
@@ -105,183 +105,39 @@ AI_A2A_GCI = {
|
||||
--- Creates a new AI_A2A_GCI object
|
||||
-- @param #AI_A2A_GCI self
|
||||
-- @param Wrapper.Group#GROUP AIIntercept
|
||||
-- @param DCS#Speed EngageMinSpeed The minimum speed of the @{Wrapper.Group} in km/h when engaging a target.
|
||||
-- @param DCS#Speed EngageMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h when engaging a target.
|
||||
-- @param DCS#Altitude EngageFloorAltitude The lowest altitude in meters where to execute the engagement.
|
||||
-- @param DCS#Altitude EngageCeilingAltitude The highest altitude in meters where to execute the engagement.
|
||||
-- @param DCS#AltitudeType EngageAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to "RADIO".
|
||||
-- @return #AI_A2A_GCI
|
||||
function AI_A2A_GCI:New( AIIntercept, EngageMinSpeed, EngageMaxSpeed )
|
||||
function AI_A2A_GCI:New2( AIIntercept, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType )
|
||||
|
||||
-- Inherits from BASE
|
||||
local self = BASE:Inherit( self, AI_A2A:New( AIIntercept ) ) -- #AI_A2A_GCI
|
||||
local AI_Air = AI_AIR:New( AIIntercept )
|
||||
local AI_Air_Engage = AI_AIR_ENGAGE:New( AI_Air, AIIntercept, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType )
|
||||
local self = BASE:Inherit( self, AI_Air_Engage ) -- #AI_A2A_GCI
|
||||
|
||||
self.Accomplished = false
|
||||
self.Engaging = false
|
||||
|
||||
self.EngageMinSpeed = EngageMinSpeed
|
||||
self.EngageMaxSpeed = EngageMaxSpeed
|
||||
self.PatrolMinSpeed = EngageMinSpeed
|
||||
self.PatrolMaxSpeed = EngageMaxSpeed
|
||||
|
||||
self.PatrolAltType = "RADIO"
|
||||
|
||||
self:AddTransition( { "Started", "Engaging", "Returning", "Airborne" }, "Engage", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_A2A_GCI.
|
||||
|
||||
--- OnBefore Transition Handler for Event Engage.
|
||||
-- @function [parent=#AI_A2A_GCI] OnBeforeEngage
|
||||
-- @param #AI_A2A_GCI self
|
||||
-- @param Wrapper.Group#GROUP AIIntercept The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event Engage.
|
||||
-- @function [parent=#AI_A2A_GCI] OnAfterEngage
|
||||
-- @param #AI_A2A_GCI self
|
||||
-- @param Wrapper.Group#GROUP AIIntercept The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event Engage.
|
||||
-- @function [parent=#AI_A2A_GCI] Engage
|
||||
-- @param #AI_A2A_GCI self
|
||||
|
||||
--- Asynchronous Event Trigger for Event Engage.
|
||||
-- @function [parent=#AI_A2A_GCI] __Engage
|
||||
-- @param #AI_A2A_GCI self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
|
||||
--- OnLeave Transition Handler for State Engaging.
|
||||
-- @function [parent=#AI_A2A_GCI] OnLeaveEngaging
|
||||
-- @param #AI_A2A_GCI self
|
||||
-- @param Wrapper.Group#GROUP AIIntercept The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnEnter Transition Handler for State Engaging.
|
||||
-- @function [parent=#AI_A2A_GCI] OnEnterEngaging
|
||||
-- @param #AI_A2A_GCI self
|
||||
-- @param Wrapper.Group#GROUP AIIntercept The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
self:AddTransition( "Engaging", "Fired", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_A2A_GCI.
|
||||
|
||||
--- OnBefore Transition Handler for Event Fired.
|
||||
-- @function [parent=#AI_A2A_GCI] OnBeforeFired
|
||||
-- @param #AI_A2A_GCI self
|
||||
-- @param Wrapper.Group#GROUP AIIntercept The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event Fired.
|
||||
-- @function [parent=#AI_A2A_GCI] OnAfterFired
|
||||
-- @param #AI_A2A_GCI self
|
||||
-- @param Wrapper.Group#GROUP AIIntercept The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event Fired.
|
||||
-- @function [parent=#AI_A2A_GCI] Fired
|
||||
-- @param #AI_A2A_GCI self
|
||||
|
||||
--- Asynchronous Event Trigger for Event Fired.
|
||||
-- @function [parent=#AI_A2A_GCI] __Fired
|
||||
-- @param #AI_A2A_GCI self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
|
||||
self:AddTransition( "*", "Destroy", "*" ) -- FSM_CONTROLLABLE Transition for type #AI_A2A_GCI.
|
||||
|
||||
--- OnBefore Transition Handler for Event Destroy.
|
||||
-- @function [parent=#AI_A2A_GCI] OnBeforeDestroy
|
||||
-- @param #AI_A2A_GCI self
|
||||
-- @param Wrapper.Group#GROUP AIIntercept The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event Destroy.
|
||||
-- @function [parent=#AI_A2A_GCI] OnAfterDestroy
|
||||
-- @param #AI_A2A_GCI self
|
||||
-- @param Wrapper.Group#GROUP AIIntercept The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event Destroy.
|
||||
-- @function [parent=#AI_A2A_GCI] Destroy
|
||||
-- @param #AI_A2A_GCI self
|
||||
|
||||
--- Asynchronous Event Trigger for Event Destroy.
|
||||
-- @function [parent=#AI_A2A_GCI] __Destroy
|
||||
-- @param #AI_A2A_GCI self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
|
||||
|
||||
self:AddTransition( "Engaging", "Abort", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_A2A_GCI.
|
||||
|
||||
--- OnBefore Transition Handler for Event Abort.
|
||||
-- @function [parent=#AI_A2A_GCI] OnBeforeAbort
|
||||
-- @param #AI_A2A_GCI self
|
||||
-- @param Wrapper.Group#GROUP AIIntercept The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event Abort.
|
||||
-- @function [parent=#AI_A2A_GCI] OnAfterAbort
|
||||
-- @param #AI_A2A_GCI self
|
||||
-- @param Wrapper.Group#GROUP AIIntercept The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event Abort.
|
||||
-- @function [parent=#AI_A2A_GCI] Abort
|
||||
-- @param #AI_A2A_GCI self
|
||||
|
||||
--- Asynchronous Event Trigger for Event Abort.
|
||||
-- @function [parent=#AI_A2A_GCI] __Abort
|
||||
-- @param #AI_A2A_GCI self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
|
||||
self:AddTransition( "Engaging", "Accomplish", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_A2A_GCI.
|
||||
|
||||
--- OnBefore Transition Handler for Event Accomplish.
|
||||
-- @function [parent=#AI_A2A_GCI] OnBeforeAccomplish
|
||||
-- @param #AI_A2A_GCI self
|
||||
-- @param Wrapper.Group#GROUP AIIntercept The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event Accomplish.
|
||||
-- @function [parent=#AI_A2A_GCI] OnAfterAccomplish
|
||||
-- @param #AI_A2A_GCI self
|
||||
-- @param Wrapper.Group#GROUP AIIntercept The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event Accomplish.
|
||||
-- @function [parent=#AI_A2A_GCI] Accomplish
|
||||
-- @param #AI_A2A_GCI self
|
||||
|
||||
--- Asynchronous Event Trigger for Event Accomplish.
|
||||
-- @function [parent=#AI_A2A_GCI] __Accomplish
|
||||
-- @param #AI_A2A_GCI self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
self:SetFuelThreshold( .2, 60 )
|
||||
self:SetDamageThreshold( 0.4 )
|
||||
self:SetDisengageRadius( 70000 )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Creates a new AI_A2A_GCI object
|
||||
-- @param #AI_A2A_GCI self
|
||||
-- @param Wrapper.Group#GROUP AIIntercept
|
||||
-- @param DCS#Speed EngageMinSpeed The minimum speed of the @{Wrapper.Group} in km/h when engaging a target.
|
||||
-- @param DCS#Speed EngageMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h when engaging a target.
|
||||
-- @param DCS#Altitude EngageFloorAltitude The lowest altitude in meters where to execute the engagement.
|
||||
-- @param DCS#Altitude EngageCeilingAltitude The highest altitude in meters where to execute the engagement.
|
||||
-- @param DCS#AltitudeType EngageAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to "RADIO".
|
||||
-- @return #AI_A2A_GCI
|
||||
function AI_A2A_GCI:New( AIIntercept, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType )
|
||||
|
||||
return self:New2( AIIntercept, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType )
|
||||
end
|
||||
|
||||
--- onafter State Transition for Event Patrol.
|
||||
-- @param #AI_A2A_GCI self
|
||||
-- @param Wrapper.Group#GROUP AIIntercept The AI Group managed by the FSM.
|
||||
@@ -290,173 +146,29 @@ end
|
||||
-- @param #string To The To State string.
|
||||
function AI_A2A_GCI:onafterStart( AIIntercept, From, Event, To )
|
||||
|
||||
self:GetParent( self ).onafterStart( self, AIIntercept, From, Event, To )
|
||||
AIIntercept:HandleEvent( EVENTS.Takeoff, nil, self )
|
||||
|
||||
self:GetParent( self, AI_A2A_GCI ).onafterStart( self, AIIntercept, From, Event, To )
|
||||
end
|
||||
|
||||
|
||||
|
||||
--- onafter State Transition for Event Patrol.
|
||||
--- Evaluate the attack and create an AttackUnitTask list.
|
||||
-- @param #AI_A2A_GCI self
|
||||
-- @param Wrapper.Group#GROUP AIIntercept The AI Group managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
function AI_A2A_GCI:onafterEngage( AIIntercept, From, Event, To )
|
||||
-- @param Core.Set#SET_UNIT AttackSetUnit The set of units to attack.
|
||||
-- @param Wrappper.Group#GROUP DefenderGroup The group of defenders.
|
||||
-- @param #number EngageAltitude The altitude to engage the targets.
|
||||
-- @return #AI_A2A_GCI self
|
||||
function AI_A2A_GCI:CreateAttackUnitTasks( AttackSetUnit, DefenderGroup, EngageAltitude )
|
||||
|
||||
self:HandleEvent( EVENTS.Dead )
|
||||
local AttackUnitTasks = {}
|
||||
|
||||
end
|
||||
|
||||
-- todo: need to fix this global function
|
||||
|
||||
--- @param Wrapper.Group#GROUP AIControllable
|
||||
function AI_A2A_GCI.InterceptRoute( AIIntercept, Fsm )
|
||||
|
||||
AIIntercept:F( { "AI_A2A_GCI.InterceptRoute:", AIIntercept:GetName() } )
|
||||
|
||||
if AIIntercept:IsAlive() then
|
||||
Fsm:__Engage( 0.5 )
|
||||
|
||||
--local Task = AIIntercept:TaskOrbitCircle( 4000, 400 )
|
||||
--AIIntercept:SetTask( Task )
|
||||
end
|
||||
end
|
||||
|
||||
--- @param #AI_A2A_GCI self
|
||||
-- @param Wrapper.Group#GROUP AIIntercept The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
function AI_A2A_GCI:onbeforeEngage( AIIntercept, From, Event, To )
|
||||
|
||||
if self.Accomplished == true then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
--- @param #AI_A2A_GCI self
|
||||
-- @param Wrapper.Group#GROUP AIIntercept The AI Group managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
function AI_A2A_GCI:onafterAbort( AIIntercept, From, Event, To )
|
||||
AIIntercept:ClearTasks()
|
||||
self:Return()
|
||||
self:__RTB( 0.5 )
|
||||
end
|
||||
|
||||
|
||||
--- @param #AI_A2A_GCI self
|
||||
-- @param Wrapper.Group#GROUP AIIntercept The GroupGroup managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
function AI_A2A_GCI:onafterEngage( AIIntercept, From, Event, To, AttackSetUnit )
|
||||
|
||||
self:F( { AIIntercept, From, Event, To, AttackSetUnit} )
|
||||
|
||||
self.AttackSetUnit = AttackSetUnit or self.AttackSetUnit -- Core.Set#SET_UNIT
|
||||
|
||||
local FirstAttackUnit = self.AttackSetUnit:GetFirst()
|
||||
|
||||
if FirstAttackUnit and FirstAttackUnit:IsAlive() then
|
||||
|
||||
if AIIntercept:IsAlive() then
|
||||
|
||||
local EngageRoute = {}
|
||||
|
||||
local CurrentCoord = AIIntercept:GetCoordinate()
|
||||
|
||||
--- Calculate the target route point.
|
||||
|
||||
local CurrentCoord = AIIntercept:GetCoordinate()
|
||||
|
||||
local ToTargetCoord = self.AttackSetUnit:GetFirst():GetCoordinate()
|
||||
self:SetTargetDistance( ToTargetCoord ) -- For RTB status check
|
||||
|
||||
local ToTargetSpeed = math.random( self.EngageMinSpeed, self.EngageMaxSpeed )
|
||||
local ToInterceptAngle = CurrentCoord:GetAngleDegrees( CurrentCoord:GetDirectionVec3( ToTargetCoord ) )
|
||||
|
||||
--- Create a route point of type air.
|
||||
local ToPatrolRoutePoint = CurrentCoord:Translate( 15000, ToInterceptAngle ):WaypointAir(
|
||||
self.PatrolAltType,
|
||||
POINT_VEC3.RoutePointType.TurningPoint,
|
||||
POINT_VEC3.RoutePointAction.TurningPoint,
|
||||
ToTargetSpeed,
|
||||
true
|
||||
)
|
||||
|
||||
self:F( { Angle = ToInterceptAngle, ToTargetSpeed = ToTargetSpeed } )
|
||||
self:F( { self.EngageMinSpeed, self.EngageMaxSpeed, ToTargetSpeed } )
|
||||
|
||||
EngageRoute[#EngageRoute+1] = ToPatrolRoutePoint
|
||||
EngageRoute[#EngageRoute+1] = ToPatrolRoutePoint
|
||||
|
||||
local AttackTasks = {}
|
||||
|
||||
for AttackUnitID, AttackUnit in pairs( self.AttackSetUnit:GetSet() ) do
|
||||
local AttackUnit = AttackUnit -- Wrapper.Unit#UNIT
|
||||
if AttackUnit:IsAlive() and AttackUnit:IsAir() then
|
||||
self:T( { "Intercepting Unit:", AttackUnit:GetName(), AttackUnit:IsAlive(), AttackUnit:IsAir() } )
|
||||
AttackTasks[#AttackTasks+1] = AIIntercept:TaskAttackUnit( AttackUnit )
|
||||
end
|
||||
end
|
||||
|
||||
if #AttackTasks == 0 then
|
||||
self:E("No targets found -> Going RTB")
|
||||
self:Return()
|
||||
self:__RTB( 0.5 )
|
||||
else
|
||||
AIIntercept:OptionROEOpenFire()
|
||||
AIIntercept:OptionROTEvadeFire()
|
||||
|
||||
AttackTasks[#AttackTasks+1] = AIIntercept:TaskFunction( "AI_A2A_GCI.InterceptRoute", self )
|
||||
EngageRoute[#EngageRoute].task = AIIntercept:TaskCombo( AttackTasks )
|
||||
end
|
||||
|
||||
AIIntercept:Route( EngageRoute, 0.5 )
|
||||
|
||||
for AttackUnitID, AttackUnit in pairs( self.AttackSetUnit:GetSet() ) do
|
||||
local AttackUnit = AttackUnit -- Wrapper.Unit#UNIT
|
||||
self:T( { "Attacking Unit:", AttackUnit:GetName(), AttackUnit:IsAlive(), AttackUnit:IsAir() } )
|
||||
if AttackUnit:IsAlive() and AttackUnit:IsAir() then
|
||||
-- TODO: Add coalition check? Only attack units of if AttackUnit:GetCoalition()~=AICap:GetCoalition()
|
||||
-- Maybe the detected set also contains
|
||||
AttackUnitTasks[#AttackUnitTasks+1] = DefenderGroup:TaskAttackUnit( AttackUnit )
|
||||
end
|
||||
else
|
||||
self:E("No targets found -> Going RTB")
|
||||
self:Return()
|
||||
self:__RTB( 0.5 )
|
||||
end
|
||||
end
|
||||
|
||||
--- @param #AI_A2A_GCI self
|
||||
-- @param Wrapper.Group#GROUP AIIntercept The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
function AI_A2A_GCI:onafterAccomplish( AIIntercept, From, Event, To )
|
||||
self.Accomplished = true
|
||||
self:SetDetectionOff()
|
||||
end
|
||||
|
||||
--- @param #AI_A2A_GCI self
|
||||
-- @param Wrapper.Group#GROUP AIIntercept The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function AI_A2A_GCI:onafterDestroy( AIIntercept, From, Event, To, EventData )
|
||||
|
||||
if EventData.IniUnit then
|
||||
self.AttackUnits[EventData.IniUnit] = nil
|
||||
end
|
||||
end
|
||||
|
||||
--- @param #AI_A2A_GCI self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function AI_A2A_GCI:OnEventDead( EventData )
|
||||
self:F( { "EventDead", EventData } )
|
||||
|
||||
if EventData.IniDCSUnit then
|
||||
if self.AttackUnits and self.AttackUnits[EventData.IniUnit] then
|
||||
self:__Destroy( 1, EventData )
|
||||
end
|
||||
end
|
||||
|
||||
return AttackUnitTasks
|
||||
end
|
||||
|
||||
@@ -39,8 +39,8 @@
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- 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.
|
||||
-- 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.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
@@ -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 tresholds have been reached, the AI will RTB.
|
||||
-- * **Status** ( Group ): The AI is checking status (fuel and damage). When the thresholds 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 treshold is calculated.
|
||||
-- When the fuel treshold 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 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 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 treshold is reached, the AI will return immediately to the home base (RTB).
|
||||
-- Therefore, when the damage threshold 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.
|
||||
--
|
||||
-- ===
|
||||
@@ -121,13 +121,13 @@ AI_A2A_PATROL = {
|
||||
|
||||
--- Creates a new AI_A2A_PATROL object
|
||||
-- @param #AI_A2A_PATROL self
|
||||
-- @param Wrapper.Group#GROUP AIPatrol
|
||||
-- @param Wrapper.Group#GROUP AIPatrol The patrol group object.
|
||||
-- @param Core.Zone#ZONE_BASE PatrolZone The @{Zone} where the patrol needs to be executed.
|
||||
-- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol.
|
||||
-- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol.
|
||||
-- @param DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Group} in km/h.
|
||||
-- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h.
|
||||
-- @param DCS#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO
|
||||
-- @param DCS#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to BARO
|
||||
-- @return #AI_A2A_PATROL self
|
||||
-- @usage
|
||||
-- -- Define a new AI_A2A_PATROL Object. This PatrolArea will patrol a Group within PatrolZone between 3000 and 6000 meters, with a variying speed between 600 and 900 km/h.
|
||||
@@ -136,8 +136,14 @@ AI_A2A_PATROL = {
|
||||
-- PatrolArea = AI_A2A_PATROL:New( PatrolZone, 3000, 6000, 600, 900 )
|
||||
function AI_A2A_PATROL:New( AIPatrol, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType )
|
||||
|
||||
-- Inherits from BASE
|
||||
local self = BASE:Inherit( self, AI_A2A:New( AIPatrol ) ) -- #AI_A2A_PATROL
|
||||
local AI_Air = AI_AIR:New( AIPatrol )
|
||||
local AI_Air_Patrol = AI_AIR_PATROL:New( AI_Air, AIPatrol, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType )
|
||||
local self = BASE:Inherit( self, AI_Air_Patrol ) -- #AI_A2A_PATROL
|
||||
|
||||
self:SetFuelThreshold( .2, 60 )
|
||||
self:SetDamageThreshold( 0.4 )
|
||||
self:SetDisengageRadius( 70000 )
|
||||
|
||||
|
||||
self.PatrolZone = PatrolZone
|
||||
self.PatrolFloorAltitude = PatrolFloorAltitude
|
||||
@@ -145,8 +151,8 @@ function AI_A2A_PATROL:New( AIPatrol, PatrolZone, PatrolFloorAltitude, PatrolCei
|
||||
self.PatrolMinSpeed = PatrolMinSpeed
|
||||
self.PatrolMaxSpeed = PatrolMaxSpeed
|
||||
|
||||
-- defafult PatrolAltType to "RADIO" if not specified
|
||||
self.PatrolAltType = PatrolAltType or "RADIO"
|
||||
-- defafult PatrolAltType to "BARO" if not specified
|
||||
self.PatrolAltType = PatrolAltType or "BARO"
|
||||
|
||||
self:AddTransition( { "Started", "Airborne", "Refuelling" }, "Patrol", "Patrolling" )
|
||||
|
||||
@@ -281,15 +287,15 @@ function AI_A2A_PATROL:onafterPatrol( AIPatrol, From, Event, To )
|
||||
end
|
||||
|
||||
|
||||
|
||||
--- @param Wrapper.Group#GROUP AIPatrol
|
||||
-- This statis method is called from the route path within the last task at the last waaypoint of the AIPatrol.
|
||||
--- This statis method is called from the route path within the last task at the last waaypoint of the AIPatrol.
|
||||
-- Note that this method is required, as triggers the next route when patrolling for the AIPatrol.
|
||||
-- @param Wrapper.Group#GROUP AIPatrol The AI group.
|
||||
-- @param #AI_A2A_PATROL Fsm The FSM.
|
||||
function AI_A2A_PATROL.PatrolRoute( AIPatrol, Fsm )
|
||||
|
||||
AIPatrol:F( { "AI_A2A_PATROL.PatrolRoute:", AIPatrol:GetName() } )
|
||||
|
||||
if AIPatrol:IsAlive() then
|
||||
if AIPatrol and AIPatrol:IsAlive() then
|
||||
Fsm:Route()
|
||||
end
|
||||
|
||||
@@ -303,7 +309,6 @@ end
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
function AI_A2A_PATROL:onafterRoute( AIPatrol, From, Event, To )
|
||||
|
||||
self:F2()
|
||||
|
||||
-- When RTB, don't allow anymore the routing.
|
||||
@@ -312,7 +317,7 @@ function AI_A2A_PATROL:onafterRoute( AIPatrol, From, Event, To )
|
||||
end
|
||||
|
||||
|
||||
if AIPatrol:IsAlive() then
|
||||
if AIPatrol and AIPatrol:IsAlive() then
|
||||
|
||||
local PatrolRoute = {}
|
||||
|
||||
@@ -320,43 +325,80 @@ function AI_A2A_PATROL:onafterRoute( AIPatrol, From, Event, To )
|
||||
|
||||
local CurrentCoord = AIPatrol:GetCoordinate()
|
||||
|
||||
local ToTargetCoord = self.PatrolZone:GetRandomPointVec2()
|
||||
ToTargetCoord:SetAlt( math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ) )
|
||||
self:SetTargetDistance( ToTargetCoord ) -- For RTB status check
|
||||
-- Random altitude.
|
||||
local altitude=math.random(self.PatrolFloorAltitude, self.PatrolCeilingAltitude)
|
||||
|
||||
-- Random speed in km/h.
|
||||
local speedkmh = math.random(self.PatrolMinSpeed, self.PatrolMaxSpeed)
|
||||
|
||||
local ToTargetSpeed = math.random( self.PatrolMinSpeed, self.PatrolMaxSpeed )
|
||||
-- First waypoint is current position.
|
||||
PatrolRoute[1]=CurrentCoord:WaypointAirTurningPoint(nil, speedkmh, {}, "Current")
|
||||
|
||||
--- Create a route point of type air.
|
||||
local ToPatrolRoutePoint = ToTargetCoord:WaypointAir(
|
||||
self.PatrolAltType,
|
||||
POINT_VEC3.RoutePointType.TurningPoint,
|
||||
POINT_VEC3.RoutePointAction.TurningPoint,
|
||||
ToTargetSpeed,
|
||||
true
|
||||
)
|
||||
if self.racetrack then
|
||||
|
||||
-- Random heading.
|
||||
local heading = math.random(self.racetrackheadingmin, self.racetrackheadingmax)
|
||||
|
||||
-- Random leg length.
|
||||
local leg=math.random(self.racetracklegmin, self.racetracklegmax)
|
||||
|
||||
-- Random duration if any.
|
||||
local duration = self.racetrackdurationmin
|
||||
if self.racetrackdurationmax then
|
||||
duration=math.random(self.racetrackdurationmin, self.racetrackdurationmax)
|
||||
end
|
||||
|
||||
-- CAP coordinate.
|
||||
local c0=self.PatrolZone:GetRandomCoordinate()
|
||||
if self.racetrackcapcoordinates and #self.racetrackcapcoordinates>0 then
|
||||
c0=self.racetrackcapcoordinates[math.random(#self.racetrackcapcoordinates)]
|
||||
end
|
||||
|
||||
-- Race track points.
|
||||
local c1=c0:SetAltitude(altitude) --Core.Point#COORDINATE
|
||||
local c2=c1:Translate(leg, heading):SetAltitude(altitude)
|
||||
|
||||
self:SetTargetDistance(c0) -- For RTB status check
|
||||
|
||||
-- Debug:
|
||||
self:T(string.format("Patrol zone race track: v=%.1f knots, h=%.1f ft, heading=%03d, leg=%d m, t=%s sec", UTILS.KmphToKnots(speedkmh), UTILS.MetersToFeet(altitude), heading, leg, tostring(duration)))
|
||||
--c1:MarkToAll("Race track c1")
|
||||
--c2:MarkToAll("Race track c2")
|
||||
|
||||
PatrolRoute[#PatrolRoute+1] = ToPatrolRoutePoint
|
||||
PatrolRoute[#PatrolRoute+1] = ToPatrolRoutePoint
|
||||
|
||||
local Tasks = {}
|
||||
Tasks[#Tasks+1] = AIPatrol:TaskFunction( "AI_A2A_PATROL.PatrolRoute", self )
|
||||
PatrolRoute[#PatrolRoute].task = AIPatrol:TaskCombo( Tasks )
|
||||
|
||||
-- Task to orbit.
|
||||
local taskOrbit=AIPatrol:TaskOrbit(c1, altitude, UTILS.KmphToMps(speedkmh), c2)
|
||||
|
||||
-- Task function to redo the patrol at other random position.
|
||||
local taskPatrol=AIPatrol:TaskFunction("AI_A2A_PATROL.PatrolRoute", self)
|
||||
|
||||
-- Controlled task with task condition.
|
||||
local taskCond=AIPatrol:TaskCondition(nil, nil, nil, nil, duration, nil)
|
||||
local taskCont=AIPatrol:TaskControlled(taskOrbit, taskCond)
|
||||
|
||||
-- Second waypoint
|
||||
PatrolRoute[2]=c1:WaypointAirTurningPoint(self.PatrolAltType, speedkmh, {taskCont, taskPatrol}, "CAP Orbit")
|
||||
|
||||
else
|
||||
|
||||
-- Target coordinate.
|
||||
local ToTargetCoord=self.PatrolZone:GetRandomCoordinate() --Core.Point#COORDINATE
|
||||
ToTargetCoord:SetAltitude(altitude)
|
||||
|
||||
self:SetTargetDistance( ToTargetCoord ) -- For RTB status check
|
||||
|
||||
local taskReRoute=AIPatrol:TaskFunction( "AI_A2A_PATROL.PatrolRoute", self )
|
||||
|
||||
PatrolRoute[2]=ToTargetCoord:WaypointAirTurningPoint(self.PatrolAltType, speedkmh, {taskReRoute}, "Patrol Point")
|
||||
|
||||
end
|
||||
|
||||
-- ROE
|
||||
AIPatrol:OptionROEReturnFire()
|
||||
AIPatrol:OptionROTEvadeFire()
|
||||
|
||||
AIPatrol:Route( PatrolRoute, 0.5 )
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- @param Wrapper.Group#GROUP AIPatrol
|
||||
function AI_A2A_PATROL.Resume( AIPatrol, Fsm )
|
||||
|
||||
AIPatrol:I( { "AI_A2A_PATROL.Resume:", AIPatrol:GetName() } )
|
||||
if AIPatrol:IsAlive() then
|
||||
Fsm:__Reset( 1 )
|
||||
Fsm:__Route( 5 )
|
||||
end
|
||||
|
||||
-- Patrol.
|
||||
AIPatrol:Route( PatrolRoute, 0.5)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
99
Moose Development/Moose/AI/AI_A2G_BAI.lua
Normal file
99
Moose Development/Moose/AI/AI_A2G_BAI.lua
Normal file
@@ -0,0 +1,99 @@
|
||||
--- **AI** -- Models the process of air to ground BAI engagement for airplanes and helicopters.
|
||||
--
|
||||
-- This is a class used in the @{AI_A2G_Dispatcher}.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module AI.AI_A2G_BAI
|
||||
-- @image AI_Air_To_Ground_Engage.JPG
|
||||
|
||||
|
||||
|
||||
--- @type AI_A2G_BAI
|
||||
-- @extends AI.AI_A2A_Engage#AI_A2A_Engage
|
||||
|
||||
|
||||
--- Implements the core functions to intercept intruders. Use the Engage trigger to intercept intruders.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @field #AI_A2G_BAI
|
||||
AI_A2G_BAI = {
|
||||
ClassName = "AI_A2G_BAI",
|
||||
}
|
||||
|
||||
|
||||
|
||||
--- Creates a new AI_A2G_BAI object
|
||||
-- @param #AI_A2G_BAI self
|
||||
-- @param Wrapper.Group#GROUP AIGroup
|
||||
-- @param DCS#Speed EngageMinSpeed The minimum speed of the @{Wrapper.Group} in km/h when engaging a target.
|
||||
-- @param DCS#Speed EngageMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h when engaging a target.
|
||||
-- @param DCS#Altitude EngageFloorAltitude The lowest altitude in meters where to execute the engagement.
|
||||
-- @param DCS#Altitude EngageCeilingAltitude The highest altitude in meters where to execute the engagement.
|
||||
-- @param DCS#AltitudeType EngageAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to "RADIO".
|
||||
-- @param Core.Zone#ZONE_BASE PatrolZone The @{Zone} where the patrol needs to be executed.
|
||||
-- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol.
|
||||
-- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol.
|
||||
-- @param DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Group} in km/h.
|
||||
-- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h.
|
||||
-- @param DCS#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO
|
||||
-- @return #AI_A2G_BAI
|
||||
function AI_A2G_BAI:New2( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType )
|
||||
|
||||
local AI_Air = AI_AIR:New( AIGroup )
|
||||
local AI_Air_Patrol = AI_AIR_PATROL:New( AI_Air, AIGroup, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) -- #AI_AIR_PATROL
|
||||
local AI_Air_Engage = AI_AIR_ENGAGE:New( AI_Air_Patrol, AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType )
|
||||
local self = BASE:Inherit( self, AI_Air_Engage )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Creates a new AI_A2G_BAI object
|
||||
-- @param #AI_A2G_BAI self
|
||||
-- @param Wrapper.Group#GROUP AIGroup
|
||||
-- @param DCS#Speed EngageMinSpeed The minimum speed of the @{Wrapper.Group} in km/h when engaging a target.
|
||||
-- @param DCS#Speed EngageMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h when engaging a target.
|
||||
-- @param DCS#Altitude EngageFloorAltitude The lowest altitude in meters where to execute the engagement.
|
||||
-- @param DCS#Altitude EngageCeilingAltitude The highest altitude in meters where to execute the engagement.
|
||||
-- @param Core.Zone#ZONE_BASE PatrolZone The @{Zone} where the patrol needs to be executed.
|
||||
-- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol.
|
||||
-- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol.
|
||||
-- @param DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Group} in km/h.
|
||||
-- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h.
|
||||
-- @param DCS#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO
|
||||
-- @return #AI_A2G_BAI
|
||||
function AI_A2G_BAI:New( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType )
|
||||
|
||||
return self:New2( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, PatrolAltType, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType)
|
||||
end
|
||||
|
||||
--- Evaluate the attack and create an AttackUnitTask list.
|
||||
-- @param #AI_A2G_BAI self
|
||||
-- @param Core.Set#SET_UNIT AttackSetUnit The set of units to attack.
|
||||
-- @param Wrappper.Group#GROUP DefenderGroup The group of defenders.
|
||||
-- @param #number EngageAltitude The altitude to engage the targets.
|
||||
-- @return #AI_A2G_BAI self
|
||||
function AI_A2G_BAI:CreateAttackUnitTasks( AttackSetUnit, DefenderGroup, EngageAltitude )
|
||||
|
||||
local AttackUnitTasks = {}
|
||||
|
||||
local AttackSetUnitPerThreatLevel = AttackSetUnit:GetSetPerThreatLevel( 10, 0 )
|
||||
for AttackUnitIndex, AttackUnit in ipairs( AttackSetUnitPerThreatLevel or {} ) do
|
||||
if AttackUnit then
|
||||
if AttackUnit:IsAlive() and AttackUnit:IsGround() then
|
||||
self:T( { "BAI Unit:", AttackUnit:GetName() } )
|
||||
AttackUnitTasks[#AttackUnitTasks+1] = DefenderGroup:TaskAttackUnit( AttackUnit, true, false, nil, nil, EngageAltitude )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return AttackUnitTasks
|
||||
end
|
||||
|
||||
|
||||
100
Moose Development/Moose/AI/AI_A2G_CAS.lua
Normal file
100
Moose Development/Moose/AI/AI_A2G_CAS.lua
Normal file
@@ -0,0 +1,100 @@
|
||||
--- **AI** -- Models the process of air to ground engagement for airplanes and helicopters.
|
||||
--
|
||||
-- This is a class used in the @{AI_A2G_Dispatcher}.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module AI.AI_A2G_CAS
|
||||
-- @image AI_Air_To_Ground_Engage.JPG
|
||||
|
||||
|
||||
|
||||
--- @type AI_A2G_CAS
|
||||
-- @extends AI.AI_A2G_Patrol#AI_AIR_PATROL
|
||||
|
||||
|
||||
--- Implements the core functions to intercept intruders. Use the Engage trigger to intercept intruders.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @field #AI_A2G_CAS
|
||||
AI_A2G_CAS = {
|
||||
ClassName = "AI_A2G_CAS",
|
||||
}
|
||||
|
||||
|
||||
|
||||
--- Creates a new AI_A2G_CAS object
|
||||
-- @param #AI_A2G_CAS self
|
||||
-- @param Wrapper.Group#GROUP AIGroup
|
||||
-- @param DCS#Speed EngageMinSpeed The minimum speed of the @{Wrapper.Group} in km/h when engaging a target.
|
||||
-- @param DCS#Speed EngageMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h when engaging a target.
|
||||
-- @param DCS#Altitude EngageFloorAltitude The lowest altitude in meters where to execute the engagement.
|
||||
-- @param DCS#Altitude EngageCeilingAltitude The highest altitude in meters where to execute the engagement.
|
||||
-- @param DCS#AltitudeType EngageAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to "RADIO".
|
||||
-- @param Core.Zone#ZONE_BASE PatrolZone The @{Zone} where the patrol needs to be executed.
|
||||
-- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol.
|
||||
-- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol.
|
||||
-- @param DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Group} in km/h.
|
||||
-- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h.
|
||||
-- @param DCS#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO
|
||||
-- @return #AI_A2G_CAS
|
||||
function AI_A2G_CAS:New2( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType )
|
||||
|
||||
local AI_Air = AI_AIR:New( AIGroup )
|
||||
local AI_Air_Patrol = AI_AIR_PATROL:New( AI_Air, AIGroup, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) -- #AI_AIR_PATROL
|
||||
local AI_Air_Engage = AI_AIR_ENGAGE:New( AI_Air_Patrol, AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType )
|
||||
local self = BASE:Inherit( self, AI_Air_Engage )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Creates a new AI_A2G_CAS object
|
||||
-- @param #AI_A2G_CAS self
|
||||
-- @param Wrapper.Group#GROUP AIGroup
|
||||
-- @param DCS#Speed EngageMinSpeed The minimum speed of the @{Wrapper.Group} in km/h when engaging a target.
|
||||
-- @param DCS#Speed EngageMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h when engaging a target.
|
||||
-- @param DCS#Altitude EngageFloorAltitude The lowest altitude in meters where to execute the engagement.
|
||||
-- @param DCS#Altitude EngageCeilingAltitude The highest altitude in meters where to execute the engagement.
|
||||
-- @param Core.Zone#ZONE_BASE PatrolZone The @{Zone} where the patrol needs to be executed.
|
||||
-- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol.
|
||||
-- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol.
|
||||
-- @param DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Group} in km/h.
|
||||
-- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h.
|
||||
-- @param DCS#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO
|
||||
-- @return #AI_A2G_CAS
|
||||
function AI_A2G_CAS:New( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType )
|
||||
|
||||
return self:New2( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, PatrolAltType, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType)
|
||||
end
|
||||
|
||||
--- Evaluate the attack and create an AttackUnitTask list.
|
||||
-- @param #AI_A2G_CAS self
|
||||
-- @param Core.Set#SET_UNIT AttackSetUnit The set of units to attack.
|
||||
-- @param Wrappper.Group#GROUP DefenderGroup The group of defenders.
|
||||
-- @param #number EngageAltitude The altitude to engage the targets.
|
||||
-- @return #AI_A2G_CAS self
|
||||
function AI_A2G_CAS:CreateAttackUnitTasks( AttackSetUnit, DefenderGroup, EngageAltitude )
|
||||
|
||||
local AttackUnitTasks = {}
|
||||
|
||||
local AttackSetUnitPerThreatLevel = AttackSetUnit:GetSetPerThreatLevel( 10, 0 )
|
||||
for AttackUnitIndex, AttackUnit in ipairs( AttackSetUnitPerThreatLevel or {} ) do
|
||||
if AttackUnit then
|
||||
if AttackUnit:IsAlive() and AttackUnit:IsGround() then
|
||||
self:T( { "CAS Unit:", AttackUnit:GetName() } )
|
||||
AttackUnitTasks[#AttackUnitTasks+1] = DefenderGroup:TaskAttackUnit( AttackUnit, true, false, nil, nil, EngageAltitude )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return AttackUnitTasks
|
||||
end
|
||||
|
||||
|
||||
|
||||
4757
Moose Development/Moose/AI/AI_A2G_Dispatcher.lua
Normal file
4757
Moose Development/Moose/AI/AI_A2G_Dispatcher.lua
Normal file
File diff suppressed because it is too large
Load Diff
152
Moose Development/Moose/AI/AI_A2G_SEAD.lua
Normal file
152
Moose Development/Moose/AI/AI_A2G_SEAD.lua
Normal file
@@ -0,0 +1,152 @@
|
||||
--- **AI** -- Models the process of air to ground SEAD engagement for airplanes and helicopters.
|
||||
--
|
||||
-- This is a class used in the @{AI_A2G_Dispatcher}.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module AI.AI_A2G_SEAD
|
||||
-- @image AI_Air_To_Ground_Engage.JPG
|
||||
|
||||
|
||||
|
||||
--- @type AI_A2G_SEAD
|
||||
-- @extends AI.AI_A2G_Patrol#AI_AIR_PATROL
|
||||
|
||||
|
||||
--- Implements the core functions to SEAD intruders. Use the Engage trigger to intercept intruders.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- The AI_A2G_SEAD is assigned a @{Wrapper.Group} and this must be done before the AI_A2G_SEAD process can be started using the **Start** event.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- The AI will fly towards the random 3D point within the 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.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- During the patrol, the AI will detect enemy targets, which are reported through the **Detected** event.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- When enemies are detected, the AI will automatically engage the enemy.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- 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.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- ## 1. AI_A2G_SEAD constructor
|
||||
--
|
||||
-- * @{#AI_A2G_SEAD.New}(): Creates a new AI_A2G_SEAD object.
|
||||
--
|
||||
-- ## 3. Set the Range of Engagement
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- An optional range can be set in meters,
|
||||
-- that will define when the AI will engage with the detected airborne enemy targets.
|
||||
-- The range can be beyond or smaller than the range of the Patrol Zone.
|
||||
-- The range is applied at the position of the AI.
|
||||
-- Use the method @{AI.AI_GCI#AI_A2G_SEAD.SetEngageRange}() to define that range.
|
||||
--
|
||||
-- ## 4. Set the Zone of Engagement
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- An optional @{Zone} can be set,
|
||||
-- that will define when the AI will engage with the detected airborne enemy targets.
|
||||
-- Use the method @{AI.AI_Cap#AI_A2G_SEAD.SetEngageZone}() to define that Zone.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @field #AI_A2G_SEAD
|
||||
AI_A2G_SEAD = {
|
||||
ClassName = "AI_A2G_SEAD",
|
||||
}
|
||||
|
||||
|
||||
|
||||
--- Creates a new AI_A2G_SEAD object
|
||||
-- @param #AI_A2G_SEAD self
|
||||
-- @param Wrapper.Group#GROUP AIGroup
|
||||
-- @param DCS#Speed EngageMinSpeed The minimum speed of the @{Wrapper.Group} in km/h when engaging a target.
|
||||
-- @param DCS#Speed EngageMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h when engaging a target.
|
||||
-- @param DCS#Altitude EngageFloorAltitude The lowest altitude in meters where to execute the engagement.
|
||||
-- @param DCS#Altitude EngageCeilingAltitude The highest altitude in meters where to execute the engagement.
|
||||
-- @param DCS#AltitudeType EngageAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to "RADIO".
|
||||
-- @param Core.Zone#ZONE_BASE PatrolZone The @{Zone} where the patrol needs to be executed.
|
||||
-- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol.
|
||||
-- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol.
|
||||
-- @param DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Group} in km/h.
|
||||
-- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h.
|
||||
-- @param DCS#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO
|
||||
-- @return #AI_A2G_SEAD
|
||||
function AI_A2G_SEAD:New2( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType )
|
||||
|
||||
local AI_Air = AI_AIR:New( AIGroup )
|
||||
local AI_Air_Patrol = AI_AIR_PATROL:New( AI_Air, AIGroup, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) -- #AI_AIR_PATROL
|
||||
local AI_Air_Engage = AI_AIR_ENGAGE:New( AI_Air_Patrol, AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType )
|
||||
local self = BASE:Inherit( self, AI_Air_Engage )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Creates a new AI_A2G_SEAD object
|
||||
-- @param #AI_A2G_SEAD self
|
||||
-- @param Wrapper.Group#GROUP AIGroup
|
||||
-- @param DCS#Speed EngageMinSpeed The minimum speed of the @{Wrapper.Group} in km/h when engaging a target.
|
||||
-- @param DCS#Speed EngageMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h when engaging a target.
|
||||
-- @param DCS#Altitude EngageFloorAltitude The lowest altitude in meters where to execute the engagement.
|
||||
-- @param DCS#Altitude EngageCeilingAltitude The highest altitude in meters where to execute the engagement.
|
||||
-- @param Core.Zone#ZONE_BASE PatrolZone The @{Zone} where the patrol needs to be executed.
|
||||
-- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol.
|
||||
-- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol.
|
||||
-- @param DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Group} in km/h.
|
||||
-- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h.
|
||||
-- @param DCS#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO
|
||||
-- @return #AI_A2G_SEAD
|
||||
function AI_A2G_SEAD:New( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType )
|
||||
|
||||
return self:New2( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, PatrolAltType, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType )
|
||||
end
|
||||
|
||||
|
||||
--- Evaluate the attack and create an AttackUnitTask list.
|
||||
-- @param #AI_A2G_SEAD self
|
||||
-- @param Core.Set#SET_UNIT AttackSetUnit The set of units to attack.
|
||||
-- @param Wrappper.Group#GROUP DefenderGroup The group of defenders.
|
||||
-- @param #number EngageAltitude The altitude to engage the targets.
|
||||
-- @return #AI_A2G_SEAD self
|
||||
function AI_A2G_SEAD:CreateAttackUnitTasks( AttackSetUnit, DefenderGroup, EngageAltitude )
|
||||
|
||||
local AttackUnitTasks = {}
|
||||
|
||||
local AttackSetUnitPerThreatLevel = AttackSetUnit:GetSetPerThreatLevel( 10, 0 )
|
||||
for AttackUnitID, AttackUnit in ipairs( AttackSetUnitPerThreatLevel ) do
|
||||
if AttackUnit then
|
||||
if AttackUnit:IsAlive() and AttackUnit:IsGround() then
|
||||
local HasRadar = AttackUnit:HasSEAD()
|
||||
if HasRadar then
|
||||
self:F( { "SEAD Unit:", AttackUnit:GetName() } )
|
||||
AttackUnitTasks[#AttackUnitTasks+1] = DefenderGroup:TaskAttackUnit( AttackUnit, true, false, nil, nil, EngageAltitude )
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return AttackUnitTasks
|
||||
end
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
3244
Moose Development/Moose/AI/AI_Air_Dispatcher.lua
Normal file
3244
Moose Development/Moose/AI/AI_Air_Dispatcher.lua
Normal file
File diff suppressed because it is too large
Load Diff
603
Moose Development/Moose/AI/AI_Air_Engage.lua
Normal file
603
Moose Development/Moose/AI/AI_Air_Engage.lua
Normal file
@@ -0,0 +1,603 @@
|
||||
--- **AI** -- Models the process of air to ground engagement for airplanes and helicopters.
|
||||
--
|
||||
-- This is a class used in the @{AI_A2G_Dispatcher}.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module AI.AI_Air_Engage
|
||||
-- @image AI_Air_To_Ground_Engage.JPG
|
||||
|
||||
|
||||
|
||||
--- @type AI_AIR_ENGAGE
|
||||
-- @extends AI.AI_AIR#AI_AIR
|
||||
|
||||
|
||||
--- Implements the core functions to intercept intruders. Use the Engage trigger to intercept intruders.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- The AI_AIR_ENGAGE is assigned a @{Wrapper.Group} and this must be done before the AI_AIR_ENGAGE process can be started using the **Start** event.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- The AI will fly towards the random 3D point within the 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.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- During the patrol, the AI will detect enemy targets, which are reported through the **Detected** event.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- When enemies are detected, the AI will automatically engage the enemy.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- 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.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- ## 1. AI_AIR_ENGAGE constructor
|
||||
--
|
||||
-- * @{#AI_AIR_ENGAGE.New}(): Creates a new AI_AIR_ENGAGE object.
|
||||
--
|
||||
-- ## 3. Set the Range of Engagement
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- An optional range can be set in meters,
|
||||
-- that will define when the AI will engage with the detected airborne enemy targets.
|
||||
-- The range can be beyond or smaller than the range of the Patrol Zone.
|
||||
-- The range is applied at the position of the AI.
|
||||
-- Use the method @{AI.AI_GCI#AI_AIR_ENGAGE.SetEngageRange}() to define that range.
|
||||
--
|
||||
-- ## 4. Set the Zone of Engagement
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- An optional @{Zone} can be set,
|
||||
-- that will define when the AI will engage with the detected airborne enemy targets.
|
||||
-- Use the method @{AI.AI_Cap#AI_AIR_ENGAGE.SetEngageZone}() to define that Zone.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @field #AI_AIR_ENGAGE
|
||||
AI_AIR_ENGAGE = {
|
||||
ClassName = "AI_AIR_ENGAGE",
|
||||
}
|
||||
|
||||
|
||||
|
||||
--- Creates a new AI_AIR_ENGAGE object
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
-- @param AI.AI_Air#AI_AIR AI_Air The AI_AIR FSM.
|
||||
-- @param Wrapper.Group#GROUP AIGroup The AI group.
|
||||
-- @param DCS#Speed EngageMinSpeed (optional, default = 50% of max speed) The minimum speed of the @{Wrapper.Group} in km/h when engaging a target.
|
||||
-- @param DCS#Speed EngageMaxSpeed (optional, default = 75% of max speed) The maximum speed of the @{Wrapper.Group} in km/h when engaging a target.
|
||||
-- @param DCS#Altitude EngageFloorAltitude (optional, default = 1000m ) The lowest altitude in meters where to execute the engagement.
|
||||
-- @param DCS#Altitude EngageCeilingAltitude (optional, default = 1500m ) The highest altitude in meters where to execute the engagement.
|
||||
-- @param DCS#AltitudeType EngageAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to "RADIO".
|
||||
-- @return #AI_AIR_ENGAGE
|
||||
function AI_AIR_ENGAGE:New( AI_Air, AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType )
|
||||
|
||||
-- Inherits from BASE
|
||||
local self = BASE:Inherit( self, AI_Air ) -- #AI_AIR_ENGAGE
|
||||
|
||||
self.Accomplished = false
|
||||
self.Engaging = false
|
||||
|
||||
local SpeedMax = AIGroup:GetSpeedMax()
|
||||
|
||||
self.EngageMinSpeed = EngageMinSpeed or SpeedMax * 0.5
|
||||
self.EngageMaxSpeed = EngageMaxSpeed or SpeedMax * 0.75
|
||||
self.EngageFloorAltitude = EngageFloorAltitude or 1000
|
||||
self.EngageCeilingAltitude = EngageCeilingAltitude or 1500
|
||||
self.EngageAltType = EngageAltType or "RADIO"
|
||||
|
||||
self:AddTransition( { "Started", "Engaging", "Returning", "Airborne", "Patrolling" }, "EngageRoute", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_AIR_ENGAGE.
|
||||
|
||||
--- OnBefore Transition Handler for Event EngageRoute.
|
||||
-- @function [parent=#AI_AIR_ENGAGE] OnBeforeEngageRoute
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event EngageRoute.
|
||||
-- @function [parent=#AI_AIR_ENGAGE] OnAfterEngageRoute
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event EngageRoute.
|
||||
-- @function [parent=#AI_AIR_ENGAGE] EngageRoute
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
|
||||
--- Asynchronous Event Trigger for Event EngageRoute.
|
||||
-- @function [parent=#AI_AIR_ENGAGE] __EngageRoute
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
|
||||
--- OnLeave Transition Handler for State Engaging.
|
||||
-- @function [parent=#AI_AIR_ENGAGE] OnLeaveEngaging
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnEnter Transition Handler for State Engaging.
|
||||
-- @function [parent=#AI_AIR_ENGAGE] OnEnterEngaging
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
self:AddTransition( { "Started", "Engaging", "Returning", "Airborne", "Patrolling" }, "Engage", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_AIR_ENGAGE.
|
||||
|
||||
--- OnBefore Transition Handler for Event Engage.
|
||||
-- @function [parent=#AI_AIR_ENGAGE] OnBeforeEngage
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event Engage.
|
||||
-- @function [parent=#AI_AIR_ENGAGE] OnAfterEngage
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event Engage.
|
||||
-- @function [parent=#AI_AIR_ENGAGE] Engage
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
|
||||
--- Asynchronous Event Trigger for Event Engage.
|
||||
-- @function [parent=#AI_AIR_ENGAGE] __Engage
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
|
||||
--- OnLeave Transition Handler for State Engaging.
|
||||
-- @function [parent=#AI_AIR_ENGAGE] OnLeaveEngaging
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnEnter Transition Handler for State Engaging.
|
||||
-- @function [parent=#AI_AIR_ENGAGE] OnEnterEngaging
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
self:AddTransition( "Engaging", "Fired", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_AIR_ENGAGE.
|
||||
|
||||
--- OnBefore Transition Handler for Event Fired.
|
||||
-- @function [parent=#AI_AIR_ENGAGE] OnBeforeFired
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event Fired.
|
||||
-- @function [parent=#AI_AIR_ENGAGE] OnAfterFired
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event Fired.
|
||||
-- @function [parent=#AI_AIR_ENGAGE] Fired
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
|
||||
--- Asynchronous Event Trigger for Event Fired.
|
||||
-- @function [parent=#AI_AIR_ENGAGE] __Fired
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
|
||||
self:AddTransition( "*", "Destroy", "*" ) -- FSM_CONTROLLABLE Transition for type #AI_AIR_ENGAGE.
|
||||
|
||||
--- OnBefore Transition Handler for Event Destroy.
|
||||
-- @function [parent=#AI_AIR_ENGAGE] OnBeforeDestroy
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event Destroy.
|
||||
-- @function [parent=#AI_AIR_ENGAGE] OnAfterDestroy
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event Destroy.
|
||||
-- @function [parent=#AI_AIR_ENGAGE] Destroy
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
|
||||
--- Asynchronous Event Trigger for Event Destroy.
|
||||
-- @function [parent=#AI_AIR_ENGAGE] __Destroy
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
|
||||
|
||||
self:AddTransition( "Engaging", "Abort", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_AIR_ENGAGE.
|
||||
|
||||
--- OnBefore Transition Handler for Event Abort.
|
||||
-- @function [parent=#AI_AIR_ENGAGE] OnBeforeAbort
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event Abort.
|
||||
-- @function [parent=#AI_AIR_ENGAGE] OnAfterAbort
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event Abort.
|
||||
-- @function [parent=#AI_AIR_ENGAGE] Abort
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
|
||||
--- Asynchronous Event Trigger for Event Abort.
|
||||
-- @function [parent=#AI_AIR_ENGAGE] __Abort
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
|
||||
self:AddTransition( "Engaging", "Accomplish", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_AIR_ENGAGE.
|
||||
|
||||
--- OnBefore Transition Handler for Event Accomplish.
|
||||
-- @function [parent=#AI_AIR_ENGAGE] OnBeforeAccomplish
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event Accomplish.
|
||||
-- @function [parent=#AI_AIR_ENGAGE] OnAfterAccomplish
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event Accomplish.
|
||||
-- @function [parent=#AI_AIR_ENGAGE] Accomplish
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
|
||||
--- Asynchronous Event Trigger for Event Accomplish.
|
||||
-- @function [parent=#AI_AIR_ENGAGE] __Accomplish
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
|
||||
self:AddTransition( { "Patrolling", "Engaging" }, "Refuel", "Refuelling" )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- onafter event handler for Start event.
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
-- @param Wrapper.Group#GROUP AIGroup The AI group managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
function AI_AIR_ENGAGE:onafterStart( AIGroup, From, Event, To )
|
||||
|
||||
self:GetParent( self, AI_AIR_ENGAGE ).onafterStart( self, AIGroup, From, Event, To )
|
||||
|
||||
AIGroup:HandleEvent( EVENTS.Takeoff, nil, self )
|
||||
|
||||
end
|
||||
|
||||
|
||||
|
||||
--- onafter event handler for Engage event.
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
-- @param Wrapper.Group#GROUP AIGroup The AI Group managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
function AI_AIR_ENGAGE:onafterEngage( AIGroup, From, Event, To )
|
||||
-- TODO: This function is overwritten below!
|
||||
self:HandleEvent( EVENTS.Dead )
|
||||
end
|
||||
|
||||
-- todo: need to fix this global function
|
||||
|
||||
|
||||
--- onbefore event handler for Engage event.
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
-- @param Wrapper.Group#GROUP AIGroup The group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
function AI_AIR_ENGAGE:onbeforeEngage( AIGroup, From, Event, To )
|
||||
if self.Accomplished == true then
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
--- onafter event handler for Abort event.
|
||||
-- @param #AI_AIR_ENGAGE self
|
||||
-- @param Wrapper.Group#GROUP AIGroup The AI Group managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
function AI_AIR_ENGAGE:onafterAbort( AIGroup, From, Event, To )
|
||||
AIGroup:ClearTasks()
|
||||
self:Return()
|
||||
end
|
||||
|
||||
|
||||
--- @param #AI_AIR_ENGAGE self
|
||||
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
function AI_AIR_ENGAGE:onafterAccomplish( AIGroup, From, Event, To )
|
||||
self.Accomplished = true
|
||||
--self:SetDetectionOff()
|
||||
end
|
||||
|
||||
--- @param #AI_AIR_ENGAGE self
|
||||
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function AI_AIR_ENGAGE:onafterDestroy( AIGroup, From, Event, To, EventData )
|
||||
|
||||
if EventData.IniUnit then
|
||||
self.AttackUnits[EventData.IniUnit] = nil
|
||||
end
|
||||
end
|
||||
|
||||
--- @param #AI_AIR_ENGAGE self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function AI_AIR_ENGAGE:OnEventDead( EventData )
|
||||
self:F( { "EventDead", EventData } )
|
||||
|
||||
if EventData.IniDCSUnit then
|
||||
if self.AttackUnits and self.AttackUnits[EventData.IniUnit] then
|
||||
self:__Destroy( self.TaskDelay, EventData )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- @param Wrapper.Group#GROUP AIControllable
|
||||
function AI_AIR_ENGAGE.___EngageRoute( AIGroup, Fsm, AttackSetUnit )
|
||||
Fsm:I(string.format("AI_AIR_ENGAGE.___EngageRoute: %s", tostring(AIGroup:GetName())))
|
||||
|
||||
if AIGroup and AIGroup:IsAlive() then
|
||||
Fsm:__EngageRoute( Fsm.TaskDelay or 0.1, AttackSetUnit )
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- @param #AI_AIR_ENGAGE self
|
||||
-- @param Wrapper.Group#GROUP DefenderGroup The GroupGroup managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @param Core.Set#SET_UNIT AttackSetUnit Unit set to be attacked.
|
||||
function AI_AIR_ENGAGE:onafterEngageRoute( DefenderGroup, From, Event, To, AttackSetUnit )
|
||||
self:I( { DefenderGroup, From, Event, To, AttackSetUnit } )
|
||||
|
||||
local DefenderGroupName = DefenderGroup:GetName()
|
||||
|
||||
self.AttackSetUnit = AttackSetUnit -- Kept in memory in case of resume from refuel in air!
|
||||
|
||||
local AttackCount = AttackSetUnit:CountAlive()
|
||||
|
||||
if AttackCount > 0 then
|
||||
|
||||
if DefenderGroup:IsAlive() then
|
||||
|
||||
local EngageAltitude = math.random( self.EngageFloorAltitude, self.EngageCeilingAltitude )
|
||||
local EngageSpeed = math.random( self.EngageMinSpeed, self.EngageMaxSpeed )
|
||||
|
||||
-- Determine the distance to the target.
|
||||
-- If it is less than 10km, then attack without a route.
|
||||
-- Otherwise perform a route attack.
|
||||
|
||||
local DefenderCoord = DefenderGroup:GetPointVec3()
|
||||
DefenderCoord:SetY( EngageAltitude ) -- Ground targets don't have an altitude.
|
||||
|
||||
local TargetCoord = AttackSetUnit:GetFirst():GetPointVec3()
|
||||
TargetCoord:SetY( EngageAltitude ) -- Ground targets don't have an altitude.
|
||||
|
||||
local TargetDistance = DefenderCoord:Get2DDistance( TargetCoord )
|
||||
local EngageDistance = ( DefenderGroup:IsHelicopter() and 5000 ) or ( DefenderGroup:IsAirPlane() and 10000 )
|
||||
|
||||
-- TODO: A factor of * 3 is way too close. This causes the AI not to engange until merged sometimes!
|
||||
if TargetDistance <= EngageDistance * 9 then
|
||||
|
||||
self:I(string.format("AI_AIR_ENGAGE onafterEngageRoute ==> __Engage - target distance = %.1f km", TargetDistance/1000))
|
||||
self:__Engage( 0.1, AttackSetUnit )
|
||||
|
||||
else
|
||||
|
||||
self:I(string.format("FF AI_AIR_ENGAGE onafterEngageRoute ==> Routing - target distance = %.1f km", TargetDistance/1000))
|
||||
|
||||
local EngageRoute = {}
|
||||
local AttackTasks = {}
|
||||
|
||||
--- Calculate the target route point.
|
||||
|
||||
local FromWP = DefenderCoord:WaypointAir(self.PatrolAltType or "RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, EngageSpeed, true)
|
||||
|
||||
EngageRoute[#EngageRoute+1] = FromWP
|
||||
|
||||
self:SetTargetDistance( TargetCoord ) -- For RTB status check
|
||||
|
||||
local FromEngageAngle = DefenderCoord:GetAngleDegrees( DefenderCoord:GetDirectionVec3( TargetCoord ) )
|
||||
local ToCoord=DefenderCoord:Translate( EngageDistance, FromEngageAngle, true )
|
||||
|
||||
local ToWP = ToCoord:WaypointAir(self.PatrolAltType or "RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, EngageSpeed, true)
|
||||
|
||||
EngageRoute[#EngageRoute+1] = ToWP
|
||||
|
||||
AttackTasks[#AttackTasks+1] = DefenderGroup:TaskFunction( "AI_AIR_ENGAGE.___EngageRoute", self, AttackSetUnit )
|
||||
EngageRoute[#EngageRoute].task = DefenderGroup:TaskCombo( AttackTasks )
|
||||
|
||||
DefenderGroup:OptionROEReturnFire()
|
||||
DefenderGroup:OptionROTEvadeFire()
|
||||
|
||||
DefenderGroup:Route( EngageRoute, self.TaskDelay or 0.1 )
|
||||
end
|
||||
|
||||
end
|
||||
else
|
||||
-- TODO: This will make an A2A Dispatcher CAP flight to return rather than going back to patrolling!
|
||||
self:I( DefenderGroupName .. ": No targets found -> Going RTB")
|
||||
self:Return()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- @param Wrapper.Group#GROUP AIControllable
|
||||
function AI_AIR_ENGAGE.___Engage( AIGroup, Fsm, AttackSetUnit )
|
||||
|
||||
Fsm:I(string.format("AI_AIR_ENGAGE.___Engage: %s", tostring(AIGroup:GetName())))
|
||||
|
||||
if AIGroup and AIGroup:IsAlive() then
|
||||
local delay=Fsm.TaskDelay or 0.1
|
||||
Fsm:__Engage(delay, AttackSetUnit)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- @param #AI_AIR_ENGAGE self
|
||||
-- @param Wrapper.Group#GROUP DefenderGroup The GroupGroup managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @param Core.Set#SET_UNIT AttackSetUnit Set of units to be attacked.
|
||||
function AI_AIR_ENGAGE:onafterEngage( DefenderGroup, From, Event, To, AttackSetUnit )
|
||||
self:F( { DefenderGroup, From, Event, To, AttackSetUnit} )
|
||||
|
||||
local DefenderGroupName = DefenderGroup:GetName()
|
||||
|
||||
self.AttackSetUnit = AttackSetUnit -- Kept in memory in case of resume from refuel in air!
|
||||
|
||||
local AttackCount = AttackSetUnit:CountAlive()
|
||||
self:T({AttackCount = AttackCount})
|
||||
|
||||
if AttackCount > 0 then
|
||||
|
||||
if DefenderGroup and DefenderGroup:IsAlive() then
|
||||
|
||||
local EngageAltitude = math.random( self.EngageFloorAltitude or 500, self.EngageCeilingAltitude or 1000 )
|
||||
local EngageSpeed = math.random( self.EngageMinSpeed, self.EngageMaxSpeed )
|
||||
|
||||
local DefenderCoord = DefenderGroup:GetPointVec3()
|
||||
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 )
|
||||
|
||||
local EngageDistance = ( DefenderGroup:IsHelicopter() and 5000 ) or ( DefenderGroup:IsAirPlane() and 10000 )
|
||||
|
||||
local EngageRoute = {}
|
||||
local AttackTasks = {}
|
||||
|
||||
local FromWP = DefenderCoord:WaypointAir(self.EngageAltType or "RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, EngageSpeed, true)
|
||||
EngageRoute[#EngageRoute+1] = FromWP
|
||||
|
||||
self:SetTargetDistance( TargetCoord ) -- For RTB status check
|
||||
|
||||
local FromEngageAngle = DefenderCoord:GetAngleDegrees( DefenderCoord:GetDirectionVec3( TargetCoord ) )
|
||||
local ToCoord=DefenderCoord:Translate( EngageDistance, FromEngageAngle, true )
|
||||
|
||||
local ToWP = ToCoord:WaypointAir(self.EngageAltType or "RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, EngageSpeed, true)
|
||||
EngageRoute[#EngageRoute+1] = ToWP
|
||||
|
||||
-- TODO: A factor of * 3 this way too low. This causes the AI NOT to engage until very close or even merged sometimes. Some A2A missiles have a much longer range! Needs more frequent updates of the task!
|
||||
if TargetDistance <= EngageDistance * 9 then
|
||||
|
||||
local AttackUnitTasks = self:CreateAttackUnitTasks( AttackSetUnit, DefenderGroup, EngageAltitude ) -- Polymorphic
|
||||
|
||||
if #AttackUnitTasks == 0 then
|
||||
self:I( DefenderGroupName .. ": No valid targets found -> Going RTB")
|
||||
self:Return()
|
||||
return
|
||||
else
|
||||
local text=string.format("%s: Engaging targets at distance %.2f NM", DefenderGroupName, UTILS.MetersToNM(TargetDistance))
|
||||
self:I(text)
|
||||
DefenderGroup:OptionROEOpenFire()
|
||||
DefenderGroup:OptionROTEvadeFire()
|
||||
DefenderGroup:OptionKeepWeaponsOnThreat()
|
||||
|
||||
AttackTasks[#AttackTasks+1] = DefenderGroup:TaskCombo( AttackUnitTasks )
|
||||
end
|
||||
end
|
||||
|
||||
AttackTasks[#AttackTasks+1] = DefenderGroup:TaskFunction( "AI_AIR_ENGAGE.___Engage", self, AttackSetUnit )
|
||||
EngageRoute[#EngageRoute].task = DefenderGroup:TaskCombo( AttackTasks )
|
||||
|
||||
DefenderGroup:Route( EngageRoute, self.TaskDelay or 0.1 )
|
||||
|
||||
end
|
||||
else
|
||||
-- TODO: This will make an A2A Dispatcher CAP flight to return rather than going back to patrolling!
|
||||
self:I( DefenderGroupName .. ": No targets found -> returning.")
|
||||
self:Return()
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
--- @param Wrapper.Group#GROUP AIEngage
|
||||
function AI_AIR_ENGAGE.Resume( AIEngage, Fsm )
|
||||
|
||||
AIEngage:F( { "Resume:", AIEngage:GetName() } )
|
||||
if AIEngage and AIEngage:IsAlive() then
|
||||
Fsm:__Reset( Fsm.TaskDelay or 0.1 )
|
||||
Fsm:__EngageRoute( Fsm.TaskDelay or 0.2, Fsm.AttackSetUnit )
|
||||
end
|
||||
|
||||
end
|
||||
398
Moose Development/Moose/AI/AI_Air_Patrol.lua
Normal file
398
Moose Development/Moose/AI/AI_Air_Patrol.lua
Normal file
@@ -0,0 +1,398 @@
|
||||
--- **AI** -- Models the process of A2G patrolling and engaging ground targets for airplanes and helicopters.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module AI.AI_Air_Patrol
|
||||
-- @image AI_Air_To_Ground_Patrol.JPG
|
||||
|
||||
--- @type AI_AIR_PATROL
|
||||
-- @extends AI.AI_Air#AI_AIR
|
||||
|
||||
|
||||
--- The AI_AIR_PATROL class implements the core functions to patrol a @{Zone} by an AI @{Wrapper.Group} or @{Wrapper.Group}
|
||||
-- and automatically engage any airborne enemies that are within a certain range or within a certain zone.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- The AI_AIR_PATROL is assigned a @{Wrapper.Group} and this must be done before the AI_AIR_PATROL process can be started using the **Start** event.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- The AI will fly towards the random 3D point within the 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.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- During the patrol, the AI will detect enemy targets, which are reported through the **Detected** event.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- When enemies are detected, the AI will automatically engage the enemy.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- 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.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- ## 1. AI_AIR_PATROL constructor
|
||||
--
|
||||
-- * @{#AI_AIR_PATROL.New}(): Creates a new AI_AIR_PATROL object.
|
||||
--
|
||||
-- ## 2. AI_AIR_PATROL is a FSM
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- ### 2.1 AI_AIR_PATROL States
|
||||
--
|
||||
-- * **None** ( Group ): The process is not started yet.
|
||||
-- * **Patrolling** ( Group ): The AI is patrolling the Patrol Zone.
|
||||
-- * **Engaging** ( Group ): The AI is engaging the bogeys.
|
||||
-- * **Returning** ( Group ): The AI is returning to Base..
|
||||
--
|
||||
-- ### 2.2 AI_AIR_PATROL Events
|
||||
--
|
||||
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Start}**: Start the process.
|
||||
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.PatrolRoute}**: Route the AI to a new random 3D point within the Patrol Zone.
|
||||
-- * **@{#AI_AIR_PATROL.Engage}**: Let the AI engage the bogeys.
|
||||
-- * **@{#AI_AIR_PATROL.Abort}**: Aborts the engagement and return patrolling in the patrol zone.
|
||||
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.RTB}**: Route the AI to the home base.
|
||||
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detect}**: The AI is detecting targets.
|
||||
-- * **@{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.
|
||||
--
|
||||
-- ## 3. Set the Range of Engagement
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- An optional range can be set in meters,
|
||||
-- that will define when the AI will engage with the detected airborne enemy targets.
|
||||
-- The range can be beyond or smaller than the range of the Patrol Zone.
|
||||
-- The range is applied at the position of the AI.
|
||||
-- Use the method @{AI.AI_CAP#AI_AIR_PATROL.SetEngageRange}() to define that range.
|
||||
--
|
||||
-- ## 4. Set the Zone of Engagement
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- An optional @{Zone} can be set,
|
||||
-- that will define when the AI will engage with the detected airborne enemy targets.
|
||||
-- Use the method @{AI.AI_Cap#AI_AIR_PATROL.SetEngageZone}() to define that Zone.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @field #AI_AIR_PATROL
|
||||
AI_AIR_PATROL = {
|
||||
ClassName = "AI_AIR_PATROL",
|
||||
}
|
||||
|
||||
--- Creates a new AI_AIR_PATROL object
|
||||
-- @param #AI_AIR_PATROL self
|
||||
-- @param AI.AI_Air#AI_AIR AI_Air The AI_AIR FSM.
|
||||
-- @param Wrapper.Group#GROUP AIGroup The AI group.
|
||||
-- @param Core.Zone#ZONE_BASE PatrolZone The @{Zone} where the patrol needs to be executed.
|
||||
-- @param DCS#Altitude PatrolFloorAltitude (optional, default = 1000m ) The lowest altitude in meters where to execute the patrol.
|
||||
-- @param DCS#Altitude PatrolCeilingAltitude (optional, default = 1500m ) The highest altitude in meters where to execute the patrol.
|
||||
-- @param DCS#Speed PatrolMinSpeed (optional, default = 50% of max speed) The minimum speed of the @{Wrapper.Group} in km/h.
|
||||
-- @param DCS#Speed PatrolMaxSpeed (optional, default = 75% of max speed) The maximum speed of the @{Wrapper.Group} in km/h.
|
||||
-- @param DCS#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO.
|
||||
-- @return #AI_AIR_PATROL
|
||||
function AI_AIR_PATROL:New( AI_Air, AIGroup, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType )
|
||||
|
||||
-- Inherits from BASE
|
||||
local self = BASE:Inherit( self, AI_Air ) -- #AI_AIR_PATROL
|
||||
|
||||
local SpeedMax = AIGroup:GetSpeedMax()
|
||||
|
||||
self.PatrolZone = PatrolZone
|
||||
|
||||
self.PatrolFloorAltitude = PatrolFloorAltitude or 1000
|
||||
self.PatrolCeilingAltitude = PatrolCeilingAltitude or 1500
|
||||
self.PatrolMinSpeed = PatrolMinSpeed or SpeedMax * 0.5
|
||||
self.PatrolMaxSpeed = PatrolMaxSpeed or SpeedMax * 0.75
|
||||
|
||||
-- defafult PatrolAltType to "RADIO" if not specified
|
||||
self.PatrolAltType = PatrolAltType or "RADIO"
|
||||
|
||||
self:AddTransition( { "Started", "Airborne", "Refuelling" }, "Patrol", "Patrolling" )
|
||||
|
||||
--- OnBefore Transition Handler for Event Patrol.
|
||||
-- @function [parent=#AI_AIR_PATROL] OnBeforePatrol
|
||||
-- @param #AI_AIR_PATROL self
|
||||
-- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event Patrol.
|
||||
-- @function [parent=#AI_AIR_PATROL] OnAfterPatrol
|
||||
-- @param #AI_AIR_PATROL self
|
||||
-- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event Patrol.
|
||||
-- @function [parent=#AI_AIR_PATROL] Patrol
|
||||
-- @param #AI_AIR_PATROL self
|
||||
|
||||
--- Asynchronous Event Trigger for Event Patrol.
|
||||
-- @function [parent=#AI_AIR_PATROL] __Patrol
|
||||
-- @param #AI_AIR_PATROL self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
|
||||
--- OnLeave Transition Handler for State Patrolling.
|
||||
-- @function [parent=#AI_AIR_PATROL] OnLeavePatrolling
|
||||
-- @param #AI_AIR_PATROL self
|
||||
-- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnEnter Transition Handler for State Patrolling.
|
||||
-- @function [parent=#AI_AIR_PATROL] OnEnterPatrolling
|
||||
-- @param #AI_AIR_PATROL self
|
||||
-- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
self:AddTransition( "Patrolling", "PatrolRoute", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_AIR_PATROL.
|
||||
|
||||
--- OnBefore Transition Handler for Event PatrolRoute.
|
||||
-- @function [parent=#AI_AIR_PATROL] OnBeforePatrolRoute
|
||||
-- @param #AI_AIR_PATROL self
|
||||
-- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event PatrolRoute.
|
||||
-- @function [parent=#AI_AIR_PATROL] OnAfterPatrolRoute
|
||||
-- @param #AI_AIR_PATROL self
|
||||
-- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
--- Synchronous Event Trigger for Event PatrolRoute.
|
||||
-- @function [parent=#AI_AIR_PATROL] PatrolRoute
|
||||
-- @param #AI_AIR_PATROL self
|
||||
|
||||
--- Asynchronous Event Trigger for Event PatrolRoute.
|
||||
-- @function [parent=#AI_AIR_PATROL] __PatrolRoute
|
||||
-- @param #AI_AIR_PATROL self
|
||||
-- @param #number Delay The delay in seconds.
|
||||
|
||||
|
||||
self:AddTransition( "*", "Reset", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_AIR_PATROL.
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Set the Engage Range when the AI will engage with airborne enemies.
|
||||
-- @param #AI_AIR_PATROL self
|
||||
-- @param #number EngageRange The Engage Range.
|
||||
-- @return #AI_AIR_PATROL self
|
||||
function AI_AIR_PATROL:SetEngageRange( EngageRange )
|
||||
self:F2()
|
||||
|
||||
if EngageRange then
|
||||
self.EngageRange = EngageRange
|
||||
else
|
||||
self.EngageRange = nil
|
||||
end
|
||||
end
|
||||
|
||||
--- Set race track parameters. CAP flights will perform race track patterns rather than randomly patrolling the zone.
|
||||
-- @param #AI_AIR_PATROL self
|
||||
-- @param #number LegMin Min Length of the race track leg in meters. Default 10,000 m.
|
||||
-- @param #number LegMax Max length of the race track leg in meters. Default 15,000 m.
|
||||
-- @param #number HeadingMin Min heading of the race track in degrees. Default 0 deg, i.e. from South to North.
|
||||
-- @param #number HeadingMax Max heading of the race track in degrees. Default 180 deg, i.e. from South to North.
|
||||
-- @param #number DurationMin (Optional) Min duration before switching the orbit position. Default is keep same orbit until RTB or engage.
|
||||
-- @param #number DurationMax (Optional) Max duration before switching the orbit position. Default is keep same orbit until RTB or engage.
|
||||
-- @param #table CapCoordinates Table of coordinates of first race track point. Second point is determined by leg length and heading.
|
||||
-- @return #AI_AIR_PATROL self
|
||||
function AI_AIR_PATROL:SetRaceTrackPattern(LegMin, LegMax, HeadingMin, HeadingMax, DurationMin, DurationMax, CapCoordinates)
|
||||
|
||||
self.racetrack=true
|
||||
self.racetracklegmin=LegMin or 10000
|
||||
self.racetracklegmax=LegMax or 15000
|
||||
self.racetrackheadingmin=HeadingMin or 0
|
||||
self.racetrackheadingmax=HeadingMax or 180
|
||||
self.racetrackdurationmin=DurationMin
|
||||
self.racetrackdurationmax=DurationMax
|
||||
|
||||
if self.racetrackdurationmax and not self.racetrackdurationmin then
|
||||
self.racetrackdurationmin=self.racetrackdurationmax
|
||||
end
|
||||
|
||||
self.racetrackcapcoordinates=CapCoordinates
|
||||
|
||||
end
|
||||
|
||||
|
||||
|
||||
--- Defines a new patrol route using the @{Process_PatrolZone} parameters and settings.
|
||||
-- @param #AI_AIR_PATROL self
|
||||
-- @return #AI_AIR_PATROL self
|
||||
-- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
function AI_AIR_PATROL:onafterPatrol( AIPatrol, From, Event, To )
|
||||
self:F2()
|
||||
|
||||
self:ClearTargetDistance()
|
||||
|
||||
self:__PatrolRoute( self.TaskDelay )
|
||||
|
||||
AIPatrol:OnReSpawn(
|
||||
function( PatrolGroup )
|
||||
self:__Reset( self.TaskDelay )
|
||||
self:__PatrolRoute( self.TaskDelay )
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
--- This statis method is called from the route path within the last task at the last waaypoint of the AIPatrol.
|
||||
-- Note that this method is required, as triggers the next route when patrolling for the AIPatrol.
|
||||
-- @param Wrapper.Group#GROUP AIPatrol The AI group.
|
||||
-- @param #AI_AIR_PATROL Fsm The FSM.
|
||||
function AI_AIR_PATROL.___PatrolRoute( AIPatrol, Fsm )
|
||||
|
||||
AIPatrol:F( { "AI_AIR_PATROL.___PatrolRoute:", AIPatrol:GetName() } )
|
||||
|
||||
if AIPatrol and AIPatrol:IsAlive() then
|
||||
Fsm:PatrolRoute()
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Defines a new patrol route using the @{Process_PatrolZone} parameters and settings.
|
||||
-- @param #AI_AIR_PATROL self
|
||||
-- @param Wrapper.Group#GROUP AIPatrol The Group managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
function AI_AIR_PATROL:onafterPatrolRoute( AIPatrol, From, Event, To )
|
||||
|
||||
self:F2()
|
||||
|
||||
-- When RTB, don't allow anymore the routing.
|
||||
if From == "RTB" then
|
||||
return
|
||||
end
|
||||
|
||||
|
||||
if AIPatrol and AIPatrol:IsAlive() then
|
||||
|
||||
local PatrolRoute = {}
|
||||
|
||||
--- Calculate the target route point.
|
||||
|
||||
local CurrentCoord = AIPatrol:GetCoordinate()
|
||||
|
||||
local altitude= math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude )
|
||||
|
||||
local ToTargetCoord = self.PatrolZone:GetRandomPointVec2()
|
||||
ToTargetCoord:SetAlt( altitude )
|
||||
self:SetTargetDistance( ToTargetCoord ) -- For RTB status check
|
||||
|
||||
local ToTargetSpeed = math.random( self.PatrolMinSpeed, self.PatrolMaxSpeed )
|
||||
local speedkmh=ToTargetSpeed
|
||||
|
||||
local FromWP = CurrentCoord:WaypointAir(self.PatrolAltType or "RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, ToTargetSpeed, true)
|
||||
PatrolRoute[#PatrolRoute+1] = FromWP
|
||||
|
||||
if self.racetrack then
|
||||
|
||||
-- Random heading.
|
||||
local heading = math.random(self.racetrackheadingmin, self.racetrackheadingmax)
|
||||
|
||||
-- Random leg length.
|
||||
local leg=math.random(self.racetracklegmin, self.racetracklegmax)
|
||||
|
||||
-- Random duration if any.
|
||||
local duration = self.racetrackdurationmin
|
||||
if self.racetrackdurationmax then
|
||||
duration=math.random(self.racetrackdurationmin, self.racetrackdurationmax)
|
||||
end
|
||||
|
||||
-- CAP coordinate.
|
||||
local c0=self.PatrolZone:GetRandomCoordinate()
|
||||
if self.racetrackcapcoordinates and #self.racetrackcapcoordinates>0 then
|
||||
c0=self.racetrackcapcoordinates[math.random(#self.racetrackcapcoordinates)]
|
||||
end
|
||||
|
||||
-- Race track points.
|
||||
local c1=c0:SetAltitude(altitude) --Core.Point#COORDINATE
|
||||
local c2=c1:Translate(leg, heading):SetAltitude(altitude)
|
||||
|
||||
self:SetTargetDistance(c0) -- For RTB status check
|
||||
|
||||
-- Debug:
|
||||
self:T(string.format("Patrol zone race track: v=%.1f knots, h=%.1f ft, heading=%03d, leg=%d m, t=%s sec", UTILS.KmphToKnots(speedkmh), UTILS.MetersToFeet(altitude), heading, leg, tostring(duration)))
|
||||
--c1:MarkToAll("Race track c1")
|
||||
--c2:MarkToAll("Race track c2")
|
||||
|
||||
-- Task to orbit.
|
||||
local taskOrbit=AIPatrol:TaskOrbit(c1, altitude, UTILS.KmphToMps(speedkmh), c2)
|
||||
|
||||
-- Task function to redo the patrol at other random position.
|
||||
local taskPatrol=AIPatrol:TaskFunction("AI_AIR_PATROL.___PatrolRoute", self)
|
||||
|
||||
-- Controlled task with task condition.
|
||||
local taskCond=AIPatrol:TaskCondition(nil, nil, nil, nil, duration, nil)
|
||||
local taskCont=AIPatrol:TaskControlled(taskOrbit, taskCond)
|
||||
|
||||
-- Second waypoint
|
||||
PatrolRoute[2]=c1:WaypointAirTurningPoint(self.PatrolAltType, speedkmh, {taskCont, taskPatrol}, "CAP Orbit")
|
||||
|
||||
else
|
||||
|
||||
--- Create a route point of type air.
|
||||
local ToWP = ToTargetCoord:WaypointAir(self.PatrolAltType, POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, ToTargetSpeed, true)
|
||||
PatrolRoute[#PatrolRoute+1] = ToWP
|
||||
|
||||
local Tasks = {}
|
||||
Tasks[#Tasks+1] = AIPatrol:TaskFunction("AI_AIR_PATROL.___PatrolRoute", self)
|
||||
PatrolRoute[#PatrolRoute].task = AIPatrol:TaskCombo( Tasks )
|
||||
|
||||
end
|
||||
|
||||
AIPatrol:OptionROEReturnFire()
|
||||
AIPatrol:OptionROTEvadeFire()
|
||||
|
||||
AIPatrol:Route( PatrolRoute, self.TaskDelay )
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- @param Wrapper.Group#GROUP AIPatrol
|
||||
function AI_AIR_PATROL.Resume( AIPatrol, Fsm )
|
||||
|
||||
AIPatrol:F( { "AI_AIR_PATROL.Resume:", AIPatrol:GetName() } )
|
||||
if AIPatrol and AIPatrol:IsAlive() then
|
||||
Fsm:__Reset( Fsm.TaskDelay )
|
||||
Fsm:__PatrolRoute( Fsm.TaskDelay )
|
||||
end
|
||||
|
||||
end
|
||||
289
Moose Development/Moose/AI/AI_Air_Squadron.lua
Normal file
289
Moose Development/Moose/AI/AI_Air_Squadron.lua
Normal file
@@ -0,0 +1,289 @@
|
||||
--- **AI** - Models squadrons for airplanes and helicopters.
|
||||
--
|
||||
-- This is a class used in the @{AI_Air_Dispatcher} and derived dispatcher classes.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module AI.AI_Air_Squadron
|
||||
-- @image MOOSE.JPG
|
||||
|
||||
|
||||
|
||||
--- @type AI_AIR_SQUADRON
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
|
||||
--- Implements the core functions modeling squadrons for airplanes and helicopters.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @field #AI_AIR_SQUADRON
|
||||
AI_AIR_SQUADRON = {
|
||||
ClassName = "AI_AIR_SQUADRON",
|
||||
}
|
||||
|
||||
|
||||
|
||||
--- Creates a new AI_AIR_SQUADRON object
|
||||
-- @param #AI_AIR_SQUADRON self
|
||||
-- @return #AI_AIR_SQUADRON
|
||||
function AI_AIR_SQUADRON:New( SquadronName, AirbaseName, TemplatePrefixes, ResourceCount )
|
||||
|
||||
self:I( { Air_Squadron = { SquadronName, AirbaseName, TemplatePrefixes, ResourceCount } } )
|
||||
|
||||
local AI_Air_Squadron = BASE:New() -- #AI_AIR_SQUADRON
|
||||
|
||||
AI_Air_Squadron.Name = SquadronName
|
||||
AI_Air_Squadron.Airbase = AIRBASE:FindByName( AirbaseName )
|
||||
AI_Air_Squadron.AirbaseName = AI_Air_Squadron.Airbase:GetName()
|
||||
if not AI_Air_Squadron.Airbase then
|
||||
error( "Cannot find airbase with name:" .. AirbaseName )
|
||||
end
|
||||
|
||||
AI_Air_Squadron.Spawn = {}
|
||||
if type( TemplatePrefixes ) == "string" then
|
||||
local SpawnTemplate = TemplatePrefixes
|
||||
self.DefenderSpawns[SpawnTemplate] = self.DefenderSpawns[SpawnTemplate] or SPAWN:New( SpawnTemplate ) -- :InitCleanUp( 180 )
|
||||
AI_Air_Squadron.Spawn[1] = self.DefenderSpawns[SpawnTemplate]
|
||||
else
|
||||
for TemplateID, SpawnTemplate in pairs( TemplatePrefixes ) do
|
||||
self.DefenderSpawns[SpawnTemplate] = self.DefenderSpawns[SpawnTemplate] or SPAWN:New( SpawnTemplate ) -- :InitCleanUp( 180 )
|
||||
AI_Air_Squadron.Spawn[#AI_Air_Squadron.Spawn+1] = self.DefenderSpawns[SpawnTemplate]
|
||||
end
|
||||
end
|
||||
AI_Air_Squadron.ResourceCount = ResourceCount
|
||||
AI_Air_Squadron.TemplatePrefixes = TemplatePrefixes
|
||||
AI_Air_Squadron.Captured = false -- Not captured. This flag will be set to true, when the airbase where the squadron is located, is captured.
|
||||
|
||||
self:SetSquadronLanguage( SquadronName, "EN" ) -- Squadrons speak English by default.
|
||||
|
||||
return AI_Air_Squadron
|
||||
end
|
||||
|
||||
--- Set the Name of the Squadron.
|
||||
-- @param #AI_AIR_SQUADRON self
|
||||
-- @param #string Name The Squadron Name.
|
||||
-- @return #AI_AIR_SQUADRON The Squadron.
|
||||
function AI_AIR_SQUADRON:SetName( Name )
|
||||
|
||||
self.Name = Name
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get the Name of the Squadron.
|
||||
-- @param #AI_AIR_SQUADRON self
|
||||
-- @return #string The Squadron Name.
|
||||
function AI_AIR_SQUADRON:GetName()
|
||||
|
||||
return self.Name
|
||||
end
|
||||
|
||||
--- Set the ResourceCount of the Squadron.
|
||||
-- @param #AI_AIR_SQUADRON self
|
||||
-- @param #number ResourceCount The Squadron ResourceCount.
|
||||
-- @return #AI_AIR_SQUADRON The Squadron.
|
||||
function AI_AIR_SQUADRON:SetResourceCount( ResourceCount )
|
||||
|
||||
self.ResourceCount = ResourceCount
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get the ResourceCount of the Squadron.
|
||||
-- @param #AI_AIR_SQUADRON self
|
||||
-- @return #number The Squadron ResourceCount.
|
||||
function AI_AIR_SQUADRON:GetResourceCount()
|
||||
|
||||
return self.ResourceCount
|
||||
end
|
||||
|
||||
--- Add Resources to the Squadron.
|
||||
-- @param #AI_AIR_SQUADRON self
|
||||
-- @param #number Resources The Resources to be added.
|
||||
-- @return #AI_AIR_SQUADRON The Squadron.
|
||||
function AI_AIR_SQUADRON:AddResources( Resources )
|
||||
|
||||
self.ResourceCount = self.ResourceCount + Resources
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Remove Resources to the Squadron.
|
||||
-- @param #AI_AIR_SQUADRON self
|
||||
-- @param #number Resources The Resources to be removed.
|
||||
-- @return #AI_AIR_SQUADRON The Squadron.
|
||||
function AI_AIR_SQUADRON:RemoveResources( Resources )
|
||||
|
||||
self.ResourceCount = self.ResourceCount - Resources
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set the Overhead of the Squadron.
|
||||
-- @param #AI_AIR_SQUADRON self
|
||||
-- @param #number Overhead The Squadron Overhead.
|
||||
-- @return #AI_AIR_SQUADRON The Squadron.
|
||||
function AI_AIR_SQUADRON:SetOverhead( Overhead )
|
||||
|
||||
self.Overhead = Overhead
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get the Overhead of the Squadron.
|
||||
-- @param #AI_AIR_SQUADRON self
|
||||
-- @return #number The Squadron Overhead.
|
||||
function AI_AIR_SQUADRON:GetOverhead()
|
||||
|
||||
return self.Overhead
|
||||
end
|
||||
|
||||
--- Set the Grouping of the Squadron.
|
||||
-- @param #AI_AIR_SQUADRON self
|
||||
-- @param #number Grouping The Squadron Grouping.
|
||||
-- @return #AI_AIR_SQUADRON The Squadron.
|
||||
function AI_AIR_SQUADRON:SetGrouping( Grouping )
|
||||
|
||||
self.Grouping = Grouping
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get the Grouping of the Squadron.
|
||||
-- @param #AI_AIR_SQUADRON self
|
||||
-- @return #number The Squadron Grouping.
|
||||
function AI_AIR_SQUADRON:GetGrouping()
|
||||
|
||||
return self.Grouping
|
||||
end
|
||||
|
||||
--- Set the FuelThreshold of the Squadron.
|
||||
-- @param #AI_AIR_SQUADRON self
|
||||
-- @param #number FuelThreshold The Squadron FuelThreshold.
|
||||
-- @return #AI_AIR_SQUADRON The Squadron.
|
||||
function AI_AIR_SQUADRON:SetFuelThreshold( FuelThreshold )
|
||||
|
||||
self.FuelThreshold = FuelThreshold
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get the FuelThreshold of the Squadron.
|
||||
-- @param #AI_AIR_SQUADRON self
|
||||
-- @return #number The Squadron FuelThreshold.
|
||||
function AI_AIR_SQUADRON:GetFuelThreshold()
|
||||
|
||||
return self.FuelThreshold
|
||||
end
|
||||
|
||||
--- Set the EngageProbability of the Squadron.
|
||||
-- @param #AI_AIR_SQUADRON self
|
||||
-- @param #number EngageProbability The Squadron EngageProbability.
|
||||
-- @return #AI_AIR_SQUADRON The Squadron.
|
||||
function AI_AIR_SQUADRON:SetEngageProbability( EngageProbability )
|
||||
|
||||
self.EngageProbability = EngageProbability
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get the EngageProbability of the Squadron.
|
||||
-- @param #AI_AIR_SQUADRON self
|
||||
-- @return #number The Squadron EngageProbability.
|
||||
function AI_AIR_SQUADRON:GetEngageProbability()
|
||||
|
||||
return self.EngageProbability
|
||||
end
|
||||
|
||||
--- Set the Takeoff of the Squadron.
|
||||
-- @param #AI_AIR_SQUADRON self
|
||||
-- @param #number Takeoff The Squadron Takeoff.
|
||||
-- @return #AI_AIR_SQUADRON The Squadron.
|
||||
function AI_AIR_SQUADRON:SetTakeoff( Takeoff )
|
||||
|
||||
self.Takeoff = Takeoff
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get the Takeoff of the Squadron.
|
||||
-- @param #AI_AIR_SQUADRON self
|
||||
-- @return #number The Squadron Takeoff.
|
||||
function AI_AIR_SQUADRON:GetTakeoff()
|
||||
|
||||
return self.Takeoff
|
||||
end
|
||||
|
||||
--- Set the Landing of the Squadron.
|
||||
-- @param #AI_AIR_SQUADRON self
|
||||
-- @param #number Landing The Squadron Landing.
|
||||
-- @return #AI_AIR_SQUADRON The Squadron.
|
||||
function AI_AIR_SQUADRON:SetLanding( Landing )
|
||||
|
||||
self.Landing = Landing
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get the Landing of the Squadron.
|
||||
-- @param #AI_AIR_SQUADRON self
|
||||
-- @return #number The Squadron Landing.
|
||||
function AI_AIR_SQUADRON:GetLanding()
|
||||
|
||||
return self.Landing
|
||||
end
|
||||
|
||||
--- Set the TankerName of the Squadron.
|
||||
-- @param #AI_AIR_SQUADRON self
|
||||
-- @param #string TankerName The Squadron Tanker Name.
|
||||
-- @return #AI_AIR_SQUADRON The Squadron.
|
||||
function AI_AIR_SQUADRON:SetTankerName( TankerName )
|
||||
|
||||
self.TankerName = TankerName
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get the Tanker Name of the Squadron.
|
||||
-- @param #AI_AIR_SQUADRON self
|
||||
-- @return #string The Squadron Tanker Name.
|
||||
function AI_AIR_SQUADRON:GetTankerName()
|
||||
|
||||
return self.TankerName
|
||||
end
|
||||
|
||||
|
||||
--- Set the Radio of the Squadron.
|
||||
-- @param #AI_AIR_SQUADRON self
|
||||
-- @param #number RadioFrequency The frequency of communication.
|
||||
-- @param #number RadioModulation The modulation of communication.
|
||||
-- @param #number RadioPower The power in Watts of communication.
|
||||
-- @param #string Language The language of the radio speech.
|
||||
-- @return #AI_AIR_SQUADRON The Squadron.
|
||||
function AI_AIR_SQUADRON:SetRadio( RadioFrequency, RadioModulation, RadioPower, Language )
|
||||
|
||||
self.RadioFrequency = RadioFrequency
|
||||
self.RadioModulation = RadioModulation or radio.modulation.AM
|
||||
self.RadioPower = RadioPower or 100
|
||||
|
||||
if self.RadioSpeech then
|
||||
self.RadioSpeech:Stop()
|
||||
end
|
||||
|
||||
self.RadioSpeech = nil
|
||||
|
||||
self.RadioSpeech = RADIOSPEECH:New( RadioFrequency, RadioModulation )
|
||||
self.RadioSpeech.power = RadioPower
|
||||
self.RadioSpeech:Start( 0.5 )
|
||||
|
||||
self.RadioSpeech:SetLanguage( Language )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
@@ -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 treshold has been reached by the AI, or when the AI is commanded to RTB.
|
||||
-- This cycle will continue until a fuel or damage threshold 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 treshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
|
||||
-- When the fuel threshold 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 tresholds have been reached, the AI will RTB.
|
||||
-- * **Status**: The AI is checking status (fuel and damage). When the thresholds 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 Targetting
|
||||
self:__Target( -2 ) -- Start targeting
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release/AIB%20-%20AI%20Balancing)
|
||||
-- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/AIB%20-%20AI%20Balancing)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
@@ -196,8 +196,12 @@ function AI_BALANCER:onenterDestroying( SetGroup, From, Event, To, ClientName, A
|
||||
SetGroup:Flush( self )
|
||||
end
|
||||
|
||||
--- @param #AI_BALANCER self
|
||||
--- RTB
|
||||
-- @param #AI_BALANCER self
|
||||
-- @param Core.Set#SET_GROUP SetGroup
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param Wrapper.Group#GROUP AIGroup
|
||||
function AI_BALANCER:onenterReturning( SetGroup, From, Event, To, AIGroup )
|
||||
|
||||
@@ -213,10 +217,13 @@ function AI_BALANCER:onenterReturning( SetGroup, From, Event, To, AIGroup )
|
||||
local PointVec2 = POINT_VEC2:New( AIGroup:GetVec2().x, AIGroup:GetVec2().y )
|
||||
local ClosestAirbase = self.ReturnAirbaseSet:FindNearestAirbaseFromPointVec2( PointVec2 )
|
||||
self:T( ClosestAirbase.AirbaseName )
|
||||
--[[
|
||||
AIGroup:MessageToRed( "Returning to " .. ClosestAirbase:GetName().. " ...", 30 )
|
||||
local RTBRoute = AIGroup:RouteReturnToAirbase( ClosestAirbase )
|
||||
AIGroupTemplate.route = RTBRoute
|
||||
AIGroup:Respawn( AIGroupTemplate )
|
||||
]]
|
||||
AIGroup:RouteRTB(ClosestAirbase)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -4,13 +4,13 @@
|
||||
--
|
||||
-- * Patrol AI airplanes within a given zone.
|
||||
-- * Trigger detected events when enemy airplanes are detected.
|
||||
-- * Manage a fuel treshold to RTB on time.
|
||||
-- * Manage a fuel threshold to RTB on time.
|
||||
-- * Engage the enemy when detected.
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release/CAP%20-%20Combat%20Air%20Patrol)
|
||||
-- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/CAP%20-%20Combat%20Air%20Patrol)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
@@ -65,8 +65,8 @@
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- 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.
|
||||
-- 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.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
@@ -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 tresholds have been reached, the AI will RTB.
|
||||
-- * **Status** ( Group ): The AI is checking status (fuel and damage). When the thresholds have been reached, the AI will RTB.
|
||||
--
|
||||
-- ## 3. Set the Range of Engagement
|
||||
--
|
||||
@@ -421,15 +421,19 @@ end
|
||||
-- @param #string To The To State string.
|
||||
function AI_CAP_ZONE:onafterEngage( Controllable, From, Event, To )
|
||||
|
||||
if Controllable:IsAlive() then
|
||||
if Controllable and Controllable:IsAlive() then
|
||||
|
||||
local EngageRoute = {}
|
||||
|
||||
--- 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 -- flight dead at this point
|
||||
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(
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release/CAS%20-%20Close%20Air%20Support)
|
||||
-- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/CAS%20-%20Close%20Air%20Support)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
@@ -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 treshold has been reached by the AI, or when the AI is commanded to RTB.
|
||||
-- This cycle will continue until a fuel or damage threshold 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 treshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
|
||||
-- When the fuel threshold 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 tresholds have been reached, the AI will RTB.
|
||||
-- * **Status**: The AI is checking status (fuel and damage). When the thresholds have been reached, the AI will RTB.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
@@ -460,7 +460,7 @@ function AI_CAS_ZONE:onafterEngage( Controllable, From, Event, To,
|
||||
local CurrentVec2 = self.Controllable:GetVec2()
|
||||
|
||||
--TODO: 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 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 Targetting
|
||||
self:__Target( -2 ) -- Start targeting
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
--- **AI** -- (R2.4) - Models the intelligent transportation of infantry and other cargo.
|
||||
--- **AI** - Models the intelligent transportation of infantry and other cargo.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
@@ -35,10 +35,9 @@ AI_CARGO = {
|
||||
|
||||
--- Creates a new AI_CARGO object.
|
||||
-- @param #AI_CARGO self
|
||||
-- @param Wrapper.Group#GROUP Carrier
|
||||
-- @param Core.Set#SET_CARGO CargoSet
|
||||
-- @param #number CombatRadius
|
||||
-- @return #AI_CARGO
|
||||
-- @param Wrapper.Group#GROUP Carrier Cargo carrier group.
|
||||
-- @param Core.Set#SET_CARGO CargoSet Set of cargo(s) to transport.
|
||||
-- @return #AI_CARGO self
|
||||
function AI_CARGO:New( Carrier, CargoSet )
|
||||
|
||||
local self = BASE:Inherit( self, FSM_CONTROLLABLE:New( Carrier ) ) -- #AI_CARGO
|
||||
@@ -48,18 +47,21 @@ function AI_CARGO:New( Carrier, CargoSet )
|
||||
|
||||
self:SetStartState( "Unloaded" )
|
||||
|
||||
self:AddTransition( "Unloaded", "Pickup", "*" )
|
||||
self:AddTransition( "Loaded", "Deploy", "*" )
|
||||
-- Board
|
||||
self:AddTransition( "Unloaded", "Pickup", "Unloaded" )
|
||||
self:AddTransition( "*", "Load", "*" )
|
||||
self:AddTransition( "*", "Reload", "*" )
|
||||
self:AddTransition( "*", "Board", "*" )
|
||||
self:AddTransition( "*", "Loaded", "Loaded" )
|
||||
self:AddTransition( "Loaded", "PickedUp", "Loaded" )
|
||||
|
||||
self:AddTransition( "*", "Load", "Boarding" )
|
||||
self:AddTransition( { "Boarding", "Loaded" }, "Board", "Boarding" )
|
||||
self:AddTransition( "Boarding", "Loaded", "Boarding" )
|
||||
self:AddTransition( "Boarding", "PickedUp", "Loaded" )
|
||||
|
||||
self:AddTransition( "Loaded", "Unload", "Unboarding" )
|
||||
self:AddTransition( "Unboarding", "Unboard", "Unboarding" )
|
||||
self:AddTransition( "Unboarding", "Unloaded", "Unboarding" )
|
||||
self:AddTransition( "Unboarding", "Deployed", "Unloaded" )
|
||||
-- Unload
|
||||
self:AddTransition( "Loaded", "Deploy", "*" )
|
||||
self:AddTransition( "*", "Unload", "*" )
|
||||
self:AddTransition( "*", "Unboard", "*" )
|
||||
self:AddTransition( "*", "Unloaded", "Unloaded" )
|
||||
self:AddTransition( "Unloaded", "Deployed", "Unloaded" )
|
||||
|
||||
|
||||
--- Pickup Handler OnBefore for AI_CARGO
|
||||
-- @function [parent=#AI_CARGO] OnBeforePickup
|
||||
@@ -141,6 +143,17 @@ function AI_CARGO:New( Carrier, CargoSet )
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
|
||||
--- On after Deployed event.
|
||||
-- @function [parent=#AI_CARGO] OnAfterDeployed
|
||||
-- @param #AI_CARGO self
|
||||
-- @param Wrapper.Group#GROUP Carrier
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE.
|
||||
-- @param #boolean Defend Defend for APCs.
|
||||
|
||||
|
||||
for _, CarrierUnit in pairs( Carrier:GetUnits() ) do
|
||||
local CarrierUnit = CarrierUnit -- Wrapper.Unit#UNIT
|
||||
@@ -256,9 +269,10 @@ function AI_CARGO:onbeforeLoad( Carrier, From, Event, To, PickupZone )
|
||||
self:F( { "In radius", CarrierUnit:GetName() } )
|
||||
|
||||
local CargoWeight = Cargo:GetWeight()
|
||||
local CarrierSpace=Carrier_Weight[CarrierUnit]
|
||||
|
||||
-- Only when there is space within the bay to load the next cargo item!
|
||||
if Carrier_Weight[CarrierUnit] > CargoWeight then --and CargoBayFreeVolume > CargoVolume then
|
||||
if CarrierSpace > CargoWeight then
|
||||
Carrier:RouteStop()
|
||||
--Cargo:Ungroup()
|
||||
Cargo:__Board( -LoadDelay, CarrierUnit )
|
||||
@@ -275,6 +289,8 @@ function AI_CARGO:onbeforeLoad( Carrier, From, Event, To, PickupZone )
|
||||
|
||||
-- Ok, we loaded a cargo, now we can stop the loop.
|
||||
break
|
||||
else
|
||||
self:T(string.format("WARNING: Cargo too heavy for carrier %s. Cargo=%.1f > %.1f free space", tostring(CarrierUnit:GetName()), CargoWeight, CarrierSpace))
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -554,6 +570,7 @@ end
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE.
|
||||
-- @param #boolean Defend Defend for APCs.
|
||||
function AI_CARGO:onafterDeployed( Carrier, From, Event, To, DeployZone, Defend )
|
||||
self:F( { Carrier, From, Event, To, DeployZone = DeployZone, Defend = Defend } )
|
||||
|
||||
@@ -565,4 +582,3 @@ function AI_CARGO:onafterDeployed( Carrier, From, Event, To, DeployZone, Defend
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
--- **AI** -- (R2.4) - Models the intelligent transportation of infantry and other cargo.
|
||||
--- **AI** - Models the intelligent transportation of cargo using ground vehicles.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
@@ -98,7 +98,8 @@ function AI_CARGO_APC:New( APC, CargoSet, CombatRadius )
|
||||
self:AddTransition( "*", "Guard", "Unloaded" )
|
||||
self:AddTransition( "*", "Home", "*" )
|
||||
self:AddTransition( "*", "Reload", "Boarding" )
|
||||
|
||||
self:AddTransition( "*", "Deployed", "*" )
|
||||
self:AddTransition( "*", "PickedUp", "*" )
|
||||
self:AddTransition( "*", "Destroyed", "Destroyed" )
|
||||
|
||||
self:SetCombatRadius( CombatRadius )
|
||||
@@ -157,6 +158,45 @@ function AI_CARGO_APC:SetCarrier( CargoCarrier )
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set whether or not the carrier will use roads to *pickup* and *deploy* the cargo.
|
||||
-- @param #AI_CARGO_APC self
|
||||
-- @param #boolean Offroad If true, carrier will not use roads. If `nil` or `false` the carrier will use roads when available.
|
||||
-- @param #number Formation Offroad formation used. Default is `ENUMS.Formation.Vehicle.Offroad`.
|
||||
-- @return #AI_CARGO_APC self
|
||||
function AI_CARGO_APC:SetOffRoad(Offroad, Formation)
|
||||
|
||||
self:SetPickupOffRoad(Offroad, Formation)
|
||||
self:SetDeployOffRoad(Offroad, Formation)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set whether the carrier will *not* use roads to *pickup* the cargo.
|
||||
-- @param #AI_CARGO_APC self
|
||||
-- @param #boolean Offroad If true, carrier will not use roads.
|
||||
-- @param #number Formation Offroad formation used. Default is `ENUMS.Formation.Vehicle.Offroad`.
|
||||
-- @return #AI_CARGO_APC self
|
||||
function AI_CARGO_APC:SetPickupOffRoad(Offroad, Formation)
|
||||
|
||||
self.pickupOffroad=Offroad
|
||||
self.pickupFormation=Formation or ENUMS.Formation.Vehicle.OffRoad
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set whether the carrier will *not* use roads to *deploy* the cargo.
|
||||
-- @param #AI_CARGO_APC self
|
||||
-- @param #boolean Offroad If true, carrier will not use roads.
|
||||
-- @param #number Formation Offroad formation used. Default is `ENUMS.Formation.Vehicle.Offroad`.
|
||||
-- @return #AI_CARGO_APC self
|
||||
function AI_CARGO_APC:SetDeployOffRoad(Offroad, Formation)
|
||||
|
||||
self.deployOffroad=Offroad
|
||||
self.deployFormation=Formation or ENUMS.Formation.Vehicle.OffRoad
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Find a free Carrier within a radius.
|
||||
-- @param #AI_CARGO_APC self
|
||||
@@ -350,10 +390,13 @@ function AI_CARGO_APC:onafterFollow( APC, From, Event, To )
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- @param #AI_CARGO_APC
|
||||
-- @param Wrapper.Group#GROUP APC
|
||||
function AI_CARGO_APC._Pickup( APC, self, Coordinate, Speed, PickupZone )
|
||||
--- Pickup task function. Triggers Load event.
|
||||
-- @param Wrapper.Group#GROUP APC The cargo carrier group.
|
||||
-- @param #AI_CARGO_APC sel `AI_CARGO_APC` class.
|
||||
-- @param Core.Point#COORDINATE Coordinate. The coordinate (not used).
|
||||
-- @param #number Speed Speed (not used).
|
||||
-- @param Core.Zone#ZONE PickupZone Pickup zone.
|
||||
function AI_CARGO_APC._Pickup(APC, self, Coordinate, Speed, PickupZone)
|
||||
|
||||
APC:F( { "AI_CARGO_APC._Pickup:", APC:GetName() } )
|
||||
|
||||
@@ -362,8 +405,12 @@ function AI_CARGO_APC._Pickup( APC, self, Coordinate, Speed, PickupZone )
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function AI_CARGO_APC._Deploy( APC, self, Coordinate, DeployZone )
|
||||
--- Deploy task function. Triggers Unload event.
|
||||
-- @param Wrapper.Group#GROUP APC The cargo carrier group.
|
||||
-- @param #AI_CARGO_APC self `AI_CARGO_APC` class.
|
||||
-- @param Core.Point#COORDINATE Coordinate. The coordinate (not used).
|
||||
-- @param Core.Zone#ZONE DeployZone Deploy zone.
|
||||
function AI_CARGO_APC._Deploy(APC, self, Coordinate, DeployZone)
|
||||
|
||||
APC:F( { "AI_CARGO_APC._Deploy:", APC } )
|
||||
|
||||
@@ -392,12 +439,20 @@ function AI_CARGO_APC:onafterPickup( APC, From, Event, To, Coordinate, Speed, He
|
||||
self.RoutePickup = true
|
||||
|
||||
local _speed=Speed or APC:GetSpeedMax()*0.5
|
||||
|
||||
-- Route on road.
|
||||
local Waypoints = {}
|
||||
|
||||
if self.pickupOffroad then
|
||||
Waypoints[1]=APC:GetCoordinate():WaypointGround(Speed, self.pickupFormation)
|
||||
Waypoints[2]=Coordinate:WaypointGround(_speed, self.pickupFormation, DCSTasks)
|
||||
else
|
||||
Waypoints=APC:TaskGroundOnRoad(Coordinate, _speed, ENUMS.Formation.Vehicle.OffRoad, true)
|
||||
end
|
||||
|
||||
local Waypoints = APC:TaskGroundOnRoad( Coordinate, _speed, "Line abreast", true )
|
||||
|
||||
local TaskFunction = APC:TaskFunction( "AI_CARGO_APC._Pickup", self, Coordinate, Speed, PickupZone )
|
||||
|
||||
self:F({Waypoints = Waypoints})
|
||||
|
||||
local Waypoint = Waypoints[#Waypoints]
|
||||
APC:SetTaskWaypoint( Waypoint, TaskFunction ) -- Set for the given Route at Waypoint 2 the TaskRouteToZone.
|
||||
|
||||
@@ -428,18 +483,34 @@ function AI_CARGO_APC:onafterDeploy( APC, From, Event, To, Coordinate, Speed, He
|
||||
|
||||
self.RouteDeploy = true
|
||||
|
||||
local _speed=Speed or APC:GetSpeedMax()*0.5
|
||||
|
||||
local Waypoints = APC:TaskGroundOnRoad( Coordinate, _speed, "Line abreast", true )
|
||||
-- Set speed in km/h.
|
||||
local speedmax=APC:GetSpeedMax()
|
||||
local _speed=Speed or speedmax*0.5
|
||||
_speed=math.min(_speed, speedmax)
|
||||
|
||||
-- Route on road.
|
||||
local Waypoints = {}
|
||||
|
||||
if self.deployOffroad then
|
||||
Waypoints[1]=APC:GetCoordinate():WaypointGround(Speed, self.deployFormation)
|
||||
Waypoints[2]=Coordinate:WaypointGround(_speed, self.deployFormation, DCSTasks)
|
||||
else
|
||||
Waypoints=APC:TaskGroundOnRoad(Coordinate, _speed, ENUMS.Formation.Vehicle.OffRoad, true)
|
||||
end
|
||||
|
||||
-- Task function
|
||||
local TaskFunction = APC:TaskFunction( "AI_CARGO_APC._Deploy", self, Coordinate, DeployZone )
|
||||
|
||||
self:F({Waypoints = Waypoints})
|
||||
-- Last waypoint
|
||||
local Waypoint = Waypoints[#Waypoints]
|
||||
APC:SetTaskWaypoint( Waypoint, TaskFunction ) -- Set for the given Route at Waypoint 2 the TaskRouteToZone.
|
||||
|
||||
-- Set task function
|
||||
APC:SetTaskWaypoint(Waypoint, TaskFunction) -- Set for the given Route at Waypoint 2 the TaskRouteToZone.
|
||||
|
||||
-- Route group
|
||||
APC:Route( Waypoints, 1 ) -- Move after a random seconds to the Route. See the Route method for details.
|
||||
|
||||
-- Call parent function.
|
||||
self:GetParent( self, AI_CARGO_APC ).onafterDeploy( self, APC, From, Event, To, Coordinate, Speed, Height, DeployZone )
|
||||
|
||||
end
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
--- **AI** -- (R2.4) - Models the intelligent transportation of infantry (cargo).
|
||||
--- **AI** - Models the intelligent transportation of cargo using airplanes.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
@@ -76,25 +76,31 @@ function AI_CARGO_AIRPLANE:New( Airplane, CargoSet )
|
||||
--- Pickup Handler OnAfter for AI_CARGO_AIRPLANE
|
||||
-- @function [parent=#AI_CARGO_AIRPLANE] OnAfterPickup
|
||||
-- @param #AI_CARGO_AIRPLANE self
|
||||
-- @param Wrapper.Group#GROUP Airplane Cargo plane.
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param Wrapper.Airbase#AIRBASE Airbase Airbase where troops are picked up.
|
||||
-- @param #number Speed in km/h for travelling to pickup base.
|
||||
-- @param Wrapper.Group#GROUP Airplane Cargo transport plane.
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param Core.Point#COORDINATE Coordinate The coordinate where to pickup stuff.
|
||||
-- @param #number Speed Speed in km/h for travelling to pickup base.
|
||||
-- @param #number Height Height in meters to move to the pickup coordinate.
|
||||
-- @param Core.Zone#ZONE_AIRBASE PickupZone The airbase zone where the cargo will be picked up.
|
||||
|
||||
--- Pickup Trigger for AI_CARGO_AIRPLANE
|
||||
-- @function [parent=#AI_CARGO_AIRPLANE] Pickup
|
||||
-- @param #AI_CARGO_AIRPLANE self
|
||||
-- @param Wrapper.Airbase#AIRBASE Airbase Airbase where troops are picked up.
|
||||
-- @param #number Speed in km/h for travelling to pickup base.
|
||||
-- @param Core.Point#COORDINATE Coordinate The coordinate where to pickup stuff.
|
||||
-- @param #number Speed Speed in km/h for travelling to pickup base.
|
||||
-- @param #number Height Height in meters to move to the pickup coordinate.
|
||||
-- @param Core.Zone#ZONE_AIRBASE PickupZone The airbase zone where the cargo will be picked up.
|
||||
|
||||
--- Pickup Asynchronous Trigger for AI_CARGO_AIRPLANE
|
||||
-- @function [parent=#AI_CARGO_AIRPLANE] __Pickup
|
||||
-- @param #AI_CARGO_AIRPLANE self
|
||||
-- @param #number Delay Delay in seconds.
|
||||
-- @param Wrapper.Airbase#AIRBASE Airbase Airbase where troops are picked up.
|
||||
-- @param #number Speed in km/h for travelling to pickup base.
|
||||
-- @param Core.Point#COORDINATE Coordinate The coordinate where to pickup stuff.
|
||||
-- @param #number Speed Speed in km/h for travelling to pickup base.
|
||||
-- @param #number Height Height in meters to move to the pickup coordinate.
|
||||
-- @param Core.Zone#ZONE_AIRBASE PickupZone The airbase zone where the cargo will be picked up.
|
||||
|
||||
--- Deploy Handler OnBefore for AI_CARGO_AIRPLANE
|
||||
-- @function [parent=#AI_CARGO_AIRPLANE] OnBeforeDeploy
|
||||
@@ -111,24 +117,30 @@ function AI_CARGO_AIRPLANE:New( Airplane, CargoSet )
|
||||
-- @function [parent=#AI_CARGO_AIRPLANE] OnAfterDeploy
|
||||
-- @param #AI_CARGO_AIRPLANE self
|
||||
-- @param Wrapper.Group#GROUP Airplane Cargo plane.
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param Wrapper.Airbase#AIRBASE Airbase Destination airbase where troops are deployed.
|
||||
-- @param #number Speed Speed in km/h for travelling to deploy base.
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param Core.Point#COORDINATE Coordinate Coordinate where to deploy stuff.
|
||||
-- @param #number Speed Speed in km/h for travelling to the deploy base.
|
||||
-- @param #number Height Height in meters to move to the home coordinate.
|
||||
-- @param Core.Zone#ZONE_AIRBASE DeployZone The airbase zone where the cargo will be deployed.
|
||||
|
||||
--- Deploy Trigger for AI_CARGO_AIRPLANE
|
||||
-- @function [parent=#AI_CARGO_AIRPLANE] Deploy
|
||||
-- @param #AI_CARGO_AIRPLANE self
|
||||
-- @param Wrapper.Airbase#AIRBASE Airbase Destination airbase where troops are deployed.
|
||||
-- @param #number Speed Speed in km/h for travelling to deploy base.
|
||||
-- @param Core.Point#COORDINATE Coordinate Coordinate where to deploy stuff.
|
||||
-- @param #number Speed Speed in km/h for travelling to the deploy base.
|
||||
-- @param #number Height Height in meters to move to the home coordinate.
|
||||
-- @param Core.Zone#ZONE_AIRBASE DeployZone The airbase zone where the cargo will be deployed.
|
||||
|
||||
--- Deploy Asynchronous Trigger for AI_CARGO_AIRPLANE
|
||||
-- @function [parent=#AI_CARGO_AIRPLANE] __Deploy
|
||||
-- @param #AI_CARGO_AIRPLANE self
|
||||
-- @param #number Delay Delay in seconds.
|
||||
-- @param Wrapper.Airbase#AIRBASE Airbase Destination airbase where troops are deployed.
|
||||
-- @param #number Speed Speed in km/h for travelling to deploy base.
|
||||
-- @param Core.Point#COORDINATE Coordinate Coordinate where to deploy stuff.
|
||||
-- @param #number Speed Speed in km/h for travelling to the deploy base.
|
||||
-- @param #number Height Height in meters to move to the home coordinate.
|
||||
-- @param Core.Zone#ZONE_AIRBASE DeployZone The airbase zone where the cargo will be deployed.
|
||||
|
||||
--- On after Loaded event, i.e. triggered when the cargo is inside the carrier.
|
||||
-- @function [parent=#AI_CARGO_AIRPLANE] OnAfterLoaded
|
||||
@@ -137,6 +149,16 @@ function AI_CARGO_AIRPLANE:New( Airplane, CargoSet )
|
||||
-- @param From
|
||||
-- @param Event
|
||||
-- @param To
|
||||
|
||||
|
||||
--- On after Deployed event.
|
||||
-- @function [parent=#AI_CARGO_AIRPLANE] OnAfterDeployed
|
||||
-- @param #AI_CARGO_AIRPLANE self
|
||||
-- @param Wrapper.Group#GROUP Airplane Cargo plane.
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed.
|
||||
|
||||
-- Set carrier.
|
||||
self:SetCarrier( Airplane )
|
||||
@@ -259,15 +281,17 @@ end
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param Core.Point#COORDINATE Coordinate
|
||||
-- @param #number Speed in km/h for travelling to pickup base.
|
||||
-- @param Core.Point#COORDINATE Coordinate The coordinate where to pickup stuff.
|
||||
-- @param #number Speed Speed in km/h for travelling to pickup base.
|
||||
-- @param #number Height Height in meters to move to the pickup coordinate.
|
||||
-- @param Core.Zone#ZONE_AIRBASE (optional) PickupZone The zone where the cargo will be picked up.
|
||||
-- @param Core.Zone#ZONE_AIRBASE PickupZone The airbase zone where the cargo will be picked up.
|
||||
function AI_CARGO_AIRPLANE:onafterPickup( Airplane, From, Event, To, Coordinate, Speed, Height, PickupZone )
|
||||
|
||||
if Airplane and Airplane:IsAlive() then
|
||||
|
||||
self.PickupZone = PickupZone
|
||||
local airbasepickup=Coordinate:GetClosestAirbase()
|
||||
|
||||
self.PickupZone = PickupZone or ZONE_AIRBASE:New(airbasepickup:GetName())
|
||||
|
||||
-- Get closest airbase of current position.
|
||||
local ClosestAirbase, DistToAirbase=Airplane:GetCoordinate():GetClosestAirbase()
|
||||
@@ -280,11 +304,10 @@ function AI_CARGO_AIRPLANE:onafterPickup( Airplane, From, Event, To, Coordinate,
|
||||
end
|
||||
|
||||
-- Set pickup airbase.
|
||||
local Airbase = PickupZone:GetAirbase()
|
||||
local Airbase = self.PickupZone:GetAirbase()
|
||||
|
||||
-- Distance from closest to pickup airbase ==> we need to know if we are already at the pickup airbase.
|
||||
local Dist = Airbase:GetCoordinate():Get2DDistance(ClosestAirbase:GetCoordinate())
|
||||
--env.info("Distance closest to pickup airbase = "..Dist)
|
||||
|
||||
if Airplane:InAir() or Dist>500 then
|
||||
|
||||
@@ -305,7 +328,7 @@ function AI_CARGO_AIRPLANE:onafterPickup( Airplane, From, Event, To, Coordinate,
|
||||
|
||||
end
|
||||
|
||||
self:GetParent( self, AI_CARGO_AIRPLANE ).onafterPickup( self, Airplane, From, Event, To, Coordinate, Speed, Height, PickupZone )
|
||||
self:GetParent( self, AI_CARGO_AIRPLANE ).onafterPickup( self, Airplane, From, Event, To, Coordinate, Speed, Height, self.PickupZone )
|
||||
|
||||
end
|
||||
|
||||
@@ -318,15 +341,19 @@ end
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param Core.Point#COORDINATE Coordinate
|
||||
-- @param #number Speed in km/h for travelling to pickup base.
|
||||
-- @param Core.Point#COORDINATE Coordinate Coordinate where to deploy stuff.
|
||||
-- @param #number Speed Speed in km/h for travelling to the deploy base.
|
||||
-- @param #number Height Height in meters to move to the home coordinate.
|
||||
-- @param Core.Zone#ZONE_AIRBASE DeployZone The zone where the cargo will be deployed.
|
||||
-- @param Core.Zone#ZONE_AIRBASE DeployZone The airbase zone where the cargo will be deployed.
|
||||
function AI_CARGO_AIRPLANE:onafterDeploy( Airplane, From, Event, To, Coordinate, Speed, Height, DeployZone )
|
||||
|
||||
if Airplane and Airplane:IsAlive()~=nil then
|
||||
|
||||
local Airbase = DeployZone:GetAirbase()
|
||||
local Airbase = Coordinate:GetClosestAirbase()
|
||||
|
||||
if DeployZone then
|
||||
Airbase=DeployZone:GetAirbase()
|
||||
end
|
||||
|
||||
-- Activate uncontrolled airplane.
|
||||
if Airplane:IsAlive()==false then
|
||||
@@ -354,6 +381,7 @@ end
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param Core.Zone#ZONE_AIRBASE DeployZone The airbase zone where the cargo will be deployed.
|
||||
function AI_CARGO_AIRPLANE:onafterUnload( Airplane, From, Event, To, DeployZone )
|
||||
|
||||
local UnboardInterval = 10
|
||||
@@ -380,9 +408,6 @@ function AI_CARGO_AIRPLANE:onafterUnload( Airplane, From, Event, To, DeployZone
|
||||
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
--- Route the airplane from one airport or it's current position to another airbase.
|
||||
-- @param #AI_CARGO_AIRPLANE self
|
||||
-- @param Wrapper.Group#GROUP Airplane Airplane group to be routed.
|
||||
@@ -410,14 +435,10 @@ function AI_CARGO_AIRPLANE:Route( Airplane, Airbase, Speed, Height, Uncontrolled
|
||||
|
||||
-- To point.
|
||||
local AirbasePointVec2 = Airbase:GetPointVec2()
|
||||
local ToWaypoint = AirbasePointVec2:WaypointAir(
|
||||
POINT_VEC3.RoutePointAltType.BARO,
|
||||
"Land",
|
||||
"Landing",
|
||||
Speed or Airplane:GetSpeedMax()*0.8
|
||||
)
|
||||
ToWaypoint["airdromeId"] = Airbase:GetID()
|
||||
ToWaypoint["speed_locked"] = true
|
||||
local ToWaypoint = AirbasePointVec2:WaypointAir(POINT_VEC3.RoutePointAltType.BARO, "Land", "Landing", Speed or Airplane:GetSpeedMax()*0.8, true, Airbase)
|
||||
|
||||
--ToWaypoint["airdromeId"] = Airbase:GetID()
|
||||
--ToWaypoint["speed_locked"] = true
|
||||
|
||||
|
||||
-- If self.Airbase~=nil then group is currently at an airbase, where it should be respawned.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
--- **AI** -- (R2.4) - Models the intelligent transportation of infantry and other cargo.
|
||||
--- **AI** - Models the intelligent transportation of infantry and other cargo.
|
||||
--
|
||||
-- ## Features:
|
||||
--
|
||||
@@ -1104,7 +1104,7 @@ function AI_CARGO_DISPATCHER:onafterMonitor()
|
||||
-- The Pickup sequence ...
|
||||
-- Check if this Carrier need to go and Pickup something...
|
||||
-- So, if the cargo bay is not full yet with cargo to be loaded ...
|
||||
self:I( { Carrier = CarrierGroupName, IsRelocating = AI_Cargo:IsRelocating(), IsTransporting = AI_Cargo:IsTransporting() } )
|
||||
self:T( { Carrier = CarrierGroupName, IsRelocating = AI_Cargo:IsRelocating(), IsTransporting = AI_Cargo:IsTransporting() } )
|
||||
if AI_Cargo:IsRelocating() == false and AI_Cargo:IsTransporting() == false then
|
||||
-- ok, so there is a free Carrier
|
||||
-- now find the first cargo that is Unloaded
|
||||
@@ -1151,6 +1151,10 @@ function AI_CARGO_DISPATCHER:onafterMonitor()
|
||||
self.PickupCargo[Carrier] = CargoCoordinate
|
||||
PickupCargo = Cargo
|
||||
break
|
||||
else
|
||||
local text=string.format("WARNING: Cargo %s is too heavy to be loaded into transport. Cargo weight %.1f > %.1f load capacity of carrier %s.",
|
||||
tostring(Cargo:GetName()), Cargo:GetWeight(), LargestLoadCapacity, tostring(Carrier:GetName()))
|
||||
self:I(text)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
--- **AI** -- (2.4) - Models the intelligent transportation of infantry and other cargo using APCs.
|
||||
--- **AI** - Models the intelligent transportation of infantry and other cargo using APCs.
|
||||
--
|
||||
-- ## Features:
|
||||
--
|
||||
@@ -181,25 +181,36 @@ function AI_CARGO_DISPATCHER_APC:New( APCSet, CargoSet, PickupZoneSet, DeployZon
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- AI cargo
|
||||
-- @param #AI_CARGO_DISPATCHER_APC self
|
||||
-- @param Wrapper.Group#GROUP APC The APC carrier.
|
||||
-- @param Core.Set#SET_CARGO CargoSet Cargo set.
|
||||
-- @return AI.AI_Cargo_APC#AI_CARGO_DISPATCHER_APC AI cargo APC object.
|
||||
function AI_CARGO_DISPATCHER_APC:AICargo( APC, CargoSet )
|
||||
|
||||
return AI_CARGO_APC:New( APC, CargoSet, self.CombatRadius )
|
||||
local aicargoapc=AI_CARGO_APC:New(APC, CargoSet, self.CombatRadius)
|
||||
|
||||
aicargoapc:SetDeployOffRoad(self.deployOffroad, self.deployFormation)
|
||||
aicargoapc:SetPickupOffRoad(self.pickupOffroad, self.pickupFormation)
|
||||
|
||||
return aicargoapc
|
||||
end
|
||||
|
||||
--- Enable/Disable unboarding of cargo (infantry) when enemies are nearby (to help defend the carrier).
|
||||
-- This is only valid for APCs and trucks etc, thus ground vehicles.
|
||||
-- @param #AI_CARGO_DISPATCHER_APC self
|
||||
-- @param #number CombatRadius Provide the combat radius to defend the carrier by unboarding the cargo when enemies are nearby.
|
||||
-- When the combat radius is 0, no defense will happen of the carrier.
|
||||
-- When the combat radius is 0 (default), no defense will happen of the carrier.
|
||||
-- When the combat radius is not provided, no defense will happen!
|
||||
-- @return #AI_CARGO_DISPATCHER_APC
|
||||
-- @usage
|
||||
--
|
||||
-- -- Disembark the infantry when the carrier is under attack.
|
||||
-- AICargoDispatcher:SetCombatRadius( true )
|
||||
-- AICargoDispatcher:SetCombatRadius( 500 )
|
||||
--
|
||||
-- -- Keep the cargo in the carrier when the carrier is under attack.
|
||||
-- AICargoDispatcher:SetCombatRadius( false )
|
||||
-- AICargoDispatcher:SetCombatRadius( 0 )
|
||||
function AI_CARGO_DISPATCHER_APC:SetCombatRadius( CombatRadius )
|
||||
|
||||
self.CombatRadius = CombatRadius or 0
|
||||
@@ -207,3 +218,41 @@ function AI_CARGO_DISPATCHER_APC:SetCombatRadius( CombatRadius )
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set whether the carrier will *not* use roads to *pickup* and *deploy* the cargo.
|
||||
-- @param #AI_CARGO_DISPATCHER_APC self
|
||||
-- @param #boolean Offroad If true, carrier will not use roads.
|
||||
-- @param #number Formation Offroad formation used. Default is `ENUMS.Formation.Vehicle.Offroad`.
|
||||
-- @return #AI_CARGO_DISPATCHER_APC self
|
||||
function AI_CARGO_DISPATCHER_APC:SetOffRoad(Offroad, Formation)
|
||||
|
||||
self:SetPickupOffRoad(Offroad, Formation)
|
||||
self:SetDeployOffRoad(Offroad, Formation)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set whether the carrier will *not* use roads to *pickup* the cargo.
|
||||
-- @param #AI_CARGO_DISPATCHER_APC self
|
||||
-- @param #boolean Offroad If true, carrier will not use roads.
|
||||
-- @param #number Formation Offroad formation used. Default is `ENUMS.Formation.Vehicle.Offroad`.
|
||||
-- @return #AI_CARGO_DISPATCHER_APC self
|
||||
function AI_CARGO_DISPATCHER_APC:SetPickupOffRoad(Offroad, Formation)
|
||||
|
||||
self.pickupOffroad=Offroad
|
||||
self.pickupFormation=Formation or ENUMS.Formation.Vehicle.OffRoad
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set whether the carrier will *not* use roads to *deploy* the cargo.
|
||||
-- @param #AI_CARGO_DISPATCHER_APC self
|
||||
-- @param #boolean Offroad If true, carrier will not use roads.
|
||||
-- @param #number Formation Offroad formation used. Default is `ENUMS.Formation.Vehicle.Offroad`.
|
||||
-- @return #AI_CARGO_DISPATCHER_APC self
|
||||
function AI_CARGO_DISPATCHER_APC:SetDeployOffRoad(Offroad, Formation)
|
||||
|
||||
self.deployOffroad=Offroad
|
||||
self.deployFormation=Formation or ENUMS.Formation.Vehicle.OffRoad
|
||||
|
||||
return self
|
||||
end
|
||||
193
Moose Development/Moose/AI/AI_Cargo_Dispatcher_Ship.lua
Normal file
193
Moose Development/Moose/AI/AI_Cargo_Dispatcher_Ship.lua
Normal file
@@ -0,0 +1,193 @@
|
||||
--- **AI** -- (2.5.1) - Models the intelligent transportation of infantry and other cargo using Ships
|
||||
--
|
||||
-- ## Features:
|
||||
--
|
||||
-- * Transport cargo to various deploy zones using naval vehicles.
|
||||
-- * Various @{Cargo.Cargo#CARGO} types can be transported, including infantry, vehicles, and crates.
|
||||
-- * Define a deploy zone of various types to determine the destination of the cargo.
|
||||
-- * Ships will follow shipping lanes as defined in the Mission Editor.
|
||||
-- * Multiple ships can transport multiple cargo as a single group.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ## Test Missions:
|
||||
--
|
||||
-- NEED TO DO
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **acrojason** (derived from AI_Cargo_Dispatcher_APC by FlightControl)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module AI.AI_Cargo_Dispatcher_Ship
|
||||
-- @image AI_Cargo_Dispatching_For_Ship.JPG
|
||||
|
||||
--- @type AI_CARGO_DISPATCHER_SHIP
|
||||
-- @extends AI.AI_Cargo_Dispatcher#AI_CARGO_DISPATCHER
|
||||
|
||||
|
||||
--- A dynamic cargo transportation capability for AI groups.
|
||||
--
|
||||
-- Naval vessels can be mobilized to semi-intelligently transport cargo within the simulation.
|
||||
--
|
||||
-- The AI_CARGO_DISPATCHER_SHIP module is derived from the AI_CARGO_DISPATCHER module.
|
||||
--
|
||||
-- ## Note! In order to fully understand the mechanisms of the AI_CARGO_DISPATCHER_SHIP class, it is recommended that you first consult and READ the documentation of the @{AI.AI_Cargo_Dispatcher} module!!!
|
||||
--
|
||||
-- This will be particularly helpful in order to determine how to **Tailor the different cargo handling events**.
|
||||
--
|
||||
-- The AI_CARGO_DISPATCHER_SHIP class uses the @{Cargo.Cargo} capabilities within the MOOSE framwork.
|
||||
-- Also ensure that you fully understand how to declare and setup Cargo objects within the MOOSE framework before using this class.
|
||||
-- CARGO derived objects must generally be declared within the mission to make the AI_CARGO_DISPATCHER_SHIP object recognize the cargo.
|
||||
--
|
||||
--
|
||||
-- # 1) AI_CARGO_DISPATCHER_SHIP constructor.
|
||||
--
|
||||
-- * @{AI_CARGO_DISPATCHER_SHIP.New}(): Creates a new AI_CARGO_DISPATCHER_SHIP object.
|
||||
--
|
||||
-- ---
|
||||
--
|
||||
-- # 2) AI_CARGO_DISPATCHER_SHIP is a Finite State Machine.
|
||||
--
|
||||
-- This section must be read as follows... Each of the rows indicate a state transition, triggered through an event, and with an ending state of the event was executed.
|
||||
-- The first column is the **From** state, the second column the **Event**, and the third column the **To** state.
|
||||
--
|
||||
-- So, each of the rows have the following structure.
|
||||
--
|
||||
-- * **From** => **Event** => **To**
|
||||
--
|
||||
-- Important to know is that an event can only be executed if the **current state** is the **From** state.
|
||||
-- This, when an **Event** that is being triggered has a **From** state that is equal to the **Current** state of the state machine, the event will be executed,
|
||||
-- and the resulting state will be the **To** state.
|
||||
--
|
||||
-- These are the different possible state transitions of this state machine implementation:
|
||||
--
|
||||
-- * Idle => Start => Monitoring
|
||||
-- * Monitoring => Monitor => Monitoring
|
||||
-- * Monitoring => Stop => Idle
|
||||
--
|
||||
-- * Monitoring => Pickup => Monitoring
|
||||
-- * Monitoring => Load => Monitoring
|
||||
-- * Monitoring => Loading => Monitoring
|
||||
-- * Monitoring => Loaded => Monitoring
|
||||
-- * Monitoring => PickedUp => Monitoring
|
||||
-- * Monitoring => Deploy => Monitoring
|
||||
-- * Monitoring => Unload => Monitoring
|
||||
-- * Monitoring => Unloaded => Monitoring
|
||||
-- * Monitoring => Deployed => Monitoring
|
||||
-- * Monitoring => Home => Monitoring
|
||||
--
|
||||
--
|
||||
-- ## 2.1) AI_CARGO_DISPATCHER States.
|
||||
--
|
||||
-- * **Monitoring**: The process is dispatching.
|
||||
-- * **Idle**: The process is idle.
|
||||
--
|
||||
-- ## 2.2) AI_CARGO_DISPATCHER Events.
|
||||
--
|
||||
-- * **Start**: Start the transport process.
|
||||
-- * **Stop**: Stop the transport process.
|
||||
-- * **Monitor**: Monitor and take action.
|
||||
--
|
||||
-- * **Pickup**: Pickup cargo.
|
||||
-- * **Load**: Load the cargo.
|
||||
-- * **Loading**: The dispatcher is coordinating the loading of a cargo.
|
||||
-- * **Loaded**: Flag that the cargo is loaded.
|
||||
-- * **PickedUp**: The dispatcher has loaded all requested cargo into the CarrierGroup.
|
||||
-- * **Deploy**: Deploy cargo to a location.
|
||||
-- * **Unload**: Unload the cargo.
|
||||
-- * **Unloaded**: Flag that the cargo is unloaded.
|
||||
-- * **Deployed**: All cargo is unloaded from the carriers in the group.
|
||||
-- * **Home**: A Carrier is going home.
|
||||
--
|
||||
-- ## 2.3) Enhance your mission scripts with **Tailored** Event Handling!
|
||||
--
|
||||
-- Within your mission, you can capture these events when triggered, and tailor the events with your own code!
|
||||
-- Check out the @{AI.AI_Cargo_Dispatcher#AI_CARGO_DISPATCHER} class at chapter 3 for details on the different event handlers that are available and how to use them.
|
||||
--
|
||||
-- **There are a lot of templates available that allows you to quickly setup an event handler for a specific event type!**
|
||||
--
|
||||
-- ---
|
||||
--
|
||||
-- # 3) Set the pickup parameters.
|
||||
--
|
||||
-- Several parameters can be set to pickup cargo:
|
||||
--
|
||||
-- * @{#AI_CARGO_DISPATCHER_SHIP.SetPickupRadius}(): Sets or randomizes the pickup location for the Ship around the cargo coordinate in a radius defined an outer and optional inner radius.
|
||||
-- * @{#AI_CARGO_DISPATCHER_SHIP.SetPickupSpeed}(): Set the speed or randomizes the speed in km/h to pickup the cargo.
|
||||
--
|
||||
-- # 4) Set the deploy parameters.
|
||||
--
|
||||
-- Several parameters can be set to deploy cargo:
|
||||
--
|
||||
-- * @{#AI_CARGO_DISPATCHER_SHIP.SetDeployRadius}(): Sets or randomizes the deploy location for the Ship around the cargo coordinate in a radius defined an outer and an optional inner radius.
|
||||
-- * @{#AI_CARGO_DISPATCHER_SHIP.SetDeploySpeed}(): Set the speed or randomizes the speed in km/h to deploy the cargo.
|
||||
--
|
||||
-- # 5) Set the home zone when there isn't any more cargo to pickup.
|
||||
--
|
||||
-- A home zone can be specified to where the Ship will move when there isn't any cargo left for pickup.
|
||||
-- Use @{#AI_CARGO_DISPATCHER_SHIP.SetHomeZone}() to specify the home zone.
|
||||
--
|
||||
-- If no home zone is specified, the Ship will wait near the deploy zone for a new pickup command.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @field #AI_CARGO_DISPATCHER_SHIP
|
||||
AI_CARGO_DISPATCHER_SHIP = {
|
||||
ClassName = "AI_CARGO_DISPATCHER_SHIP"
|
||||
}
|
||||
|
||||
--- Creates a new AI_CARGO_DISPATCHER_SHIP object.
|
||||
-- @param #AI_CARGO_DISPATCHER_SHIP self
|
||||
-- @param Core.Set#SET_GROUP ShipSet The set of @{Wrapper.Group#GROUP} objects of Ships that will transport the cargo
|
||||
-- @param Core.Set#SET_CARGO CargoSet The set of @{Cargo.Cargo#CARGO} objects, which can be CARGO_GROUP, CARGO_CRATE, or CARGO_SLINGLOAD objects.
|
||||
-- @param Core.Set#SET_ZONE PickupZoneSet The set of pickup zones which are used to determine from where the cargo can be picked up by the Ship.
|
||||
-- @param Core.Set#SET_ZONE DeployZoneSet The set of deploy zones which determine where the cargo will be deployed by the Ship.
|
||||
-- @param #table ShippingLane Table containing list of Shipping Lanes to be used
|
||||
-- @return #AI_CARGO_DISPATCHER_SHIP
|
||||
-- @usage
|
||||
--
|
||||
-- -- An AI dispatcher object for a naval group, moving cargo from pickup zones to deploy zones via a predetermined Shipping Lane
|
||||
--
|
||||
-- local SetCargoInfantry = SET_CARGO:New():FilterTypes( "Infantry" ):FilterStart()
|
||||
-- local SetShip = SET_GROUP:New():FilterPrefixes( "Ship" ):FilterStart()
|
||||
-- local SetPickupZones = SET_ZONE:New():FilterPrefixes( "Pickup" ):FilterStart()
|
||||
-- local SetDeployZones = SET_ZONE:New():FilterPrefixes( "Deploy" ):FilterStart()
|
||||
-- NEED MORE THOUGHT - ShippingLane is part of Warehouse.......
|
||||
-- local ShippingLane = GROUP:New():FilterPrefixes( "ShippingLane" ):FilterStart()
|
||||
--
|
||||
-- AICargoDispatcherShip = AI_CARGO_DISPATCHER_SHIP:New( SetShip, SetCargoInfantry, SetPickupZones, SetDeployZones, ShippingLane )
|
||||
-- AICargoDispatcherShip:Start()
|
||||
--
|
||||
function AI_CARGO_DISPATCHER_SHIP:New( ShipSet, CargoSet, PickupZoneSet, DeployZoneSet, ShippingLane )
|
||||
|
||||
local self = BASE:Inherit( self, AI_CARGO_DISPATCHER:New( ShipSet, CargoSet, PickupZoneSet, DeployZoneSet ) )
|
||||
|
||||
self:SetPickupSpeed( 60, 10 )
|
||||
self:SetDeploySpeed( 60, 10 )
|
||||
|
||||
self:SetPickupRadius( 500, 6000 )
|
||||
self:SetDeployRadius( 500, 6000 )
|
||||
|
||||
self:SetPickupHeight( 0, 0 )
|
||||
self:SetDeployHeight( 0, 0 )
|
||||
|
||||
self:SetShippingLane( ShippingLane )
|
||||
|
||||
self:SetMonitorTimeInterval( 600 )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function AI_CARGO_DISPATCHER_SHIP:SetShippingLane( ShippingLane )
|
||||
self.ShippingLane = ShippingLane
|
||||
|
||||
return self
|
||||
|
||||
end
|
||||
|
||||
function AI_CARGO_DISPATCHER_SHIP:AICargo( Ship, CargoSet )
|
||||
|
||||
return AI_CARGO_SHIP:New( Ship, CargoSet, 0, self.ShippingLane )
|
||||
end
|
||||
@@ -1,4 +1,4 @@
|
||||
--- **AI** -- (R2.4) - Models the intelligent transportation of infantry (cargo).
|
||||
--- **AI** - Models the intelligent transportation of cargo using helicopters.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
@@ -33,7 +33,7 @@
|
||||
--
|
||||
-- ## Infantry health.
|
||||
--
|
||||
-- When infantry is unboarded from the APCs, the infantry is actually respawned into the battlefield.
|
||||
-- When infantry is unboarded from the helicopters, the infantry is actually respawned into the battlefield.
|
||||
-- As a result, the unboarding infantry is very _healthy_ every time it unboards.
|
||||
-- This is due to the limitation of the DCS simulator, which is not able to specify the health of new spawned units as a parameter.
|
||||
-- However, infantry that was destroyed when unboarded, won't be respawned again. Destroyed is destroyed.
|
||||
@@ -64,25 +64,24 @@ function AI_CARGO_HELICOPTER:New( Helicopter, CargoSet )
|
||||
self.Zone = ZONE_GROUP:New( Helicopter:GetName(), Helicopter, 300 )
|
||||
|
||||
self:SetStartState( "Unloaded" )
|
||||
-- Boarding
|
||||
self:AddTransition( "Unloaded", "Pickup", "Unloaded" )
|
||||
self:AddTransition( "*", "Landed", "*" )
|
||||
self:AddTransition( "*", "Load", "*" )
|
||||
self:AddTransition( "*", "Loaded", "Loaded" )
|
||||
self:AddTransition( "Loaded", "PickedUp", "Loaded" )
|
||||
|
||||
self:AddTransition( "Unloaded", "Pickup", "*" )
|
||||
self:AddTransition( "Loaded", "Deploy", "*" )
|
||||
-- Unboarding
|
||||
self:AddTransition( "Loaded", "Deploy", "*" )
|
||||
self:AddTransition( "*", "Queue", "*" )
|
||||
self:AddTransition( "*", "Orbit" , "*" )
|
||||
self:AddTransition( "*", "Destroyed", "*" )
|
||||
self:AddTransition( "*", "Unload", "*" )
|
||||
self:AddTransition( "*", "Unloaded", "Unloaded" )
|
||||
self:AddTransition( "Unloaded", "Deployed", "Unloaded" )
|
||||
|
||||
self:AddTransition( { "Unloaded", "Loading" }, "Load", "Boarding" )
|
||||
self:AddTransition( "Boarding", "Board", "Boarding" )
|
||||
self:AddTransition( "Boarding", "Loaded", "Boarding" )
|
||||
self:AddTransition( "Boarding", "PickedUp", "Loaded" )
|
||||
self:AddTransition( "Loaded", "Unload", "Unboarding" )
|
||||
self:AddTransition( "Unboarding", "Unboard", "Unboarding" )
|
||||
self:AddTransition( "Unboarding", "Unloaded", "Unboarding" )
|
||||
self:AddTransition( "Unboarding", "Deployed", "Unloaded" )
|
||||
|
||||
self:AddTransition( "*", "Landed", "*" )
|
||||
self:AddTransition( "*", "Queue", "*" )
|
||||
self:AddTransition( "*", "Orbit" , "*" )
|
||||
-- RTB
|
||||
self:AddTransition( "*", "Home" , "*" )
|
||||
|
||||
self:AddTransition( "*", "Destroyed", "Destroyed" )
|
||||
|
||||
--- Pickup Handler OnBefore for AI_CARGO_HELICOPTER
|
||||
-- @function [parent=#AI_CARGO_HELICOPTER] OnBeforePickup
|
||||
@@ -100,13 +99,31 @@ function AI_CARGO_HELICOPTER:New( Helicopter, CargoSet )
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param Core.Point#COORDINATE Coordinate
|
||||
-- @param #number Speed Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
-- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
|
||||
--- PickedUp Handler OnAfter for AI_CARGO_HELICOPTER - Cargo set has been picked up, ready to deploy
|
||||
-- @function [parent=#AI_CARGO_HELICOPTER] OnAfterPickedUp
|
||||
-- @param #AI_CARGO_HELICOPTER self
|
||||
-- @param Wrapper.Group#GROUP Helicopter The helicopter #GROUP object
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param Wrapper.Unit#UNIT Unit The helicopter #UNIT object
|
||||
|
||||
--- Unloaded Handler OnAfter for AI_CARGO_HELICOPTER - Cargo unloaded, carrier is empty
|
||||
-- @function [parent=#AI_CARGO_HELICOPTER] OnAfterUnloaded
|
||||
-- @param #AI_CARGO_HELICOPTER self
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param Cargo.CargoGroup#CARGO_GROUP Cargo The #CARGO_GROUP object.
|
||||
-- @param Wrapper.Unit#UNIT Unit The helicopter #UNIT object
|
||||
|
||||
--- Pickup Trigger for AI_CARGO_HELICOPTER
|
||||
-- @function [parent=#AI_CARGO_HELICOPTER] Pickup
|
||||
-- @param #AI_CARGO_HELICOPTER self
|
||||
-- @param Core.Point#COORDINATE Coordinate
|
||||
-- @param #number Speed Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
-- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
|
||||
--- Pickup Asynchronous Trigger for AI_CARGO_HELICOPTER
|
||||
-- @function [parent=#AI_CARGO_HELICOPTER] __Pickup
|
||||
@@ -122,7 +139,7 @@ function AI_CARGO_HELICOPTER:New( Helicopter, CargoSet )
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param Core.Point#COORDINATE Coordinate Place at which cargo is deployed.
|
||||
-- @param #number Speed Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
-- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
-- @return #boolean
|
||||
|
||||
--- Deploy Handler OnAfter for AI_CARGO_HELICOPTER
|
||||
@@ -132,21 +149,42 @@ function AI_CARGO_HELICOPTER:New( Helicopter, CargoSet )
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param Core.Point#COORDINATE Coordinate
|
||||
-- @param #number Speed Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
-- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
|
||||
--- Deployed Handler OnAfter for AI_CARGO_HELICOPTER
|
||||
-- @function [parent=#AI_CARGO_HELICOPTER] OnAfterDeployed
|
||||
-- @param #AI_CARGO_HELICOPTER self
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
|
||||
--- Deploy Trigger for AI_CARGO_HELICOPTER
|
||||
-- @function [parent=#AI_CARGO_HELICOPTER] Deploy
|
||||
-- @param #AI_CARGO_HELICOPTER self
|
||||
-- @param Core.Point#COORDINATE Coordinate Place at which the cargo is deployed.
|
||||
-- @param #number Speed Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
-- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
|
||||
--- Deploy Asynchronous Trigger for AI_CARGO_HELICOPTER
|
||||
-- @function [parent=#AI_CARGO_HELICOPTER] __Deploy
|
||||
-- @param #number Delay Delay in seconds.
|
||||
-- @param #AI_CARGO_HELICOPTER self
|
||||
-- @param Core.Point#COORDINATE Coordinate Place at which the cargo is deployed.
|
||||
-- @param #number Speed Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
-- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
|
||||
--- Home Trigger for AI_CARGO_HELICOPTER
|
||||
-- @function [parent=#AI_CARGO_HELICOPTER] Home
|
||||
-- @param #AI_CARGO_HELICOPTER self
|
||||
-- @param Core.Point#COORDINATE Coordinate Place to which the helicopter will go.
|
||||
-- @param #number Speed (optional) Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
-- @param #number Height (optional) Height the Helicopter should be flying at.
|
||||
|
||||
--- Home Asynchronous Trigger for AI_CARGO_HELICOPTER
|
||||
-- @function [parent=#AI_CARGO_HELICOPTER] __Home
|
||||
-- @param #number Delay Delay in seconds.
|
||||
-- @param #AI_CARGO_HELICOPTER self
|
||||
-- @param Core.Point#COORDINATE Coordinate Place to which the helicopter will go.
|
||||
-- @param #number Speed (optional) Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
-- @param #number Height (optional) Height the Helicopter should be flying at.
|
||||
|
||||
-- We need to capture the Crash events for the helicopters.
|
||||
-- The helicopter reference is used in the semaphore AI_CARGO_QUEUE.
|
||||
@@ -173,6 +211,9 @@ function AI_CARGO_HELICOPTER:New( Helicopter, CargoSet )
|
||||
|
||||
self:SetCarrier( Helicopter )
|
||||
|
||||
self.landingspeed = 15 -- kph
|
||||
self.landingheight = 5.5 -- meter
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -221,6 +262,25 @@ function AI_CARGO_HELICOPTER:SetCarrier( Helicopter )
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set landingspeed and -height for helicopter landings. Adjust after tracing if your helis get stuck after landing.
|
||||
-- @param #AI_CARGO_HELICOPTER self
|
||||
-- @param #number speed Landing speed in kph(!), e.g. 15
|
||||
-- @param #number height Landing height in meters(!), e.g. 5.5
|
||||
-- @return #AI_CARGO_HELICOPTER self
|
||||
-- @usage If your choppers get stuck, add tracing to your script to determine if they hit the right parameters like so:
|
||||
--
|
||||
-- BASE:TraceOn()
|
||||
-- BASE:TraceClass("AI_CARGO_HELICOPTER")
|
||||
--
|
||||
-- Watch the DCS.log for entries stating `Helicopter:<name>, Height = Helicopter:<number>, Velocity = Helicopter:<number>`
|
||||
-- Adjust if necessary.
|
||||
function AI_CARGO_HELICOPTER:SetLandingSpeedAndHeight(speed, height)
|
||||
local _speed = speed or 15
|
||||
local _height = height or 5.5
|
||||
self.landingheight = _height
|
||||
self.landingspeed = _speed
|
||||
return self
|
||||
end
|
||||
|
||||
--- @param #AI_CARGO_HELICOPTER self
|
||||
-- @param Wrapper.Group#GROUP Helicopter
|
||||
@@ -228,7 +288,7 @@ end
|
||||
-- @param Event
|
||||
-- @param To
|
||||
function AI_CARGO_HELICOPTER:onafterLanded( Helicopter, From, Event, To )
|
||||
|
||||
self:F({From, Event, To})
|
||||
Helicopter:F( { Name = Helicopter:GetName() } )
|
||||
|
||||
if Helicopter and Helicopter:IsAlive() then
|
||||
@@ -237,13 +297,13 @@ function AI_CARGO_HELICOPTER:onafterLanded( Helicopter, From, Event, To )
|
||||
-- 1 - When the helo lands normally on the ground.
|
||||
-- 2 - when the helo is hit and goes RTB or even when it is destroyed.
|
||||
-- For point 2, this is an issue, the infantry may not unload in this case!
|
||||
-- So we check if the helo is on the ground, and velocity< 5.
|
||||
-- So we check if the helo is on the ground, and velocity< 15.
|
||||
-- Only then the infantry can unload (and load too, for consistency)!
|
||||
|
||||
self:F( { Helicopter:GetName(), Height = Helicopter:GetHeight( true ), Velocity = Helicopter:GetVelocityKMH() } )
|
||||
self:T( { Helicopter:GetName(), Height = Helicopter:GetHeight( true ), Velocity = Helicopter:GetVelocityKMH() } )
|
||||
|
||||
if self.RoutePickup == true then
|
||||
if Helicopter:GetHeight( true ) <= 5 and Helicopter:GetVelocityKMH() < 10 then
|
||||
if Helicopter:GetHeight( true ) <= self.landingheight then --and Helicopter:GetVelocityKMH() < self.landingspeed then
|
||||
--self:Load( Helicopter:GetPointVec2() )
|
||||
self:Load( self.PickupZone )
|
||||
self.RoutePickup = false
|
||||
@@ -251,7 +311,7 @@ function AI_CARGO_HELICOPTER:onafterLanded( Helicopter, From, Event, To )
|
||||
end
|
||||
|
||||
if self.RouteDeploy == true then
|
||||
if Helicopter:GetHeight( true ) <= 5 and Helicopter:GetVelocityKMH() < 10 then
|
||||
if Helicopter:GetHeight( true ) <= self.landingheight then --and Helicopter:GetVelocityKMH() < self.landingspeed then
|
||||
self:Unload( self.DeployZone )
|
||||
self.RouteDeploy = false
|
||||
end
|
||||
@@ -269,7 +329,7 @@ end
|
||||
-- @param Core.Point#COORDINATE Coordinate
|
||||
-- @param #number Speed
|
||||
function AI_CARGO_HELICOPTER:onafterQueue( Helicopter, From, Event, To, Coordinate, Speed, DeployZone )
|
||||
|
||||
self:F({From, Event, To, Coordinate, Speed, DeployZone})
|
||||
local HelicopterInZone = false
|
||||
|
||||
if Helicopter and Helicopter:IsAlive() == true then
|
||||
@@ -308,7 +368,11 @@ function AI_CARGO_HELICOPTER:onafterQueue( Helicopter, From, Event, To, Coordina
|
||||
-- true
|
||||
-- )
|
||||
-- Route[#Route+1] = WaypointFrom
|
||||
local CoordinateTo = Coordinate
|
||||
local CoordinateTo = Coordinate
|
||||
|
||||
local landheight = CoordinateTo:GetLandHeight() -- get target height
|
||||
CoordinateTo.y = landheight + 50 -- flight height should be 50m above ground
|
||||
|
||||
local WaypointTo = CoordinateTo:WaypointAir(
|
||||
"RADIO",
|
||||
POINT_VEC3.RoutePointType.TurningPoint,
|
||||
@@ -348,28 +412,17 @@ end
|
||||
-- @param Core.Point#COORDINATE Coordinate
|
||||
-- @param #number Speed
|
||||
function AI_CARGO_HELICOPTER:onafterOrbit( Helicopter, From, Event, To, Coordinate )
|
||||
|
||||
self:F({From, Event, To, Coordinate})
|
||||
|
||||
if Helicopter and Helicopter:IsAlive() then
|
||||
|
||||
local Route = {}
|
||||
|
||||
-- local CoordinateFrom = Helicopter:GetCoordinate()
|
||||
-- local WaypointFrom = CoordinateFrom:WaypointAir(
|
||||
-- "RADIO",
|
||||
-- POINT_VEC3.RoutePointType.TurningPoint,
|
||||
-- POINT_VEC3.RoutePointAction.TurningPoint,
|
||||
-- Speed,
|
||||
-- true
|
||||
-- )
|
||||
-- Route[#Route+1] = WaypointFrom
|
||||
local CoordinateTo = Coordinate
|
||||
local WaypointTo = CoordinateTo:WaypointAir(
|
||||
"RADIO",
|
||||
POINT_VEC3.RoutePointType.TurningPoint,
|
||||
POINT_VEC3.RoutePointAction.TurningPoint,
|
||||
50,
|
||||
true
|
||||
)
|
||||
local CoordinateTo = Coordinate
|
||||
local landheight = CoordinateTo:GetLandHeight() -- get target height
|
||||
CoordinateTo.y = landheight + 50 -- flight height should be 50m above ground
|
||||
|
||||
local WaypointTo = CoordinateTo:WaypointAir("RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, 50, true)
|
||||
Route[#Route+1] = WaypointTo
|
||||
|
||||
local Tasks = {}
|
||||
@@ -379,7 +432,7 @@ function AI_CARGO_HELICOPTER:onafterOrbit( Helicopter, From, Event, To, Coordina
|
||||
Route[#Route+1] = WaypointTo
|
||||
|
||||
-- Now route the helicopter
|
||||
Helicopter:Route( Route, 0 )
|
||||
Helicopter:Route(Route, 0)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -395,7 +448,7 @@ end
|
||||
-- @param #boolean Deployed Cargo is deployed.
|
||||
-- @return #boolean True if all cargo has been unloaded.
|
||||
function AI_CARGO_HELICOPTER:onafterDeployed( Helicopter, From, Event, To, DeployZone )
|
||||
self:F( { Helicopter, From, Event, To, DeployZone = DeployZone } )
|
||||
self:F( { From, Event, To, DeployZone = DeployZone } )
|
||||
|
||||
self:Orbit( Helicopter:GetCoordinate(), 50 )
|
||||
|
||||
@@ -408,7 +461,6 @@ function AI_CARGO_HELICOPTER:onafterDeployed( Helicopter, From, Event, To, Deplo
|
||||
|
||||
self:GetParent( self, AI_CARGO_HELICOPTER ).onafterDeployed( self, Helicopter, From, Event, To, DeployZone )
|
||||
|
||||
|
||||
end
|
||||
|
||||
--- On after Pickup event.
|
||||
@@ -418,11 +470,12 @@ end
|
||||
-- @param Event
|
||||
-- @param To
|
||||
-- @param Core.Point#COORDINATE Coordinate Pickup place.
|
||||
-- @param #number Speed Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
-- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
-- @param #number Height Height in meters to move to the pickup coordinate. This parameter is ignored for APCs.
|
||||
-- @param Core.Zone#ZONE PickupZone (optional) The zone where the cargo will be picked up. The PickupZone can be nil, if there wasn't any PickupZoneSet provided.
|
||||
function AI_CARGO_HELICOPTER:onafterPickup( Helicopter, From, Event, To, Coordinate, Speed, Height, PickupZone )
|
||||
|
||||
self:F({Coordinate, Speed, Height, PickupZone })
|
||||
|
||||
if Helicopter and Helicopter:IsAlive() ~= nil then
|
||||
|
||||
Helicopter:Activate()
|
||||
@@ -436,25 +489,16 @@ function AI_CARGO_HELICOPTER:onafterPickup( Helicopter, From, Event, To, Coordin
|
||||
|
||||
--- Calculate the target route point.
|
||||
local CoordinateFrom = Helicopter:GetCoordinate()
|
||||
local CoordinateTo = Coordinate
|
||||
|
||||
--- Create a route point of type air.
|
||||
local WaypointFrom = CoordinateFrom:WaypointAir(
|
||||
"RADIO",
|
||||
POINT_VEC3.RoutePointType.TurningPoint,
|
||||
POINT_VEC3.RoutePointAction.TurningPoint,
|
||||
_speed,
|
||||
true
|
||||
)
|
||||
local WaypointFrom = CoordinateFrom:WaypointAir("RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, _speed, true)
|
||||
|
||||
--- Create a route point of type air.
|
||||
local WaypointTo = CoordinateTo:WaypointAir(
|
||||
"RADIO",
|
||||
POINT_VEC3.RoutePointType.TurningPoint,
|
||||
POINT_VEC3.RoutePointAction.TurningPoint,
|
||||
_speed,
|
||||
true
|
||||
)
|
||||
local CoordinateTo = Coordinate
|
||||
local landheight = CoordinateTo:GetLandHeight() -- get target height
|
||||
CoordinateTo.y = landheight + 50 -- flight height should be 50m above ground
|
||||
|
||||
local WaypointTo = CoordinateTo:WaypointAir("RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint,_speed, true)
|
||||
|
||||
Route[#Route+1] = WaypointFrom
|
||||
Route[#Route+1] = WaypointTo
|
||||
@@ -495,10 +539,10 @@ end
|
||||
-- @param Event
|
||||
-- @param To
|
||||
-- @param Core.Point#COORDINATE Coordinate Place at which the cargo is deployed.
|
||||
-- @param #number Speed Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
-- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
-- @param #number Height Height in meters to move to the deploy coordinate.
|
||||
function AI_CARGO_HELICOPTER:onafterDeploy( Helicopter, From, Event, To, Coordinate, Speed, Height, DeployZone )
|
||||
|
||||
self:F({From, Event, To, Coordinate, Speed, Height, DeployZone})
|
||||
if Helicopter and Helicopter:IsAlive() ~= nil then
|
||||
|
||||
self.RouteDeploy = true
|
||||
@@ -514,25 +558,17 @@ function AI_CARGO_HELICOPTER:onafterDeploy( Helicopter, From, Event, To, Coordin
|
||||
|
||||
--- Create a route point of type air.
|
||||
local CoordinateFrom = Helicopter:GetCoordinate()
|
||||
local WaypointFrom = CoordinateFrom:WaypointAir(
|
||||
"RADIO",
|
||||
POINT_VEC3.RoutePointType.TurningPoint,
|
||||
POINT_VEC3.RoutePointAction.TurningPoint,
|
||||
_speed,
|
||||
true
|
||||
)
|
||||
local WaypointFrom = CoordinateFrom:WaypointAir("RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, _speed, true)
|
||||
Route[#Route+1] = WaypointFrom
|
||||
Route[#Route+1] = WaypointFrom
|
||||
|
||||
--- Create a route point of type air.
|
||||
local CoordinateTo = Coordinate
|
||||
local WaypointTo = CoordinateTo:WaypointAir(
|
||||
"RADIO",
|
||||
POINT_VEC3.RoutePointType.TurningPoint,
|
||||
POINT_VEC3.RoutePointAction.TurningPoint,
|
||||
_speed,
|
||||
true
|
||||
)
|
||||
|
||||
local CoordinateTo = Coordinate
|
||||
local landheight = CoordinateTo:GetLandHeight() -- get target height
|
||||
CoordinateTo.y = landheight + 50 -- flight height should be 50m above ground
|
||||
|
||||
local WaypointTo = CoordinateTo:WaypointAir("RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, _speed, true)
|
||||
|
||||
Route[#Route+1] = WaypointTo
|
||||
Route[#Route+1] = WaypointTo
|
||||
@@ -542,7 +578,9 @@ function AI_CARGO_HELICOPTER:onafterDeploy( Helicopter, From, Event, To, Coordin
|
||||
|
||||
local Tasks = {}
|
||||
|
||||
-- The _Deploy function does not exist.
|
||||
Tasks[#Tasks+1] = Helicopter:TaskFunction( "AI_CARGO_HELICOPTER._Deploy", self, Coordinate, DeployZone )
|
||||
|
||||
Tasks[#Tasks+1] = Helicopter:TaskOrbitCircle( math.random( 30, 100 ), _speed, CoordinateTo:GetRandomCoordinateInRadius( 800, 500 ) )
|
||||
|
||||
--Tasks[#Tasks+1] = Helicopter:TaskLandAtVec2( CoordinateTo:GetVec2() )
|
||||
@@ -566,11 +604,12 @@ end
|
||||
-- @param Event
|
||||
-- @param To
|
||||
-- @param Core.Point#COORDINATE Coordinate Home place.
|
||||
-- @param #number Speed Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
-- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
-- @param #number Height Height in meters to move to the home coordinate.
|
||||
-- @param Core.Zone#ZONE HomeZone The zone wherein the carrier will return when all cargo has been transported. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE.
|
||||
function AI_CARGO_HELICOPTER:onafterHome( Helicopter, From, Event, To, Coordinate, Speed, Height, HomeZone )
|
||||
|
||||
self:F({From, Event, To, Coordinate, Speed, Height})
|
||||
|
||||
if Helicopter and Helicopter:IsAlive() ~= nil then
|
||||
|
||||
self.RouteHome = true
|
||||
@@ -579,30 +618,23 @@ function AI_CARGO_HELICOPTER:onafterHome( Helicopter, From, Event, To, Coordinat
|
||||
|
||||
--- Calculate the target route point.
|
||||
|
||||
Coordinate.y = Height
|
||||
--Coordinate.y = Height
|
||||
Height = Height or 50
|
||||
|
||||
Speed = Speed or Helicopter:GetSpeedMax()*0.5
|
||||
|
||||
--- Create a route point of type air.
|
||||
local CoordinateFrom = Helicopter:GetCoordinate()
|
||||
local WaypointFrom = CoordinateFrom:WaypointAir(
|
||||
"RADIO",
|
||||
POINT_VEC3.RoutePointType.TurningPoint,
|
||||
POINT_VEC3.RoutePointAction.TurningPoint,
|
||||
Speed ,
|
||||
true
|
||||
)
|
||||
|
||||
local WaypointFrom = CoordinateFrom:WaypointAir("RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, Speed, true)
|
||||
Route[#Route+1] = WaypointFrom
|
||||
|
||||
--- Create a route point of type air.
|
||||
local CoordinateTo = Coordinate
|
||||
local WaypointTo = CoordinateTo:WaypointAir(
|
||||
"RADIO",
|
||||
POINT_VEC3.RoutePointType.TurningPoint,
|
||||
POINT_VEC3.RoutePointAction.TurningPoint,
|
||||
Speed ,
|
||||
true
|
||||
)
|
||||
local CoordinateTo = Coordinate
|
||||
local landheight = CoordinateTo:GetLandHeight() -- get target height
|
||||
CoordinateTo.y = landheight + Height -- flight height should be 50m above ground
|
||||
|
||||
local WaypointTo = CoordinateTo:WaypointAir("RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, Speed, true)
|
||||
|
||||
Route[#Route+1] = WaypointTo
|
||||
|
||||
@@ -613,12 +645,11 @@ function AI_CARGO_HELICOPTER:onafterHome( Helicopter, From, Event, To, Coordinat
|
||||
|
||||
Tasks[#Tasks+1] = Helicopter:TaskLandAtVec2( CoordinateTo:GetVec2() )
|
||||
Route[#Route].task = Helicopter:TaskCombo( Tasks )
|
||||
|
||||
|
||||
Route[#Route+1] = WaypointTo
|
||||
|
||||
-- Now route the helicopter
|
||||
Helicopter:Route( Route, 0 )
|
||||
|
||||
Helicopter:Route(Route, 0)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
397
Moose Development/Moose/AI/AI_Cargo_Ship.lua
Normal file
397
Moose Development/Moose/AI/AI_Cargo_Ship.lua
Normal file
@@ -0,0 +1,397 @@
|
||||
--- **AI** -- (R2.5.1) - Models the intelligent transportation of infantry and other cargo.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **acrojason** (derived from AI_Cargo_APC by FlightControl)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module AI.AI_Cargo_Ship
|
||||
-- @image AI_Cargo_Dispatching_For_Ship.JPG
|
||||
|
||||
--- @type AI_CARGO_SHIP
|
||||
-- @extends AI.AI_Cargo#AI_CARGO
|
||||
|
||||
--- Brings a dynamic cargo handling capability for an AI naval group.
|
||||
--
|
||||
-- Naval ships can be utilized to transport cargo around the map following naval shipping lanes.
|
||||
-- The AI_CARGO_SHIP class uses the @{Cargo.Cargo} capabilities within the MOOSE framework.
|
||||
-- @{Cargo.Cargo} must be declared within the mission or warehouse to make the AI_CARGO_SHIP recognize the cargo.
|
||||
-- Please consult the @{Cargo.Cargo} module for more information.
|
||||
--
|
||||
-- ## Cargo loading.
|
||||
--
|
||||
-- The module will automatically load cargo when the Ship is within boarding or loading radius.
|
||||
-- The boarding or loading radius is specified when the cargo is created in the simulation and depends on the type of
|
||||
-- cargo and the specified boarding radius.
|
||||
--
|
||||
-- ## Defending the Ship when enemies are nearby
|
||||
-- This is not supported for naval cargo because most tanks don't float. Protect your transports...
|
||||
--
|
||||
-- ## Infantry or cargo **health**.
|
||||
-- When cargo is unboarded from the Ship, the cargo is actually respawned into the battlefield.
|
||||
-- As a result, the unboarding cargo is very _healthy_ every time it unboards.
|
||||
-- This is due to the limitation of the DCS simulator, which is not able to specify the health of newly spawned units as a parameter.
|
||||
-- However, cargo that was destroyed when unboarded and following the Ship won't be respawned again (this is likely not a thing for
|
||||
-- naval cargo due to the lack of support for defending the Ship mentioned above). Destroyed is destroyed.
|
||||
-- As a result, there is some additional strength that is gained when an unboarding action happens, but in terms of simulation balance
|
||||
-- this has marginal impact on the overall battlefield simulation. Given the relatively short duration of DCS missions and the somewhat
|
||||
-- lengthy naval transport times, most units entering the Ship as cargo will be freshly en route to an amphibious landing or transporting
|
||||
-- between warehouses.
|
||||
--
|
||||
-- ## Control the Ships on the map.
|
||||
--
|
||||
-- Currently, naval transports can only be controlled via scripts due to their reliance upon predefined Shipping Lanes created in the Mission
|
||||
-- Editor. An interesting future enhancement could leverage new pathfinding functionality for ships in the Ops module.
|
||||
--
|
||||
-- ## Cargo deployment.
|
||||
--
|
||||
-- Using the @{AI_CARGO_SHIP.Deploy}() method, you are able to direct the Ship towards a Deploy zone to unboard/unload the cargo at the
|
||||
-- specified coordinate. The Ship will follow the Shipping Lane to ensure consistent cargo transportation within the simulation environment.
|
||||
--
|
||||
-- ## Cargo pickup.
|
||||
--
|
||||
-- Using the @{AI_CARGO_SHIP.Pickup}() method, you are able to direct the Ship towards a Pickup zone to board/load the cargo at the specified
|
||||
-- coordinate. The Ship will follow the Shipping Lane to ensure consistent cargo transportation within the simulation environment.
|
||||
--
|
||||
--
|
||||
-- @field #AI_CARGO_SHIP
|
||||
AI_CARGO_SHIP = {
|
||||
ClassName = "AI_CARGO_SHIP",
|
||||
Coordinate = nil -- Core.Point#COORDINATE
|
||||
}
|
||||
|
||||
--- Creates a new AI_CARGO_SHIP object.
|
||||
-- @param #AI_CARGO_SHIP self
|
||||
-- @param Wrapper.Group#GROUP Ship The carrier Ship group
|
||||
-- @param Core.Set#SET_CARGO CargoSet The set of cargo to be transported
|
||||
-- @param #number CombatRadius Provide the combat radius to defend the carrier by unboarding the cargo when enemies are nearby. When CombatRadius is 0, no defense will occur.
|
||||
-- @param #table ShippingLane Table containing list of Shipping Lanes to be used
|
||||
-- @return #AI_CARGO_SHIP
|
||||
function AI_CARGO_SHIP:New( Ship, CargoSet, CombatRadius, ShippingLane )
|
||||
|
||||
local self = BASE:Inherit( self, AI_CARGO:New( Ship, CargoSet ) ) -- #AI_CARGO_SHIP
|
||||
|
||||
self:AddTransition( "*", "Monitor", "*" )
|
||||
self:AddTransition( "*", "Destroyed", "Destroyed" )
|
||||
self:AddTransition( "*", "Home", "*" )
|
||||
|
||||
self:SetCombatRadius( 0 ) -- Don't want to deploy cargo in middle of water to defend Ship, so set CombatRadius to 0
|
||||
self:SetShippingLane ( ShippingLane )
|
||||
|
||||
self:SetCarrier( Ship )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set the Carrier
|
||||
-- @param #AI_CARGO_SHIP self
|
||||
-- @param Wrapper.Group#GROUP CargoCarrier
|
||||
-- @return #AI_CARGO_SHIP
|
||||
function AI_CARGO_SHIP:SetCarrier( CargoCarrier )
|
||||
self.CargoCarrier = CargoCarrier -- Wrapper.Group#GROUIP
|
||||
self.CargoCarrier:SetState( self.CargoCarrier, "AI_CARGO_SHIP", self )
|
||||
|
||||
CargoCarrier:HandleEvent( EVENTS.Dead )
|
||||
|
||||
function CargoCarrier:OnEventDead( EventData )
|
||||
self:F({"dead"})
|
||||
local AICargoTroops = self:GetState( self, "AI_CARGO_SHIP" )
|
||||
self:F({AICargoTroops=AICargoTroops})
|
||||
if AICargoTroops then
|
||||
self:F({})
|
||||
if not AICargoTroops:Is( "Loaded" ) then
|
||||
-- Better hope they can swim!
|
||||
AICargoTroops:Destroyed()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
self.Zone = ZONE_UNIT:New( self.CargoCarrier:GetName() .. "-Zone", self.CargoCarrier, self.CombatRadius )
|
||||
self.Coalition = self.CargoCarrier:GetCoalition()
|
||||
|
||||
self:SetControllable( CargoCarrier )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- FInd a free Carrier within a radius
|
||||
-- @param #AI_CARGO_SHIP self
|
||||
-- @param Core.Point#COORDINATE Coordinate
|
||||
-- @param #number Radius
|
||||
-- @return Wrapper.Group#GROUP NewCarrier
|
||||
function AI_CARGO_SHIP:FindCarrier( Coordinate, Radius )
|
||||
|
||||
local CoordinateZone = ZONE_RADIUS:New( "Zone", Coordinate:GetVec2(), Radius )
|
||||
CoordinateZone:Scan( { Object.Category.UNIT } )
|
||||
for _, DCSUnit in pairs( CoordinateZone:GetScannedUnits() ) do
|
||||
local NearUnit = UNIT:Find( DCSUnit )
|
||||
self:F({NearUnit=NearUnit})
|
||||
if not NearUnit:GetState( NearUnit, "AI_CARGO_SHIP" ) then
|
||||
local Attributes = NearUnit:GetDesc()
|
||||
self:F({Desc=Attributes})
|
||||
if NearUnit:HasAttributes( "Trucks" ) then
|
||||
return NearUnit:GetGroup()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
function AI_CARGO_SHIP:SetShippingLane( ShippingLane )
|
||||
self.ShippingLane = ShippingLane
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function AI_CARGO_SHIP:SetCombatRadius( CombatRadius )
|
||||
self.CombatRadius = CombatRadius or 0
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Follow Infantry to the Carrier
|
||||
-- @param #AI_CARGO_SHIP self
|
||||
-- @param #AI_CARGO_SHIP Me
|
||||
-- @param Wrapper.Unit#UNIT ShipUnit
|
||||
-- @param Cargo.CargoGroup#CARGO_GROUP Cargo
|
||||
-- @return #AI_CARGO_SHIP
|
||||
function AI_CARGO_SHIP:FollowToCarrier( Me, ShipUnit, CargoGroup )
|
||||
|
||||
local InfantryGroup = CargoGroup:GetGroup()
|
||||
|
||||
self:F( { self=self:GetClassNameAndID(), InfantryGroup = InfantryGroup:GetName() } )
|
||||
|
||||
if ShipUnit:IsAlive() then
|
||||
-- Check if the Cargo is near the CargoCarrier
|
||||
if InfantryGroup:IsPartlyInZone( ZONE_UNIT:New( "Radius", ShipUnit, 1000 ) ) then
|
||||
|
||||
-- Cargo does not need to navigate to Carrier
|
||||
Me:Guard()
|
||||
else
|
||||
|
||||
self:F( { InfantryGroup = InfantryGroup:GetName() } )
|
||||
if InfantryGroup:IsAlive() then
|
||||
|
||||
self:F( { InfantryGroup = InfantryGroup:GetName() } )
|
||||
local Waypoints = {}
|
||||
|
||||
-- Calculate new route
|
||||
local FromCoord = InfantryGroup:GetCoordinate()
|
||||
local FromGround = FromCoord:WaypointGround( 10, "Diamond" )
|
||||
self:F({FromGround=FromGround})
|
||||
table.insert( Waypoints, FromGround )
|
||||
|
||||
local ToCoord = ShipUnit:GetCoordinate():GetRandomCoordinateInRadius( 10, 5 )
|
||||
local ToGround = ToCoord:WaypointGround( 10, "Diamond" )
|
||||
self:F({ToGround=ToGround})
|
||||
table.insert( Waypoints, ToGround )
|
||||
|
||||
local TaskRoute = InfantryGroup:TaskFunction( "AI_CARGO_SHIP.FollowToCarrier", Me, ShipUnit, CargoGroup )
|
||||
|
||||
self:F({Waypoints=Waypoints})
|
||||
local Waypoint = Waypoints[#Waypoints]
|
||||
InfantryGroup:SetTaskWaypoint( Waypoint, TaskRoute ) -- Set for the given Route at Waypoint 2 the TaskRouteToZone
|
||||
|
||||
InfantryGroup:Route( Waypoints, 1 ) -- Move after a random number of seconds to the Route. See Route method for details
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function AI_CARGO_SHIP:onafterMonitor( Ship, From, Event, To )
|
||||
self:F( { Ship, From, Event, To, IsTransporting = self:IsTransporting() } )
|
||||
|
||||
if self.CombatRadius > 0 then
|
||||
-- We really shouldn't find ourselves in here for Ships since the CombatRadius should always be 0.
|
||||
-- This is to avoid Unloading the Ship in the middle of the sea.
|
||||
if Ship and Ship:IsAlive() then
|
||||
if self.CarrierCoordinate then
|
||||
if self:IsTransporting() == true then
|
||||
local Coordinate = Ship:GetCoordinate()
|
||||
if self:Is( "Unloaded" ) or self:Is( "Loaded" ) then
|
||||
self.Zone:Scan( { Object.Category.UNIT } )
|
||||
if self.Zone:IsAllInZoneOfCoalition( self.Coalition ) then
|
||||
if self:Is( "Unloaded" ) then
|
||||
-- There are no enemies within combat radius. Reload the CargoCarrier.
|
||||
self:Reload()
|
||||
end
|
||||
else
|
||||
if self:Is( "Loaded" ) then
|
||||
-- There are enemies within combat radius. Unload the CargoCarrier.
|
||||
self:__Unload( 1, nil, true ) -- The 2nd parameter is true, which means that the unload is for defending the carrier, not to deploy!
|
||||
else
|
||||
if self:Is( "Unloaded" ) then
|
||||
--self:Follow()
|
||||
end
|
||||
self:F( "I am here" .. self:GetCurrentState() )
|
||||
if self:Is( "Following" ) then
|
||||
for Cargo, ShipUnit in pairs( self.Carrier_Cargo ) do
|
||||
local Cargo = Cargo -- Cargo.Cargo#CARGO
|
||||
local ShipUnit = ShipUnit -- Wrapper.Unit#UNIT
|
||||
if Cargo:IsAlive() then
|
||||
if not Cargo:IsNear( ShipUnit, 40 ) then
|
||||
ShipUnit:RouteStop()
|
||||
self.CarrierStopped = true
|
||||
else
|
||||
if self.CarrierStopped then
|
||||
if Cargo:IsNear( ShipUnit, 25 ) then
|
||||
ShipUnit:RouteResume()
|
||||
self.CarrierStopped = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
self.CarrierCoordinate = Ship:GetCoordinate()
|
||||
end
|
||||
self:__Monitor( -5 )
|
||||
end
|
||||
end
|
||||
|
||||
--- Check if cargo ship is alive and trigger Load event
|
||||
-- @param Wrapper.Group#Group Ship
|
||||
-- @param #AI_CARGO_SHIP self
|
||||
function AI_CARGO_SHIP._Pickup( Ship, self, Coordinate, Speed, PickupZone )
|
||||
|
||||
Ship:F( { "AI_CARGO_Ship._Pickup:", Ship:GetName() } )
|
||||
|
||||
if Ship:IsAlive() then
|
||||
self:Load( PickupZone )
|
||||
end
|
||||
end
|
||||
|
||||
--- Check if cargo ship is alive and trigger Unload event. Good time to remind people that Lua is case sensitive and Unload != UnLoad
|
||||
-- @param Wrapper.Group#GROUP Ship
|
||||
-- @param #AI_CARGO_SHIP self
|
||||
function AI_CARGO_SHIP._Deploy( Ship, self, Coordinate, DeployZone )
|
||||
Ship:F( { "AI_CARGO_Ship._Deploy:", Ship } )
|
||||
|
||||
if Ship:IsAlive() then
|
||||
self:Unload( DeployZone )
|
||||
end
|
||||
end
|
||||
|
||||
--- on after Pickup event.
|
||||
-- @param AI_CARGO_SHIP Ship
|
||||
-- @param From
|
||||
-- @param Event
|
||||
-- @param To
|
||||
-- @param Core.Point#COORDINATE Coordinate of the pickup point
|
||||
-- @param #number Speed Speed in km/h to sail to the pickup coordinate. Default is 50% of max speed for the unit
|
||||
-- @param #number Height Altitude in meters to move to the pickup coordinate. This parameter is ignored for Ships
|
||||
-- @param Core.Zone#ZONE PickupZone (optional) The zone where the cargo will be picked up. The PickupZone can be nil if there was no PickupZoneSet provided
|
||||
function AI_CARGO_SHIP:onafterPickup( Ship, From, Event, To, Coordinate, Speed, Height, PickupZone )
|
||||
|
||||
if Ship and Ship:IsAlive() then
|
||||
AI_CARGO_SHIP._Pickup( Ship, self, Coordinate, Speed, PickupZone )
|
||||
self:GetParent( self, AI_CARGO_SHIP ).onafterPickup( self, Ship, From, Event, To, Coordinate, Speed, Height, PickupZone )
|
||||
end
|
||||
end
|
||||
|
||||
--- On after Deploy event.
|
||||
-- @param #AI_CARGO_SHIP self
|
||||
-- @param Wrapper.Group#GROUP SHIP
|
||||
-- @param From
|
||||
-- @param Event
|
||||
-- @param To
|
||||
-- @param Core.Point#COORDINATE Coordinate Coordinate of the deploy point
|
||||
-- @param #number Speed Speed in km/h to sail to the deploy coordinate. Default is 50% of max speed for the unit
|
||||
-- @param #number Height Altitude in meters to move to the deploy coordinate. This parameter is ignored for Ships
|
||||
-- @param Core.Zone#ZONE DeployZone The zone where the cargo will be deployed.
|
||||
function AI_CARGO_SHIP:onafterDeploy( Ship, From, Event, To, Coordinate, Speed, Height, DeployZone )
|
||||
|
||||
if Ship and Ship:IsAlive() then
|
||||
|
||||
Speed = Speed or Ship:GetSpeedMax()*0.8
|
||||
local lane = self.ShippingLane
|
||||
|
||||
if lane then
|
||||
local Waypoints = {}
|
||||
|
||||
for i=1, #lane do
|
||||
local coord = lane[i]
|
||||
local Waypoint = coord:WaypointGround(_speed)
|
||||
table.insert(Waypoints, Waypoint)
|
||||
end
|
||||
|
||||
local TaskFunction = Ship:TaskFunction( "AI_CARGO_SHIP._Deploy", self, Coordinate, DeployZone )
|
||||
local Waypoint = Waypoints[#Waypoints]
|
||||
Ship:SetTaskWaypoint( Waypoint, TaskFunction )
|
||||
Ship:Route(Waypoints, 1)
|
||||
self:GetParent( self, AI_CARGO_SHIP ).onafterDeploy( self, Ship, From, Event, To, Coordinate, Speed, Height, DeployZone )
|
||||
else
|
||||
self:E(self.lid.."ERROR: No shipping lane defined for Naval Transport!")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- On after Unload event.
|
||||
-- @param #AI_CARGO_SHIP self
|
||||
-- @param Wrapper.Group#GROUP Ship
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE.
|
||||
function AI_CARGO_SHIP:onafterUnload( Ship, From, Event, To, DeployZone, Defend )
|
||||
self:F( { Ship, From, Event, To, DeployZone, Defend = Defend } )
|
||||
|
||||
local UnboardInterval = 5
|
||||
local UnboardDelay = 5
|
||||
|
||||
if Ship and Ship:IsAlive() then
|
||||
for _, ShipUnit in pairs( Ship:GetUnits() ) do
|
||||
local ShipUnit = ShipUnit -- Wrapper.Unit#UNIT
|
||||
Ship:RouteStop()
|
||||
for _, Cargo in pairs( ShipUnit:GetCargo() ) do
|
||||
self:F( { Cargo = Cargo:GetName(), Isloaded = Cargo:IsLoaded() } )
|
||||
if Cargo:IsLoaded() then
|
||||
local unboardCoord = DeployZone:GetRandomPointVec2()
|
||||
Cargo:__UnBoard( UnboardDelay, unboardCoord, 1000)
|
||||
UnboardDelay = UnboardDelay + Cargo:GetCount() * UnboardInterval
|
||||
self:__Unboard( UnboardDelay, Cargo, ShipUnit, DeployZone, Defend )
|
||||
if not Defend == true then
|
||||
Cargo:SetDeployed( true )
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function AI_CARGO_SHIP:onafterHome( Ship, From, Event, To, Coordinate, Speed, Height, HomeZone )
|
||||
if Ship and Ship:IsAlive() then
|
||||
|
||||
self.RouteHome = true
|
||||
Speed = Speed or Ship:GetSpeedMax()*0.8
|
||||
local lane = self.ShippingLane
|
||||
|
||||
if lane then
|
||||
local Waypoints = {}
|
||||
|
||||
-- Need to find a more generalized way to do this instead of reversing the shipping lane.
|
||||
-- This only works if the Source/Dest route waypoints are numbered 1..n and not n..1
|
||||
for i=#lane, 1, -1 do
|
||||
local coord = lane[i]
|
||||
local Waypoint = coord:WaypointGround(_speed)
|
||||
table.insert(Waypoints, Waypoint)
|
||||
end
|
||||
|
||||
local Waypoint = Waypoints[#Waypoints]
|
||||
Ship:Route(Waypoints, 1)
|
||||
|
||||
else
|
||||
self:E(self.lid.."ERROR: No shipping lane defined for Naval Transport!")
|
||||
end
|
||||
end
|
||||
end
|
||||
2182
Moose Development/Moose/AI/AI_Escort.lua
Normal file
2182
Moose Development/Moose/AI/AI_Escort.lua
Normal file
File diff suppressed because it is too large
Load Diff
185
Moose Development/Moose/AI/AI_Escort_Dispatcher.lua
Normal file
185
Moose Development/Moose/AI/AI_Escort_Dispatcher.lua
Normal file
@@ -0,0 +1,185 @@
|
||||
--- **AI** - Models the automatic assignment of AI escorts to player flights.
|
||||
--
|
||||
-- ## Features:
|
||||
-- --
|
||||
-- * Provides the facilities to trigger escorts when players join flight slots.
|
||||
-- *
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module AI.AI_Escort_Dispatcher
|
||||
-- @image MOOSE.JPG
|
||||
|
||||
|
||||
--- @type AI_ESCORT_DISPATCHER
|
||||
-- @extends Core.Fsm#FSM
|
||||
|
||||
|
||||
--- Models the automatic assignment of AI escorts to player flights.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @field #AI_ESCORT_DISPATCHER
|
||||
AI_ESCORT_DISPATCHER = {
|
||||
ClassName = "AI_ESCORT_DISPATCHER",
|
||||
}
|
||||
|
||||
--- @field #list
|
||||
AI_ESCORT_DISPATCHER.AI_Escorts = {}
|
||||
|
||||
|
||||
--- Creates a new AI_ESCORT_DISPATCHER object.
|
||||
-- @param #AI_ESCORT_DISPATCHER self
|
||||
-- @param Core.Set#SET_GROUP CarrierSet The set of @{Wrapper.Group#GROUP} objects of carriers for which escorts are spawned in.
|
||||
-- @param Core.Spawn#SPAWN EscortSpawn The spawn object that will spawn in the Escorts.
|
||||
-- @param Wrapper.Airbase#AIRBASE EscortAirbase The airbase where the escorts are spawned.
|
||||
-- @param #string EscortName Name of the escort, which will also be the name of the escort menu.
|
||||
-- @param #string EscortBriefing A text showing the briefing to the player. Note that if no EscortBriefing is provided, the default briefing will be shown.
|
||||
-- @return #AI_ESCORT_DISPATCHER
|
||||
-- @usage
|
||||
--
|
||||
-- -- Create a new escort when a player joins an SU-25T plane.
|
||||
-- Create a carrier set, which contains the player slots that can be joined by the players, for which escorts will be defined.
|
||||
-- local Red_SU25T_CarrierSet = SET_GROUP:New():FilterPrefixes( "Red A2G Player Su-25T" ):FilterStart()
|
||||
--
|
||||
-- -- Create a spawn object that will spawn in the escorts, once the player has joined the player slot.
|
||||
-- local Red_SU25T_EscortSpawn = SPAWN:NewWithAlias( "Red A2G Su-25 Escort", "Red AI A2G SU-25 Escort" ):InitLimit( 10, 10 )
|
||||
--
|
||||
-- -- Create an airbase object, where the escorts will be spawned.
|
||||
-- local Red_SU25T_Airbase = AIRBASE:FindByName( AIRBASE.Caucasus.Maykop_Khanskaya )
|
||||
--
|
||||
-- -- Park the airplanes at the airbase, visible before start.
|
||||
-- Red_SU25T_EscortSpawn:ParkAtAirbase( Red_SU25T_Airbase, AIRBASE.TerminalType.OpenMedOrBig )
|
||||
--
|
||||
-- -- New create the escort dispatcher, using the carrier set, the escort spawn object at the escort airbase.
|
||||
-- -- Provide a name of the escort, which will be also the name appearing on the radio menu for the group.
|
||||
-- -- And a briefing to appear when the player joins the player slot.
|
||||
-- Red_SU25T_EscortDispatcher = AI_ESCORT_DISPATCHER:New( Red_SU25T_CarrierSet, Red_SU25T_EscortSpawn, Red_SU25T_Airbase, "Escort Su-25", "You Su-25T is escorted by one Su-25. Use the radio menu to control the escorts." )
|
||||
--
|
||||
-- -- The dispatcher needs to be started using the :Start() method.
|
||||
-- Red_SU25T_EscortDispatcher:Start()
|
||||
function AI_ESCORT_DISPATCHER:New( CarrierSet, EscortSpawn, EscortAirbase, EscortName, EscortBriefing )
|
||||
|
||||
local self = BASE:Inherit( self, FSM:New() ) -- #AI_ESCORT_DISPATCHER
|
||||
|
||||
self.CarrierSet = CarrierSet
|
||||
self.EscortSpawn = EscortSpawn
|
||||
self.EscortAirbase = EscortAirbase
|
||||
self.EscortName = EscortName
|
||||
self.EscortBriefing = EscortBriefing
|
||||
|
||||
self:SetStartState( "Idle" )
|
||||
|
||||
self:AddTransition( "Monitoring", "Monitor", "Monitoring" )
|
||||
|
||||
self:AddTransition( "Idle", "Start", "Monitoring" )
|
||||
self:AddTransition( "Monitoring", "Stop", "Idle" )
|
||||
|
||||
-- Put a Dead event handler on CarrierSet, to ensure that when a carrier is destroyed, that all internal parameters are reset.
|
||||
function self.CarrierSet.OnAfterRemoved( CarrierSet, From, Event, To, CarrierName, Carrier )
|
||||
self:F( { Carrier = Carrier:GetName() } )
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function AI_ESCORT_DISPATCHER:onafterStart( From, Event, To )
|
||||
|
||||
self:HandleEvent( EVENTS.Birth )
|
||||
|
||||
self:HandleEvent( EVENTS.PlayerLeaveUnit, self.OnEventExit )
|
||||
self:HandleEvent( EVENTS.Crash, self.OnEventExit )
|
||||
self:HandleEvent( EVENTS.Dead, self.OnEventExit )
|
||||
|
||||
end
|
||||
|
||||
--- @param #AI_ESCORT_DISPATCHER self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function AI_ESCORT_DISPATCHER:OnEventExit( EventData )
|
||||
|
||||
local PlayerGroupName = EventData.IniGroupName
|
||||
local PlayerGroup = EventData.IniGroup
|
||||
local PlayerUnit = EventData.IniUnit
|
||||
|
||||
self:I({EscortAirbase= self.EscortAirbase } )
|
||||
self:I({PlayerGroupName = PlayerGroupName } )
|
||||
self:I({PlayerGroup = PlayerGroup})
|
||||
self:I({FirstGroup = self.CarrierSet:GetFirst()})
|
||||
self:I({FindGroup = self.CarrierSet:FindGroup( PlayerGroupName )})
|
||||
|
||||
if self.CarrierSet:FindGroup( PlayerGroupName ) then
|
||||
if self.AI_Escorts[PlayerGroupName] then
|
||||
self.AI_Escorts[PlayerGroupName]:Stop()
|
||||
self.AI_Escorts[PlayerGroupName] = nil
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- @param #AI_ESCORT_DISPATCHER self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function AI_ESCORT_DISPATCHER:OnEventBirth( EventData )
|
||||
|
||||
local PlayerGroupName = EventData.IniGroupName
|
||||
local PlayerGroup = EventData.IniGroup
|
||||
local PlayerUnit = EventData.IniUnit
|
||||
|
||||
self:I({EscortAirbase= self.EscortAirbase } )
|
||||
self:I({PlayerGroupName = PlayerGroupName } )
|
||||
self:I({PlayerGroup = PlayerGroup})
|
||||
self:I({FirstGroup = self.CarrierSet:GetFirst()})
|
||||
self:I({FindGroup = self.CarrierSet:FindGroup( PlayerGroupName )})
|
||||
|
||||
if self.CarrierSet:FindGroup( PlayerGroupName ) then
|
||||
if not self.AI_Escorts[PlayerGroupName] then
|
||||
local LeaderUnit = PlayerUnit
|
||||
local EscortGroup = self.EscortSpawn:SpawnAtAirbase( self.EscortAirbase, SPAWN.Takeoff.Hot )
|
||||
self:I({EscortGroup = EscortGroup})
|
||||
|
||||
self:ScheduleOnce( 1,
|
||||
function( EscortGroup )
|
||||
local EscortSet = SET_GROUP:New()
|
||||
EscortSet:AddGroup( EscortGroup )
|
||||
self.AI_Escorts[PlayerGroupName] = AI_ESCORT:New( LeaderUnit, EscortSet, self.EscortName, self.EscortBriefing )
|
||||
self.AI_Escorts[PlayerGroupName]:FormationTrail( 0, 100, 0 )
|
||||
if EscortGroup:IsHelicopter() then
|
||||
self.AI_Escorts[PlayerGroupName]:MenusHelicopters()
|
||||
else
|
||||
self.AI_Escorts[PlayerGroupName]:MenusAirplanes()
|
||||
end
|
||||
self.AI_Escorts[PlayerGroupName]:__Start( 0.1 )
|
||||
end, EscortGroup
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- Start Trigger for AI_ESCORT_DISPATCHER
|
||||
-- @function [parent=#AI_ESCORT_DISPATCHER] Start
|
||||
-- @param #AI_ESCORT_DISPATCHER self
|
||||
|
||||
--- Start Asynchronous Trigger for AI_ESCORT_DISPATCHER
|
||||
-- @function [parent=#AI_ESCORT_DISPATCHER] __Start
|
||||
-- @param #AI_ESCORT_DISPATCHER self
|
||||
-- @param #number Delay
|
||||
|
||||
--- Stop Trigger for AI_ESCORT_DISPATCHER
|
||||
-- @function [parent=#AI_ESCORT_DISPATCHER] Stop
|
||||
-- @param #AI_ESCORT_DISPATCHER self
|
||||
|
||||
--- Stop Asynchronous Trigger for AI_ESCORT_DISPATCHER
|
||||
-- @function [parent=#AI_ESCORT_DISPATCHER] __Stop
|
||||
-- @param #AI_ESCORT_DISPATCHER self
|
||||
-- @param #number Delay
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
146
Moose Development/Moose/AI/AI_Escort_Dispatcher_Request.lua
Normal file
146
Moose Development/Moose/AI/AI_Escort_Dispatcher_Request.lua
Normal file
@@ -0,0 +1,146 @@
|
||||
--- **AI** - Models the assignment of AI escorts to player flights upon request using the radio menu.
|
||||
--
|
||||
-- ## Features:
|
||||
--
|
||||
-- * Provides the facilities to trigger escorts when players join flight units.
|
||||
-- * Provide a menu for which escorts can be requested.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module AI.AI_ESCORT_DISPATCHER_REQUEST
|
||||
-- @image MOOSE.JPG
|
||||
|
||||
|
||||
--- @type AI_ESCORT_DISPATCHER_REQUEST
|
||||
-- @extends Core.Fsm#FSM
|
||||
|
||||
|
||||
--- Models the assignment of AI escorts to player flights upon request using the radio menu.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @field #AI_ESCORT_DISPATCHER_REQUEST
|
||||
AI_ESCORT_DISPATCHER_REQUEST = {
|
||||
ClassName = "AI_ESCORT_DISPATCHER_REQUEST",
|
||||
}
|
||||
|
||||
--- @field #list
|
||||
AI_ESCORT_DISPATCHER_REQUEST.AI_Escorts = {}
|
||||
|
||||
|
||||
--- Creates a new AI_ESCORT_DISPATCHER_REQUEST object.
|
||||
-- @param #AI_ESCORT_DISPATCHER_REQUEST self
|
||||
-- @param Core.Set#SET_GROUP CarrierSet The set of @{Wrapper.Group#GROUP} objects of carriers for which escorts are requested.
|
||||
-- @param Core.Spawn#SPAWN EscortSpawn The spawn object that will spawn in the Escorts.
|
||||
-- @param Wrapper.Airbase#AIRBASE EscortAirbase The airbase where the escorts are spawned.
|
||||
-- @param #string EscortName Name of the escort, which will also be the name of the escort menu.
|
||||
-- @param #string EscortBriefing A text showing the briefing to the player. Note that if no EscortBriefing is provided, the default briefing will be shown.
|
||||
-- @return #AI_ESCORT_DISPATCHER_REQUEST
|
||||
function AI_ESCORT_DISPATCHER_REQUEST:New( CarrierSet, EscortSpawn, EscortAirbase, EscortName, EscortBriefing )
|
||||
|
||||
local self = BASE:Inherit( self, FSM:New() ) -- #AI_ESCORT_DISPATCHER_REQUEST
|
||||
|
||||
self.CarrierSet = CarrierSet
|
||||
self.EscortSpawn = EscortSpawn
|
||||
self.EscortAirbase = EscortAirbase
|
||||
self.EscortName = EscortName
|
||||
self.EscortBriefing = EscortBriefing
|
||||
|
||||
self:SetStartState( "Idle" )
|
||||
|
||||
self:AddTransition( "Monitoring", "Monitor", "Monitoring" )
|
||||
|
||||
self:AddTransition( "Idle", "Start", "Monitoring" )
|
||||
self:AddTransition( "Monitoring", "Stop", "Idle" )
|
||||
|
||||
-- Put a Dead event handler on CarrierSet, to ensure that when a carrier is destroyed, that all internal parameters are reset.
|
||||
function self.CarrierSet.OnAfterRemoved( CarrierSet, From, Event, To, CarrierName, Carrier )
|
||||
self:F( { Carrier = Carrier:GetName() } )
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function AI_ESCORT_DISPATCHER_REQUEST:onafterStart( From, Event, To )
|
||||
|
||||
self:HandleEvent( EVENTS.Birth )
|
||||
|
||||
self:HandleEvent( EVENTS.PlayerLeaveUnit, self.OnEventExit )
|
||||
self:HandleEvent( EVENTS.Crash, self.OnEventExit )
|
||||
self:HandleEvent( EVENTS.Dead, self.OnEventExit )
|
||||
|
||||
end
|
||||
|
||||
--- @param #AI_ESCORT_DISPATCHER_REQUEST self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function AI_ESCORT_DISPATCHER_REQUEST:OnEventExit( EventData )
|
||||
|
||||
local PlayerGroupName = EventData.IniGroupName
|
||||
local PlayerGroup = EventData.IniGroup
|
||||
local PlayerUnit = EventData.IniUnit
|
||||
|
||||
if self.CarrierSet:FindGroup( PlayerGroupName ) then
|
||||
if self.AI_Escorts[PlayerGroupName] then
|
||||
self.AI_Escorts[PlayerGroupName]:Stop()
|
||||
self.AI_Escorts[PlayerGroupName] = nil
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- @param #AI_ESCORT_DISPATCHER_REQUEST self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function AI_ESCORT_DISPATCHER_REQUEST:OnEventBirth( EventData )
|
||||
|
||||
local PlayerGroupName = EventData.IniGroupName
|
||||
local PlayerGroup = EventData.IniGroup
|
||||
local PlayerUnit = EventData.IniUnit
|
||||
|
||||
if self.CarrierSet:FindGroup( PlayerGroupName ) then
|
||||
if not self.AI_Escorts[PlayerGroupName] then
|
||||
local LeaderUnit = PlayerUnit
|
||||
self:ScheduleOnce( 0.1,
|
||||
function()
|
||||
self.AI_Escorts[PlayerGroupName] = AI_ESCORT_REQUEST:New( LeaderUnit, self.EscortSpawn, self.EscortAirbase, self.EscortName, self.EscortBriefing )
|
||||
self.AI_Escorts[PlayerGroupName]:FormationTrail( 0, 100, 0 )
|
||||
if PlayerGroup:IsHelicopter() then
|
||||
self.AI_Escorts[PlayerGroupName]:MenusHelicopters()
|
||||
else
|
||||
self.AI_Escorts[PlayerGroupName]:MenusAirplanes()
|
||||
end
|
||||
self.AI_Escorts[PlayerGroupName]:__Start( 0.1 )
|
||||
end
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- Start Trigger for AI_ESCORT_DISPATCHER_REQUEST
|
||||
-- @function [parent=#AI_ESCORT_DISPATCHER_REQUEST] Start
|
||||
-- @param #AI_ESCORT_DISPATCHER_REQUEST self
|
||||
|
||||
--- Start Asynchronous Trigger for AI_ESCORT_DISPATCHER_REQUEST
|
||||
-- @function [parent=#AI_ESCORT_DISPATCHER_REQUEST] __Start
|
||||
-- @param #AI_ESCORT_DISPATCHER_REQUEST self
|
||||
-- @param #number Delay
|
||||
|
||||
--- Stop Trigger for AI_ESCORT_DISPATCHER_REQUEST
|
||||
-- @function [parent=#AI_ESCORT_DISPATCHER_REQUEST] Stop
|
||||
-- @param #AI_ESCORT_DISPATCHER_REQUEST self
|
||||
|
||||
--- Stop Asynchronous Trigger for AI_ESCORT_DISPATCHER_REQUEST
|
||||
-- @function [parent=#AI_ESCORT_DISPATCHER_REQUEST] __Stop
|
||||
-- @param #AI_ESCORT_DISPATCHER_REQUEST self
|
||||
-- @param #number Delay
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
316
Moose Development/Moose/AI/AI_Escort_Request.lua
Normal file
316
Moose Development/Moose/AI/AI_Escort_Request.lua
Normal file
@@ -0,0 +1,316 @@
|
||||
--- **Functional** -- Taking the lead of AI escorting your flight or of other AI, upon request using the menu.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ## Features:
|
||||
--
|
||||
-- * Escort navigation commands.
|
||||
-- * Escort hold at position commands.
|
||||
-- * Escorts reporting detected targets.
|
||||
-- * Escorts scanning targets in advance.
|
||||
-- * Escorts attacking specific targets.
|
||||
-- * Request assistance from other groups for attack.
|
||||
-- * Manage rule of engagement of escorts.
|
||||
-- * Manage the allowed evasion techniques of escorts.
|
||||
-- * Make escort to execute a defined mission or path.
|
||||
-- * Escort tactical situation reporting.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ## Missions:
|
||||
--
|
||||
-- [ESC - Escorting](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/ESC%20-%20Escorting)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- Allows you to interact with escorting AI on your flight and take the lead.
|
||||
--
|
||||
-- Each escorting group can be commanded with a complete set of radio commands (radio menu in your flight, and then F10).
|
||||
--
|
||||
-- The radio commands will vary according the category of the group. The richest set of commands are with helicopters and airPlanes.
|
||||
-- Ships and Ground troops will have a more limited set, but they can provide support through the bombing of targets designated by the other escorts.
|
||||
--
|
||||
-- Escorts detect targets using a built-in detection mechanism. The detected targets are reported at a specified time interval.
|
||||
-- Once targets are reported, each escort has these targets as menu options to command the attack of these targets.
|
||||
-- Targets are by default grouped per area of 5000 meters, but the kind of detection and the grouping range can be altered.
|
||||
--
|
||||
-- Different formations can be selected in the Flight menu: Trail, Stack, Left Line, Right Line, Left Wing, Right Wing, Central Wing and Boxed formations are available.
|
||||
-- The Flight menu also allows for a mass attack, where all of the escorts are commanded to attack a target.
|
||||
--
|
||||
-- Escorts can emit flares to reports their location. They can be commanded to hold at a location, which can be their current or the leader location.
|
||||
-- In this way, you can spread out the escorts over the battle field before a coordinated attack.
|
||||
--
|
||||
-- But basically, the escort class provides 4 modes of operation, and depending on the mode, you are either leading the flight, or following the flight.
|
||||
--
|
||||
-- ## Leading the flight
|
||||
--
|
||||
-- When leading the flight, you are expected to guide the escorts towards the target areas,
|
||||
-- and carefully coordinate the attack based on the threat levels reported, and the available weapons
|
||||
-- carried by the escorts. Ground ships or ground troops can execute A-assisted attacks, when they have long-range ground precision weapons for attack.
|
||||
--
|
||||
-- ## Following the flight
|
||||
--
|
||||
-- Escorts can be commanded to execute a specific mission path. In this mode, the escorts are in the lead.
|
||||
-- You as a player, are following the escorts, and are commanding them to progress the mission while
|
||||
-- ensuring that the escorts survive. You are joining the escorts in the battlefield. They will detect and report targets
|
||||
-- and you will ensure that the attacks are well coordinated, assigning the correct escort type for the detected target
|
||||
-- type. Once the attack is finished, the escort will resume the mission it was assigned.
|
||||
-- In other words, you can use the escorts for reconnaissance, and for guiding the attack.
|
||||
-- Imagine you as a mi-8 pilot, assigned to pickup cargo. Two ka-50s are guiding the way, and you are
|
||||
-- following. You are in control. The ka-50s detect targets, report them, and you command how the attack
|
||||
-- will commence and from where. You can control where the escorts are holding position and which targets
|
||||
-- are attacked first. You are in control how the ka-50s will follow their mission path.
|
||||
--
|
||||
-- Escorts can act as part of a AI A2G dispatcher offensive. In this way, You was a player are in control.
|
||||
-- The mission is defined by the A2G dispatcher, and you are responsible to join the flight and ensure that the
|
||||
-- attack is well coordinated.
|
||||
--
|
||||
-- It is with great proud that I present you this class, and I hope you will enjoy the functionality and the dynamism
|
||||
-- it brings in your DCS world simulations.
|
||||
--
|
||||
-- # RADIO MENUs that can be created:
|
||||
--
|
||||
-- Find a summary below of the current available commands:
|
||||
--
|
||||
-- ## Navigation ...:
|
||||
--
|
||||
-- Escort group navigation functions:
|
||||
--
|
||||
-- * **"Join-Up":** The escort group fill follow you in the assigned formation.
|
||||
-- * **"Flare":** Provides menu commands to let the escort group shoot a flare in the air in a color.
|
||||
-- * **"Smoke":** Provides menu commands to let the escort group smoke the air in a color. Note that smoking is only available for ground and naval troops.
|
||||
--
|
||||
-- ## Hold position ...:
|
||||
--
|
||||
-- Escort group navigation functions:
|
||||
--
|
||||
-- * **"At current location":** The escort group will hover above the ground at the position they were. The altitude can be specified as a parameter.
|
||||
-- * **"At my location":** The escort group will hover or orbit at the position where you are. The escort will fly to your location and hold position. The altitude can be specified as a parameter.
|
||||
--
|
||||
-- ## Report targets ...:
|
||||
--
|
||||
-- Report targets will make the escort group to report any target that it identifies within detection range. Any detected target can be attacked using the "Attack Targets" menu function. (see below).
|
||||
--
|
||||
-- * **"Report now":** Will report the current detected targets.
|
||||
-- * **"Report targets on":** Will make the escorts to report the detected targets and will fill the "Attack Targets" menu list.
|
||||
-- * **"Report targets off":** Will stop detecting targets.
|
||||
--
|
||||
-- ## Attack targets ...:
|
||||
--
|
||||
-- This menu item will list all detected targets within a 15km range. Depending on the level of detection (known/unknown) and visuality, the targets type will also be listed.
|
||||
-- This menu will be available in Flight menu or in each Escort menu.
|
||||
--
|
||||
-- ## Scan targets ...:
|
||||
--
|
||||
-- Menu items to pop-up the escort group for target scanning. After scanning, the escort group will resume with the mission or rejoin formation.
|
||||
--
|
||||
-- * **"Scan targets 30 seconds":** Scan 30 seconds for targets.
|
||||
-- * **"Scan targets 60 seconds":** Scan 60 seconds for targets.
|
||||
--
|
||||
-- ## Request assistance from ...:
|
||||
--
|
||||
-- This menu item will list all detected targets within a 15km range, similar as with the menu item **Attack Targets**.
|
||||
-- This menu item allows to request attack support from other ground based escorts supporting the current escort.
|
||||
-- eg. the function allows a player to request support from the Ship escort to attack a target identified by the Plane escort with its Tomahawk missiles.
|
||||
-- eg. the function allows a player to request support from other Planes escorting to bomb the unit with illumination missiles or bombs, so that the main plane escort can attack the area.
|
||||
--
|
||||
-- ## ROE ...:
|
||||
--
|
||||
-- Sets the Rules of Engagement (ROE) of the escort group when in flight.
|
||||
--
|
||||
-- * **"Hold Fire":** The escort group will hold fire.
|
||||
-- * **"Return Fire":** The escort group will return fire.
|
||||
-- * **"Open Fire":** The escort group will open fire on designated targets.
|
||||
-- * **"Weapon Free":** The escort group will engage with any target.
|
||||
--
|
||||
-- ## Evasion ...:
|
||||
--
|
||||
-- Will define the evasion techniques that the escort group will perform during flight or combat.
|
||||
--
|
||||
-- * **"Fight until death":** The escort group will have no reaction to threats.
|
||||
-- * **"Use flares, chaff and jammers":** The escort group will use passive defense using flares and jammers. No evasive manoeuvres are executed.
|
||||
-- * **"Evade enemy fire":** The rescort group will evade enemy fire before firing.
|
||||
-- * **"Go below radar and evade fire":** The escort group will perform evasive vertical manoeuvres.
|
||||
--
|
||||
-- ## Resume Mission ...:
|
||||
--
|
||||
-- Escort groups can have their own mission. This menu item will allow the escort group to resume their Mission from a given waypoint.
|
||||
-- Note that this is really fantastic, as you now have the dynamic of taking control of the escort groups, and allowing them to resume their path or mission.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Authors: **FlightControl**
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module AI.AI_Escort
|
||||
-- @image Escorting.JPG
|
||||
|
||||
|
||||
|
||||
--- @type AI_ESCORT_REQUEST
|
||||
-- @extends AI.AI_Escort#AI_ESCORT
|
||||
|
||||
--- AI_ESCORT_REQUEST class
|
||||
--
|
||||
-- # AI_ESCORT_REQUEST construction methods.
|
||||
--
|
||||
-- Create a new AI_ESCORT_REQUEST object with the @{#AI_ESCORT_REQUEST.New} method:
|
||||
--
|
||||
-- * @{#AI_ESCORT_REQUEST.New}: Creates a new AI_ESCORT_REQUEST object from a @{Wrapper.Group#GROUP} for a @{Wrapper.Client#CLIENT}, with an optional briefing text.
|
||||
--
|
||||
-- @usage
|
||||
-- -- Declare a new EscortPlanes object as follows:
|
||||
--
|
||||
-- -- First find the GROUP object and the CLIENT object.
|
||||
-- local EscortUnit = CLIENT:FindByName( "Unit Name" ) -- The Unit Name is the name of the unit flagged with the skill Client in the mission editor.
|
||||
-- local EscortGroup = GROUP:FindByName( "Group Name" ) -- The Group Name is the name of the group that will escort the Escort Client.
|
||||
--
|
||||
-- -- Now use these 2 objects to construct the new EscortPlanes object.
|
||||
-- EscortPlanes = AI_ESCORT_REQUEST:New( EscortUnit, EscortGroup, "Desert", "Welcome to the mission. You are escorted by a plane with code name 'Desert', which can be instructed through the F10 radio menu." )
|
||||
--
|
||||
-- @field #AI_ESCORT_REQUEST
|
||||
AI_ESCORT_REQUEST = {
|
||||
ClassName = "AI_ESCORT_REQUEST",
|
||||
}
|
||||
|
||||
--- AI_ESCORT_REQUEST.Mode class
|
||||
-- @type AI_ESCORT_REQUEST.MODE
|
||||
-- @field #number FOLLOW
|
||||
-- @field #number MISSION
|
||||
|
||||
--- MENUPARAM type
|
||||
-- @type MENUPARAM
|
||||
-- @field #AI_ESCORT_REQUEST ParamSelf
|
||||
-- @field #Distance ParamDistance
|
||||
-- @field #function ParamFunction
|
||||
-- @field #string ParamMessage
|
||||
|
||||
--- AI_ESCORT_REQUEST class constructor for an AI group
|
||||
-- @param #AI_ESCORT_REQUEST self
|
||||
-- @param Wrapper.Client#CLIENT EscortUnit The client escorted by the EscortGroup.
|
||||
-- @param Core.Spawn#SPAWN EscortSpawn The spawn object of AI, escorting the EscortUnit.
|
||||
-- @param Wrapper.Airbase#AIRBASE EscortAirbase The airbase where escorts will be spawned once requested.
|
||||
-- @param #string EscortName Name of the escort.
|
||||
-- @param #string EscortBriefing A text showing the AI_ESCORT_REQUEST briefing to the player. Note that if no EscortBriefing is provided, the default briefing will be shown.
|
||||
-- @return #AI_ESCORT_REQUEST
|
||||
-- @usage
|
||||
-- EscortSpawn = SPAWN:NewWithAlias( "Red A2G Escort Template", "Red A2G Escort AI" ):InitLimit( 10, 10 )
|
||||
-- EscortSpawn:ParkAtAirbase( AIRBASE:FindByName( AIRBASE.Caucasus.Sochi_Adler ), AIRBASE.TerminalType.OpenBig )
|
||||
--
|
||||
-- local EscortUnit = UNIT:FindByName( "Red A2G Pilot" )
|
||||
--
|
||||
-- Escort = AI_ESCORT_REQUEST:New( EscortUnit, EscortSpawn, AIRBASE:FindByName(AIRBASE.Caucasus.Sochi_Adler), "A2G", "Briefing" )
|
||||
-- Escort:FormationTrail( 50, 100, 100 )
|
||||
-- Escort:Menus()
|
||||
-- Escort:__Start( 5 )
|
||||
function AI_ESCORT_REQUEST:New( EscortUnit, EscortSpawn, EscortAirbase, EscortName, EscortBriefing )
|
||||
|
||||
local EscortGroupSet = SET_GROUP:New():FilterDeads():FilterCrashes()
|
||||
local self = BASE:Inherit( self, AI_ESCORT:New( EscortUnit, EscortGroupSet, EscortName, EscortBriefing ) ) -- #AI_ESCORT_REQUEST
|
||||
|
||||
self.EscortGroupSet = EscortGroupSet
|
||||
self.EscortSpawn = EscortSpawn
|
||||
self.EscortAirbase = EscortAirbase
|
||||
|
||||
self.LeaderGroup = self.PlayerUnit:GetGroup()
|
||||
|
||||
self.Detection = DETECTION_AREAS:New( self.EscortGroupSet, 5000 )
|
||||
self.Detection:__Start( 30 )
|
||||
|
||||
self.SpawnMode = self.__Enum.Mode.Mission
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- @param #AI_ESCORT_REQUEST self
|
||||
function AI_ESCORT_REQUEST:SpawnEscort()
|
||||
|
||||
local EscortGroup = self.EscortSpawn:SpawnAtAirbase( self.EscortAirbase, SPAWN.Takeoff.Hot )
|
||||
|
||||
self:ScheduleOnce( 0.1,
|
||||
function( EscortGroup )
|
||||
|
||||
EscortGroup:OptionROTVertical()
|
||||
EscortGroup:OptionROEHoldFire()
|
||||
|
||||
self.EscortGroupSet:AddGroup( EscortGroup )
|
||||
|
||||
local LeaderEscort = self.EscortGroupSet:GetFirst() -- Wrapper.Group#GROUP
|
||||
local Report = REPORT:New()
|
||||
Report:Add( "Joining Up " .. self.EscortGroupSet:GetUnitTypeNames():Text( ", " ) .. " from " .. LeaderEscort:GetCoordinate():ToString( self.EscortUnit ) )
|
||||
LeaderEscort:MessageTypeToGroup( Report:Text(), MESSAGE.Type.Information, self.PlayerUnit )
|
||||
|
||||
self:SetFlightModeFormation( EscortGroup )
|
||||
self:FormationTrail()
|
||||
|
||||
self:_InitFlightMenus()
|
||||
self:_InitEscortMenus( EscortGroup )
|
||||
self:_InitEscortRoute( EscortGroup )
|
||||
|
||||
--- @param #AI_ESCORT self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function EscortGroup:OnEventDeadOrCrash( EventData )
|
||||
self:F( { "EventDead", EventData } )
|
||||
self.EscortMenu:Remove()
|
||||
end
|
||||
|
||||
EscortGroup:HandleEvent( EVENTS.Dead, EscortGroup.OnEventDeadOrCrash )
|
||||
EscortGroup:HandleEvent( EVENTS.Crash, EscortGroup.OnEventDeadOrCrash )
|
||||
|
||||
end, EscortGroup
|
||||
)
|
||||
|
||||
end
|
||||
|
||||
--- @param #AI_ESCORT_REQUEST self
|
||||
-- @param Core.Set#SET_GROUP EscortGroupSet
|
||||
function AI_ESCORT_REQUEST:onafterStart( EscortGroupSet )
|
||||
|
||||
self:F()
|
||||
|
||||
if not self.MenuRequestEscort then
|
||||
self.MainMenu = MENU_GROUP:New( self.PlayerGroup, self.EscortName )
|
||||
self.MenuRequestEscort = MENU_GROUP_COMMAND:New( self.LeaderGroup, "Request new escort ", self.MainMenu,
|
||||
function()
|
||||
self:SpawnEscort()
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
self:GetParent( self ).onafterStart( self, EscortGroupSet )
|
||||
|
||||
self:HandleEvent( EVENTS.Dead, self.OnEventDeadOrCrash )
|
||||
self:HandleEvent( EVENTS.Crash, self.OnEventDeadOrCrash )
|
||||
|
||||
end
|
||||
|
||||
--- @param #AI_ESCORT_REQUEST self
|
||||
-- @param Core.Set#SET_GROUP EscortGroupSet
|
||||
function AI_ESCORT_REQUEST:onafterStop( EscortGroupSet )
|
||||
|
||||
self:F()
|
||||
|
||||
EscortGroupSet:ForEachGroup(
|
||||
--- @param Core.Group#GROUP EscortGroup
|
||||
function( EscortGroup )
|
||||
EscortGroup:WayPointInitialize()
|
||||
|
||||
EscortGroup:OptionROTVertical()
|
||||
EscortGroup:OptionROEOpenFire()
|
||||
end
|
||||
)
|
||||
|
||||
self.Detection:Stop()
|
||||
|
||||
self.MainMenu:Remove()
|
||||
|
||||
end
|
||||
|
||||
--- Set the spawn mode to be mission execution.
|
||||
-- @param #AI_ESCORT_REQUEST self
|
||||
function AI_ESCORT_REQUEST:SetEscortSpawnMission()
|
||||
|
||||
self.SpawnMode = self.__Enum.Mode.Mission
|
||||
|
||||
end
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,7 @@
|
||||
--
|
||||
-- * Patrol AI airplanes within a given zone.
|
||||
-- * Trigger detected events when enemy airplanes are detected.
|
||||
-- * Manage a fuel treshold to RTB on time.
|
||||
-- * Manage a fuel threshold to RTB on time.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
@@ -16,7 +16,7 @@
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release/PAT%20-%20Patrolling)
|
||||
-- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/PAT%20-%20Patrolling)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
@@ -72,8 +72,8 @@
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- 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.
|
||||
-- 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.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
@@ -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 tresholds have been reached, the AI will RTB.
|
||||
-- * **Status** ( Group ): The AI is checking status (fuel and damage). When the thresholds 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 treshold is calculated.
|
||||
-- When the fuel treshold 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 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 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 treshold is reached, the AI will return immediately to the home base (RTB).
|
||||
-- Therefore, when the damage threshold 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.
|
||||
--
|
||||
-- ===
|
||||
@@ -178,8 +178,8 @@ function AI_PATROL_ZONE:New( PatrolZone, PatrolFloorAltitude, PatrolCeilingAltit
|
||||
self.PatrolMinSpeed = PatrolMinSpeed
|
||||
self.PatrolMaxSpeed = PatrolMaxSpeed
|
||||
|
||||
-- defafult PatrolAltType to "RADIO" if not specified
|
||||
self.PatrolAltType = PatrolAltType or "RADIO"
|
||||
-- defafult PatrolAltType to "BARO" if not specified
|
||||
self.PatrolAltType = PatrolAltType or "BARO"
|
||||
|
||||
self:SetRefreshTimeInterval( 30 )
|
||||
|
||||
@@ -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 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.
|
||||
-- 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.
|
||||
-- Once the time is finished, the old AI will return to the base.
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @param #number PatrolFuelThresholdPercentage The treshold in percentage (between 0 and 1) when the AIControllable is considered to get out of fuel.
|
||||
-- @param #number PatrolFuelThresholdPercentage The threshold 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 treshold, it is required that the AI returns to the home base.
|
||||
--- When the AI is damaged beyond a certain threshold, it is required that the AI returns to the home base.
|
||||
-- However, damage cannot be foreseen early on.
|
||||
-- Therefore, when the damage treshold is reached,
|
||||
-- Therefore, when the damage threshold 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 treshold will be 0.25.
|
||||
-- So, in a group of 4 airplanes, 2 lost and 2 with damage 0.2, the damage threshold will be 0.25.
|
||||
-- @param #AI_PATROL_ZONE self
|
||||
-- @param #number PatrolDamageThreshold The treshold in percentage (between 0 and 1) when the AI is considered to be damaged.
|
||||
-- @param #number PatrolDamageThreshold The threshold 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 )
|
||||
|
||||
@@ -636,7 +636,7 @@ function AI_PATROL_ZONE:onafterStart( Controllable, From, Event, To )
|
||||
|
||||
self.Controllable:OnReSpawn(
|
||||
function( PatrolGroup )
|
||||
self:E( "ReSpawn" )
|
||||
self:T( "ReSpawn" )
|
||||
self:__Reset( 1 )
|
||||
self:__Route( 5 )
|
||||
end
|
||||
@@ -667,21 +667,27 @@ function AI_PATROL_ZONE:onafterDetect( Controllable, From, Event, To )
|
||||
if TargetObject and TargetObject:isExist() and TargetObject.id_ < 50000000 then
|
||||
|
||||
local TargetUnit = UNIT:Find( TargetObject )
|
||||
local TargetUnitName = TargetUnit:GetName()
|
||||
|
||||
if self.DetectionZone then
|
||||
if TargetUnit:IsInZone( self.DetectionZone ) then
|
||||
self:T( {"Detected ", TargetUnit } )
|
||||
-- Check that target is alive due to issue https://github.com/FlightControl-Master/MOOSE/issues/1234
|
||||
if TargetUnit and TargetUnit:IsAlive() then
|
||||
|
||||
local TargetUnitName = TargetUnit:GetName()
|
||||
|
||||
if self.DetectionZone then
|
||||
if TargetUnit:IsInZone( self.DetectionZone ) then
|
||||
self:T( {"Detected ", TargetUnit } )
|
||||
if self.DetectedUnits[TargetUnit] == nil then
|
||||
self.DetectedUnits[TargetUnit] = true
|
||||
end
|
||||
Detected = true
|
||||
end
|
||||
else
|
||||
if self.DetectedUnits[TargetUnit] == nil then
|
||||
self.DetectedUnits[TargetUnit] = true
|
||||
end
|
||||
Detected = true
|
||||
Detected = true
|
||||
end
|
||||
else
|
||||
if self.DetectedUnits[TargetUnit] == nil then
|
||||
self.DetectedUnits[TargetUnit] = true
|
||||
end
|
||||
Detected = true
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -720,7 +726,8 @@ function AI_PATROL_ZONE:onafterRoute( Controllable, From, Event, To )
|
||||
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.
|
||||
|
||||
@@ -735,10 +742,11 @@ function AI_PATROL_ZONE:onafterRoute( Controllable, From, Event, To )
|
||||
-- This will make the plane fly immediately to the patrol zone.
|
||||
|
||||
if self.Controllable:InAir() == false then
|
||||
self:E( "Not in the air, finding route path within PatrolZone" )
|
||||
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(
|
||||
@@ -750,10 +758,11 @@ function AI_PATROL_ZONE:onafterRoute( Controllable, From, Event, To )
|
||||
)
|
||||
PatrolRoute[#PatrolRoute+1] = CurrentRoutePoint
|
||||
else
|
||||
self:E( "In the air, finding route path within PatrolZone" )
|
||||
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(
|
||||
@@ -825,7 +834,7 @@ function AI_PATROL_ZONE:onafterStatus()
|
||||
|
||||
local Fuel = self.Controllable:GetFuelMin()
|
||||
if Fuel < self.PatrolFuelThresholdPercentage then
|
||||
self:E( self.Controllable:GetName() .. " is out of fuel:" .. Fuel .. ", RTB!" )
|
||||
self:I( self.Controllable:GetName() .. " is out of fuel:" .. Fuel .. ", RTB!" )
|
||||
local OldAIControllable = self.Controllable
|
||||
|
||||
local OrbitTask = OldAIControllable:TaskOrbitCircle( math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ), self.PatrolMinSpeed )
|
||||
@@ -839,7 +848,7 @@ function AI_PATROL_ZONE:onafterStatus()
|
||||
-- TODO: Check GROUP damage function.
|
||||
local Damage = self.Controllable:GetLife()
|
||||
if Damage <= self.PatrolDamageThreshold then
|
||||
self:E( self.Controllable:GetName() .. " is damaged:" .. Damage .. ", RTB!" )
|
||||
self:I( self.Controllable:GetName() .. " is damaged:" .. Damage .. ", RTB!" )
|
||||
RTB = true
|
||||
end
|
||||
|
||||
@@ -864,9 +873,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(
|
||||
@@ -900,7 +910,6 @@ end
|
||||
function AI_PATROL_ZONE:OnCrash( EventData )
|
||||
|
||||
if self.Controllable:IsAlive() and EventData.IniDCSGroupName == self.Controllable:GetName() then
|
||||
self:E( self.Controllable:GetUnits() )
|
||||
if #self.Controllable:GetUnits() == 1 then
|
||||
self:__Crash( 1, EventData )
|
||||
end
|
||||
|
||||
@@ -1,34 +1,34 @@
|
||||
--- **Actions** - ACT_ACCOUNT_ classes **account for** (detect, count & report) various DCS events occuring on @{Wrapper.Unit}s.
|
||||
--
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module Actions.Account
|
||||
-- @image MOOSE.JPG
|
||||
|
||||
do -- ACT_ACCOUNT
|
||||
|
||||
|
||||
--- # @{#ACT_ACCOUNT} FSM class, extends @{Core.Fsm#FSM_PROCESS}
|
||||
--
|
||||
-- ## ACT_ACCOUNT state machine:
|
||||
--
|
||||
--
|
||||
-- ## ACT_ACCOUNT state machine:
|
||||
--
|
||||
-- This class is a state machine: it manages a process that is triggered by events causing state transitions to occur.
|
||||
-- All derived classes from this class will start with the class name, followed by a \_. See the relevant derived class descriptions below.
|
||||
-- Each derived class follows exactly the same process, using the same events and following the same state transitions,
|
||||
-- Each derived class follows exactly the same process, using the same events and following the same state transitions,
|
||||
-- but will have **different implementation behaviour** upon each event or state transition.
|
||||
--
|
||||
-- ### ACT_ACCOUNT States
|
||||
--
|
||||
--
|
||||
-- ### ACT_ACCOUNT States
|
||||
--
|
||||
-- * **Asigned**: The player is assigned.
|
||||
-- * **Waiting**: Waiting for an event.
|
||||
-- * **Report**: Reporting.
|
||||
-- * **Account**: Account for an event.
|
||||
-- * **Accounted**: All events have been accounted for, end of the process.
|
||||
-- * **Failed**: Failed the process.
|
||||
--
|
||||
-- ### ACT_ACCOUNT Events
|
||||
--
|
||||
--
|
||||
-- ### ACT_ACCOUNT Events
|
||||
--
|
||||
-- * **Start**: Start the process.
|
||||
-- * **Wait**: Wait for an event.
|
||||
-- * **Report**: Report the status of the accounting.
|
||||
@@ -36,32 +36,32 @@ do -- ACT_ACCOUNT
|
||||
-- * **More**: More targets.
|
||||
-- * **NoMore (*)**: No more targets.
|
||||
-- * **Fail (*)**: The action process has failed.
|
||||
--
|
||||
--
|
||||
-- (*) End states of the process.
|
||||
--
|
||||
--
|
||||
-- ### ACT_ACCOUNT state transition methods:
|
||||
--
|
||||
--
|
||||
-- State transition functions can be set **by the mission designer** customizing or improving the behaviour of the state.
|
||||
-- There are 2 moments when state transition methods will be called by the state machine:
|
||||
--
|
||||
-- * **Before** the state transition.
|
||||
-- The state transition method needs to start with the name **OnBefore + the name of the state**.
|
||||
--
|
||||
-- * **Before** the state transition.
|
||||
-- The state transition method needs to start with the name **OnBefore + the name of the state**.
|
||||
-- If the state transition method returns false, then the processing of the state transition will not be done!
|
||||
-- If you want to change the behaviour of the AIControllable at this event, return false,
|
||||
-- If you want to change the behaviour of the AIControllable at this event, return false,
|
||||
-- but then you'll need to specify your own logic using the AIControllable!
|
||||
--
|
||||
-- * **After** the state transition.
|
||||
-- The state transition method needs to start with the name **OnAfter + the name of the state**.
|
||||
--
|
||||
-- * **After** the state transition.
|
||||
-- The state transition method needs to start with the name **OnAfter + the name of the state**.
|
||||
-- These state transition methods need to provide a return value, which is specified at the function description.
|
||||
--
|
||||
--
|
||||
-- @type ACT_ACCOUNT
|
||||
-- @field Core.Set#SET_UNIT TargetSetUnit
|
||||
-- @extends Core.Fsm#FSM_PROCESS
|
||||
ACT_ACCOUNT = {
|
||||
ACT_ACCOUNT = {
|
||||
ClassName = "ACT_ACCOUNT",
|
||||
TargetSetUnit = nil,
|
||||
}
|
||||
|
||||
|
||||
--- Creates a new DESTROY process.
|
||||
-- @param #ACT_ACCOUNT self
|
||||
-- @return #ACT_ACCOUNT
|
||||
@@ -69,7 +69,7 @@ do -- ACT_ACCOUNT
|
||||
|
||||
-- Inherits from BASE
|
||||
local self = BASE:Inherit( self, FSM_PROCESS:New() ) -- Core.Fsm#FSM_PROCESS
|
||||
|
||||
|
||||
self:AddTransition( "Assigned", "Start", "Waiting" )
|
||||
self:AddTransition( "*", "Wait", "Waiting" )
|
||||
self:AddTransition( "*", "Report", "Report" )
|
||||
@@ -79,16 +79,16 @@ do -- ACT_ACCOUNT
|
||||
self:AddTransition( { "Account", "AccountForPlayer", "AccountForOther" }, "More", "Wait" )
|
||||
self:AddTransition( { "Account", "AccountForPlayer", "AccountForOther" }, "NoMore", "Accounted" )
|
||||
self:AddTransition( "*", "Fail", "Failed" )
|
||||
|
||||
|
||||
self:AddEndState( "Failed" )
|
||||
|
||||
self:SetStartState( "Assigned" )
|
||||
|
||||
|
||||
self:SetStartState( "Assigned" )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Process Events
|
||||
|
||||
|
||||
--- StateMachine callback function
|
||||
-- @param #ACT_ACCOUNT self
|
||||
-- @param Wrapper.Unit#UNIT ProcessUnit
|
||||
@@ -104,7 +104,7 @@ do -- ACT_ACCOUNT
|
||||
self:__Wait( 1 )
|
||||
end
|
||||
|
||||
|
||||
|
||||
--- StateMachine callback function
|
||||
-- @param #ACT_ACCOUNT self
|
||||
-- @param Wrapper.Unit#UNIT ProcessUnit
|
||||
@@ -112,17 +112,17 @@ do -- ACT_ACCOUNT
|
||||
-- @param #string From
|
||||
-- @param #string To
|
||||
function ACT_ACCOUNT:onenterWaiting( ProcessUnit, From, Event, To )
|
||||
|
||||
|
||||
if self.DisplayCount >= self.DisplayInterval then
|
||||
self:Report()
|
||||
self.DisplayCount = 1
|
||||
else
|
||||
self.DisplayCount = self.DisplayCount + 1
|
||||
end
|
||||
|
||||
|
||||
return true -- Process always the event.
|
||||
end
|
||||
|
||||
|
||||
--- StateMachine callback function
|
||||
-- @param #ACT_ACCOUNT self
|
||||
-- @param Wrapper.Unit#UNIT ProcessUnit
|
||||
@@ -130,30 +130,30 @@ do -- ACT_ACCOUNT
|
||||
-- @param #string From
|
||||
-- @param #string To
|
||||
function ACT_ACCOUNT:onafterEvent( ProcessUnit, From, Event, To, Event )
|
||||
|
||||
|
||||
self:__NoMore( 1 )
|
||||
end
|
||||
|
||||
|
||||
end -- ACT_ACCOUNT
|
||||
|
||||
do -- ACT_ACCOUNT_DEADS
|
||||
|
||||
--- # @{#ACT_ACCOUNT_DEADS} FSM class, extends @{Core.Fsm.Account#ACT_ACCOUNT}
|
||||
--
|
||||
--
|
||||
-- The ACT_ACCOUNT_DEADS class accounts (detects, counts and reports) successful kills of DCS units.
|
||||
-- The process is given a @{Set} of units that will be tracked upon successful destruction.
|
||||
-- The process will end after each target has been successfully destroyed.
|
||||
-- Each successful dead will trigger an Account state transition that can be scored, modified or administered.
|
||||
--
|
||||
--
|
||||
--
|
||||
--
|
||||
-- ## ACT_ACCOUNT_DEADS constructor:
|
||||
--
|
||||
--
|
||||
-- * @{#ACT_ACCOUNT_DEADS.New}(): Creates a new ACT_ACCOUNT_DEADS object.
|
||||
--
|
||||
--
|
||||
-- @type ACT_ACCOUNT_DEADS
|
||||
-- @field Core.Set#SET_UNIT TargetSetUnit
|
||||
-- @extends #ACT_ACCOUNT
|
||||
ACT_ACCOUNT_DEADS = {
|
||||
ACT_ACCOUNT_DEADS = {
|
||||
ClassName = "ACT_ACCOUNT_DEADS",
|
||||
}
|
||||
|
||||
@@ -165,24 +165,24 @@ do -- ACT_ACCOUNT_DEADS
|
||||
function ACT_ACCOUNT_DEADS:New()
|
||||
-- Inherits from BASE
|
||||
local self = BASE:Inherit( self, ACT_ACCOUNT:New() ) -- #ACT_ACCOUNT_DEADS
|
||||
|
||||
|
||||
self.DisplayInterval = 30
|
||||
self.DisplayCount = 30
|
||||
self.DisplayMessage = true
|
||||
self.DisplayTime = 10 -- 10 seconds is the default
|
||||
self.DisplayCategory = "HQ" -- Targets is the default display category
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
function ACT_ACCOUNT_DEADS:Init( FsmAccount )
|
||||
|
||||
self.Task = self:GetTask()
|
||||
|
||||
self.Task = self:GetTask()
|
||||
self.TaskName = self.Task:GetName()
|
||||
end
|
||||
|
||||
--- Process Events
|
||||
|
||||
|
||||
--- StateMachine callback function
|
||||
-- @param #ACT_ACCOUNT_DEADS self
|
||||
-- @param Wrapper.Unit#UNIT ProcessUnit
|
||||
@@ -190,12 +190,12 @@ do -- ACT_ACCOUNT_DEADS
|
||||
-- @param #string From
|
||||
-- @param #string To
|
||||
function ACT_ACCOUNT_DEADS:onenterReport( ProcessUnit, Task, From, Event, To )
|
||||
|
||||
|
||||
local MessageText = "Your group with assigned " .. self.TaskName .. " task has " .. Task.TargetSetUnit:GetUnitTypesText() .. " targets left to be destroyed."
|
||||
self:GetCommandCenter():MessageTypeToGroup( MessageText, ProcessUnit:GetGroup(), MESSAGE.Type.Information )
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
--- StateMachine callback function
|
||||
-- @param #ACT_ACCOUNT_DEADS self
|
||||
-- @param Wrapper.Unit#UNIT ProcessUnit
|
||||
@@ -206,7 +206,7 @@ do -- ACT_ACCOUNT_DEADS
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function ACT_ACCOUNT_DEADS:onafterEvent( ProcessUnit, Task, From, Event, To, EventData )
|
||||
self:T( { ProcessUnit:GetName(), Task:GetName(), From, Event, To, EventData } )
|
||||
|
||||
|
||||
if Task.TargetSetUnit:FindUnit( EventData.IniUnitName ) then
|
||||
local PlayerName = ProcessUnit:GetPlayerName()
|
||||
local PlayerHit = self.PlayerHits and self.PlayerHits[EventData.IniUnitName]
|
||||
@@ -228,14 +228,14 @@ do -- ACT_ACCOUNT_DEADS
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function ACT_ACCOUNT_DEADS:onenterAccountForPlayer( ProcessUnit, Task, From, Event, To, EventData )
|
||||
self:T( { ProcessUnit:GetName(), Task:GetName(), From, Event, To, EventData } )
|
||||
|
||||
|
||||
local TaskGroup = ProcessUnit:GetGroup()
|
||||
|
||||
Task.TargetSetUnit:Remove( EventData.IniUnitName )
|
||||
|
||||
|
||||
local MessageText = "You have destroyed a target.\nYour group assigned with task " .. self.TaskName .. " has\n" .. Task.TargetSetUnit:Count() .. " targets ( " .. Task.TargetSetUnit:GetUnitTypesText() .. " ) left to be destroyed."
|
||||
self:GetCommandCenter():MessageTypeToGroup( MessageText, ProcessUnit:GetGroup(), MESSAGE.Type.Information )
|
||||
|
||||
|
||||
local PlayerName = ProcessUnit:GetPlayerName()
|
||||
Task:AddProgress( PlayerName, "Destroyed " .. EventData.IniTypeName, timer.getTime(), 1 )
|
||||
|
||||
@@ -256,10 +256,10 @@ do -- ACT_ACCOUNT_DEADS
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function ACT_ACCOUNT_DEADS:onenterAccountForOther( ProcessUnit, Task, From, Event, To, EventData )
|
||||
self:T( { ProcessUnit:GetName(), Task:GetName(), From, Event, To, EventData } )
|
||||
|
||||
|
||||
local TaskGroup = ProcessUnit:GetGroup()
|
||||
Task.TargetSetUnit:Remove( EventData.IniUnitName )
|
||||
|
||||
|
||||
local MessageText = "One of the task targets has been destroyed.\nYour group assigned with task " .. self.TaskName .. " has\n" .. Task.TargetSetUnit:Count() .. " targets ( " .. Task.TargetSetUnit:GetUnitTypesText() .. " ) left to be destroyed."
|
||||
self:GetCommandCenter():MessageTypeToGroup( MessageText, ProcessUnit:GetGroup(), MESSAGE.Type.Information )
|
||||
|
||||
@@ -270,9 +270,9 @@ do -- ACT_ACCOUNT_DEADS
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
--- DCS Events
|
||||
|
||||
|
||||
--- @param #ACT_ACCOUNT_DEADS self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function ACT_ACCOUNT_DEADS:OnEventHit( EventData )
|
||||
@@ -282,8 +282,8 @@ do -- ACT_ACCOUNT_DEADS
|
||||
self.PlayerHits = self.PlayerHits or {}
|
||||
self.PlayerHits[EventData.TgtDCSUnitName] = EventData.IniPlayerName
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- @param #ACT_ACCOUNT_DEADS self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function ACT_ACCOUNT_DEADS:onfuncEventDead( EventData )
|
||||
@@ -295,7 +295,7 @@ do -- ACT_ACCOUNT_DEADS
|
||||
end
|
||||
|
||||
--- DCS Events
|
||||
|
||||
|
||||
--- @param #ACT_ACCOUNT_DEADS self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function ACT_ACCOUNT_DEADS:onfuncEventCrash( EventData )
|
||||
|
||||
@@ -1,82 +1,82 @@
|
||||
--- (SP) (MP) (FSM) Accept or reject process for player (task) assignments.
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- # @{#ACT_ASSIGN} FSM template class, extends @{Core.Fsm#FSM_PROCESS}
|
||||
--
|
||||
--
|
||||
-- ## ACT_ASSIGN state machine:
|
||||
--
|
||||
--
|
||||
-- This class is a state machine: it manages a process that is triggered by events causing state transitions to occur.
|
||||
-- All derived classes from this class will start with the class name, followed by a \_. See the relevant derived class descriptions below.
|
||||
-- Each derived class follows exactly the same process, using the same events and following the same state transitions,
|
||||
-- Each derived class follows exactly the same process, using the same events and following the same state transitions,
|
||||
-- but will have **different implementation behaviour** upon each event or state transition.
|
||||
--
|
||||
--
|
||||
-- ### ACT_ASSIGN **Events**:
|
||||
--
|
||||
--
|
||||
-- These are the events defined in this class:
|
||||
--
|
||||
--
|
||||
-- * **Start**: Start the tasking acceptance process.
|
||||
-- * **Assign**: Assign the task.
|
||||
-- * **Reject**: Reject the task..
|
||||
--
|
||||
--
|
||||
-- ### ACT_ASSIGN **Event methods**:
|
||||
--
|
||||
--
|
||||
-- Event methods are available (dynamically allocated by the state machine), that accomodate for state transitions occurring in the process.
|
||||
-- There are two types of event methods, which you can use to influence the normal mechanisms in the state machine:
|
||||
--
|
||||
--
|
||||
-- * **Immediate**: The event method has exactly the name of the event.
|
||||
-- * **Delayed**: The event method starts with a __ + the name of the event. The first parameter of the event method is a number value, expressing the delay in seconds when the event will be executed.
|
||||
--
|
||||
-- * **Delayed**: The event method starts with a __ + the name of the event. The first parameter of the event method is a number value, expressing the delay in seconds when the event will be executed.
|
||||
--
|
||||
-- ### ACT_ASSIGN **States**:
|
||||
--
|
||||
--
|
||||
-- * **UnAssigned**: The player has not accepted the task.
|
||||
-- * **Assigned (*)**: The player has accepted the task.
|
||||
-- * **Rejected (*)**: The player has not accepted the task.
|
||||
-- * **Waiting**: The process is awaiting player feedback.
|
||||
-- * **Failed (*)**: The process has failed.
|
||||
--
|
||||
--
|
||||
-- (*) End states of the process.
|
||||
--
|
||||
--
|
||||
-- ### ACT_ASSIGN state transition methods:
|
||||
--
|
||||
--
|
||||
-- State transition functions can be set **by the mission designer** customizing or improving the behaviour of the state.
|
||||
-- There are 2 moments when state transition methods will be called by the state machine:
|
||||
--
|
||||
-- * **Before** the state transition.
|
||||
-- The state transition method needs to start with the name **OnBefore + the name of the state**.
|
||||
--
|
||||
-- * **Before** the state transition.
|
||||
-- The state transition method needs to start with the name **OnBefore + the name of the state**.
|
||||
-- If the state transition method returns false, then the processing of the state transition will not be done!
|
||||
-- If you want to change the behaviour of the AIControllable at this event, return false,
|
||||
-- If you want to change the behaviour of the AIControllable at this event, return false,
|
||||
-- but then you'll need to specify your own logic using the AIControllable!
|
||||
--
|
||||
-- * **After** the state transition.
|
||||
-- The state transition method needs to start with the name **OnAfter + the name of the state**.
|
||||
--
|
||||
-- * **After** the state transition.
|
||||
-- The state transition method needs to start with the name **OnAfter + the name of the state**.
|
||||
-- These state transition methods need to provide a return value, which is specified at the function description.
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- # 1) @{#ACT_ASSIGN_ACCEPT} class, extends @{Core.Fsm.Assign#ACT_ASSIGN}
|
||||
--
|
||||
--
|
||||
-- The ACT_ASSIGN_ACCEPT class accepts by default a task for a player. No player intervention is allowed to reject the task.
|
||||
--
|
||||
--
|
||||
-- ## 1.1) ACT_ASSIGN_ACCEPT constructor:
|
||||
--
|
||||
--
|
||||
-- * @{#ACT_ASSIGN_ACCEPT.New}(): Creates a new ACT_ASSIGN_ACCEPT object.
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- # 2) @{#ACT_ASSIGN_MENU_ACCEPT} class, extends @{Core.Fsm.Assign#ACT_ASSIGN}
|
||||
--
|
||||
--
|
||||
-- The ACT_ASSIGN_MENU_ACCEPT class accepts a task when the player accepts the task through an added menu option.
|
||||
-- This assignment type is useful to conditionally allow the player to choose whether or not he would accept the task.
|
||||
-- The assignment type also allows to reject the task.
|
||||
--
|
||||
--
|
||||
-- ## 2.1) ACT_ASSIGN_MENU_ACCEPT constructor:
|
||||
-- -----------------------------------------
|
||||
--
|
||||
--
|
||||
-- * @{#ACT_ASSIGN_MENU_ACCEPT.New}(): Creates a new ACT_ASSIGN_MENU_ACCEPT object.
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- @module Actions.Assign
|
||||
-- @image MOOSE.JPG
|
||||
|
||||
@@ -89,11 +89,11 @@ do -- ACT_ASSIGN
|
||||
-- @field Wrapper.Unit#UNIT ProcessUnit
|
||||
-- @field Core.Zone#ZONE_BASE TargetZone
|
||||
-- @extends Core.Fsm#FSM_PROCESS
|
||||
ACT_ASSIGN = {
|
||||
ACT_ASSIGN = {
|
||||
ClassName = "ACT_ASSIGN",
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
--- Creates a new task assignment state machine. The process will accept the task by default, no player intervention accepted.
|
||||
-- @param #ACT_ASSIGN self
|
||||
-- @return #ACT_ASSIGN The task acceptance process.
|
||||
@@ -106,16 +106,16 @@ do -- ACT_ASSIGN
|
||||
self:AddTransition( "Waiting", "Assign", "Assigned" )
|
||||
self:AddTransition( "Waiting", "Reject", "Rejected" )
|
||||
self:AddTransition( "*", "Fail", "Failed" )
|
||||
|
||||
|
||||
self:AddEndState( "Assigned" )
|
||||
self:AddEndState( "Rejected" )
|
||||
self:AddEndState( "Failed" )
|
||||
|
||||
self:SetStartState( "UnAssigned" )
|
||||
|
||||
|
||||
self:SetStartState( "UnAssigned" )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
end -- ACT_ASSIGN
|
||||
|
||||
|
||||
@@ -128,26 +128,26 @@ do -- ACT_ASSIGN_ACCEPT
|
||||
-- @field Wrapper.Unit#UNIT ProcessUnit
|
||||
-- @field Core.Zone#ZONE_BASE TargetZone
|
||||
-- @extends #ACT_ASSIGN
|
||||
ACT_ASSIGN_ACCEPT = {
|
||||
ACT_ASSIGN_ACCEPT = {
|
||||
ClassName = "ACT_ASSIGN_ACCEPT",
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
--- Creates a new task assignment state machine. The process will accept the task by default, no player intervention accepted.
|
||||
-- @param #ACT_ASSIGN_ACCEPT self
|
||||
-- @param #string TaskBriefing
|
||||
function ACT_ASSIGN_ACCEPT:New( TaskBriefing )
|
||||
|
||||
|
||||
local self = BASE:Inherit( self, ACT_ASSIGN:New() ) -- #ACT_ASSIGN_ACCEPT
|
||||
|
||||
self.TaskBriefing = TaskBriefing
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function ACT_ASSIGN_ACCEPT:Init( FsmAssign )
|
||||
|
||||
self.TaskBriefing = FsmAssign.TaskBriefing
|
||||
|
||||
self.TaskBriefing = FsmAssign.TaskBriefing
|
||||
end
|
||||
|
||||
--- StateMachine callback function
|
||||
@@ -157,8 +157,8 @@ do -- ACT_ASSIGN_ACCEPT
|
||||
-- @param #string From
|
||||
-- @param #string To
|
||||
function ACT_ASSIGN_ACCEPT:onafterStart( ProcessUnit, Task, From, Event, To )
|
||||
|
||||
self:__Assign( 1 )
|
||||
|
||||
self:__Assign( 1 )
|
||||
end
|
||||
|
||||
--- StateMachine callback function
|
||||
@@ -167,11 +167,11 @@ do -- ACT_ASSIGN_ACCEPT
|
||||
-- @param #string Event
|
||||
-- @param #string From
|
||||
-- @param #string To
|
||||
function ACT_ASSIGN_ACCEPT:onenterAssigned( ProcessUnit, Task, From, Event, To )
|
||||
|
||||
function ACT_ASSIGN_ACCEPT:onenterAssigned( ProcessUnit, Task, From, Event, To, TaskGroup )
|
||||
|
||||
self.Task:Assign( ProcessUnit, ProcessUnit:GetPlayerName() )
|
||||
end
|
||||
|
||||
|
||||
end -- ACT_ASSIGN_ACCEPT
|
||||
|
||||
|
||||
@@ -183,7 +183,7 @@ do -- ACT_ASSIGN_MENU_ACCEPT
|
||||
-- @field Wrapper.Unit#UNIT ProcessUnit
|
||||
-- @field Core.Zone#ZONE_BASE TargetZone
|
||||
-- @extends #ACT_ASSIGN
|
||||
ACT_ASSIGN_MENU_ACCEPT = {
|
||||
ACT_ASSIGN_MENU_ACCEPT = {
|
||||
ClassName = "ACT_ASSIGN_MENU_ACCEPT",
|
||||
}
|
||||
|
||||
@@ -197,7 +197,7 @@ do -- ACT_ASSIGN_MENU_ACCEPT
|
||||
local self = BASE:Inherit( self, ACT_ASSIGN:New() ) -- #ACT_ASSIGN_MENU_ACCEPT
|
||||
|
||||
self.TaskBriefing = TaskBriefing
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -207,12 +207,12 @@ do -- ACT_ASSIGN_MENU_ACCEPT
|
||||
-- @param #string TaskBriefing
|
||||
-- @return #ACT_ASSIGN_MENU_ACCEPT self
|
||||
function ACT_ASSIGN_MENU_ACCEPT:Init( TaskBriefing )
|
||||
|
||||
|
||||
self.TaskBriefing = TaskBriefing
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- StateMachine callback function
|
||||
-- @param #ACT_ASSIGN_MENU_ACCEPT self
|
||||
-- @param Wrapper.Unit#UNIT ProcessUnit
|
||||
@@ -222,30 +222,30 @@ do -- ACT_ASSIGN_MENU_ACCEPT
|
||||
function ACT_ASSIGN_MENU_ACCEPT:onafterStart( ProcessUnit, Task, From, Event, To )
|
||||
|
||||
self:GetCommandCenter():MessageToGroup( "Task " .. self.Task:GetName() .. " has been assigned to you and your group!\nRead the briefing and use the Radio Menu (F10) / Task ... CONFIRMATION menu to accept or reject the task.\nYou have 2 minutes to accept, or the task assignment will be cancelled!", ProcessUnit:GetGroup(), 120 )
|
||||
|
||||
local TaskGroup = ProcessUnit:GetGroup()
|
||||
|
||||
local TaskGroup = ProcessUnit:GetGroup()
|
||||
|
||||
self.Menu = MENU_GROUP:New( TaskGroup, "Task " .. self.Task:GetName() .. " CONFIRMATION" )
|
||||
self.MenuAcceptTask = MENU_GROUP_COMMAND:New( TaskGroup, "Accept task " .. self.Task:GetName(), self.Menu, self.MenuAssign, self, TaskGroup )
|
||||
self.MenuRejectTask = MENU_GROUP_COMMAND:New( TaskGroup, "Reject task " .. self.Task:GetName(), self.Menu, self.MenuReject, self, TaskGroup )
|
||||
|
||||
|
||||
self:__Reject( 120, TaskGroup )
|
||||
end
|
||||
|
||||
|
||||
--- Menu function.
|
||||
-- @param #ACT_ASSIGN_MENU_ACCEPT self
|
||||
function ACT_ASSIGN_MENU_ACCEPT:MenuAssign( TaskGroup )
|
||||
|
||||
|
||||
self:__Assign( -1, TaskGroup )
|
||||
end
|
||||
|
||||
|
||||
--- Menu function.
|
||||
-- @param #ACT_ASSIGN_MENU_ACCEPT self
|
||||
function ACT_ASSIGN_MENU_ACCEPT:MenuReject( TaskGroup )
|
||||
|
||||
|
||||
self:__Reject( -1, TaskGroup )
|
||||
end
|
||||
|
||||
|
||||
--- StateMachine callback function
|
||||
-- @param #ACT_ASSIGN_MENU_ACCEPT self
|
||||
-- @param Wrapper.Unit#UNIT ProcessUnit
|
||||
@@ -253,10 +253,10 @@ do -- ACT_ASSIGN_MENU_ACCEPT
|
||||
-- @param #string From
|
||||
-- @param #string To
|
||||
function ACT_ASSIGN_MENU_ACCEPT:onafterAssign( ProcessUnit, Task, From, Event, To, TaskGroup )
|
||||
|
||||
|
||||
self.Menu:Remove()
|
||||
end
|
||||
|
||||
|
||||
--- StateMachine callback function
|
||||
-- @param #ACT_ASSIGN_MENU_ACCEPT self
|
||||
-- @param Wrapper.Unit#UNIT ProcessUnit
|
||||
@@ -265,7 +265,7 @@ do -- ACT_ASSIGN_MENU_ACCEPT
|
||||
-- @param #string To
|
||||
function ACT_ASSIGN_MENU_ACCEPT:onafterReject( ProcessUnit, Task, From, Event, To, TaskGroup )
|
||||
self:F( { TaskGroup = TaskGroup } )
|
||||
|
||||
|
||||
self.Menu:Remove()
|
||||
--TODO: need to resolve this problem ... it has to do with the events ...
|
||||
--self.Task:UnAssignFromUnit( ProcessUnit )needs to become a callback funtion call upon the event
|
||||
@@ -279,7 +279,7 @@ do -- ACT_ASSIGN_MENU_ACCEPT
|
||||
-- @param #string From
|
||||
-- @param #string To
|
||||
function ACT_ASSIGN_MENU_ACCEPT:onenterAssigned( ProcessUnit, Task, From, Event, To, TaskGroup )
|
||||
|
||||
|
||||
--self.Task:AssignToGroup( TaskGroup )
|
||||
self.Task:Assign( ProcessUnit, ProcessUnit:GetPlayerName() )
|
||||
end
|
||||
|
||||
@@ -1,65 +1,65 @@
|
||||
--- (SP) (MP) (FSM) Route AI or players through waypoints or to zones.
|
||||
--
|
||||
--
|
||||
-- ## ACT_ASSIST state machine:
|
||||
--
|
||||
--
|
||||
-- This class is a state machine: it manages a process that is triggered by events causing state transitions to occur.
|
||||
-- All derived classes from this class will start with the class name, followed by a \_. See the relevant derived class descriptions below.
|
||||
-- Each derived class follows exactly the same process, using the same events and following the same state transitions,
|
||||
-- Each derived class follows exactly the same process, using the same events and following the same state transitions,
|
||||
-- but will have **different implementation behaviour** upon each event or state transition.
|
||||
--
|
||||
--
|
||||
-- ### ACT_ASSIST **Events**:
|
||||
--
|
||||
--
|
||||
-- These are the events defined in this class:
|
||||
--
|
||||
--
|
||||
-- * **Start**: The process is started.
|
||||
-- * **Next**: The process is smoking the targets in the given zone.
|
||||
--
|
||||
--
|
||||
-- ### ACT_ASSIST **Event methods**:
|
||||
--
|
||||
--
|
||||
-- Event methods are available (dynamically allocated by the state machine), that accomodate for state transitions occurring in the process.
|
||||
-- There are two types of event methods, which you can use to influence the normal mechanisms in the state machine:
|
||||
--
|
||||
--
|
||||
-- * **Immediate**: The event method has exactly the name of the event.
|
||||
-- * **Delayed**: The event method starts with a __ + the name of the event. The first parameter of the event method is a number value, expressing the delay in seconds when the event will be executed.
|
||||
--
|
||||
-- * **Delayed**: The event method starts with a __ + the name of the event. The first parameter of the event method is a number value, expressing the delay in seconds when the event will be executed.
|
||||
--
|
||||
-- ### ACT_ASSIST **States**:
|
||||
--
|
||||
--
|
||||
-- * **None**: The controllable did not receive route commands.
|
||||
-- * **AwaitSmoke (*)**: The process is awaiting to smoke the targets in the zone.
|
||||
-- * **Smoking (*)**: The process is smoking the targets in the zone.
|
||||
-- * **Failed (*)**: The process has failed.
|
||||
--
|
||||
--
|
||||
-- (*) End states of the process.
|
||||
--
|
||||
--
|
||||
-- ### ACT_ASSIST state transition methods:
|
||||
--
|
||||
--
|
||||
-- State transition functions can be set **by the mission designer** customizing or improving the behaviour of the state.
|
||||
-- There are 2 moments when state transition methods will be called by the state machine:
|
||||
--
|
||||
-- * **Before** the state transition.
|
||||
-- The state transition method needs to start with the name **OnBefore + the name of the state**.
|
||||
--
|
||||
-- * **Before** the state transition.
|
||||
-- The state transition method needs to start with the name **OnBefore + the name of the state**.
|
||||
-- If the state transition method returns false, then the processing of the state transition will not be done!
|
||||
-- If you want to change the behaviour of the AIControllable at this event, return false,
|
||||
-- If you want to change the behaviour of the AIControllable at this event, return false,
|
||||
-- but then you'll need to specify your own logic using the AIControllable!
|
||||
--
|
||||
-- * **After** the state transition.
|
||||
-- The state transition method needs to start with the name **OnAfter + the name of the state**.
|
||||
--
|
||||
-- * **After** the state transition.
|
||||
-- The state transition method needs to start with the name **OnAfter + the name of the state**.
|
||||
-- These state transition methods need to provide a return value, which is specified at the function description.
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- # 1) @{#ACT_ASSIST_SMOKE_TARGETS_ZONE} class, extends @{Core.Fsm.Route#ACT_ASSIST}
|
||||
--
|
||||
--
|
||||
-- The ACT_ASSIST_SMOKE_TARGETS_ZONE class implements the core functions to smoke targets in a @{Zone}.
|
||||
-- The targets are smoked within a certain range around each target, simulating a realistic smoking behaviour.
|
||||
-- The targets are smoked within a certain range around each target, simulating a realistic smoking behaviour.
|
||||
-- At random intervals, a new target is smoked.
|
||||
--
|
||||
--
|
||||
-- # 1.1) ACT_ASSIST_SMOKE_TARGETS_ZONE constructor:
|
||||
--
|
||||
--
|
||||
-- * @{#ACT_ASSIST_SMOKE_TARGETS_ZONE.New}(): Creates a new ACT_ASSIST_SMOKE_TARGETS_ZONE object.
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- @module Actions.Assist
|
||||
-- @image MOOSE.JPG
|
||||
|
||||
@@ -69,7 +69,7 @@ do -- ACT_ASSIST
|
||||
--- ACT_ASSIST class
|
||||
-- @type ACT_ASSIST
|
||||
-- @extends Core.Fsm#FSM_PROCESS
|
||||
ACT_ASSIST = {
|
||||
ACT_ASSIST = {
|
||||
ClassName = "ACT_ASSIST",
|
||||
}
|
||||
|
||||
@@ -86,15 +86,15 @@ do -- ACT_ASSIST
|
||||
self:AddTransition( "Smoking", "Next", "AwaitSmoke" )
|
||||
self:AddTransition( "*", "Stop", "Success" )
|
||||
self:AddTransition( "*", "Fail", "Failed" )
|
||||
|
||||
|
||||
self:AddEndState( "Failed" )
|
||||
self:AddEndState( "Success" )
|
||||
|
||||
self:SetStartState( "None" )
|
||||
|
||||
self:SetStartState( "None" )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Task Events
|
||||
|
||||
--- StateMachine callback function
|
||||
@@ -104,17 +104,17 @@ do -- ACT_ASSIST
|
||||
-- @param #string From
|
||||
-- @param #string To
|
||||
function ACT_ASSIST:onafterStart( ProcessUnit, From, Event, To )
|
||||
|
||||
|
||||
local ProcessGroup = ProcessUnit:GetGroup()
|
||||
local MissionMenu = self:GetMission():GetMenu( ProcessGroup )
|
||||
|
||||
|
||||
local function MenuSmoke( MenuParam )
|
||||
local self = MenuParam.self
|
||||
local SmokeColor = MenuParam.SmokeColor
|
||||
self.SmokeColor = SmokeColor
|
||||
self:__Next( 1 )
|
||||
end
|
||||
|
||||
|
||||
self.Menu = MENU_GROUP:New( ProcessGroup, "Target acquisition", MissionMenu )
|
||||
self.MenuSmokeBlue = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop blue smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.Blue } )
|
||||
self.MenuSmokeGreen = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop green smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.Green } )
|
||||
@@ -130,10 +130,10 @@ do -- ACT_ASSIST
|
||||
-- @param #string From
|
||||
-- @param #string To
|
||||
function ACT_ASSIST:onafterStop( ProcessUnit, From, Event, To )
|
||||
|
||||
|
||||
self.Menu:Remove() -- When stopped, remove the menus
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
do -- ACT_ASSIST_SMOKE_TARGETS_ZONE
|
||||
@@ -143,17 +143,17 @@ do -- ACT_ASSIST_SMOKE_TARGETS_ZONE
|
||||
-- @field Core.Set#SET_UNIT TargetSetUnit
|
||||
-- @field Core.Zone#ZONE_BASE TargetZone
|
||||
-- @extends #ACT_ASSIST
|
||||
ACT_ASSIST_SMOKE_TARGETS_ZONE = {
|
||||
ACT_ASSIST_SMOKE_TARGETS_ZONE = {
|
||||
ClassName = "ACT_ASSIST_SMOKE_TARGETS_ZONE",
|
||||
}
|
||||
|
||||
|
||||
-- function ACT_ASSIST_SMOKE_TARGETS_ZONE:_Destructor()
|
||||
-- self:E("_Destructor")
|
||||
--
|
||||
--
|
||||
-- self.Menu:Remove()
|
||||
-- self:EventRemoveAll()
|
||||
-- end
|
||||
|
||||
|
||||
--- Creates a new target smoking state machine. The process will request from the menu if it accepts the task, if not, the unit is removed from the simulator.
|
||||
-- @param #ACT_ASSIST_SMOKE_TARGETS_ZONE self
|
||||
-- @param Core.Set#SET_UNIT TargetSetUnit
|
||||
@@ -163,29 +163,29 @@ do -- ACT_ASSIST_SMOKE_TARGETS_ZONE
|
||||
|
||||
self.TargetSetUnit = TargetSetUnit
|
||||
self.TargetZone = TargetZone
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function ACT_ASSIST_SMOKE_TARGETS_ZONE:Init( FsmSmoke )
|
||||
|
||||
|
||||
self.TargetSetUnit = FsmSmoke.TargetSetUnit
|
||||
self.TargetZone = FsmSmoke.TargetZone
|
||||
end
|
||||
|
||||
|
||||
--- Creates a new target smoking state machine. The process will request from the menu if it accepts the task, if not, the unit is removed from the simulator.
|
||||
-- @param #ACT_ASSIST_SMOKE_TARGETS_ZONE self
|
||||
-- @param Core.Set#SET_UNIT TargetSetUnit
|
||||
-- @param Core.Zone#ZONE_BASE TargetZone
|
||||
-- @return #ACT_ASSIST_SMOKE_TARGETS_ZONE self
|
||||
function ACT_ASSIST_SMOKE_TARGETS_ZONE:Init( TargetSetUnit, TargetZone )
|
||||
|
||||
|
||||
self.TargetSetUnit = TargetSetUnit
|
||||
self.TargetZone = TargetZone
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- StateMachine callback function
|
||||
-- @param #ACT_ASSIST_SMOKE_TARGETS_ZONE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit
|
||||
@@ -193,7 +193,7 @@ do -- ACT_ASSIST_SMOKE_TARGETS_ZONE
|
||||
-- @param #string From
|
||||
-- @param #string To
|
||||
function ACT_ASSIST_SMOKE_TARGETS_ZONE:onenterSmoking( ProcessUnit, From, Event, To )
|
||||
|
||||
|
||||
self.TargetSetUnit:ForEachUnit(
|
||||
--- @param Wrapper.Unit#UNIT SmokeUnit
|
||||
function( SmokeUnit )
|
||||
@@ -203,12 +203,12 @@ do -- ACT_ASSIST_SMOKE_TARGETS_ZONE
|
||||
if SmokeUnit:IsAlive() then
|
||||
SmokeUnit:Smoke( self.SmokeColor, 150 )
|
||||
end
|
||||
end, {}, math.random( 10, 60 )
|
||||
end, {}, math.random( 10, 60 )
|
||||
)
|
||||
end
|
||||
end
|
||||
)
|
||||
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
--- (SP) (MP) (FSM) Route AI or players through waypoints or to zones.
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- # @{#ACT_ROUTE} FSM class, extends @{Core.Fsm#FSM_PROCESS}
|
||||
--
|
||||
--
|
||||
-- ## ACT_ROUTE state machine:
|
||||
--
|
||||
--
|
||||
-- This class is a state machine: it manages a process that is triggered by events causing state transitions to occur.
|
||||
-- All derived classes from this class will start with the class name, followed by a \_. See the relevant derived class descriptions below.
|
||||
-- Each derived class follows exactly the same process, using the same events and following the same state transitions,
|
||||
-- Each derived class follows exactly the same process, using the same events and following the same state transitions,
|
||||
-- but will have **different implementation behaviour** upon each event or state transition.
|
||||
--
|
||||
--
|
||||
-- ### ACT_ROUTE **Events**:
|
||||
--
|
||||
--
|
||||
-- These are the events defined in this class:
|
||||
--
|
||||
--
|
||||
-- * **Start**: The process is started. The process will go into the Report state.
|
||||
-- * **Report**: The process is reporting to the player the route to be followed.
|
||||
-- * **Route**: The process is routing the controllable.
|
||||
@@ -22,56 +22,56 @@
|
||||
-- * **Arrive**: The controllable has arrived at a route point.
|
||||
-- * **More**: There are more route points that need to be followed. The process will go back into the Report state.
|
||||
-- * **NoMore**: There are no more route points that need to be followed. The process will go into the Success state.
|
||||
--
|
||||
--
|
||||
-- ### ACT_ROUTE **Event methods**:
|
||||
--
|
||||
--
|
||||
-- Event methods are available (dynamically allocated by the state machine), that accomodate for state transitions occurring in the process.
|
||||
-- There are two types of event methods, which you can use to influence the normal mechanisms in the state machine:
|
||||
--
|
||||
--
|
||||
-- * **Immediate**: The event method has exactly the name of the event.
|
||||
-- * **Delayed**: The event method starts with a __ + the name of the event. The first parameter of the event method is a number value, expressing the delay in seconds when the event will be executed.
|
||||
--
|
||||
-- * **Delayed**: The event method starts with a __ + the name of the event. The first parameter of the event method is a number value, expressing the delay in seconds when the event will be executed.
|
||||
--
|
||||
-- ### ACT_ROUTE **States**:
|
||||
--
|
||||
--
|
||||
-- * **None**: The controllable did not receive route commands.
|
||||
-- * **Arrived (*)**: The controllable has arrived at a route point.
|
||||
-- * **Aborted (*)**: The controllable has aborted the route path.
|
||||
-- * **Routing**: The controllable is understay to the route point.
|
||||
-- * **Pausing**: The process is pausing the routing. AI air will go into hover, AI ground will stop moving. Players can fly around.
|
||||
-- * **Success (*)**: All route points were reached.
|
||||
-- * **Success (*)**: All route points were reached.
|
||||
-- * **Failed (*)**: The process has failed.
|
||||
--
|
||||
--
|
||||
-- (*) End states of the process.
|
||||
--
|
||||
--
|
||||
-- ### ACT_ROUTE state transition methods:
|
||||
--
|
||||
--
|
||||
-- State transition functions can be set **by the mission designer** customizing or improving the behaviour of the state.
|
||||
-- There are 2 moments when state transition methods will be called by the state machine:
|
||||
--
|
||||
-- * **Before** the state transition.
|
||||
-- The state transition method needs to start with the name **OnBefore + the name of the state**.
|
||||
--
|
||||
-- * **Before** the state transition.
|
||||
-- The state transition method needs to start with the name **OnBefore + the name of the state**.
|
||||
-- If the state transition method returns false, then the processing of the state transition will not be done!
|
||||
-- If you want to change the behaviour of the AIControllable at this event, return false,
|
||||
-- If you want to change the behaviour of the AIControllable at this event, return false,
|
||||
-- but then you'll need to specify your own logic using the AIControllable!
|
||||
--
|
||||
-- * **After** the state transition.
|
||||
-- The state transition method needs to start with the name **OnAfter + the name of the state**.
|
||||
--
|
||||
-- * **After** the state transition.
|
||||
-- The state transition method needs to start with the name **OnAfter + the name of the state**.
|
||||
-- These state transition methods need to provide a return value, which is specified at the function description.
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- # 1) @{#ACT_ROUTE_ZONE} class, extends @{Core.Fsm.Route#ACT_ROUTE}
|
||||
--
|
||||
--
|
||||
-- The ACT_ROUTE_ZONE class implements the core functions to route an AIR @{Wrapper.Controllable} player @{Wrapper.Unit} to a @{Zone}.
|
||||
-- The player receives on perioding times messages with the coordinates of the route to follow.
|
||||
-- The player receives on perioding times messages with the coordinates of the route to follow.
|
||||
-- Upon arrival at the zone, a confirmation of arrival is sent, and the process will be ended.
|
||||
--
|
||||
--
|
||||
-- # 1.1) ACT_ROUTE_ZONE constructor:
|
||||
--
|
||||
--
|
||||
-- * @{#ACT_ROUTE_ZONE.New}(): Creates a new ACT_ROUTE_ZONE object.
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- @module Actions.Route
|
||||
-- @image MOOSE.JPG
|
||||
|
||||
@@ -85,11 +85,11 @@ do -- ACT_ROUTE
|
||||
-- @field Core.Zone#ZONE_BASE Zone
|
||||
-- @field Core.Point#COORDINATE Coordinate
|
||||
-- @extends Core.Fsm#FSM_PROCESS
|
||||
ACT_ROUTE = {
|
||||
ACT_ROUTE = {
|
||||
ClassName = "ACT_ROUTE",
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
--- Creates a new routing state machine. The process will route a CLIENT to a ZONE until the CLIENT is within that ZONE.
|
||||
-- @param #ACT_ROUTE self
|
||||
-- @return #ACT_ROUTE self
|
||||
@@ -97,7 +97,7 @@ do -- ACT_ROUTE
|
||||
|
||||
-- Inherits from BASE
|
||||
local self = BASE:Inherit( self, FSM_PROCESS:New( "ACT_ROUTE" ) ) -- Core.Fsm#FSM_PROCESS
|
||||
|
||||
|
||||
self:AddTransition( "*", "Reset", "None" )
|
||||
self:AddTransition( "None", "Start", "Routing" )
|
||||
self:AddTransition( "*", "Report", "*" )
|
||||
@@ -109,23 +109,23 @@ do -- ACT_ROUTE
|
||||
self:AddTransition( "*", "Fail", "Failed" )
|
||||
self:AddTransition( "", "", "" )
|
||||
self:AddTransition( "", "", "" )
|
||||
|
||||
|
||||
self:AddEndState( "Arrived" )
|
||||
self:AddEndState( "Failed" )
|
||||
self:AddEndState( "Cancelled" )
|
||||
|
||||
|
||||
self:SetStartState( "None" )
|
||||
|
||||
self:SetRouteMode( "C" )
|
||||
|
||||
|
||||
self:SetRouteMode( "C" )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Set a Cancel Menu item.
|
||||
-- @param #ACT_ROUTE self
|
||||
-- @return #ACT_ROUTE
|
||||
function ACT_ROUTE:SetMenuCancel( MenuGroup, MenuText, ParentMenu, MenuTime, MenuTag )
|
||||
|
||||
|
||||
self.CancelMenuGroupCommand = MENU_GROUP_COMMAND:New(
|
||||
MenuGroup,
|
||||
MenuText,
|
||||
@@ -135,47 +135,47 @@ do -- ACT_ROUTE
|
||||
):SetTime( MenuTime ):SetTag( MenuTag )
|
||||
|
||||
ParentMenu:SetTime( MenuTime )
|
||||
|
||||
|
||||
ParentMenu:Remove( MenuTime, MenuTag )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Set the route mode.
|
||||
-- There are 2 route modes supported:
|
||||
--
|
||||
--
|
||||
-- * SetRouteMode( "B" ): Route mode is Bearing and Range.
|
||||
-- * SetRouteMode( "C" ): Route mode is LL or MGRS according coordinate system setup.
|
||||
--
|
||||
--
|
||||
-- @param #ACT_ROUTE self
|
||||
-- @return #ACT_ROUTE
|
||||
function ACT_ROUTE:SetRouteMode( RouteMode )
|
||||
|
||||
|
||||
self.RouteMode = RouteMode
|
||||
|
||||
return self
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Get the routing text to be displayed.
|
||||
-- The route mode determines the text displayed.
|
||||
-- @param #ACT_ROUTE self
|
||||
-- @param Wrapper.Unit#UNIT Controllable
|
||||
-- @return #string
|
||||
function ACT_ROUTE:GetRouteText( Controllable )
|
||||
|
||||
|
||||
local RouteText = ""
|
||||
|
||||
local Coordinate = nil -- Core.Point#COORDINATE
|
||||
|
||||
|
||||
if self.Coordinate then
|
||||
Coordinate = self.Coordinate
|
||||
end
|
||||
|
||||
|
||||
if self.Zone then
|
||||
Coordinate = self.Zone:GetPointVec3( self.Altitude )
|
||||
Coordinate:SetHeading( self.Heading )
|
||||
end
|
||||
|
||||
|
||||
|
||||
local Task = self:GetTask() -- This is to dermine that the coordinates are for a specific task mode (A2A or A2G).
|
||||
local CC = self:GetTask():GetMission():GetCommandCenter()
|
||||
@@ -190,7 +190,7 @@ do -- ACT_ROUTE
|
||||
self:F( { ZoneName = ZoneName } )
|
||||
local Zone = Zone -- Core.Zone#ZONE
|
||||
local ZoneCoord = Zone:GetCoordinate()
|
||||
local ZoneDistance = ZoneCoord:Get2DDistance( self.Coordinate )
|
||||
local ZoneDistance = ZoneCoord:Get2DDistance( Coordinate )
|
||||
self:F( { ShortestDistance, ShortestReferenceName } )
|
||||
if ShortestDistance == 0 or ZoneDistance < ShortestDistance then
|
||||
ShortestDistance = ZoneDistance
|
||||
@@ -209,7 +209,7 @@ do -- ACT_ROUTE
|
||||
return RouteText
|
||||
end
|
||||
|
||||
|
||||
|
||||
function ACT_ROUTE:MenuCancel()
|
||||
self:F("Cancelled")
|
||||
self.CancelMenuGroupCommand:Remove()
|
||||
@@ -225,11 +225,11 @@ do -- ACT_ROUTE
|
||||
-- @param #string From
|
||||
-- @param #string To
|
||||
function ACT_ROUTE:onafterStart( ProcessUnit, From, Event, To )
|
||||
|
||||
|
||||
|
||||
self:__Route( 1 )
|
||||
end
|
||||
|
||||
|
||||
--- Check if the controllable has arrived.
|
||||
-- @param #ACT_ROUTE self
|
||||
-- @param Wrapper.Unit#UNIT ProcessUnit
|
||||
@@ -237,7 +237,7 @@ do -- ACT_ROUTE
|
||||
function ACT_ROUTE:onfuncHasArrived( ProcessUnit )
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
--- StateMachine callback function
|
||||
-- @param #ACT_ROUTE self
|
||||
-- @param Wrapper.Unit#UNIT ProcessUnit
|
||||
@@ -245,7 +245,7 @@ do -- ACT_ROUTE
|
||||
-- @param #string From
|
||||
-- @param #string To
|
||||
function ACT_ROUTE:onbeforeRoute( ProcessUnit, From, Event, To )
|
||||
|
||||
|
||||
if ProcessUnit:IsAlive() then
|
||||
local HasArrived = self:onfuncHasArrived( ProcessUnit ) -- Polymorphic
|
||||
if self.DisplayCount >= self.DisplayInterval then
|
||||
@@ -257,18 +257,18 @@ do -- ACT_ROUTE
|
||||
else
|
||||
self.DisplayCount = self.DisplayCount + 1
|
||||
end
|
||||
|
||||
|
||||
if HasArrived then
|
||||
self:__Arrive( 1 )
|
||||
else
|
||||
self:__Route( 1 )
|
||||
end
|
||||
|
||||
|
||||
return HasArrived -- if false, then the event will not be executed...
|
||||
end
|
||||
|
||||
|
||||
return false
|
||||
|
||||
|
||||
end
|
||||
|
||||
end -- ACT_ROUTE
|
||||
@@ -280,12 +280,12 @@ do -- ACT_ROUTE_POINT
|
||||
-- @type ACT_ROUTE_POINT
|
||||
-- @field Tasking.Task#TASK TASK
|
||||
-- @extends #ACT_ROUTE
|
||||
ACT_ROUTE_POINT = {
|
||||
ACT_ROUTE_POINT = {
|
||||
ClassName = "ACT_ROUTE_POINT",
|
||||
}
|
||||
|
||||
|
||||
--- Creates a new routing state machine.
|
||||
--- Creates a new routing state machine.
|
||||
-- The task will route a controllable to a Coordinate until the controllable is within the Range.
|
||||
-- @param #ACT_ROUTE_POINT self
|
||||
-- @param Core.Point#COORDINATE The Coordinate to Target.
|
||||
@@ -296,29 +296,29 @@ do -- ACT_ROUTE_POINT
|
||||
|
||||
self.Coordinate = Coordinate
|
||||
self.Range = Range or 0
|
||||
|
||||
|
||||
self.DisplayInterval = 30
|
||||
self.DisplayCount = 30
|
||||
self.DisplayMessage = true
|
||||
self.DisplayTime = 10 -- 10 seconds is the default
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Creates a new routing state machine.
|
||||
|
||||
--- Creates a new routing state machine.
|
||||
-- The task will route a controllable to a Coordinate until the controllable is within the Range.
|
||||
-- @param #ACT_ROUTE_POINT self
|
||||
function ACT_ROUTE_POINT:Init( FsmRoute )
|
||||
|
||||
|
||||
self.Coordinate = FsmRoute.Coordinate
|
||||
self.Range = FsmRoute.Range or 0
|
||||
|
||||
|
||||
self.DisplayInterval = 30
|
||||
self.DisplayCount = 30
|
||||
self.DisplayMessage = true
|
||||
self.DisplayTime = 10 -- 10 seconds is the default
|
||||
self:SetStartState("None")
|
||||
end
|
||||
end
|
||||
|
||||
--- Set Coordinate
|
||||
-- @param #ACT_ROUTE_POINT self
|
||||
@@ -326,7 +326,7 @@ do -- ACT_ROUTE_POINT
|
||||
function ACT_ROUTE_POINT:SetCoordinate( Coordinate )
|
||||
self:F2( { Coordinate } )
|
||||
self.Coordinate = Coordinate
|
||||
end
|
||||
end
|
||||
|
||||
--- Get Coordinate
|
||||
-- @param #ACT_ROUTE_POINT self
|
||||
@@ -334,7 +334,7 @@ do -- ACT_ROUTE_POINT
|
||||
function ACT_ROUTE_POINT:GetCoordinate()
|
||||
self:F2( { self.Coordinate } )
|
||||
return self.Coordinate
|
||||
end
|
||||
end
|
||||
|
||||
--- Set Range around Coordinate
|
||||
-- @param #ACT_ROUTE_POINT self
|
||||
@@ -342,16 +342,16 @@ do -- ACT_ROUTE_POINT
|
||||
function ACT_ROUTE_POINT:SetRange( Range )
|
||||
self:F2( { Range } )
|
||||
self.Range = Range or 10000
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Get Range around Coordinate
|
||||
-- @param #ACT_ROUTE_POINT self
|
||||
-- @return #number The Range to consider the arrival. Default is 10000 meters.
|
||||
function ACT_ROUTE_POINT:GetRange()
|
||||
self:F2( { self.Range } )
|
||||
return self.Range
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Method override to check if the controllable has arrived.
|
||||
-- @param #ACT_ROUTE_POINT self
|
||||
-- @param Wrapper.Unit#UNIT ProcessUnit
|
||||
@@ -360,7 +360,7 @@ do -- ACT_ROUTE_POINT
|
||||
|
||||
if ProcessUnit:IsAlive() then
|
||||
local Distance = self.Coordinate:Get2DDistance( ProcessUnit:GetCoordinate() )
|
||||
|
||||
|
||||
if Distance <= self.Range then
|
||||
local RouteText = "Task \"" .. self:GetTask():GetName() .. "\", you have arrived."
|
||||
self:GetCommandCenter():MessageTypeToGroup( RouteText, ProcessUnit:GetGroup(), MESSAGE.Type.Information )
|
||||
@@ -370,9 +370,9 @@ do -- ACT_ROUTE_POINT
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
--- Task Events
|
||||
|
||||
|
||||
--- StateMachine callback function
|
||||
-- @param #ACT_ROUTE_POINT self
|
||||
-- @param Wrapper.Unit#UNIT ProcessUnit
|
||||
@@ -380,9 +380,9 @@ do -- ACT_ROUTE_POINT
|
||||
-- @param #string From
|
||||
-- @param #string To
|
||||
function ACT_ROUTE_POINT:onafterReport( ProcessUnit, From, Event, To )
|
||||
|
||||
|
||||
local RouteText = "Task \"" .. self:GetTask():GetName() .. "\", " .. self:GetRouteText( ProcessUnit )
|
||||
|
||||
|
||||
self:GetCommandCenter():MessageTypeToGroup( RouteText, ProcessUnit:GetGroup(), MESSAGE.Type.Update )
|
||||
end
|
||||
|
||||
@@ -397,7 +397,7 @@ do -- ACT_ROUTE_ZONE
|
||||
-- @field Wrapper.Unit#UNIT ProcessUnit
|
||||
-- @field Core.Zone#ZONE_BASE Zone
|
||||
-- @extends #ACT_ROUTE
|
||||
ACT_ROUTE_ZONE = {
|
||||
ACT_ROUTE_ZONE = {
|
||||
ClassName = "ACT_ROUTE_ZONE",
|
||||
}
|
||||
|
||||
@@ -409,25 +409,25 @@ do -- ACT_ROUTE_ZONE
|
||||
local self = BASE:Inherit( self, ACT_ROUTE:New() ) -- #ACT_ROUTE_ZONE
|
||||
|
||||
self.Zone = Zone
|
||||
|
||||
|
||||
self.DisplayInterval = 30
|
||||
self.DisplayCount = 30
|
||||
self.DisplayMessage = true
|
||||
self.DisplayTime = 10 -- 10 seconds is the default
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
function ACT_ROUTE_ZONE:Init( FsmRoute )
|
||||
|
||||
|
||||
self.Zone = FsmRoute.Zone
|
||||
|
||||
|
||||
self.DisplayInterval = 30
|
||||
self.DisplayCount = 30
|
||||
self.DisplayMessage = true
|
||||
self.DisplayTime = 10 -- 10 seconds is the default
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Set Zone
|
||||
-- @param #ACT_ROUTE_ZONE self
|
||||
-- @param Core.Zone#ZONE_BASE Zone The Zone object where to route to.
|
||||
@@ -437,14 +437,14 @@ do -- ACT_ROUTE_ZONE
|
||||
self.Zone = Zone
|
||||
self.Altitude = Altitude
|
||||
self.Heading = Heading
|
||||
end
|
||||
end
|
||||
|
||||
--- Get Zone
|
||||
-- @param #ACT_ROUTE_ZONE self
|
||||
-- @return Core.Zone#ZONE_BASE Zone The Zone object where to route to.
|
||||
function ACT_ROUTE_ZONE:GetZone()
|
||||
return self.Zone
|
||||
end
|
||||
return self.Zone
|
||||
end
|
||||
|
||||
--- Method override to check if the controllable has arrived.
|
||||
-- @param #ACT_ROUTE self
|
||||
@@ -459,9 +459,9 @@ do -- ACT_ROUTE_ZONE
|
||||
|
||||
return ProcessUnit:IsInZone( self.Zone )
|
||||
end
|
||||
|
||||
|
||||
--- Task Events
|
||||
|
||||
|
||||
--- StateMachine callback function
|
||||
-- @param #ACT_ROUTE_ZONE self
|
||||
-- @param Wrapper.Unit#UNIT ProcessUnit
|
||||
@@ -470,7 +470,7 @@ do -- ACT_ROUTE_ZONE
|
||||
-- @param #string To
|
||||
function ACT_ROUTE_ZONE:onafterReport( ProcessUnit, From, Event, To )
|
||||
self:F( { ProcessUnit = ProcessUnit } )
|
||||
|
||||
|
||||
local RouteText = "Task \"" .. self:GetTask():GetName() .. "\", " .. self:GetRouteText( ProcessUnit )
|
||||
self:GetCommandCenter():MessageTypeToGroup( RouteText, ProcessUnit:GetGroup(), MESSAGE.Type.Update )
|
||||
end
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
--- **Core** -- Management of CARGO logistics, that can be transported from and to transportation carriers.
|
||||
--- **Cargo** - Management of CARGO logistics, that can be transported from and to transportation carriers.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
@@ -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 dissapear when the cargo is within 25 meters from the carrier during boarding
|
||||
-- * will disappear 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 dissapear when the cargo is within 25 meters from the carrier during boarding
|
||||
-- * will disappear when the cargo is within 25 meters from the carrier during boarding
|
||||
--
|
||||
-- So the overall syntax of the #CARGO naming tag and arguments are:
|
||||
--
|
||||
@@ -1066,18 +1066,26 @@ do -- CARGO_REPRESENTABLE
|
||||
|
||||
--- CARGO_REPRESENTABLE Constructor.
|
||||
-- @param #CARGO_REPRESENTABLE self
|
||||
-- @param #string Type
|
||||
-- @param #string Name
|
||||
-- @param #number LoadRadius (optional)
|
||||
-- @param #number NearRadius (optional)
|
||||
-- @param Wrapper.Positionable#POSITIONABLE CargoObject The cargo object.
|
||||
-- @param #string Type Type name
|
||||
-- @param #string Name Name.
|
||||
-- @param #number LoadRadius (optional) Radius in meters.
|
||||
-- @param #number NearRadius (optional) Radius in meters when the cargo is loaded into the carrier.
|
||||
-- @return #CARGO_REPRESENTABLE
|
||||
function CARGO_REPRESENTABLE:New( CargoObject, Type, Name, LoadRadius, NearRadius )
|
||||
|
||||
-- Inherit CARGO.
|
||||
local self = BASE:Inherit( self, CARGO:New( Type, Name, 0, LoadRadius, NearRadius ) ) -- #CARGO_REPRESENTABLE
|
||||
self:F( { Type, Name, LoadRadius, NearRadius } )
|
||||
|
||||
local Desc = CargoObject:GetDesc()
|
||||
self:I( { Desc = Desc } )
|
||||
-- Descriptors.
|
||||
local Desc=CargoObject:GetDesc()
|
||||
self:T({Desc=Desc})
|
||||
|
||||
-- Weight.
|
||||
local Weight = math.random( 80, 120 )
|
||||
|
||||
-- Adjust weight..
|
||||
if Desc then
|
||||
if Desc.typeName == "2B11 mortar" then
|
||||
Weight = 210
|
||||
@@ -1086,13 +1094,8 @@ do -- CARGO_REPRESENTABLE
|
||||
end
|
||||
end
|
||||
|
||||
-- Set weight.
|
||||
self:SetWeight( Weight )
|
||||
|
||||
-- local Box = CargoUnit:GetBoundingBox()
|
||||
-- local VolumeUnit = ( Box.max.x - Box.min.x ) * ( Box.max.y - Box.min.y ) * ( Box.max.z - Box.min.z )
|
||||
-- self:I( { VolumeUnit = VolumeUnit, WeightUnit = WeightUnit } )
|
||||
--self:SetVolume( VolumeUnit )
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
--- **Cargo** -- Management of grouped cargo logistics, which are based on a @{Wrapper.Group} object.
|
||||
--- **Cargo** - Management of grouped cargo logistics, which are based on a @{Wrapper.Group} object.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
@@ -56,6 +56,8 @@ do -- CARGO_GROUP
|
||||
-- @param #number NearRadius (optional) Once the units are within this radius of the carrier, they are actually loaded, i.e. disappear from the scene.
|
||||
-- @return #CARGO_GROUP Cargo group object.
|
||||
function CARGO_GROUP:New( CargoGroup, Type, Name, LoadRadius, NearRadius )
|
||||
|
||||
-- Inherit CAROG_REPORTABLE
|
||||
local self = BASE:Inherit( self, CARGO_REPORTABLE:New( Type, Name, 0, LoadRadius, NearRadius ) ) -- #CARGO_GROUP
|
||||
self:F( { Type, Name, LoadRadius } )
|
||||
|
||||
@@ -76,6 +78,9 @@ do -- CARGO_GROUP
|
||||
local GroupName = CargoGroup:GetName()
|
||||
self.CargoName = Name
|
||||
self.CargoTemplate = UTILS.DeepCopy( _DATABASE:GetGroupTemplate( GroupName ) )
|
||||
|
||||
-- Deactivate late activation.
|
||||
self.CargoTemplate.lateActivation=false
|
||||
|
||||
self.GroupTemplate = UTILS.DeepCopy( self.CargoTemplate )
|
||||
self.GroupTemplate.name = self.CargoName .. "#CARGO"
|
||||
@@ -481,7 +486,7 @@ do -- CARGO_GROUP
|
||||
-- @param #string Event
|
||||
-- @param #string From
|
||||
-- @param #string To
|
||||
-- @param Core.Point#POINT_VEC2
|
||||
-- @param Core.Point#POINT_VEC2 ToPointVec2
|
||||
function CARGO_GROUP:onafterUnLoad( From, Event, To, ToPointVec2, ... )
|
||||
--self:F( { From, Event, To, ToPointVec2 } )
|
||||
|
||||
@@ -491,7 +496,10 @@ do -- CARGO_GROUP
|
||||
self.CargoSet:ForEach(
|
||||
function( Cargo )
|
||||
--Cargo:UnLoad( ToPointVec2 )
|
||||
local RandomVec2=ToPointVec2:GetRandomPointVec2InRadius(20, 10)
|
||||
local RandomVec2=nil
|
||||
if ToPointVec2 then
|
||||
RandomVec2=ToPointVec2:GetRandomPointVec2InRadius(20, 10)
|
||||
end
|
||||
Cargo:UnBoard( RandomVec2 )
|
||||
end
|
||||
)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
--- **Cargo** -- Management of single cargo logistics, which are based on a @{Wrapper.Unit} object.
|
||||
--- **Cargo** - Management of single cargo logistics, which are based on a @{Wrapper.Unit} object.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
@@ -46,14 +46,17 @@ do -- CARGO_UNIT
|
||||
-- @param #number NearRadius (optional)
|
||||
-- @return #CARGO_UNIT
|
||||
function CARGO_UNIT:New( CargoUnit, Type, Name, LoadRadius, NearRadius )
|
||||
local self = BASE:Inherit( self, CARGO_REPRESENTABLE:New( CargoUnit, Type, Name, LoadRadius, NearRadius ) ) -- #CARGO_UNIT
|
||||
self:I( { Type, Name, LoadRadius, NearRadius } )
|
||||
|
||||
self:T( CargoUnit )
|
||||
-- Inherit CARGO_REPRESENTABLE.
|
||||
local self = BASE:Inherit( self, CARGO_REPRESENTABLE:New( CargoUnit, Type, Name, LoadRadius, NearRadius ) ) -- #CARGO_UNIT
|
||||
|
||||
-- Debug info.
|
||||
self:T({Type=Type, Name=Name, LoadRadius=LoadRadius, NearRadius=NearRadius})
|
||||
|
||||
-- Set cargo object.
|
||||
self.CargoObject = CargoUnit
|
||||
|
||||
self:T( self.ClassName )
|
||||
|
||||
-- Set event prio.
|
||||
self:SetEventPriority( 5 )
|
||||
|
||||
return self
|
||||
@@ -100,7 +103,12 @@ do -- CARGO_UNIT
|
||||
|
||||
-- Respawn the group...
|
||||
if self.CargoObject then
|
||||
self.CargoObject:ReSpawnAt( FromPointVec2, CargoDeployHeading )
|
||||
if CargoCarrier:IsShip() then
|
||||
-- If CargoCarrier is a ship, we don't want to spawn the units in the water next to the boat. Use destination coord instead.
|
||||
self.CargoObject:ReSpawnAt( ToPointVec2, CargoDeployHeading )
|
||||
else
|
||||
self.CargoObject:ReSpawnAt( FromPointVec2, CargoDeployHeading )
|
||||
end
|
||||
self:F( { "CargoUnits:", self.CargoObject:GetGroup():GetName() } )
|
||||
self.CargoCarrier = nil
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
479
Moose Development/Moose/Core/Beacon.lua
Normal file
479
Moose Development/Moose/Core/Beacon.lua
Normal file
@@ -0,0 +1,479 @@
|
||||
--- **Core** - TACAN and other beacons.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ## Features:
|
||||
--
|
||||
-- * Provide beacon functionality to assist pilots.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Authors: Hugues "Grey_Echo" Bousquet, funkyfranky
|
||||
--
|
||||
-- @module Core.Beacon
|
||||
-- @image Core_Radio.JPG
|
||||
|
||||
--- *In order for the light to shine so brightly, the darkness must be present.* -- Francis Bacon
|
||||
--
|
||||
-- After attaching a @{#BEACON} to your @{Wrapper.Positionable#POSITIONABLE}, you need to select the right function to activate the kind of beacon you want.
|
||||
-- There are two types of BEACONs available : the (aircraft) 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 useful to simulate the battery time if your BEACON is
|
||||
-- attach to a cargo crate, for exemple.
|
||||
--
|
||||
-- ## Aircraft TACAN Beacon usage
|
||||
--
|
||||
-- This beacon only works with airborne @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP}. Use @{#BEACON.ActivateTACAN}() to set the beacon parameters and start the beacon.
|
||||
-- Use @{#BEACON.StopRadioBeacon}() to stop it.
|
||||
--
|
||||
-- ## General Purpose Radio Beacon usage
|
||||
--
|
||||
-- This beacon will work with any @{Wrapper.Positionable#POSITIONABLE}, but **it won't follow the @{Wrapper.Positionable#POSITIONABLE}** ! This means that you should only use it with
|
||||
-- @{Wrapper.Positionable#POSITIONABLE} that don't move, or move very slowly. Use @{#BEACON.RadioBeacon}() to set the beacon parameters and start the beacon.
|
||||
-- Use @{#BEACON.StopRadioBeacon}() to stop it.
|
||||
--
|
||||
-- @type BEACON
|
||||
-- @field #string ClassName Name of the class "BEACON".
|
||||
-- @field Wrapper.Controllable#CONTROLLABLE Positionable The @{#CONTROLLABLE} that will receive radio capabilities.
|
||||
-- @extends Core.Base#BASE
|
||||
BEACON = {
|
||||
ClassName = "BEACON",
|
||||
Positionable = nil,
|
||||
name = nil,
|
||||
}
|
||||
|
||||
--- Beacon types supported by DCS.
|
||||
-- @type BEACON.Type
|
||||
-- @field #number NULL
|
||||
-- @field #number VOR
|
||||
-- @field #number DME
|
||||
-- @field #number VOR_DME
|
||||
-- @field #number TACAN TACtical Air Navigation system.
|
||||
-- @field #number VORTAC
|
||||
-- @field #number RSBN
|
||||
-- @field #number BROADCAST_STATION
|
||||
-- @field #number HOMER
|
||||
-- @field #number AIRPORT_HOMER
|
||||
-- @field #number AIRPORT_HOMER_WITH_MARKER
|
||||
-- @field #number ILS_FAR_HOMER
|
||||
-- @field #number ILS_NEAR_HOMER
|
||||
-- @field #number ILS_LOCALIZER
|
||||
-- @field #number ILS_GLIDESLOPE
|
||||
-- @field #number PRMG_LOCALIZER
|
||||
-- @field #number PRMG_GLIDESLOPE
|
||||
-- @field #number ICLS Same as ICLS glideslope.
|
||||
-- @field #number ICLS_LOCALIZER
|
||||
-- @field #number ICLS_GLIDESLOPE
|
||||
-- @field #number NAUTICAL_HOMER
|
||||
BEACON.Type={
|
||||
NULL = 0,
|
||||
VOR = 1,
|
||||
DME = 2,
|
||||
VOR_DME = 3,
|
||||
TACAN = 4,
|
||||
VORTAC = 5,
|
||||
RSBN = 128,
|
||||
BROADCAST_STATION = 1024,
|
||||
HOMER = 8,
|
||||
AIRPORT_HOMER = 4104,
|
||||
AIRPORT_HOMER_WITH_MARKER = 4136,
|
||||
ILS_FAR_HOMER = 16408,
|
||||
ILS_NEAR_HOMER = 16424,
|
||||
ILS_LOCALIZER = 16640,
|
||||
ILS_GLIDESLOPE = 16896,
|
||||
PRMG_LOCALIZER = 33024,
|
||||
PRMG_GLIDESLOPE = 33280,
|
||||
ICLS = 131584, --leaving this in here but it is the same as ICLS_GLIDESLOPE
|
||||
ICLS_LOCALIZER = 131328,
|
||||
ICLS_GLIDESLOPE = 131584,
|
||||
NAUTICAL_HOMER = 65536,
|
||||
}
|
||||
|
||||
--- Beacon systems supported by DCS. https://wiki.hoggitworld.com/view/DCS_command_activateBeacon
|
||||
-- @type BEACON.System
|
||||
-- @field #number PAR_10 ?
|
||||
-- @field #number RSBN_5 Russian VOR/DME system.
|
||||
-- @field #number TACAN TACtical Air Navigation system on ground.
|
||||
-- @field #number TACAN_TANKER_X TACtical Air Navigation system for tankers on X band.
|
||||
-- @field #number TACAN_TANKER_Y TACtical Air Navigation system for tankers on Y band.
|
||||
-- @field #number VOR Very High Frequency Omni-Directional Range
|
||||
-- @field #number ILS_LOCALIZER ILS localizer
|
||||
-- @field #number ILS_GLIDESLOPE ILS glide slope.
|
||||
-- @field #number PRGM_LOCALIZER PRGM localizer.
|
||||
-- @field #number PRGM_GLIDESLOPE PRGM glide slope.
|
||||
-- @field #number BROADCAST_STATION Broadcast station.
|
||||
-- @field #number VORTAC Radio-based navigational aid for aircraft pilots consisting of a co-located VHF omni-directional range (VOR) beacon and a tactical air navigation system (TACAN) beacon.
|
||||
-- @field #number TACAN_AA_MODE_X TACtical Air Navigation for aircraft on X band.
|
||||
-- @field #number TACAN_AA_MODE_Y TACtical Air Navigation for aircraft on Y band.
|
||||
-- @field #number VORDME Radio beacon that combines a VHF omnidirectional range (VOR) with a distance measuring equipment (DME).
|
||||
-- @field #number ICLS_LOCALIZER Carrier landing system.
|
||||
-- @field #number ICLS_GLIDESLOPE Carrier landing system.
|
||||
BEACON.System={
|
||||
PAR_10 = 1,
|
||||
RSBN_5 = 2,
|
||||
TACAN = 3,
|
||||
TACAN_TANKER_X = 4,
|
||||
TACAN_TANKER_Y = 5,
|
||||
VOR = 6,
|
||||
ILS_LOCALIZER = 7,
|
||||
ILS_GLIDESLOPE = 8,
|
||||
PRMG_LOCALIZER = 9,
|
||||
PRMG_GLIDESLOPE = 10,
|
||||
BROADCAST_STATION = 11,
|
||||
VORTAC = 12,
|
||||
TACAN_AA_MODE_X = 13,
|
||||
TACAN_AA_MODE_Y = 14,
|
||||
VORDME = 15,
|
||||
ICLS_LOCALIZER = 16,
|
||||
ICLS_GLIDESLOPE = 17,
|
||||
}
|
||||
|
||||
--- Create a new BEACON Object. This doesn't activate the beacon, though, use @{#BEACON.ActivateTACAN} etc.
|
||||
-- If you want to create a BEACON, you probably should use @{Wrapper.Positionable#POSITIONABLE.GetBeacon}() instead.
|
||||
-- @param #BEACON self
|
||||
-- @param Wrapper.Positionable#POSITIONABLE Positionable The @{Positionable} that will receive radio capabilities.
|
||||
-- @return #BEACON Beacon object or #nil if the positionable is invalid.
|
||||
function BEACON:New(Positionable)
|
||||
|
||||
-- Inherit BASE.
|
||||
local self=BASE:Inherit(self, BASE:New()) --#BEACON
|
||||
|
||||
-- Debug.
|
||||
self:F(Positionable)
|
||||
|
||||
-- Set positionable.
|
||||
if Positionable:GetPointVec2() then -- It's stupid, but the only way I found to make sure positionable is valid
|
||||
self.Positionable = Positionable
|
||||
self.name=Positionable:GetName()
|
||||
self:I(string.format("New BEACON %s", tostring(self.name)))
|
||||
return self
|
||||
end
|
||||
|
||||
self:E({"The passed positionable is invalid, no BEACON created", Positionable})
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
--- Activates a TACAN BEACON.
|
||||
-- @param #BEACON self
|
||||
-- @param #number Channel TACAN channel, i.e. the "10" part in "10Y".
|
||||
-- @param #string Mode TACAN mode, i.e. the "Y" part in "10Y".
|
||||
-- @param #string Message The Message that is going to be coded in Morse and broadcasted by the beacon.
|
||||
-- @param #boolean Bearing If true, beacon provides bearing information. If false (or nil), only distance information is available.
|
||||
-- @param #number Duration How long will the beacon last in seconds. Omit for forever.
|
||||
-- @return #BEACON self
|
||||
-- @usage
|
||||
-- -- Let's create a TACAN Beacon for a tanker
|
||||
-- local myUnit = UNIT:FindByName("MyUnit")
|
||||
-- local myBeacon = myUnit:GetBeacon() -- Creates the beacon
|
||||
--
|
||||
-- myBeacon:ActivateTACAN(20, "Y", "TEXACO", true) -- Activate the beacon
|
||||
function BEACON:ActivateTACAN(Channel, Mode, Message, Bearing, Duration)
|
||||
self:T({channel=Channel, mode=Mode, callsign=Message, bearing=Bearing, duration=Duration})
|
||||
|
||||
Mode=Mode or "Y"
|
||||
|
||||
-- Get frequency.
|
||||
local Frequency=UTILS.TACANToFrequency(Channel, Mode)
|
||||
|
||||
-- Check.
|
||||
if not Frequency then
|
||||
self:E({"The passed TACAN channel is invalid, the BEACON is not emitting"})
|
||||
return self
|
||||
end
|
||||
|
||||
-- Beacon type.
|
||||
local Type=BEACON.Type.TACAN
|
||||
|
||||
-- Beacon system.
|
||||
local System=BEACON.System.TACAN
|
||||
|
||||
-- Check if unit is an aircraft and set system accordingly.
|
||||
local AA=self.Positionable:IsAir()
|
||||
|
||||
|
||||
if AA then
|
||||
System=5 --NOTE: 5 is how you cat the correct tanker behaviour! --BEACON.System.TACAN_TANKER
|
||||
-- Check if "Y" mode is selected for aircraft.
|
||||
if Mode=="X" then
|
||||
--self:E({"WARNING: The POSITIONABLE you want to attach the AA Tacan Beacon is an aircraft: Mode should Y!", self.Positionable})
|
||||
System=BEACON.System.TACAN_TANKER_X
|
||||
else
|
||||
System=BEACON.System.TACAN_TANKER_Y
|
||||
end
|
||||
end
|
||||
|
||||
-- Attached unit.
|
||||
local UnitID=self.Positionable:GetID()
|
||||
|
||||
-- Debug.
|
||||
self:I({string.format("BEACON Activating TACAN %s: Channel=%d%s, Morse=%s, Bearing=%s, Duration=%s!", tostring(self.name), Channel, Mode, Message, tostring(Bearing), tostring(Duration))})
|
||||
|
||||
-- Start beacon.
|
||||
self.Positionable:CommandActivateBeacon(Type, System, Frequency, UnitID, Channel, Mode, AA, Message, Bearing)
|
||||
|
||||
-- Stop scheduler.
|
||||
if Duration then
|
||||
self.Positionable:DeactivateBeacon(Duration)
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Activates an ICLS BEACON. The unit the BEACON is attached to should be an aircraft carrier supporting this system.
|
||||
-- @param #BEACON self
|
||||
-- @param #number Channel ICLS channel.
|
||||
-- @param #string Callsign The Message that is going to be coded in Morse and broadcasted by the beacon.
|
||||
-- @param #number Duration How long will the beacon last in seconds. Omit for forever.
|
||||
-- @return #BEACON self
|
||||
function BEACON:ActivateICLS(Channel, Callsign, Duration)
|
||||
self:F({Channel=Channel, Callsign=Callsign, Duration=Duration})
|
||||
|
||||
-- Attached unit.
|
||||
local UnitID=self.Positionable:GetID()
|
||||
|
||||
-- Debug
|
||||
self:T2({"ICLS BEACON started!"})
|
||||
|
||||
-- Start beacon.
|
||||
self.Positionable:CommandActivateICLS(Channel, UnitID, Callsign)
|
||||
|
||||
-- Stop scheduler
|
||||
if Duration then -- Schedule the stop of the BEACON if asked by the MD
|
||||
self.Positionable:DeactivateBeacon(Duration)
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Activates a LINK4 BEACON. The unit the BEACON is attached to should be an aircraft carrier supporting this system.
|
||||
-- @param #BEACON self
|
||||
-- @param #number Frequency LINK4 FRequency in MHz, eg 336.
|
||||
-- @param #string Morse The ID 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:ActivateLink4(Frequency, Morse, Duration)
|
||||
self:F({Frequency=Frequency, Morse=Morse, Duration=Duration})
|
||||
|
||||
-- Attached unit.
|
||||
local UnitID=self.Positionable:GetID()
|
||||
|
||||
-- Debug
|
||||
self:T2({"LINK4 BEACON started!"})
|
||||
|
||||
-- Start beacon.
|
||||
self.Positionable:CommandActivateLink4(Frequency,UnitID,Morse)
|
||||
|
||||
-- Stop sheduler
|
||||
if Duration then -- Schedule the stop of the BEACON if asked by the MD
|
||||
self.Positionable:DeactivateLink4(Duration)
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- DEPRECATED: Please use @{BEACON:ActivateTACAN}() instead.
|
||||
-- Activates a TACAN BEACON on an Aircraft.
|
||||
-- @param #BEACON self
|
||||
-- @param #number TACANChannel (the "10" part in "10Y"). Note that AA TACAN are only available on Y Channels
|
||||
-- @param #string Message The Message that is going to be coded in Morse and broadcasted by the beacon
|
||||
-- @param #boolean Bearing Can the BEACON be homed on ?
|
||||
-- @param #number BeaconDuration How long will the beacon last in seconds. Omit for forever.
|
||||
-- @return #BEACON self
|
||||
-- @usage
|
||||
-- -- Let's create a TACAN Beacon for a tanker
|
||||
-- local myUnit = UNIT:FindByName("MyUnit")
|
||||
-- local myBeacon = myUnit:GetBeacon() -- Creates the beacon
|
||||
--
|
||||
-- myBeacon:AATACAN(20, "TEXACO", true) -- Activate the beacon
|
||||
function BEACON:AATACAN(TACANChannel, Message, Bearing, BeaconDuration)
|
||||
self:F({TACANChannel, Message, Bearing, BeaconDuration})
|
||||
|
||||
local IsValid = true
|
||||
|
||||
if not self.Positionable:IsAir() then
|
||||
self:E({"The POSITIONABLE you want to attach the AA Tacan Beacon is not an aircraft ! The BEACON is not emitting", self.Positionable})
|
||||
IsValid = false
|
||||
end
|
||||
|
||||
local Frequency = self:_TACANToFrequency(TACANChannel, "Y")
|
||||
if not Frequency then
|
||||
self:E({"The passed TACAN channel is invalid, the BEACON is not emitting"})
|
||||
IsValid = false
|
||||
end
|
||||
|
||||
-- I'm using the beacon type 4 (BEACON_TYPE_TACAN). For System, I'm using 5 (TACAN_TANKER_MODE_Y) if the bearing shows its bearing or 14 (TACAN_AA_MODE_Y) if it does not
|
||||
local System
|
||||
if Bearing then
|
||||
System = BEACON.System.TACAN_TANKER_Y
|
||||
else
|
||||
System = BEACON.System.TACAN_AA_MODE_Y
|
||||
end
|
||||
|
||||
if IsValid then -- Starts the BEACON
|
||||
self:T2({"AA TACAN BEACON started !"})
|
||||
self.Positionable:SetCommand({
|
||||
id = "ActivateBeacon",
|
||||
params = {
|
||||
type = BEACON.Type.TACAN,
|
||||
system = System,
|
||||
callsign = Message,
|
||||
AA = true,
|
||||
frequency = Frequency,
|
||||
bearing = Bearing,
|
||||
modeChannel = "Y",
|
||||
}
|
||||
})
|
||||
|
||||
if BeaconDuration then -- Schedule the stop of the BEACON if asked by the MD
|
||||
SCHEDULER:New(nil,
|
||||
function()
|
||||
self:StopAATACAN()
|
||||
end, {}, BeaconDuration)
|
||||
end
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Stops the AA TACAN BEACON
|
||||
-- @param #BEACON self
|
||||
-- @return #BEACON self
|
||||
function BEACON:StopAATACAN()
|
||||
self:F()
|
||||
if not self.Positionable then
|
||||
self:E({"Start the beacon first before stoping it !"})
|
||||
else
|
||||
self.Positionable:SetCommand({
|
||||
id = 'DeactivateBeacon',
|
||||
params = {
|
||||
}
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- Activates a general purpose 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 a few DCS Modules can home on radio beacons at the time of writing, i.e. the Mi-8, Huey, Gazelle etc.
|
||||
-- The following e.g. can home in on these specific frequencies :
|
||||
-- * **Mi8**
|
||||
-- * R-828 -> 20-60MHz
|
||||
-- * ARKUD -> 100-150MHz (canal 1 : 114166, canal 2 : 114333, canal 3 : 114583, canal 4 : 121500, canal 5 : 123100, canal 6 : 124100) AM
|
||||
-- * ARK9 -> 150-1300KHz
|
||||
-- * **Huey**
|
||||
-- * AN/ARC-131 -> 30-76 Mhz FM
|
||||
-- @param #BEACON self
|
||||
-- @param #string FileName The name of the audio file
|
||||
-- @param #number Frequency in MHz
|
||||
-- @param #number Modulation either radio.modulation.AM or radio.modulation.FM
|
||||
-- @param #number Power in W
|
||||
-- @param #number BeaconDuration How long will the beacon last in seconds. Omit for forever.
|
||||
-- @return #BEACON self
|
||||
-- @usage
|
||||
-- -- Let's create a beacon for a unit in distress.
|
||||
-- -- Frequency will be 40MHz FM (home-able by a Huey's AN/ARC-131)
|
||||
-- -- The beacon they use is battery-powered, and only lasts for 5 min
|
||||
-- local UnitInDistress = UNIT:FindByName("Unit1")
|
||||
-- local UnitBeacon = UnitInDistress:GetBeacon()
|
||||
--
|
||||
-- -- Set the beacon and start it
|
||||
-- UnitBeacon:RadioBeacon("MySoundFileSOS.ogg", 40, radio.modulation.FM, 20, 5*60)
|
||||
function BEACON:RadioBeacon(FileName, Frequency, Modulation, Power, BeaconDuration)
|
||||
self:F({FileName, Frequency, Modulation, Power, BeaconDuration})
|
||||
local IsValid = false
|
||||
|
||||
-- Check the filename
|
||||
if type(FileName) == "string" then
|
||||
if FileName:find(".ogg") or FileName:find(".wav") then
|
||||
if not FileName:find("l10n/DEFAULT/") then
|
||||
FileName = "l10n/DEFAULT/" .. FileName
|
||||
end
|
||||
IsValid = true
|
||||
end
|
||||
end
|
||||
if not IsValid then
|
||||
self:E({"File name invalid. Maybe something wrong with the extension ? ", FileName})
|
||||
end
|
||||
|
||||
-- Check the Frequency
|
||||
if type(Frequency) ~= "number" and IsValid then
|
||||
self:E({"Frequency invalid. ", Frequency})
|
||||
IsValid = false
|
||||
end
|
||||
Frequency = Frequency * 1000000 -- Conversion to Hz
|
||||
|
||||
-- Check the modulation
|
||||
if Modulation ~= radio.modulation.AM and Modulation ~= radio.modulation.FM and IsValid then --TODO Maybe make this future proof if ED decides to add an other modulation ?
|
||||
self:E({"Modulation is invalid. Use DCS's enum radio.modulation.", Modulation})
|
||||
IsValid = false
|
||||
end
|
||||
|
||||
-- Check the Power
|
||||
if type(Power) ~= "number" and IsValid then
|
||||
self:E({"Power is invalid. ", Power})
|
||||
IsValid = false
|
||||
end
|
||||
Power = math.floor(math.abs(Power)) --TODO Find what is the maximum power allowed by DCS and limit power to that
|
||||
|
||||
if IsValid then
|
||||
self:T2({"Activating Beacon on ", Frequency, Modulation})
|
||||
-- Note that this is looped. I have to give this transmission a unique name, I use the class ID
|
||||
trigger.action.radioTransmission(FileName, self.Positionable:GetPositionVec3(), Modulation, true, Frequency, Power, tostring(self.ID))
|
||||
|
||||
if BeaconDuration then -- Schedule the stop of the BEACON if asked by the MD
|
||||
SCHEDULER:New( nil,
|
||||
function()
|
||||
self:StopRadioBeacon()
|
||||
end, {}, BeaconDuration)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Stop the Radio Beacon
|
||||
-- @param #BEACON self
|
||||
-- @return #BEACON self
|
||||
function BEACON:StopRadioBeacon()
|
||||
self:F()
|
||||
-- The unique name of the transmission is the class ID
|
||||
trigger.action.stopRadioTransmission(tostring(self.ID))
|
||||
return self
|
||||
end
|
||||
|
||||
--- Converts a TACAN Channel/Mode couple into a frequency in Hz
|
||||
-- @param #BEACON self
|
||||
-- @param #number TACANChannel
|
||||
-- @param #string TACANMode
|
||||
-- @return #number Frequecy
|
||||
-- @return #nil if parameters are invalid
|
||||
function BEACON:_TACANToFrequency(TACANChannel, TACANMode)
|
||||
self:F3({TACANChannel, TACANMode})
|
||||
|
||||
if type(TACANChannel) ~= "number" then
|
||||
if TACANMode ~= "X" and TACANMode ~= "Y" then
|
||||
return nil -- error in arguments
|
||||
end
|
||||
end
|
||||
|
||||
-- This code is largely based on ED's code, in DCS World\Scripts\World\Radio\BeaconTypes.lua, line 137.
|
||||
-- I have no idea what it does but it seems to work
|
||||
local A = 1151 -- 'X', channel >= 64
|
||||
local B = 64 -- channel >= 64
|
||||
|
||||
if TACANChannel < 64 then
|
||||
B = 1
|
||||
end
|
||||
|
||||
if TACANMode == 'Y' then
|
||||
A = 1025
|
||||
if TACANChannel < 64 then
|
||||
A = 1088
|
||||
end
|
||||
else -- 'X'
|
||||
if TACANChannel < 64 then
|
||||
A = 962
|
||||
end
|
||||
end
|
||||
|
||||
return (A + TACANChannel - B) * 1000000
|
||||
end
|
||||
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
@@ -1,88 +1,87 @@
|
||||
--- **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( {} )
|
||||
|
||||
@@ -103,11 +102,10 @@ 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
|
||||
@@ -115,46 +113,44 @@ 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.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
|
||||
@@ -163,7 +159,6 @@ 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
|
||||
@@ -171,9 +166,7 @@ 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.
|
||||
@@ -181,4 +174,4 @@ do -- Goal
|
||||
return self:Is( "Achieved" )
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
-- * Only create or delete menus when required, and keep existing menus persistent.
|
||||
-- * Update menu structures.
|
||||
-- * Refresh menu structures intelligently, based on a time stamp of updates.
|
||||
-- - Delete obscolete menus.
|
||||
-- - Delete obsolete menus.
|
||||
-- - Create new one where required.
|
||||
-- - Don't touch the existing ones.
|
||||
-- * Provide a variable amount of parameters to menus.
|
||||
@@ -23,7 +23,7 @@
|
||||
-- * Provide a great tool to manage menus in your code.
|
||||
--
|
||||
-- DCS Menus can be managed using the MENU classes.
|
||||
-- The advantage of using MENU classes is that it hides the complexity of dealing with menu management in more advanced scanerios where you need to
|
||||
-- The advantage of using MENU classes is that it hides the complexity of dealing with menu management in more advanced scenarios where you need to
|
||||
-- set menus and later remove them, and later set them again. You'll find while using use normal DCS scripting functions, that setting and removing
|
||||
-- menus is not a easy feat if you have complex menu hierarchies defined.
|
||||
-- Using the MOOSE menu classes, the removal and refreshing of menus are nicely being handled within these classes, and becomes much more easy.
|
||||
@@ -53,7 +53,6 @@
|
||||
-- @module Core.Menu
|
||||
-- @image Core_Menu.JPG
|
||||
|
||||
|
||||
MENU_INDEX = {}
|
||||
MENU_INDEX.MenuMission = {}
|
||||
MENU_INDEX.MenuMission.Menus = {}
|
||||
@@ -64,10 +63,7 @@ MENU_INDEX.Coalition[coalition.side.RED] = {}
|
||||
MENU_INDEX.Coalition[coalition.side.RED].Menus = {}
|
||||
MENU_INDEX.Group = {}
|
||||
|
||||
|
||||
|
||||
function MENU_INDEX:ParentPath( ParentMenu, MenuText )
|
||||
|
||||
local Path = ParentMenu and "@" .. table.concat( ParentMenu.MenuPath or {}, "@" ) or ""
|
||||
if ParentMenu then
|
||||
if ParentMenu:IsInstanceOf( "MENU_GROUP" ) or ParentMenu:IsInstanceOf( "MENU_GROUP_COMMAND" ) then
|
||||
@@ -95,20 +91,16 @@ function MENU_INDEX:ParentPath( ParentMenu, MenuText )
|
||||
|
||||
Path = Path .. "@" .. MenuText
|
||||
return Path
|
||||
|
||||
end
|
||||
|
||||
|
||||
function MENU_INDEX:PrepareMission()
|
||||
self.MenuMission.Menus = self.MenuMission.Menus or {}
|
||||
end
|
||||
|
||||
|
||||
function MENU_INDEX:PrepareCoalition( CoalitionSide )
|
||||
self.Coalition[CoalitionSide] = self.Coalition[CoalitionSide] or {}
|
||||
self.Coalition[CoalitionSide].Menus = self.Coalition[CoalitionSide].Menus or {}
|
||||
end
|
||||
|
||||
---
|
||||
-- @param Wrapper.Group#GROUP Group
|
||||
function MENU_INDEX:PrepareGroup( Group )
|
||||
@@ -119,42 +111,26 @@ function MENU_INDEX:PrepareGroup( Group )
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
function MENU_INDEX:HasMissionMenu( Path )
|
||||
|
||||
return self.MenuMission.Menus[Path]
|
||||
end
|
||||
|
||||
function MENU_INDEX:SetMissionMenu( Path, Menu )
|
||||
|
||||
self.MenuMission.Menus[Path] = Menu
|
||||
end
|
||||
|
||||
function MENU_INDEX:ClearMissionMenu( Path )
|
||||
|
||||
self.MenuMission.Menus[Path] = nil
|
||||
end
|
||||
|
||||
|
||||
|
||||
function MENU_INDEX:HasCoalitionMenu( Coalition, Path )
|
||||
|
||||
return self.Coalition[Coalition].Menus[Path]
|
||||
end
|
||||
|
||||
function MENU_INDEX:SetCoalitionMenu( Coalition, Path, Menu )
|
||||
|
||||
self.Coalition[Coalition].Menus[Path] = Menu
|
||||
end
|
||||
|
||||
function MENU_INDEX:ClearCoalitionMenu( Coalition, Path )
|
||||
|
||||
self.Coalition[Coalition].Menus[Path] = nil
|
||||
end
|
||||
|
||||
|
||||
|
||||
function MENU_INDEX:HasGroupMenu( Group, Path )
|
||||
if Group and Group:IsAlive() then
|
||||
local MenuGroupName = Group:GetName()
|
||||
@@ -162,53 +138,36 @@ function MENU_INDEX:HasGroupMenu( Group, Path )
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
function MENU_INDEX:SetGroupMenu( Group, Path, Menu )
|
||||
|
||||
local MenuGroupName = Group:GetName()
|
||||
Group:F({MenuGroupName=MenuGroupName,Path=Path})
|
||||
self.Group[MenuGroupName].Menus[Path] = Menu
|
||||
end
|
||||
|
||||
function MENU_INDEX:ClearGroupMenu( Group, Path )
|
||||
|
||||
local MenuGroupName = Group:GetName()
|
||||
self.Group[MenuGroupName].Menus[Path] = nil
|
||||
end
|
||||
|
||||
function MENU_INDEX:Refresh( Group )
|
||||
|
||||
for MenuID, Menu in pairs( self.MenuMission.Menus ) do
|
||||
Menu:Refresh()
|
||||
end
|
||||
|
||||
for MenuID, Menu in pairs( self.Coalition[coalition.side.BLUE].Menus ) do
|
||||
Menu:Refresh()
|
||||
end
|
||||
|
||||
for MenuID, Menu in pairs( self.Coalition[coalition.side.RED].Menus ) do
|
||||
Menu:Refresh()
|
||||
end
|
||||
|
||||
local GroupName = Group:GetName()
|
||||
for MenuID, Menu in pairs( self.Group[GroupName].Menus ) do
|
||||
Menu:Refresh()
|
||||
end
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
do -- MENU_BASE
|
||||
|
||||
--- @type MENU_BASE
|
||||
-- @extends Base#BASE
|
||||
|
||||
-- @extends Core.Base#BASE
|
||||
--- Defines the main MENU class where other MENU classes are derived from.
|
||||
-- This is an abstract class, so don't use it.
|
||||
-- @field #MENU_BASE
|
||||
@@ -216,10 +175,10 @@ do -- MENU_BASE
|
||||
ClassName = "MENU_BASE",
|
||||
MenuPath = nil,
|
||||
MenuText = "",
|
||||
MenuParentPath = nil
|
||||
MenuParentPath = nil,
|
||||
}
|
||||
|
||||
--- Consructor
|
||||
--- Constructor
|
||||
-- @param #MENU_BASE
|
||||
-- @return #MENU_BASE
|
||||
function MENU_BASE:New( MenuText, ParentMenu )
|
||||
@@ -228,27 +187,25 @@ do -- MENU_BASE
|
||||
if ParentMenu ~= nil then
|
||||
MenuParentPath = ParentMenu.MenuPath
|
||||
end
|
||||
|
||||
local self = BASE:Inherit( self, BASE:New() )
|
||||
local self = BASE:Inherit( self, BASE:New() )
|
||||
|
||||
self.MenuPath = nil
|
||||
self.MenuText = MenuText
|
||||
self.ParentMenu = ParentMenu
|
||||
self.MenuParentPath = MenuParentPath
|
||||
self.Path = ( self.ParentMenu and "@" .. table.concat( self.MenuParentPath or {}, "@" ) or "" ) .. "@" .. self.MenuText
|
||||
self.MenuPath = nil
|
||||
self.MenuText = MenuText
|
||||
self.ParentMenu = ParentMenu
|
||||
self.MenuParentPath = MenuParentPath
|
||||
self.Path = ( self.ParentMenu and "@" .. table.concat( self.MenuParentPath or {}, "@" ) or "" ) .. "@" .. self.MenuText
|
||||
self.Menus = {}
|
||||
self.MenuCount = 0
|
||||
self.MenuTime = timer.getTime()
|
||||
self.MenuStamp = timer.getTime()
|
||||
self.MenuRemoveParent = false
|
||||
|
||||
|
||||
if self.ParentMenu then
|
||||
self.ParentMenu.Menus = self.ParentMenu.Menus or {}
|
||||
self.ParentMenu.Menus[MenuText] = self
|
||||
end
|
||||
|
||||
return self
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function MENU_BASE:SetParentMenu( MenuText, Menu )
|
||||
if self.ParentMenu then
|
||||
self.ParentMenu.Menus = self.ParentMenu.Menus or {}
|
||||
@@ -256,7 +213,6 @@ do -- MENU_BASE
|
||||
self.ParentMenu.MenuCount = self.ParentMenu.MenuCount + 1
|
||||
end
|
||||
end
|
||||
|
||||
function MENU_BASE:ClearParentMenu( MenuText )
|
||||
if self.ParentMenu and self.ParentMenu.Menus[MenuText] then
|
||||
self.ParentMenu.Menus[MenuText] = nil
|
||||
@@ -266,7 +222,6 @@ do -- MENU_BASE
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Sets a @{Menu} to remove automatically the parent menu when the menu removed is the last child menu of that parent @{Menu}.
|
||||
-- @param #MENU_BASE self
|
||||
-- @param #boolean RemoveParent If true, the parent menu is automatically removed when this menu is the last child menu of that parent @{Menu}.
|
||||
@@ -276,7 +231,6 @@ do -- MENU_BASE
|
||||
self.MenuRemoveParent = RemoveParent
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Gets a @{Menu} from a parent @{Menu}
|
||||
-- @param #MENU_BASE self
|
||||
@@ -285,13 +239,30 @@ do -- MENU_BASE
|
||||
function MENU_BASE:GetMenu( MenuText )
|
||||
return self.Menus[MenuText]
|
||||
end
|
||||
--- Sets a menu stamp for later prevention of menu removal.
|
||||
-- @param #MENU_BASE self
|
||||
-- @param MenuStamp
|
||||
-- @return #MENU_BASE
|
||||
function MENU_BASE:SetStamp( MenuStamp )
|
||||
self.MenuStamp = MenuStamp
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Gets a menu stamp for later prevention of menu removal.
|
||||
-- @param #MENU_BASE self
|
||||
-- @return MenuStamp
|
||||
function MENU_BASE:GetStamp()
|
||||
return timer.getTime()
|
||||
end
|
||||
|
||||
|
||||
--- Sets a time stamp for later prevention of menu removal.
|
||||
-- @param #MENU_BASE self
|
||||
-- @param MenuTime
|
||||
-- @param MenuStamp
|
||||
-- @return #MENU_BASE
|
||||
function MENU_BASE:SetTime( MenuTime )
|
||||
self.MenuTime = MenuTime
|
||||
function MENU_BASE:SetTime( MenuStamp )
|
||||
self.MenuStamp = MenuStamp
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -305,9 +276,7 @@ do -- MENU_BASE
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
do -- MENU_COMMAND_BASE
|
||||
|
||||
--- @type MENU_COMMAND_BASE
|
||||
-- @field #function MenuCallHandler
|
||||
-- @extends Core.Menu#MENU_BASE
|
||||
@@ -328,8 +297,7 @@ do -- MENU_COMMAND_BASE
|
||||
-- @return #MENU_COMMAND_BASE
|
||||
function MENU_COMMAND_BASE:New( MenuText, ParentMenu, CommandMenuFunction, CommandMenuArguments )
|
||||
|
||||
local self = BASE:Inherit( self, MENU_BASE:New( MenuText, ParentMenu ) ) -- #MENU_COMMAND_BASE
|
||||
|
||||
local self = BASE:Inherit( self, MENU_BASE:New( MenuText, ParentMenu ) ) -- #MENU_COMMAND_BASE
|
||||
-- When a menu function goes into error, DCS displays an obscure menu message.
|
||||
-- This error handler catches the menu error and displays the full call stack.
|
||||
local ErrorHandler = function( errmsg )
|
||||
@@ -349,7 +317,7 @@ do -- MENU_COMMAND_BASE
|
||||
local Status, Result = xpcall( MenuFunction, ErrorHandler )
|
||||
end
|
||||
|
||||
return self
|
||||
return self
|
||||
end
|
||||
|
||||
--- This sets the new command function of a menu,
|
||||
@@ -362,7 +330,6 @@ do -- MENU_COMMAND_BASE
|
||||
self.CommandMenuFunction = CommandMenuFunction
|
||||
return self
|
||||
end
|
||||
|
||||
--- This sets the new command arguments of a menu,
|
||||
-- so that if a menu is regenerated, or if command arguments change,
|
||||
-- that the arguments set for the menu are loosely coupled with the menu itself!!!
|
||||
@@ -373,35 +340,30 @@ do -- MENU_COMMAND_BASE
|
||||
self.CommandMenuArguments = CommandMenuArguments
|
||||
return self
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
do -- MENU_MISSION
|
||||
|
||||
--- @type MENU_MISSION
|
||||
-- @extends Core.Menu#MENU_BASE
|
||||
|
||||
--- Manages the main menus for a complete mission.
|
||||
--
|
||||
-- You can add menus with the @{#MENU_MISSION.New} method, which constructs a MENU_MISSION object and returns you the object reference.
|
||||
-- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_MISSION.Remove}.
|
||||
-- @field #MENU_MISSION
|
||||
MENU_MISSION = {
|
||||
ClassName = "MENU_MISSION"
|
||||
ClassName = "MENU_MISSION",
|
||||
}
|
||||
|
||||
--- MENU_MISSION constructor. Creates a new MENU_MISSION object and creates the menu for a complete mission file.
|
||||
-- @param #MENU_MISSION self
|
||||
-- @param #string MenuText The text for the menu.
|
||||
-- @param #table ParentMenu The parent menu. This parameter can be ignored if you want the menu to be located at the perent menu of DCS world (under F10 other).
|
||||
-- @param #table ParentMenu The parent menu. This parameter can be ignored if you want the menu to be located at the parent menu of DCS world (under F10 other).
|
||||
-- @return #MENU_MISSION
|
||||
function MENU_MISSION:New( MenuText, ParentMenu )
|
||||
|
||||
MENU_INDEX:PrepareMission()
|
||||
local Path = MENU_INDEX:ParentPath( ParentMenu, MenuText )
|
||||
local MissionMenu = MENU_INDEX:HasMissionMenu( Path )
|
||||
|
||||
if MissionMenu then
|
||||
return MissionMenu
|
||||
else
|
||||
@@ -414,17 +376,15 @@ do -- MENU_MISSION
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Refreshes a radio item for a mission
|
||||
-- @param #MENU_MISSION self
|
||||
-- @return #MENU_MISSION
|
||||
function MENU_MISSION:Refresh()
|
||||
|
||||
do
|
||||
missionCommands.removeItem( self.MenuPath )
|
||||
self.MenuPath = missionCommands.addSubMenu( self.MenuText, self.MenuParentPath )
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Removes the sub menus recursively of this MENU_MISSION. Note that the main menu is kept!
|
||||
@@ -443,15 +403,14 @@ do -- MENU_MISSION
|
||||
--- Removes the main menu and the sub menus recursively of this MENU_MISSION.
|
||||
-- @param #MENU_MISSION self
|
||||
-- @return #nil
|
||||
function MENU_MISSION:Remove( MenuTime, MenuTag )
|
||||
function MENU_MISSION:Remove( MenuStamp, MenuTag )
|
||||
|
||||
MENU_INDEX:PrepareMission()
|
||||
local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText )
|
||||
local MissionMenu = MENU_INDEX:HasMissionMenu( Path )
|
||||
|
||||
if MissionMenu == self then
|
||||
self:RemoveSubMenus()
|
||||
if not MenuTime or self.MenuTime ~= MenuTime then
|
||||
if not MenuStamp or self.MenuStamp ~= MenuStamp then
|
||||
if ( not MenuTag ) or ( MenuTag and self.MenuTag and MenuTag == self.MenuTag ) then
|
||||
self:F( { Text = self.MenuText, Path = self.MenuPath } )
|
||||
if self.MenuPath ~= nil then
|
||||
@@ -469,10 +428,7 @@ do -- MENU_MISSION
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
|
||||
end
|
||||
|
||||
do -- MENU_MISSION_COMMAND
|
||||
|
||||
--- @type MENU_MISSION_COMMAND
|
||||
@@ -485,7 +441,7 @@ do -- MENU_MISSION_COMMAND
|
||||
--
|
||||
-- @field #MENU_MISSION_COMMAND
|
||||
MENU_MISSION_COMMAND = {
|
||||
ClassName = "MENU_MISSION_COMMAND"
|
||||
ClassName = "MENU_MISSION_COMMAND",
|
||||
}
|
||||
|
||||
--- MENU_MISSION constructor. Creates a new radio command item for a complete mission file, which can invoke a function with parameters.
|
||||
@@ -500,7 +456,6 @@ do -- MENU_MISSION_COMMAND
|
||||
MENU_INDEX:PrepareMission()
|
||||
local Path = MENU_INDEX:ParentPath( ParentMenu, MenuText )
|
||||
local MissionMenu = MENU_INDEX:HasMissionMenu( Path )
|
||||
|
||||
if MissionMenu then
|
||||
MissionMenu:SetCommandMenuFunction( CommandMenuFunction )
|
||||
MissionMenu:SetCommandMenuArguments( arg )
|
||||
@@ -514,17 +469,15 @@ do -- MENU_MISSION_COMMAND
|
||||
return self
|
||||
end
|
||||
end
|
||||
|
||||
--- Refreshes a radio item for a mission
|
||||
-- @param #MENU_MISSION_COMMAND self
|
||||
-- @return #MENU_MISSION_COMMAND
|
||||
function MENU_MISSION_COMMAND:Refresh()
|
||||
|
||||
do
|
||||
missionCommands.removeItem( self.MenuPath )
|
||||
missionCommands.addCommand( self.MenuText, self.MenuParentPath, self.MenuCallHandler )
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Removes a radio command item for a coalition
|
||||
@@ -535,9 +488,8 @@ do -- MENU_MISSION_COMMAND
|
||||
MENU_INDEX:PrepareMission()
|
||||
local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText )
|
||||
local MissionMenu = MENU_INDEX:HasMissionMenu( Path )
|
||||
|
||||
if MissionMenu == self then
|
||||
if not MenuTime or self.MenuTime ~= MenuTime then
|
||||
if not MenuStamp or self.MenuStamp ~= MenuStamp then
|
||||
if ( not MenuTag ) or ( MenuTag and self.MenuTag and MenuTag == self.MenuTag ) then
|
||||
self:F( { Text = self.MenuText, Path = self.MenuPath } )
|
||||
if self.MenuPath ~= nil then
|
||||
@@ -554,13 +506,8 @@ do -- MENU_MISSION_COMMAND
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
|
||||
do -- MENU_COALITION
|
||||
|
||||
--- @type MENU_COALITION
|
||||
-- @extends Core.Menu#MENU_BASE
|
||||
|
||||
@@ -616,18 +563,15 @@ do -- MENU_COALITION
|
||||
-- @param #MENU_COALITION self
|
||||
-- @param DCS#coalition.side Coalition The coalition owning the menu.
|
||||
-- @param #string MenuText The text for the menu.
|
||||
-- @param #table ParentMenu The parent menu. This parameter can be ignored if you want the menu to be located at the perent menu of DCS world (under F10 other).
|
||||
-- @param #table ParentMenu The parent menu. This parameter can be ignored if you want the menu to be located at the parent menu of DCS world (under F10 other).
|
||||
-- @return #MENU_COALITION self
|
||||
function MENU_COALITION:New( Coalition, MenuText, ParentMenu )
|
||||
|
||||
MENU_INDEX:PrepareCoalition( Coalition )
|
||||
local Path = MENU_INDEX:ParentPath( ParentMenu, MenuText )
|
||||
local CoalitionMenu = MENU_INDEX:HasCoalitionMenu( Coalition, Path )
|
||||
|
||||
if CoalitionMenu then
|
||||
return CoalitionMenu
|
||||
else
|
||||
|
||||
local self = BASE:Inherit( self, MENU_BASE:New( MenuText, ParentMenu ) )
|
||||
MENU_INDEX:SetCoalitionMenu( Coalition, Path, self )
|
||||
|
||||
@@ -638,17 +582,15 @@ do -- MENU_COALITION
|
||||
return self
|
||||
end
|
||||
end
|
||||
|
||||
--- Refreshes a radio item for a coalition
|
||||
-- @param #MENU_COALITION self
|
||||
-- @return #MENU_COALITION
|
||||
function MENU_COALITION:Refresh()
|
||||
|
||||
do
|
||||
missionCommands.removeItemForCoalition( self.Coalition, self.MenuPath )
|
||||
missionCommands.addSubMenuForCoalition( self.Coalition, self.MenuText, self.MenuParentPath )
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Removes the sub menus recursively of this MENU_COALITION. Note that the main menu is kept!
|
||||
@@ -666,15 +608,14 @@ do -- MENU_COALITION
|
||||
--- Removes the main menu and the sub menus recursively of this MENU_COALITION.
|
||||
-- @param #MENU_COALITION self
|
||||
-- @return #nil
|
||||
function MENU_COALITION:Remove( MenuTime, MenuTag )
|
||||
function MENU_COALITION:Remove( MenuStamp, MenuTag )
|
||||
|
||||
MENU_INDEX:PrepareCoalition( self.Coalition )
|
||||
local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText )
|
||||
local CoalitionMenu = MENU_INDEX:HasCoalitionMenu( self.Coalition, Path )
|
||||
|
||||
if CoalitionMenu == self then
|
||||
self:RemoveSubMenus()
|
||||
if not MenuTime or self.MenuTime ~= MenuTime then
|
||||
if not MenuStamp or self.MenuStamp ~= MenuStamp then
|
||||
if ( not MenuTag ) or ( MenuTag and self.MenuTag and MenuTag == self.MenuTag ) then
|
||||
self:F( { Coalition = self.Coalition, Text = self.MenuText, Path = self.MenuPath } )
|
||||
if self.MenuPath ~= nil then
|
||||
@@ -691,11 +632,7 @@ do -- MENU_COALITION
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
|
||||
do -- MENU_COALITION_COMMAND
|
||||
|
||||
--- @type MENU_COALITION_COMMAND
|
||||
@@ -724,7 +661,6 @@ do -- MENU_COALITION_COMMAND
|
||||
MENU_INDEX:PrepareCoalition( Coalition )
|
||||
local Path = MENU_INDEX:ParentPath( ParentMenu, MenuText )
|
||||
local CoalitionMenu = MENU_INDEX:HasCoalitionMenu( Coalition, Path )
|
||||
|
||||
if CoalitionMenu then
|
||||
CoalitionMenu:SetCommandMenuFunction( CommandMenuFunction )
|
||||
CoalitionMenu:SetCommandMenuArguments( arg )
|
||||
@@ -739,33 +675,29 @@ do -- MENU_COALITION_COMMAND
|
||||
self:SetParentMenu( self.MenuText, self )
|
||||
return self
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- Refreshes a radio item for a coalition
|
||||
-- @param #MENU_COALITION_COMMAND self
|
||||
-- @return #MENU_COALITION_COMMAND
|
||||
function MENU_COALITION_COMMAND:Refresh()
|
||||
|
||||
do
|
||||
missionCommands.removeItemForCoalition( self.Coalition, self.MenuPath )
|
||||
missionCommands.addCommandForCoalition( self.Coalition, self.MenuText, self.MenuParentPath, self.MenuCallHandler )
|
||||
end
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Removes a radio command item for a coalition
|
||||
-- @param #MENU_COALITION_COMMAND self
|
||||
-- @return #nil
|
||||
function MENU_COALITION_COMMAND:Remove( MenuTime, MenuTag )
|
||||
function MENU_COALITION_COMMAND:Remove( MenuStamp, MenuTag )
|
||||
|
||||
MENU_INDEX:PrepareCoalition( self.Coalition )
|
||||
local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText )
|
||||
local CoalitionMenu = MENU_INDEX:HasCoalitionMenu( self.Coalition, Path )
|
||||
|
||||
if CoalitionMenu == self then
|
||||
if not MenuTime or self.MenuTime ~= MenuTime then
|
||||
if not MenuStamp or self.MenuStamp ~= MenuStamp then
|
||||
if ( not MenuTag ) or ( MenuTag and self.MenuTag and MenuTag == self.MenuTag ) then
|
||||
self:F( { Coalition = self.Coalition, Text = self.MenuText, Path = self.MenuPath } )
|
||||
if self.MenuPath ~= nil then
|
||||
@@ -782,20 +714,16 @@ do -- MENU_COALITION_COMMAND
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- MENU_GROUP
|
||||
|
||||
do
|
||||
-- This local variable is used to cache the menus registered under groups.
|
||||
-- Menus don't dissapear when groups for players are destroyed and restarted.
|
||||
-- Menus don't disappear when groups for players are destroyed and restarted.
|
||||
-- So every menu for a client created must be tracked so that program logic accidentally does not create.
|
||||
-- the same menus twice during initialization logic.
|
||||
-- These menu classes are handling this logic with this variable.
|
||||
local _MENUGROUPS = {}
|
||||
|
||||
--- @type MENU_GROUP
|
||||
-- @extends Core.Menu#MENU_BASE
|
||||
|
||||
@@ -871,16 +799,13 @@ do
|
||||
MENU_INDEX:PrepareGroup( Group )
|
||||
local Path = MENU_INDEX:ParentPath( ParentMenu, MenuText )
|
||||
local GroupMenu = MENU_INDEX:HasGroupMenu( Group, Path )
|
||||
|
||||
if GroupMenu then
|
||||
return GroupMenu
|
||||
else
|
||||
self = BASE:Inherit( self, MENU_BASE:New( MenuText, ParentMenu ) )
|
||||
MENU_INDEX:SetGroupMenu( Group, Path, self )
|
||||
|
||||
self.Group = Group
|
||||
self.GroupID = Group:GetID()
|
||||
|
||||
self.MenuPath = missionCommands.addSubMenuForGroup( self.GroupID, MenuText, self.MenuParentPath )
|
||||
|
||||
self:SetParentMenu( self.MenuText, self )
|
||||
@@ -888,12 +813,10 @@ do
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Refreshes a new radio item for a group and submenus
|
||||
-- @param #MENU_GROUP self
|
||||
-- @return #MENU_GROUP
|
||||
function MENU_GROUP:Refresh()
|
||||
|
||||
do
|
||||
missionCommands.removeItemForGroup( self.GroupID, self.MenuPath )
|
||||
missionCommands.addSubMenuForGroup( self.GroupID, self.MenuText, self.MenuParentPath )
|
||||
@@ -902,39 +825,36 @@ do
|
||||
Menu:Refresh()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Removes the sub menus recursively of this MENU_GROUP.
|
||||
-- @param #MENU_GROUP self
|
||||
-- @param MenuTime
|
||||
-- @param MenuStamp
|
||||
-- @param MenuTag A Tag or Key to filter the menus to be refreshed with the Tag set.
|
||||
-- @return #MENU_GROUP self
|
||||
function MENU_GROUP:RemoveSubMenus( MenuTime, MenuTag )
|
||||
|
||||
function MENU_GROUP:RemoveSubMenus( MenuStamp, MenuTag )
|
||||
for MenuText, Menu in pairs( self.Menus or {} ) do
|
||||
Menu:Remove( MenuTime, MenuTag )
|
||||
Menu:Remove( MenuStamp, MenuTag )
|
||||
end
|
||||
|
||||
self.Menus = nil
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- Removes the main menu and sub menus recursively of this MENU_GROUP.
|
||||
-- @param #MENU_GROUP self
|
||||
-- @param MenuTime
|
||||
-- @param MenuStamp
|
||||
-- @param MenuTag A Tag or Key to filter the menus to be refreshed with the Tag set.
|
||||
-- @return #nil
|
||||
function MENU_GROUP:Remove( MenuTime, MenuTag )
|
||||
|
||||
function MENU_GROUP:Remove( MenuStamp, MenuTag )
|
||||
MENU_INDEX:PrepareGroup( self.Group )
|
||||
local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText )
|
||||
local GroupMenu = MENU_INDEX:HasGroupMenu( self.Group, Path )
|
||||
|
||||
if GroupMenu == self then
|
||||
self:RemoveSubMenus( MenuTime, MenuTag )
|
||||
if not MenuTime or self.MenuTime ~= MenuTime then
|
||||
self:RemoveSubMenus( MenuStamp, MenuTag )
|
||||
if not MenuStamp or self.MenuStamp ~= MenuStamp then
|
||||
if ( not MenuTag ) or ( MenuTag and self.MenuTag and MenuTag == self.MenuTag ) then
|
||||
if self.MenuPath ~= nil then
|
||||
self:F( { Group = self.GroupID, Text = self.MenuText, Path = self.MenuPath } )
|
||||
@@ -975,18 +895,15 @@ do
|
||||
-- @param CommandMenuArgument An argument for the function.
|
||||
-- @return #MENU_GROUP_COMMAND
|
||||
function MENU_GROUP_COMMAND:New( Group, MenuText, ParentMenu, CommandMenuFunction, ... )
|
||||
|
||||
MENU_INDEX:PrepareGroup( Group )
|
||||
local Path = MENU_INDEX:ParentPath( ParentMenu, MenuText )
|
||||
local GroupMenu = MENU_INDEX:HasGroupMenu( Group, Path )
|
||||
|
||||
if GroupMenu then
|
||||
GroupMenu:SetCommandMenuFunction( CommandMenuFunction )
|
||||
GroupMenu:SetCommandMenuArguments( arg )
|
||||
return GroupMenu
|
||||
else
|
||||
self = BASE:Inherit( self, MENU_COMMAND_BASE:New( MenuText, ParentMenu, CommandMenuFunction, arg ) )
|
||||
|
||||
MENU_INDEX:SetGroupMenu( Group, Path, self )
|
||||
|
||||
self.Group = Group
|
||||
@@ -997,34 +914,30 @@ do
|
||||
self:SetParentMenu( self.MenuText, self )
|
||||
return self
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Refreshes a radio item for a group
|
||||
-- @param #MENU_GROUP_COMMAND self
|
||||
-- @return #MENU_GROUP_COMMAND
|
||||
function MENU_GROUP_COMMAND:Refresh()
|
||||
|
||||
do
|
||||
missionCommands.removeItemForGroup( self.GroupID, self.MenuPath )
|
||||
missionCommands.addCommandForGroup( self.GroupID, self.MenuText, self.MenuParentPath, self.MenuCallHandler )
|
||||
end
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Removes a menu structure for a group.
|
||||
-- @param #MENU_GROUP_COMMAND self
|
||||
-- @param MenuTime
|
||||
-- @param MenuStamp
|
||||
-- @param MenuTag A Tag or Key to filter the menus to be refreshed with the Tag set.
|
||||
-- @return #nil
|
||||
function MENU_GROUP_COMMAND:Remove( MenuTime, MenuTag )
|
||||
|
||||
function MENU_GROUP_COMMAND:Remove( MenuStamp, MenuTag )
|
||||
MENU_INDEX:PrepareGroup( self.Group )
|
||||
local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText )
|
||||
local GroupMenu = MENU_INDEX:HasGroupMenu( self.Group, Path )
|
||||
|
||||
if GroupMenu == self then
|
||||
if not MenuTime or self.MenuTime ~= MenuTime then
|
||||
if not MenuStamp or self.MenuStamp ~= MenuStamp then
|
||||
if ( not MenuTag ) or ( MenuTag and self.MenuTag and MenuTag == self.MenuTag ) then
|
||||
if self.MenuPath ~= nil then
|
||||
self:F( { Group = self.GroupID, Text = self.MenuText, Path = self.MenuPath } )
|
||||
@@ -1041,13 +954,9 @@ do
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- MENU_GROUP_DELAYED
|
||||
|
||||
do
|
||||
|
||||
--- @type MENU_GROUP_DELAYED
|
||||
-- @extends Core.Menu#MENU_BASE
|
||||
|
||||
@@ -1075,16 +984,13 @@ do
|
||||
MENU_INDEX:PrepareGroup( Group )
|
||||
local Path = MENU_INDEX:ParentPath( ParentMenu, MenuText )
|
||||
local GroupMenu = MENU_INDEX:HasGroupMenu( Group, Path )
|
||||
|
||||
if GroupMenu then
|
||||
return GroupMenu
|
||||
else
|
||||
self = BASE:Inherit( self, MENU_BASE:New( MenuText, ParentMenu ) )
|
||||
MENU_INDEX:SetGroupMenu( Group, Path, self )
|
||||
|
||||
self.Group = Group
|
||||
self.GroupID = Group:GetID()
|
||||
|
||||
if self.MenuParentPath then
|
||||
self.MenuPath = UTILS.DeepCopy( self.MenuParentPath )
|
||||
else
|
||||
@@ -1098,12 +1004,10 @@ do
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- Refreshes a new radio item for a group and submenus
|
||||
-- @param #MENU_GROUP_DELAYED self
|
||||
-- @return #MENU_GROUP_DELAYED
|
||||
function MENU_GROUP_DELAYED:Set()
|
||||
|
||||
do
|
||||
if not self.MenuSet then
|
||||
missionCommands.addSubMenuForGroup( self.GroupID, self.MenuText, self.MenuParentPath )
|
||||
@@ -1114,15 +1018,12 @@ do
|
||||
Menu:Set()
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- Refreshes a new radio item for a group and submenus
|
||||
-- @param #MENU_GROUP_DELAYED self
|
||||
-- @return #MENU_GROUP_DELAYED
|
||||
function MENU_GROUP_DELAYED:Refresh()
|
||||
|
||||
do
|
||||
missionCommands.removeItemForGroup( self.GroupID, self.MenuPath )
|
||||
missionCommands.addSubMenuForGroup( self.GroupID, self.MenuText, self.MenuParentPath )
|
||||
@@ -1131,39 +1032,36 @@ do
|
||||
Menu:Refresh()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Removes the sub menus recursively of this MENU_GROUP_DELAYED.
|
||||
-- @param #MENU_GROUP_DELAYED self
|
||||
-- @param MenuTime
|
||||
-- @param MenuStamp
|
||||
-- @param MenuTag A Tag or Key to filter the menus to be refreshed with the Tag set.
|
||||
-- @return #MENU_GROUP_DELAYED self
|
||||
function MENU_GROUP_DELAYED:RemoveSubMenus( MenuTime, MenuTag )
|
||||
|
||||
function MENU_GROUP_DELAYED:RemoveSubMenus( MenuStamp, MenuTag )
|
||||
for MenuText, Menu in pairs( self.Menus or {} ) do
|
||||
Menu:Remove( MenuTime, MenuTag )
|
||||
Menu:Remove( MenuStamp, MenuTag )
|
||||
end
|
||||
|
||||
self.Menus = nil
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- Removes the main menu and sub menus recursively of this MENU_GROUP.
|
||||
-- @param #MENU_GROUP_DELAYED self
|
||||
-- @param MenuTime
|
||||
-- @param MenuStamp
|
||||
-- @param MenuTag A Tag or Key to filter the menus to be refreshed with the Tag set.
|
||||
-- @return #nil
|
||||
function MENU_GROUP_DELAYED:Remove( MenuTime, MenuTag )
|
||||
|
||||
function MENU_GROUP_DELAYED:Remove( MenuStamp, MenuTag )
|
||||
MENU_INDEX:PrepareGroup( self.Group )
|
||||
local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText )
|
||||
local GroupMenu = MENU_INDEX:HasGroupMenu( self.Group, Path )
|
||||
|
||||
if GroupMenu == self then
|
||||
self:RemoveSubMenus( MenuTime, MenuTag )
|
||||
if not MenuTime or self.MenuTime ~= MenuTime then
|
||||
self:RemoveSubMenus( MenuStamp, MenuTag )
|
||||
if not MenuStamp or self.MenuStamp ~= MenuStamp then
|
||||
if ( not MenuTag ) or ( MenuTag and self.MenuTag and MenuTag == self.MenuTag ) then
|
||||
if self.MenuPath ~= nil then
|
||||
self:F( { Group = self.GroupID, Text = self.MenuText, Path = self.MenuPath } )
|
||||
@@ -1205,18 +1103,15 @@ do
|
||||
-- @param CommandMenuArgument An argument for the function.
|
||||
-- @return #MENU_GROUP_COMMAND_DELAYED
|
||||
function MENU_GROUP_COMMAND_DELAYED:New( Group, MenuText, ParentMenu, CommandMenuFunction, ... )
|
||||
|
||||
MENU_INDEX:PrepareGroup( Group )
|
||||
local Path = MENU_INDEX:ParentPath( ParentMenu, MenuText )
|
||||
local GroupMenu = MENU_INDEX:HasGroupMenu( Group, Path )
|
||||
|
||||
if GroupMenu then
|
||||
GroupMenu:SetCommandMenuFunction( CommandMenuFunction )
|
||||
GroupMenu:SetCommandMenuArguments( arg )
|
||||
return GroupMenu
|
||||
else
|
||||
self = BASE:Inherit( self, MENU_COMMAND_BASE:New( MenuText, ParentMenu, CommandMenuFunction, arg ) )
|
||||
|
||||
MENU_INDEX:SetGroupMenu( Group, Path, self )
|
||||
|
||||
self.Group = Group
|
||||
@@ -1232,48 +1127,42 @@ do
|
||||
self:SetParentMenu( self.MenuText, self )
|
||||
return self
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Refreshes a radio item for a group
|
||||
-- @param #MENU_GROUP_COMMAND_DELAYED self
|
||||
-- @return #MENU_GROUP_COMMAND_DELAYED
|
||||
function MENU_GROUP_COMMAND_DELAYED:Set()
|
||||
|
||||
do
|
||||
if not self.MenuSet then
|
||||
self.MenuPath = missionCommands.addCommandForGroup( self.GroupID, self.MenuText, self.MenuParentPath, self.MenuCallHandler )
|
||||
self.MenuSet = true
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Refreshes a radio item for a group
|
||||
-- @param #MENU_GROUP_COMMAND_DELAYED self
|
||||
-- @return #MENU_GROUP_COMMAND_DELAYED
|
||||
function MENU_GROUP_COMMAND_DELAYED:Refresh()
|
||||
|
||||
do
|
||||
missionCommands.removeItemForGroup( self.GroupID, self.MenuPath )
|
||||
missionCommands.addCommandForGroup( self.GroupID, self.MenuText, self.MenuParentPath, self.MenuCallHandler )
|
||||
end
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Removes a menu structure for a group.
|
||||
-- @param #MENU_GROUP_COMMAND_DELAYED self
|
||||
-- @param MenuTime
|
||||
-- @param MenuStamp
|
||||
-- @param MenuTag A Tag or Key to filter the menus to be refreshed with the Tag set.
|
||||
-- @return #nil
|
||||
function MENU_GROUP_COMMAND_DELAYED:Remove( MenuTime, MenuTag )
|
||||
|
||||
function MENU_GROUP_COMMAND_DELAYED:Remove( MenuStamp, MenuTag )
|
||||
MENU_INDEX:PrepareGroup( self.Group )
|
||||
local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText )
|
||||
local GroupMenu = MENU_INDEX:HasGroupMenu( self.Group, Path )
|
||||
|
||||
if GroupMenu == self then
|
||||
if not MenuTime or self.MenuTime ~= MenuTime then
|
||||
if not MenuStamp or self.MenuStamp ~= MenuStamp then
|
||||
if ( not MenuTag ) or ( MenuTag and self.MenuTag and MenuTag == self.MenuTag ) then
|
||||
if self.MenuPath ~= nil then
|
||||
self:F( { Group = self.GroupID, Text = self.MenuText, Path = self.MenuPath } )
|
||||
@@ -1290,6 +1179,5 @@ do
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
--- **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 +25,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,10 +71,9 @@ 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.
|
||||
@@ -80,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.MessageSent = false
|
||||
self.MessageGroup = false
|
||||
self.MessageCoalition = false
|
||||
self.MessageDuration = MessageDuration or 5
|
||||
self.MessageTime = timer.getTime()
|
||||
self.MessageText = MessageText:gsub( "^\n", "", 1 ):gsub( "\n$", "", 1 )
|
||||
|
||||
return self
|
||||
self.MessageSent = false
|
||||
self.MessageGroup = false
|
||||
self.MessageCoalition = false
|
||||
|
||||
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
|
||||
@@ -134,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" )
|
||||
--
|
||||
-- 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 )
|
||||
-- -- 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 )
|
||||
--
|
||||
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
|
||||
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.
|
||||
@@ -218,74 +223,105 @@ 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
|
||||
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 )
|
||||
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 ( Group and _DATABASE:GetPlayerSettings( Group:GetPlayerName() ) ) or _SETTINGS -- Core.Settings#SETTINGS
|
||||
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.outTextForGroup( Group:GetID(), 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 the Blue coalition.
|
||||
-- @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()
|
||||
function MESSAGE:ToBlue()
|
||||
self:F()
|
||||
|
||||
self:ToCoalition( coalition.side.BLUE )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- 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 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:ToCoalition( coalition.side.RED )
|
||||
|
||||
return self
|
||||
self:ToCoalition( coalition.side.BLUE )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sends a MESSAGE to a 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()
|
||||
|
||||
self:ToCoalition( coalition.side.RED )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- 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
|
||||
@@ -293,20 +329,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 )
|
||||
@@ -314,7 +350,7 @@ function MESSAGE:ToCoalitionIf( CoalitionSide, Condition )
|
||||
if Condition and Condition == true then
|
||||
self:ToCoalition( CoalitionSide )
|
||||
end
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -323,14 +359,16 @@ 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
|
||||
@@ -340,14 +378,13 @@ 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
|
||||
@@ -357,5 +394,5 @@ function MESSAGE:ToAllIf( Condition )
|
||||
self:ToAll()
|
||||
end
|
||||
|
||||
return self
|
||||
return self
|
||||
end
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,594 +0,0 @@
|
||||
--- **Core** - Is responsible for everything that is related to radio transmission and you can hear in DCS, be it TACAN beacons, Radio transmissions.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ## Features:
|
||||
--
|
||||
-- * Provide radio functionality to broadcast radio transmissions.
|
||||
-- * Provide beacon functionality to assist pilots.
|
||||
--
|
||||
-- The Radio contains 2 classes : RADIO and BEACON
|
||||
--
|
||||
-- What are radio communications in DCS ?
|
||||
--
|
||||
-- * Radio transmissions consist of **sound files** that are broadcasted on a specific **frequency** (e.g. 115MHz) and **modulation** (e.g. AM),
|
||||
-- * They can be **subtitled** for a specific **duration**, the **power** in Watts of the transmiter's antenna can be set, and the transmission can be **looped**.
|
||||
--
|
||||
-- How to supply DCS my own Sound Files ?
|
||||
--
|
||||
-- * Your sound files need to be encoded in **.ogg** or .wav,
|
||||
-- * Your sound files should be **as tiny as possible**. It is suggested you encode in .ogg with low bitrate and sampling settings,
|
||||
-- * They need to be added in .\l10n\DEFAULT\ in you .miz file (wich can be decompressed like a .zip file),
|
||||
-- * For simplicty 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 a FC3 airacraft 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 aircaft isn't compatible,
|
||||
-- you won't hear/be able to use the TACAN beacon informations.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: Hugues "Grey_Echo" Bousquet
|
||||
--
|
||||
-- @module Core.Radio
|
||||
-- @image Core_Radio.JPG
|
||||
|
||||
|
||||
--- Models the radio capabilty.
|
||||
--
|
||||
-- ## 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 transmiter 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 transmiter 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 transmiter, 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 100W antenna, a standard AA TACAN has a 120W antenna, and civilian ATC's antenna usually range between 300 and 500W,
|
||||
-- * Note that if the transmission has a subtitle, it will be readable, regardless of the quality of the transmission.
|
||||
--
|
||||
-- @type RADIO
|
||||
-- @field Positionable#POSITIONABLE Positionable The transmiter
|
||||
-- @field #string FileName Name of the sound file
|
||||
-- @field #number Frequency Frequency of the transmission in Hz
|
||||
-- @field #number Modulation Modulation of the transmission (either radio.modulation.AM or radio.modulation.FM)
|
||||
-- @field #string Subtitle Subtitle of the transmission
|
||||
-- @field #number SubtitleDuration Duration of the Subtitle in seconds
|
||||
-- @field #number Power Power of the antenna is Watts
|
||||
-- @field #boolean Loop (default true)
|
||||
-- @extends Core.Base#BASE
|
||||
RADIO = {
|
||||
ClassName = "RADIO",
|
||||
FileName = "",
|
||||
Frequency = 0,
|
||||
Modulation = radio.modulation.AM,
|
||||
Subtitle = "",
|
||||
SubtitleDuration = 0,
|
||||
Power = 100,
|
||||
Loop = true,
|
||||
}
|
||||
|
||||
--- Create a new RADIO Object. This doesn't broadcast a transmission, though, use @{#RADIO.Broadcast} to actually broadcast
|
||||
-- If you want to create a RADIO, you probably should use @{Wrapper.Positionable#POSITIONABLE.GetRadio}() instead
|
||||
-- @param #RADIO self
|
||||
-- @param Wrapper.Positionable#POSITIONABLE Positionable The @{Positionable} that will receive radio capabilities.
|
||||
-- @return #RADIO Radio
|
||||
-- @return #nil If Positionable is invalid
|
||||
function RADIO:New(Positionable)
|
||||
local self = BASE:Inherit( self, BASE:New() ) -- Core.Radio#RADIO
|
||||
|
||||
self.Loop = true -- default Loop to true (not sure the above RADIO definition actually is working)
|
||||
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({"The passed positionable is invalid, no RADIO created", Positionable})
|
||||
return nil
|
||||
end
|
||||
|
||||
--- Check validity of the filename passed and sets RADIO.FileName
|
||||
-- @param #RADIO self
|
||||
-- @param #string FileName File name of the sound file (i.e. "Noise.ogg")
|
||||
-- @return #RADIO self
|
||||
function RADIO:SetFileName(FileName)
|
||||
self:F2(FileName)
|
||||
|
||||
if type(FileName) == "string" then
|
||||
if FileName:find(".ogg") or FileName:find(".wav") then
|
||||
if not FileName:find("l10n/DEFAULT/") then
|
||||
FileName = "l10n/DEFAULT/" .. FileName
|
||||
end
|
||||
self.FileName = FileName
|
||||
return self
|
||||
end
|
||||
end
|
||||
|
||||
self:E({"File name invalid. Maybe something wrong with the extension ?", self.FileName})
|
||||
return self
|
||||
end
|
||||
|
||||
--- Check validity of the frequency passed and sets RADIO.Frequency
|
||||
-- @param #RADIO self
|
||||
-- @param #number Frequency in MHz (Ranges allowed for radio transmissions in DCS : 30-88 / 108-152 / 225-400MHz)
|
||||
-- @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 < 88) or (Frequency >= 108 and Frequency < 152) or (Frequency >= 225 and Frequency < 400) then
|
||||
self.Frequency = Frequency * 1000000 -- Conversion in Hz
|
||||
-- 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
|
||||
self.Positionable:SetCommand({
|
||||
id = "SetFrequency",
|
||||
params = {
|
||||
frequency = self.Frequency,
|
||||
modulation = self.Modulation,
|
||||
}
|
||||
})
|
||||
end
|
||||
return self
|
||||
end
|
||||
end
|
||||
self:E({"Frequency is outside of DCS Frequency ranges (30-80, 108-152, 225-400). Frequency unchanged.", self.Frequency})
|
||||
return self
|
||||
end
|
||||
|
||||
--- Check validity of the frequency passed and sets RADIO.Modulation
|
||||
-- @param #RADIO self
|
||||
-- @param #number Modulation either radio.modulation.AM or radio.modulation.FM
|
||||
-- @return #RADIO self
|
||||
function RADIO:SetModulation(Modulation)
|
||||
self:F2(Modulation)
|
||||
if type(Modulation) == "number" then
|
||||
if Modulation == radio.modulation.AM or Modulation == radio.modulation.FM then --TODO Maybe make this future proof if ED decides to add an other modulation ?
|
||||
self.Modulation = Modulation
|
||||
return self
|
||||
end
|
||||
end
|
||||
self:E({"Modulation is invalid. Use DCS's enum radio.modulation. Modulation unchanged.", self.Modulation})
|
||||
return self
|
||||
end
|
||||
|
||||
--- Check validity of the power passed and sets RADIO.Power
|
||||
-- @param #RADIO self
|
||||
-- @param #number Power in W
|
||||
-- @return #RADIO self
|
||||
function RADIO:SetPower(Power)
|
||||
self:F2(Power)
|
||||
if type(Power) == "number" then
|
||||
self.Power = math.floor(math.abs(Power)) --TODO Find what is the maximum power allowed by DCS and limit power to that
|
||||
return self
|
||||
end
|
||||
self:E({"Power is invalid. Power unchanged.", self.Power})
|
||||
return self
|
||||
end
|
||||
|
||||
--- Check validity of the loop passed and sets RADIO.Loop
|
||||
-- @param #RADIO self
|
||||
-- @param #boolean Loop
|
||||
-- @return #RADIO self
|
||||
-- @usage
|
||||
function RADIO:SetLoop(Loop)
|
||||
self:F2(Loop)
|
||||
if type(Loop) == "boolean" then
|
||||
self.Loop = Loop
|
||||
return self
|
||||
end
|
||||
self:E({"Loop is invalid. Loop unchanged.", self.Loop})
|
||||
return self
|
||||
end
|
||||
|
||||
--- Check validity of the subtitle and the subtitleDuration passed and sets RADIO.subtitle and RADIO.subtitleDuration
|
||||
-- Both parameters are mandatory, since it wouldn't make much sense to change the Subtitle and not its duration
|
||||
-- @param #RADIO self
|
||||
-- @param #string Subtitle
|
||||
-- @param #number SubtitleDuration in s
|
||||
-- @return #RADIO self
|
||||
-- @usage
|
||||
-- -- create the broadcaster and attaches it a RADIO
|
||||
-- local MyUnit = UNIT:FindByName("MyUnit")
|
||||
-- local MyUnitRadio = MyUnit:GetRadio()
|
||||
--
|
||||
-- -- add a subtitle for the next transmission, which will be up for 10s
|
||||
-- MyUnitRadio:SetSubtitle("My Subtitle, 10)
|
||||
function RADIO:SetSubtitle(Subtitle, SubtitleDuration)
|
||||
self:F2({Subtitle, SubtitleDuration})
|
||||
if type(Subtitle) == "string" then
|
||||
self.Subtitle = Subtitle
|
||||
else
|
||||
self.Subtitle = ""
|
||||
self:E({"Subtitle is invalid. Subtitle reset.", self.Subtitle})
|
||||
end
|
||||
if type(SubtitleDuration) == "number" then
|
||||
if math.floor(math.abs(SubtitleDuration)) == SubtitleDuration then
|
||||
self.SubtitleDuration = SubtitleDuration
|
||||
return self
|
||||
end
|
||||
end
|
||||
self.SubtitleDuration = 0
|
||||
self:E({"SubtitleDuration is invalid. SubtitleDuration reset.", self.SubtitleDuration})
|
||||
end
|
||||
|
||||
--- Create a new transmission, that is to say, populate the RADIO with relevant data
|
||||
-- In this function the data is especially relevant if the broadcaster is anything but a UNIT or a GROUP,
|
||||
-- but it will work with a UNIT or a GROUP anyway.
|
||||
-- Only the #RADIO and the Filename are mandatory
|
||||
-- @param #RADIO self
|
||||
-- @param #string FileName
|
||||
-- @param #number Frequency in MHz
|
||||
-- @param #number Modulation either radio.modulation.AM or radio.modulation.FM
|
||||
-- @param #number Power in W
|
||||
-- @return #RADIO self
|
||||
function RADIO:NewGenericTransmission(FileName, Frequency, Modulation, Power, Loop)
|
||||
self:F({FileName, Frequency, Modulation, Power})
|
||||
|
||||
self:SetFileName(FileName)
|
||||
if Frequency then self:SetFrequency(Frequency) end
|
||||
if Modulation then self:SetModulation(Modulation) end
|
||||
if Power then self:SetPower(Power) end
|
||||
if Loop then self:SetLoop(Loop) end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Create a new transmission, that is to say, populate the RADIO with relevant data
|
||||
-- In this function the data is especially relevant if the broadcaster is a UNIT or a GROUP,
|
||||
-- but it will work for any @{Wrapper.Positionable#POSITIONABLE}.
|
||||
-- Only the RADIO and the Filename are mandatory.
|
||||
-- @param #RADIO self
|
||||
-- @param #string FileName
|
||||
-- @param #string Subtitle
|
||||
-- @param #number SubtitleDuration in s
|
||||
-- @param #number Frequency in MHz
|
||||
-- @param #number Modulation either radio.modulation.AM or radio.modulation.FM
|
||||
-- @param #boolean Loop
|
||||
-- @return #RADIO self
|
||||
function RADIO:NewUnitTransmission(FileName, Subtitle, SubtitleDuration, Frequency, Modulation, Loop)
|
||||
self:F({FileName, Subtitle, SubtitleDuration, Frequency, Modulation, Loop})
|
||||
|
||||
self:SetFileName(FileName)
|
||||
local Duration = 5
|
||||
if SubtitleDuration then Duration = SubtitleDuration end
|
||||
-- SubtitleDuration argument was missing, adding it
|
||||
if Subtitle then self:SetSubtitle(Subtitle, Duration) end
|
||||
-- self:SetSubtitleDuration is non existent, removing faulty line
|
||||
-- if SubtitleDuration then self:SetSubtitleDuration(SubtitleDuration) end
|
||||
if Frequency then self:SetFrequency(Frequency) end
|
||||
if Modulation then self:SetModulation(Modulation) end
|
||||
if Loop then self:SetLoop(Loop) end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Actually Broadcast the transmission
|
||||
-- * The Radio has to be populated with the new transmission before broadcasting.
|
||||
-- * Please use RADIO setters or either @{#RADIO.NewGenericTransmission} or @{#RADIO.NewUnitTransmission}
|
||||
-- * This class is in fact pretty smart, it determines the right DCS function to use depending on the type of POSITIONABLE
|
||||
-- * If the POSITIONABLE is not a UNIT or a GROUP, we use the generic (but limited) trigger.action.radioTransmission()
|
||||
-- * If the POSITIONABLE is a UNIT or a GROUP, we use the "TransmitMessage" Command
|
||||
-- * If your POSITIONABLE is a UNIT or a GROUP, the Power is ignored.
|
||||
-- * If your POSITIONABLE is not a UNIT or a GROUP, the Subtitle, SubtitleDuration are ignored
|
||||
-- @param #RADIO self
|
||||
-- @return #RADIO self
|
||||
function RADIO:Broadcast()
|
||||
self:F()
|
||||
|
||||
-- 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" then
|
||||
self:T2("Broadcasting from a UNIT or a GROUP")
|
||||
self.Positionable:SetCommand({
|
||||
id = "TransmitMessage",
|
||||
params = {
|
||||
file = self.FileName,
|
||||
duration = self.SubtitleDuration,
|
||||
subtitle = self.Subtitle,
|
||||
loop = self.Loop,
|
||||
}
|
||||
})
|
||||
else
|
||||
-- If the POSITIONABLE is anything else, we revert to the general singleton function
|
||||
-- I need to give it a unique name, so that the transmission can be stopped later. I use the class ID
|
||||
self:T2("Broadcasting from a POSITIONABLE")
|
||||
trigger.action.radioTransmission(self.FileName, self.Positionable:GetPositionVec3(), self.Modulation, self.Loop, self.Frequency, self.Power, tostring(self.ID))
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Stops a transmission
|
||||
-- This function is especially usefull to stop the broadcast of looped transmissions
|
||||
-- @param #RADIO self
|
||||
-- @return #RADIO self
|
||||
function RADIO:StopBroadcast()
|
||||
self:F()
|
||||
-- If the POSITIONABLE is a UNIT or a GROUP, stop the transmission with the DCS "StopTransmission" command
|
||||
if self.Positionable.ClassName == "UNIT" or self.Positionable.ClassName == "GROUP" then
|
||||
self.Positionable:SetCommand({
|
||||
id = "StopTransmission",
|
||||
params = {}
|
||||
})
|
||||
else
|
||||
-- Else, we use the appropriate singleton funciton
|
||||
trigger.action.stopRadioTransmission(tostring(self.ID))
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- After attaching a @{#BEACON} to your @{Wrapper.Positionable#POSITIONABLE}, you need to select the right function to activate the kind of beacon you want.
|
||||
-- There are two types of BEACONs available : the AA TACAN Beacon and the general purpose Radio Beacon.
|
||||
-- Note that in both case, you can set an optional parameter : the `BeaconDuration`. This can be very usefull to simulate the battery time if your BEACON is
|
||||
-- attach to a cargo crate, for exemple.
|
||||
--
|
||||
-- ## AA TACAN Beacon usage
|
||||
--
|
||||
-- This beacon only works with airborne @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP}. Use @{#BEACON:AATACAN}() to set the beacon parameters and start the beacon.
|
||||
-- Use @#BEACON:StopAATACAN}() to stop it.
|
||||
--
|
||||
-- ## General Purpose Radio Beacon usage
|
||||
--
|
||||
-- This beacon will work with any @{Wrapper.Positionable#POSITIONABLE}, but **it won't follow the @{Wrapper.Positionable#POSITIONABLE}** ! This means that you should only use it with
|
||||
-- @{Wrapper.Positionable#POSITIONABLE} that don't move, or move very slowly. Use @{#BEACON:RadioBeacon}() to set the beacon parameters and start the beacon.
|
||||
-- Use @{#BEACON:StopRadioBeacon}() to stop it.
|
||||
--
|
||||
-- @type BEACON
|
||||
-- @extends Core.Base#BASE
|
||||
BEACON = {
|
||||
ClassName = "BEACON",
|
||||
}
|
||||
|
||||
--- Create a new BEACON Object. This doesn't activate the beacon, though, use @{#BEACON.AATACAN} or @{#BEACON.Generic}
|
||||
-- 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
|
||||
-- @return #nil If Positionable is invalid
|
||||
function BEACON:New(Positionable)
|
||||
local self = BASE:Inherit(self, BASE:New())
|
||||
|
||||
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({"The passed positionable is invalid, no BEACON created", Positionable})
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
--- Converts a TACAN Channel/Mode couple into a frequency in Hz
|
||||
-- @param #BEACON self
|
||||
-- @param #number TACANChannel
|
||||
-- @param #string TACANMode
|
||||
-- @return #number Frequecy
|
||||
-- @return #nil if parameters are invalid
|
||||
function BEACON:_TACANToFrequency(TACANChannel, TACANMode)
|
||||
self:F3({TACANChannel, TACANMode})
|
||||
|
||||
if type(TACANChannel) ~= "number" then
|
||||
if TACANMode ~= "X" and TACANMode ~= "Y" then
|
||||
return nil -- error in arguments
|
||||
end
|
||||
end
|
||||
|
||||
-- This code is largely based on ED's code, in DCS World\Scripts\World\Radio\BeaconTypes.lua, line 137.
|
||||
-- I have no idea what it does but it seems to work
|
||||
local A = 1151 -- 'X', channel >= 64
|
||||
local B = 64 -- channel >= 64
|
||||
|
||||
if TACANChannel < 64 then
|
||||
B = 1
|
||||
end
|
||||
|
||||
if TACANMode == 'Y' then
|
||||
A = 1025
|
||||
if TACANChannel < 64 then
|
||||
A = 1088
|
||||
end
|
||||
else -- 'X'
|
||||
if TACANChannel < 64 then
|
||||
A = 962
|
||||
end
|
||||
end
|
||||
|
||||
return (A + TACANChannel - B) * 1000000
|
||||
end
|
||||
|
||||
|
||||
--- Activates a TACAN BEACON on an Aircraft.
|
||||
-- @param #BEACON self
|
||||
-- @param #number TACANChannel (the "10" part in "10Y"). Note that AA TACAN are only available on Y Channels
|
||||
-- @param #string Message The Message that is going to be coded in Morse and broadcasted by the beacon
|
||||
-- @param #boolean Bearing Can the BEACON be homed on ?
|
||||
-- @param #number BeaconDuration How long will the beacon last in seconds. Omit for forever.
|
||||
-- @return #BEACON self
|
||||
-- @usage
|
||||
-- -- Let's create a TACAN Beacon for a tanker
|
||||
-- local myUnit = UNIT:FindByName("MyUnit")
|
||||
-- local myBeacon = myUnit:GetBeacon() -- Creates the beacon
|
||||
--
|
||||
-- myBeacon:AATACAN(20, "TEXACO", true) -- Activate the beacon
|
||||
function BEACON:AATACAN(TACANChannel, Message, Bearing, BeaconDuration)
|
||||
self:F({TACANChannel, Message, Bearing, BeaconDuration})
|
||||
|
||||
local IsValid = true
|
||||
|
||||
if not self.Positionable:IsAir() then
|
||||
self:E({"The POSITIONABLE you want to attach the AA Tacan Beacon is not an aircraft ! The BEACON is not emitting", self.Positionable})
|
||||
IsValid = false
|
||||
end
|
||||
|
||||
local Frequency = self:_TACANToFrequency(TACANChannel, "Y")
|
||||
if not Frequency then
|
||||
self:E({"The passed TACAN channel is invalid, the BEACON is not emitting"})
|
||||
IsValid = false
|
||||
end
|
||||
|
||||
-- I'm using the beacon type 4 (BEACON_TYPE_TACAN). For System, I'm using 5 (TACAN_TANKER_MODE_Y) if the bearing shows its bearing
|
||||
-- or 14 (TACAN_AA_MODE_Y) if it does not
|
||||
local System
|
||||
if Bearing then
|
||||
System = 5
|
||||
else
|
||||
System = 14
|
||||
end
|
||||
|
||||
if IsValid then -- Starts the BEACON
|
||||
self:T2({"AA TACAN BEACON started !"})
|
||||
self.Positionable:SetCommand({
|
||||
id = "ActivateBeacon",
|
||||
params = {
|
||||
type = 4,
|
||||
system = System,
|
||||
callsign = Message,
|
||||
frequency = Frequency,
|
||||
}
|
||||
})
|
||||
|
||||
if BeaconDuration then -- Schedule the stop of the BEACON if asked by the MD
|
||||
SCHEDULER:New( nil,
|
||||
function()
|
||||
self:StopAATACAN()
|
||||
end, {}, BeaconDuration)
|
||||
end
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Stops the AA TACAN BEACON
|
||||
-- @param #BEACON self
|
||||
-- @return #BEACON self
|
||||
function BEACON:StopAATACAN()
|
||||
self:F()
|
||||
if not self.Positionable then
|
||||
self:E({"Start the beacon first before stoping it !"})
|
||||
else
|
||||
self.Positionable:SetCommand({
|
||||
id = 'DeactivateBeacon',
|
||||
params = {
|
||||
}
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- Activates a general pupose Radio Beacon
|
||||
-- This uses the very generic singleton function "trigger.action.radioTransmission()" provided by DCS to broadcast a sound file on a specific frequency.
|
||||
-- Although any frequency could be used, only 2 DCS Modules can home on radio beacons at the time of writing : the Huey and the Mi-8.
|
||||
-- They can home in on these specific frequencies :
|
||||
-- * **Mi8**
|
||||
-- * R-828 -> 20-60MHz
|
||||
-- * ARKUD -> 100-150MHz (canal 1 : 114166, canal 2 : 114333, canal 3 : 114583, canal 4 : 121500, canal 5 : 123100, canal 6 : 124100) AM
|
||||
-- * ARK9 -> 150-1300KHz
|
||||
-- * **Huey**
|
||||
-- * AN/ARC-131 -> 30-76 Mhz FM
|
||||
-- @param #BEACON self
|
||||
-- @param #string FileName The name of the audio file
|
||||
-- @param #number Frequency in MHz
|
||||
-- @param #number Modulation either radio.modulation.AM or radio.modulation.FM
|
||||
-- @param #number Power in W
|
||||
-- @param #number BeaconDuration How long will the beacon last in seconds. Omit for forever.
|
||||
-- @return #BEACON self
|
||||
-- @usage
|
||||
-- -- Let's create a beacon for a unit in distress.
|
||||
-- -- Frequency will be 40MHz FM (home-able by a Huey's AN/ARC-131)
|
||||
-- -- The beacon they use is battery-powered, and only lasts for 5 min
|
||||
-- local UnitInDistress = UNIT:FindByName("Unit1")
|
||||
-- local UnitBeacon = UnitInDistress:GetBeacon()
|
||||
--
|
||||
-- -- Set the beacon and start it
|
||||
-- UnitBeacon:RadioBeacon("MySoundFileSOS.ogg", 40, radio.modulation.FM, 20, 5*60)
|
||||
function BEACON:RadioBeacon(FileName, Frequency, Modulation, Power, BeaconDuration)
|
||||
self:F({FileName, Frequency, Modulation, Power, BeaconDuration})
|
||||
local IsValid = false
|
||||
|
||||
-- Check the filename
|
||||
if type(FileName) == "string" then
|
||||
if FileName:find(".ogg") or FileName:find(".wav") then
|
||||
if not FileName:find("l10n/DEFAULT/") then
|
||||
FileName = "l10n/DEFAULT/" .. FileName
|
||||
end
|
||||
IsValid = true
|
||||
end
|
||||
end
|
||||
if not IsValid then
|
||||
self:E({"File name invalid. Maybe something wrong with the extension ? ", FileName})
|
||||
end
|
||||
|
||||
-- Check the Frequency
|
||||
if type(Frequency) ~= "number" and IsValid then
|
||||
self:E({"Frequency invalid. ", Frequency})
|
||||
IsValid = false
|
||||
end
|
||||
Frequency = Frequency * 1000000 -- Conversion to Hz
|
||||
|
||||
-- Check the modulation
|
||||
if Modulation ~= radio.modulation.AM and Modulation ~= radio.modulation.FM and IsValid then --TODO Maybe make this future proof if ED decides to add an other modulation ?
|
||||
self:E({"Modulation is invalid. Use DCS's enum radio.modulation.", Modulation})
|
||||
IsValid = false
|
||||
end
|
||||
|
||||
-- Check the Power
|
||||
if type(Power) ~= "number" and IsValid then
|
||||
self:E({"Power is invalid. ", Power})
|
||||
IsValid = false
|
||||
end
|
||||
Power = math.floor(math.abs(Power)) --TODO Find what is the maximum power allowed by DCS and limit power to that
|
||||
|
||||
if IsValid then
|
||||
self:T2({"Activating Beacon on ", Frequency, Modulation})
|
||||
-- Note that this is looped. I have to give this transmission a unique name, I use the class ID
|
||||
trigger.action.radioTransmission(FileName, self.Positionable:GetPositionVec3(), Modulation, true, Frequency, Power, tostring(self.ID))
|
||||
|
||||
if BeaconDuration then -- Schedule the stop of the BEACON if asked by the MD
|
||||
SCHEDULER:New( nil,
|
||||
function()
|
||||
self:StopRadioBeacon()
|
||||
end, {}, BeaconDuration)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Stops the AA TACAN BEACON
|
||||
-- @param #BEACON self
|
||||
-- @return #BEACON self
|
||||
function BEACON:StopRadioBeacon()
|
||||
self:F()
|
||||
-- The unique name of the transmission is the class ID
|
||||
trigger.action.stopRadioTransmission(tostring(self.ID))
|
||||
end
|
||||
@@ -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,7 +15,6 @@
|
||||
-- @module Core.Report
|
||||
-- @image Core_Report.JPG
|
||||
|
||||
|
||||
--- @type REPORT
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
@@ -36,7 +35,7 @@ function REPORT:New( Title )
|
||||
|
||||
self.Report = {}
|
||||
|
||||
self:SetTitle( Title or "" )
|
||||
self:SetTitle( Title or "" )
|
||||
self:SetIndent( 3 )
|
||||
|
||||
return self
|
||||
@@ -45,47 +44,46 @@ 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
|
||||
|
||||
--- Add a new line to a REPORT.
|
||||
--- Add a new line to a REPORT, but indented. A separator character can be specified to separate the reported lines visually.
|
||||
-- @param #REPORT self
|
||||
-- @param #string Text
|
||||
-- @param #string Text The report text.
|
||||
-- @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 ) --R2.1
|
||||
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 ) )
|
||||
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 ) )
|
||||
return self
|
||||
end
|
||||
|
||||
--- Produces the text of the report, taking into account an optional delimeter, which is \n by default.
|
||||
--- Produces the text of the report, taking into account an optional delimiter, 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
|
||||
|
||||
@@ -94,7 +92,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,45 +1,74 @@
|
||||
--- **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 thorought explanation.
|
||||
--
|
||||
-- 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 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 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,
|
||||
--
|
||||
-- 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,
|
||||
-- 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
|
||||
|
||||
--- SCHEDULEDISPATCHER class.
|
||||
-- @type SCHEDULEDISPATCHER
|
||||
-- @field #string ClassName Name of the class.
|
||||
-- @field #number CallID Call ID counter.
|
||||
-- @field #table PersistentSchedulers Persistent 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
|
||||
|
||||
--- The SCHEDULEDISPATCHER structure
|
||||
-- @type SCHEDULEDISPATCHER
|
||||
SCHEDULEDISPATCHER = {
|
||||
ClassName = "SCHEDULEDISPATCHER",
|
||||
CallID = 0,
|
||||
PersistentSchedulers = {},
|
||||
ObjectSchedulers = {},
|
||||
Schedule = nil,
|
||||
}
|
||||
|
||||
--- Player data table holding all important parameters of each player.
|
||||
-- @type SCHEDULEDISPATCHER.ScheduleData
|
||||
-- @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 Randomize Randomization factor [0,1].
|
||||
-- @field #number Stop Stop time in seconds.
|
||||
-- @field #number StartTime Time in seconds when the scheduler is created.
|
||||
-- @field #number ScheduleID Schedule ID.
|
||||
-- @field #function CallHandler Function to be passed to the DCS timer.scheduleFunction().
|
||||
-- @field #boolean ShowTrace If true, show tracing info.
|
||||
|
||||
--- Create a new schedule dispatcher object.
|
||||
-- @param #SCHEDULEDISPATCHER self
|
||||
-- @return #SCHEDULEDISPATCHER self
|
||||
function SCHEDULEDISPATCHER:New()
|
||||
local self = BASE:Inherit( self, BASE:New() )
|
||||
self:F3()
|
||||
@@ -48,47 +77,100 @@ 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 nillified.
|
||||
-- It is constructed as such that a garbage collection is executed on the weak tables, when the Scheduler is set to nil.
|
||||
-- Nothing of this code should be modified without testing it thoroughly.
|
||||
-- @param #SCHEDULEDISPATCHER self
|
||||
-- @param Core.Scheduler#SCHEDULER Scheduler
|
||||
function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleArguments, Start, Repeat, Randomize, Stop )
|
||||
self:F2( { Scheduler, ScheduleFunction, ScheduleArguments, Start, Repeat, Randomize, Stop } )
|
||||
-- @param Core.Scheduler#SCHEDULER Scheduler Scheduler object.
|
||||
-- @param #function ScheduleFunction Scheduler function.
|
||||
-- @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 Stop Stop time in seconds.
|
||||
-- @param #number TraceLevel Trace level [0,3].
|
||||
-- @param Core.Fsm#FSM Fsm Finite state model.
|
||||
-- @return #string Call ID or nil.
|
||||
function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleArguments, Start, Repeat, Randomize, Stop, TraceLevel, Fsm )
|
||||
self:F2( { Scheduler, ScheduleFunction, ScheduleArguments, Start, Repeat, Randomize, Stop, TraceLevel, Fsm } )
|
||||
|
||||
-- Increase counter.
|
||||
self.CallID = self.CallID + 1
|
||||
local CallID = self.CallID .. "#" .. ( Scheduler.MasterObject and Scheduler.MasterObject.GetClassNameAndID and Scheduler.MasterObject:GetClassNameAndID() or "" ) 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.
|
||||
-- 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 ) )
|
||||
|
||||
-- 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
|
||||
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
|
||||
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] = {}
|
||||
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 + .1
|
||||
self.Schedule[Scheduler][CallID].StartTime = timer.getTime() + (Start or 0)
|
||||
self.Schedule[Scheduler][CallID].Start = Start + 0.1
|
||||
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.
|
||||
-- The call stack has many levels, and the correct semantical function call depends on where in the code AddSchedule was "used".
|
||||
-- - Using SCHEDULER:New()
|
||||
-- - Using Schedule:AddSchedule()
|
||||
-- - Using Fsm:__Func()
|
||||
-- - Using Class:ScheduleOnce()
|
||||
-- - Using Class:ScheduleRepeat()
|
||||
-- - ...
|
||||
-- So for each of these scheduled call variations, AddSchedule is the workhorse which will schedule the call.
|
||||
-- But the correct level with the correct semantical function location will differ depending on the above scheduled call invocation forms.
|
||||
-- That's where the field TraceLevel contains optionally the level in the call stack where the call information is obtained.
|
||||
-- The TraceLevel field indicates the correct level where the semantical scheduled call was invoked within the source, ensuring that function name, line number and source name are correct.
|
||||
-- There is one quick ...
|
||||
-- The FSM class models scheduled calls using the __Func syntax. However, these functions are "tailed".
|
||||
-- There aren't defined anywhere within the source code, but rather implemented as triggers within the FSM logic,
|
||||
-- and using the onbefore, onafter, onenter, onleave prefixes. (See the FSM for details).
|
||||
-- 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.
|
||||
|
||||
local Info = {}
|
||||
|
||||
if debug then
|
||||
TraceLevel = TraceLevel or 2
|
||||
Info = debug.getinfo( TraceLevel, "nlS" )
|
||||
local name_fsm = debug.getinfo( TraceLevel - 1, "n" ).name -- #string
|
||||
if name_fsm then
|
||||
Info.name = name_fsm
|
||||
end
|
||||
end
|
||||
|
||||
self:T3( self.Schedule[Scheduler][CallID] )
|
||||
|
||||
self.Schedule[Scheduler][CallID].CallHandler = function( CallID )
|
||||
--self:E( CallID )
|
||||
--- 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 "?"
|
||||
local Line = Info.currentline or "?"
|
||||
local Name = Info.name or "?"
|
||||
|
||||
local ErrorHandler = function( errmsg )
|
||||
env.info( "Error in timer function: " .. errmsg )
|
||||
@@ -97,81 +179,92 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr
|
||||
end
|
||||
return errmsg
|
||||
end
|
||||
|
||||
local Scheduler = self.ObjectSchedulers[CallID]
|
||||
|
||||
-- Get object or persistent 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 Schedule = self.Schedule[Scheduler][CallID]
|
||||
|
||||
--self:T3( { Schedule = Schedule } )
|
||||
local MasterObject = tostring( Scheduler.MasterObject )
|
||||
|
||||
-- Schedule object.
|
||||
local Schedule = self.Schedule[Scheduler][CallID] -- #SCHEDULEDISPATCHER.ScheduleData
|
||||
|
||||
-- self:T3( { Schedule = Schedule } )
|
||||
|
||||
local SchedulerObject = Scheduler.MasterObject -- Scheduler.SchedulerObject Now is this the Master or Scheduler object?
|
||||
local ShowTrace = Scheduler.ShowTrace
|
||||
|
||||
local ScheduleFunction = Schedule.Function
|
||||
local ScheduleArguments = Schedule.Arguments or {}
|
||||
local Start = Schedule.Start
|
||||
local Repeat = Schedule.Repeat or 0
|
||||
local Randomize = Schedule.Randomize or 0
|
||||
local Stop = Schedule.Stop or 0
|
||||
local ScheduleID = Schedule.ScheduleID
|
||||
|
||||
local Prefix = (Repeat == 0) and "--->" or "+++>"
|
||||
|
||||
local SchedulerObject = Scheduler.SchedulerObject
|
||||
--local ScheduleObjectName = Scheduler.SchedulerObject:GetNameAndClassID()
|
||||
local ScheduleFunction = Schedule.Function
|
||||
local ScheduleArguments = Schedule.Arguments
|
||||
local Start = Schedule.Start
|
||||
local Repeat = Schedule.Repeat or 0
|
||||
local Randomize = Schedule.Randomize or 0
|
||||
local Stop = Schedule.Stop or 0
|
||||
local ScheduleID = Schedule.ScheduleID
|
||||
|
||||
local Status, Result
|
||||
--self:E( { SchedulerObject = SchedulerObject } )
|
||||
-- self:E( { SchedulerObject = SchedulerObject } )
|
||||
if SchedulerObject then
|
||||
local function Timer()
|
||||
return ScheduleFunction( SchedulerObject, unpack( ScheduleArguments ) )
|
||||
if ShowTrace then
|
||||
SchedulerObject:T( Prefix .. Name .. ":" .. Line .. " (" .. Source .. ")" )
|
||||
end
|
||||
return ScheduleFunction( SchedulerObject, unpack( ScheduleArguments ) )
|
||||
end
|
||||
Status, Result = xpcall( Timer, ErrorHandler )
|
||||
else
|
||||
local function Timer()
|
||||
return ScheduleFunction( unpack( ScheduleArguments ) )
|
||||
if ShowTrace then
|
||||
self:T( Prefix .. Name .. ":" .. Line .. " (" .. Source .. ")" )
|
||||
end
|
||||
return ScheduleFunction( unpack( ScheduleArguments ) )
|
||||
end
|
||||
Status, Result = xpcall( Timer, ErrorHandler )
|
||||
end
|
||||
|
||||
|
||||
local CurrentTime = timer.getTime()
|
||||
local StartTime = Schedule.StartTime
|
||||
|
||||
self:F3( { 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.01
|
||||
--self:T3( { Repeat = CallID, CurrentTime, ScheduleTime, ScheduleArguments } )
|
||||
-- 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 } )
|
||||
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:E( "Scheduled obsolete call for CallID: " .. CallID )
|
||||
self:I( "<<<>" .. Name .. ":" .. Line .. " (" .. Source .. ")" )
|
||||
end
|
||||
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
self:Start( Scheduler, CallID )
|
||||
|
||||
|
||||
self:Start( Scheduler, CallID, Info )
|
||||
|
||||
return CallID
|
||||
end
|
||||
|
||||
--- Remove schedule.
|
||||
-- @param #SCHEDULEDISPATCHER self
|
||||
-- @param Core.Scheduler#SCHEDULER Scheduler Scheduler object.
|
||||
-- @param #table CallID Call ID.
|
||||
function SCHEDULEDISPATCHER:RemoveSchedule( Scheduler, CallID )
|
||||
self:F2( { Remove = CallID, Scheduler = Scheduler } )
|
||||
|
||||
@@ -181,46 +274,80 @@ function SCHEDULEDISPATCHER:RemoveSchedule( Scheduler, CallID )
|
||||
end
|
||||
end
|
||||
|
||||
function SCHEDULEDISPATCHER:Start( Scheduler, CallID )
|
||||
--- Start dispatcher.
|
||||
-- @param #SCHEDULEDISPATCHER self
|
||||
-- @param Core.Scheduler#SCHEDULER Scheduler Scheduler object.
|
||||
-- @param #table CallID (Optional) Call ID.
|
||||
-- @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]
|
||||
|
||||
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[CallID].ScheduleID then
|
||||
Schedule[CallID].StartTime = timer.getTime() -- Set the StartTime field to indicate when the scheduler started.
|
||||
Schedule[CallID].ScheduleID = timer.scheduleFunction(
|
||||
Schedule[CallID].CallHandler,
|
||||
CallID,
|
||||
timer.getTime() + Schedule[CallID].Start
|
||||
)
|
||||
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.
|
||||
|
||||
-- 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 ) ) )
|
||||
end
|
||||
|
||||
else
|
||||
|
||||
-- Recursive.
|
||||
for CallID, Schedule in pairs( self.Schedule[Scheduler] or {} ) do
|
||||
self:Start( Scheduler, CallID ) -- Recursive
|
||||
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.
|
||||
function SCHEDULEDISPATCHER:Stop( Scheduler, CallID )
|
||||
self:F2( { Stop = CallID, Scheduler = Scheduler } )
|
||||
|
||||
if CallID then
|
||||
local Schedule = self.Schedule[Scheduler]
|
||||
-- Only stop when there is a ScheduleID defined for the CallID.
|
||||
-- So, when the scheduler was stopped before, do nothing.
|
||||
if Schedule[CallID].ScheduleID then
|
||||
timer.removeFunction( Schedule[CallID].ScheduleID )
|
||||
Schedule[CallID].ScheduleID = nil
|
||||
|
||||
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 ) ) )
|
||||
|
||||
-- Remove schedule function https://wiki.hoggitworld.com/view/DCS_func_removeFunction
|
||||
timer.removeFunction( Schedule.ScheduleID )
|
||||
|
||||
Schedule.ScheduleID = nil
|
||||
|
||||
else
|
||||
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
|
||||
|
||||
--- Clear all schedules by stopping all dispatchers.
|
||||
-- @param #SCHEDULEDISPATCHER self
|
||||
-- @param Core.Scheduler#SCHEDULER Scheduler Scheduler object.
|
||||
function SCHEDULEDISPATCHER:Clear( Scheduler )
|
||||
self:F2( { Scheduler = Scheduler } )
|
||||
|
||||
@@ -229,5 +356,19 @@ function SCHEDULEDISPATCHER:Clear( Scheduler )
|
||||
end
|
||||
end
|
||||
|
||||
--- Show tracing info.
|
||||
-- @param #SCHEDULEDISPATCHER self
|
||||
-- @param Core.Scheduler#SCHEDULER Scheduler Scheduler object.
|
||||
function SCHEDULEDISPATCHER:ShowTrace( Scheduler )
|
||||
self:F2( { Scheduler = Scheduler } )
|
||||
Scheduler.ShowTrace = true
|
||||
end
|
||||
|
||||
--- No tracing info.
|
||||
-- @param #SCHEDULEDISPATCHER self
|
||||
-- @param Core.Scheduler#SCHEDULER Scheduler Scheduler object.
|
||||
function SCHEDULEDISPATCHER:NoTrace( Scheduler )
|
||||
self:F2( { Scheduler = Scheduler } )
|
||||
Scheduler.ShowTrace = false
|
||||
end
|
||||
|
||||
|
||||
@@ -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 source code](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/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:
|
||||
--
|
||||
-- # YouTube Channel
|
||||
--
|
||||
-- ### [SCHEDULER YouTube Channel (none)]()
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Contributions:
|
||||
--
|
||||
-- * FlightControl : Concept & Testing
|
||||
--
|
||||
-- ### Authors:
|
||||
--
|
||||
--
|
||||
-- ### Authors:
|
||||
--
|
||||
-- * FlightControl : Design & Programming
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module Core.Scheduler
|
||||
@@ -43,80 +43,81 @@
|
||||
|
||||
--- The SCHEDULER class
|
||||
-- @type SCHEDULER
|
||||
-- @field #number ScheduleID the ID of the scheduler.
|
||||
-- @field #table Schedules Table of schedules.
|
||||
-- @field #table MasterObject Master object.
|
||||
-- @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.
|
||||
--
|
||||
-- SchedulerObject = SCHEDULER:New()
|
||||
-- SchedulerID = SchedulerObject:Schedule( nil, ScheduleFunction, {} )
|
||||
--
|
||||
-- The above example creates a new SchedulerObject, but does not schedule anything.
|
||||
-- A separate schedule is created by using the SchedulerObject using the method :Schedule..., which returns a ScheduleID
|
||||
--
|
||||
--
|
||||
-- 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 nillified 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 set to nil or destroyed, the SCHEDULER object will also be destroyed and stopped after garbage collection.
|
||||
--
|
||||
-- ZoneObject = ZONE:New( "ZoneName" )
|
||||
-- SchedulerObject = SCHEDULER:New( ZoneObject )
|
||||
-- SchedulerID = SchedulerObject:Schedule( ZoneObject, ScheduleFunction, {} )
|
||||
-- MasterObject = SCHEDULER:New( ZoneObject )
|
||||
-- SchedulerID = MasterObject:Schedule( ZoneObject, ScheduleFunction, {} )
|
||||
-- ...
|
||||
-- ZoneObject = nil
|
||||
-- garbagecollect()
|
||||
--
|
||||
-- The above example creates a new SchedulerObject, 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 SchedulerObject using the method :Schedule()..., which returns a ScheduleID
|
||||
--
|
||||
-- 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 ScheduleObject will cancel any planned schedule.
|
||||
--
|
||||
-- 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.
|
||||
--
|
||||
-- SchedulerObject, SchedulerID = SCHEDULER:New( nil, ScheduleFunction, {} )
|
||||
--
|
||||
-- The above example creates a new SchedulerObject, and does schedule the first schedule as part of the call.
|
||||
-- Note that 2 variables are returned here: SchedulerObject, ScheduleID...
|
||||
--
|
||||
--
|
||||
-- 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" )
|
||||
-- SchedulerObject, SchedulerID = SCHEDULER:New( ZoneObject, ScheduleFunction, {} )
|
||||
-- SchedulerID = SchedulerObject:Schedule( ZoneObject, ScheduleFunction, {} )
|
||||
-- MasterObject, SchedulerID = SCHEDULER:New( ZoneObject, ScheduleFunction, {} )
|
||||
-- SchedulerID = MasterObject:Schedule( ZoneObject, ScheduleFunction, {} )
|
||||
-- ...
|
||||
-- ZoneObject = nil
|
||||
-- garbagecollect()
|
||||
--
|
||||
-- The above example creates a new SchedulerObject, and schedules a method call (ScheduleFunction),
|
||||
--
|
||||
-- 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 ScheduleObject and a SchedulerID variable are returned.
|
||||
-- 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 ScheduleObject will cancel the planned schedule.
|
||||
--
|
||||
-- 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:
|
||||
@@ -125,79 +126,81 @@
|
||||
-- * @{#SCHEDULER.Stop}(): Stop the schedules within the SCHEDULER object. If a CallID is provided to :Stop(), then only the schedule referenced by CallID will be stopped.
|
||||
--
|
||||
-- ZoneObject = ZONE:New( "ZoneName" )
|
||||
-- SchedulerObject, SchedulerID = SCHEDULER:New( ZoneObject, ScheduleFunction, {} )
|
||||
-- SchedulerID = SchedulerObject:Schedule( ZoneObject, ScheduleFunction, {}, 10, 10 )
|
||||
-- MasterObject, SchedulerID = SCHEDULER:New( ZoneObject, ScheduleFunction, {} )
|
||||
-- SchedulerID = MasterObject:Schedule( ZoneObject, ScheduleFunction, {}, 10, 10 )
|
||||
-- ...
|
||||
-- SchedulerObject:Stop( SchedulerID )
|
||||
-- MasterObject:Stop( SchedulerID )
|
||||
-- ...
|
||||
-- SchedulerObject:Start( SchedulerID )
|
||||
--
|
||||
-- The above example creates a new SchedulerObject, and does schedule the first schedule as part of the call.
|
||||
-- Note that 2 variables are returned here: SchedulerObject, ScheduleID...
|
||||
-- Later in the logic, the repeating schedule with SchedulerID is stopped.
|
||||
-- A bit later, the repeating schedule with SchedulerId is (re)-started.
|
||||
--
|
||||
-- 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.
|
||||
--
|
||||
-- ## 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" )
|
||||
-- SchedulerObject = SCHEDULER:New( ZoneObject )
|
||||
--
|
||||
-- Several parameters can be specified that influence the behaviour of a Schedule.
|
||||
--
|
||||
-- MasterObject = SCHEDULER:New( ZoneObject )
|
||||
--
|
||||
-- Several parameters can be specified that influence the behavior of a Schedule.
|
||||
--
|
||||
-- ### A single schedule, immediately executed
|
||||
--
|
||||
-- SchedulerID = SchedulerObject:Schedule( ZoneObject, ScheduleFunction, {} )
|
||||
--
|
||||
-- The above example schedules a new ScheduleFunction call to be executed asynchronously, within milleseconds ...
|
||||
--
|
||||
--
|
||||
-- SchedulerID = MasterObject:Schedule( ZoneObject, ScheduleFunction, {} )
|
||||
--
|
||||
-- The above example schedules a new ScheduleFunction call to be executed asynchronously, within milliseconds ...
|
||||
--
|
||||
-- ### A single schedule, planned over time
|
||||
--
|
||||
-- SchedulerID = SchedulerObject:Schedule( ZoneObject, ScheduleFunction, {}, 10 )
|
||||
--
|
||||
--
|
||||
-- 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 = SchedulerObject:Schedule( ZoneObject, ScheduleFunction, {}, 10, 60 )
|
||||
--
|
||||
-- The above example schedules a new ScheduleFunction call to be executed asynchronously, within 10 seconds,
|
||||
--
|
||||
-- SchedulerID = MasterObject:Schedule( ZoneObject, ScheduleFunction, {}, 10, 60 )
|
||||
--
|
||||
-- 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 = SchedulerObject:Schedule( ZoneObject, ScheduleFunction, {}, 10, 60, 0.5 )
|
||||
--
|
||||
-- The above example schedules a new ScheduleFunction call to be executed asynchronously, within 10 seconds,
|
||||
--
|
||||
-- SchedulerID = MasterObject:Schedule( ZoneObject, ScheduleFunction, {}, 10, 60, 0.5 )
|
||||
--
|
||||
-- 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 = SchedulerObject:Schedule( ZoneObject, ScheduleFunction, {}, 10, 60, 0.5, 300 )
|
||||
--
|
||||
-- The above example schedules a new ScheduleFunction call to be executed asynchronously, within 10 seconds,
|
||||
--
|
||||
-- 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 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,
|
||||
}
|
||||
|
||||
--- SCHEDULER constructor.
|
||||
-- @param #SCHEDULER self
|
||||
-- @param #table SchedulerObject Specified for which Moose object the timer is setup. If a value of nil is provided, a scheduler will be setup without an object reference.
|
||||
-- @param #table MasterObject Specified for which Moose object the timer is setup. If a value of nil is provided, a scheduler will be setup without an object reference.
|
||||
-- @param #function SchedulerFunction The event function to be called when a timer event occurs. The event function needs to accept the parameters specified in SchedulerArguments.
|
||||
-- @param #table SchedulerArguments Optional arguments that can be given as part of scheduler. The arguments need to be given as a table { param1, param 2, ... }.
|
||||
-- @param #number Start Specifies the amount of seconds that will be waited before the scheduling is started, and the event function is called.
|
||||
@@ -205,110 +208,110 @@ SCHEDULER = {
|
||||
-- @param #number RandomizeFactor Specifies a randomization factor between 0 and 1 to randomize the Repeat.
|
||||
-- @param #number Stop Specifies the amount of seconds when the scheduler will be stopped.
|
||||
-- @return #SCHEDULER self.
|
||||
-- @return #number The ScheduleID of the planned schedule.
|
||||
function SCHEDULER:New( SchedulerObject, SchedulerFunction, SchedulerArguments, Start, Repeat, RandomizeFactor, Stop )
|
||||
|
||||
-- @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 = SchedulerObject
|
||||
|
||||
|
||||
self.MasterObject = MasterObject
|
||||
self.ShowTrace = false
|
||||
|
||||
if SchedulerFunction then
|
||||
ScheduleID = self:Schedule( SchedulerObject, SchedulerFunction, SchedulerArguments, Start, Repeat, RandomizeFactor, Stop )
|
||||
ScheduleID = self:Schedule( MasterObject, SchedulerFunction, SchedulerArguments, Start, Repeat, RandomizeFactor, Stop, 3 )
|
||||
end
|
||||
|
||||
return self, ScheduleID
|
||||
end
|
||||
|
||||
--function SCHEDULER:_Destructor()
|
||||
-- --self:E("_Destructor")
|
||||
--
|
||||
-- _SCHEDULEDISPATCHER:RemoveSchedule( self.CallID )
|
||||
--end
|
||||
|
||||
--- Schedule a new time event. Note that the schedule will only take place if the scheduler is *started*. Even for a single schedule event, the scheduler needs to be started also.
|
||||
-- @param #SCHEDULER self
|
||||
-- @param #table SchedulerObject Specified for which Moose object the timer is setup. If a value of nil is provided, a scheduler will be setup without an object reference.
|
||||
-- @param #table MasterObject Specified for which Moose object the timer is setup. If a value of nil is provided, a scheduler will be setup without an object reference.
|
||||
-- @param #function SchedulerFunction The event function to be called when a timer event occurs. The event function needs to accept the parameters specified in SchedulerArguments.
|
||||
-- @param #table SchedulerArguments Optional arguments that can be given as part of scheduler. The arguments need to be given as a table { param1, param 2, ... }.
|
||||
-- @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 interval in seconds when the scheduler will call the event function.
|
||||
-- @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 Specifies the amount of seconds when the scheduler will be stopped.
|
||||
-- @return #number The ScheduleID of the planned schedule.
|
||||
function SCHEDULER:Schedule( SchedulerObject, SchedulerFunction, SchedulerArguments, Start, Repeat, RandomizeFactor, Stop )
|
||||
-- @param #number Stop Time interval in seconds after which the scheduler will be stopped.
|
||||
-- @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.
|
||||
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 SchedulerObject and SchedulerObject.ClassName and SchedulerObject.ClassID then
|
||||
ObjectName = SchedulerObject.ClassName .. SchedulerObject.ClassID
|
||||
if MasterObject and MasterObject.ClassName and MasterObject.ClassID then
|
||||
ObjectName = MasterObject.ClassName .. MasterObject.ClassID
|
||||
end
|
||||
self:F3( { "Schedule :", ObjectName, tostring( SchedulerObject ), Start, Repeat, RandomizeFactor, Stop } )
|
||||
self.SchedulerObject = SchedulerObject
|
||||
|
||||
local ScheduleID = _SCHEDULEDISPATCHER:AddSchedule(
|
||||
self,
|
||||
SchedulerFunction,
|
||||
SchedulerArguments,
|
||||
Start,
|
||||
Repeat,
|
||||
RandomizeFactor,
|
||||
Stop
|
||||
)
|
||||
|
||||
self.Schedules[#self.Schedules+1] = ScheduleID
|
||||
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
|
||||
|
||||
return ScheduleID
|
||||
end
|
||||
|
||||
--- (Re-)Starts the schedules or a specific schedule if a valid ScheduleID is provided.
|
||||
-- @param #SCHEDULER self
|
||||
-- @param #number ScheduleID (optional) The ScheduleID of the planned (repeating) schedule.
|
||||
-- @param #string ScheduleID (Optional) The ScheduleID of the planned (repeating) schedule.
|
||||
function SCHEDULER:Start( ScheduleID )
|
||||
self:F3( { ScheduleID } )
|
||||
|
||||
self:T( string.format( "Starting scheduler ID=%s", tostring( ScheduleID ) ) )
|
||||
_SCHEDULEDISPATCHER:Start( self, ScheduleID )
|
||||
end
|
||||
|
||||
--- Stops the schedules or a specific schedule if a valid ScheduleID is provided.
|
||||
-- @param #SCHEDULER self
|
||||
-- @param #number ScheduleID (optional) The ScheduleID of the planned (repeating) schedule.
|
||||
-- @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 ) ) )
|
||||
_SCHEDULEDISPATCHER:Stop( self, ScheduleID )
|
||||
end
|
||||
|
||||
--- Removes a specific schedule if a valid ScheduleID is provided.
|
||||
-- @param #SCHEDULER self
|
||||
-- @param #number ScheduleID (optional) The ScheduleID of the planned (repeating) schedule.
|
||||
-- @param #string ScheduleID (optional) The ScheduleID of the planned (repeating) schedule.
|
||||
function SCHEDULER:Remove( ScheduleID )
|
||||
self:F3( { ScheduleID } )
|
||||
|
||||
_SCHEDULEDISPATCHER:Remove( self, 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:F3()
|
||||
self:T( string.format( "Clearing scheduler" ) )
|
||||
_SCHEDULEDISPATCHER:Clear( self )
|
||||
end
|
||||
|
||||
--- Show tracing for this scheduler.
|
||||
-- @param #SCHEDULER self
|
||||
function SCHEDULER:ShowTrace()
|
||||
_SCHEDULEDISPATCHER:ShowTrace( self )
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
--- No tracing for this scheduler.
|
||||
-- @param #SCHEDULER self
|
||||
function SCHEDULER:NoTrace()
|
||||
_SCHEDULEDISPATCHER:NoTrace( self )
|
||||
end
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,260 +1,497 @@
|
||||
--- **Core** - Spawn new statics in your running missions.
|
||||
--- **Core** - Spawn statics.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ## Features:
|
||||
--
|
||||
-- * Spawn new statics from a static already defined using the mission editor.
|
||||
-- * Spawn new statics from a static already defined in the mission editor.
|
||||
-- * Spawn new statics from a given template.
|
||||
-- * Spawn new statics from a given type.
|
||||
-- * Spawn with a custom heading and location.
|
||||
-- * Spawn within a zone.
|
||||
-- * Spawn statics linked to units, .e.g on aircraft carriers.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- # Demo Missions
|
||||
--
|
||||
-- ### [SPAWNSTATIC Demo Missions source code](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release/SPS - Spawning Statics)
|
||||
--
|
||||
-- ### [SPAWNSTATIC Demo Missions, only for beta testers](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/SPS%20-%20Spawning%20Statics)
|
||||
-- ## [SPAWNSTATIC Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/SPS%20-%20Spawning%20Statics)
|
||||
--
|
||||
-- ### [ALL Demo Missions pack of the last release](https://github.com/FlightControl-Master/MOOSE_MISSIONS/releases)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- # YouTube Channel
|
||||
--
|
||||
-- ### [SPAWNSTATIC YouTube Channel]()
|
||||
-- ## [SPAWNSTATIC YouTube Channel]() [No videos yet!]
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
-- ### Contributions:
|
||||
-- ### Contributions: **funkyfranky**
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module Core.SpawnStatic
|
||||
-- @image Core_Spawnstatic.JPG
|
||||
|
||||
|
||||
|
||||
--- @type SPAWNSTATIC
|
||||
-- @field #string SpawnTemplatePrefix Name of the template group.
|
||||
-- @field #number CountryID Country ID.
|
||||
-- @field #number CoalitionID Coalition ID.
|
||||
-- @field #number CategoryID Category ID.
|
||||
-- @field #number SpawnIndex Running number increased with each new Spawn.
|
||||
-- @field Wrapper.Unit#UNIT InitLinkUnit The unit the static is linked to.
|
||||
-- @field #number InitOffsetX Link offset X coordinate.
|
||||
-- @field #number InitOffsetY Link offset Y coordinate.
|
||||
-- @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 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 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
|
||||
|
||||
|
||||
--- Allows to spawn dynamically new @{Static}s.
|
||||
-- Through creating a copy of an existing static object template as defined in the Mission Editor (ME),
|
||||
-- SPAWNSTATIC can retireve the properties of the defined static object template (like type, category etc), and "copy"
|
||||
-- these properties to create a new static object and place it at the desired coordinate.
|
||||
--- Allows to spawn dynamically new @{Static}s into your mission.
|
||||
--
|
||||
-- New spawned @{Static}s get **the same name** as the name of the template Static,
|
||||
-- or gets the given name when a new name is provided at the Spawn method.
|
||||
-- Through creating a copy of an existing static object template as defined in the Mission Editor (ME), SPAWNSTATIC can retireve the properties of the defined static object template (like type, category etc),
|
||||
-- and "copy" these properties to create a new static object and place it at the desired coordinate.
|
||||
--
|
||||
-- New spawned @{Static}s get **the same name** as the name of the template Static, or gets the given name when a new name is provided at the Spawn method.
|
||||
-- By default, spawned @{Static}s will follow a naming convention at run-time:
|
||||
--
|
||||
-- * Spawned @{Static}s will have the name _StaticName_#_nnn_, where _StaticName_ is the name of the **Template Static**,
|
||||
-- and _nnn_ is a **counter from 0 to 99999**.
|
||||
-- * Spawned @{Static}s will have the name _StaticName_#_nnn_, where _StaticName_ is the name of the **Template Static**, and _nnn_ is a **counter from 0 to 99999**.
|
||||
--
|
||||
--
|
||||
-- ## SPAWNSTATIC construction methods
|
||||
-- # SPAWNSTATIC Constructors
|
||||
--
|
||||
-- Create a new SPAWNSTATIC object with the @{#SPAWNSTATIC.NewFromStatic}():
|
||||
-- Firstly, we need to create a SPAWNSTATIC object that will be used to spawn new statics into the mission. There are three ways to do this.
|
||||
--
|
||||
-- * @{#SPAWNSTATIC.NewFromStatic}(): Creates a new SPAWNSTATIC object given a name that is used as the base of the naming of each spawned Static.
|
||||
-- ## Use another Static
|
||||
--
|
||||
-- A new SPAWNSTATIC object can be created using another static by the @{#SPAWNSTATIC.NewFromStatic}() function. All parameters such as position, heading, country will be initialized
|
||||
-- from the static.
|
||||
--
|
||||
-- ## From a Template
|
||||
--
|
||||
-- A SPAWNSTATIC object can also be created from a template table using the @{#SPAWNSTATIC.NewFromTemplate}(SpawnTemplate, CountryID) function. All parameters are taken from the template.
|
||||
--
|
||||
-- ## From a Type
|
||||
--
|
||||
-- A very basic method is to create a SPAWNSTATIC object by just giving the type of the static. All parameters must be initialized from the InitXYZ functions described below. Otherwise default values
|
||||
-- are used. For example, if no spawn coordinate is given, the static will be created at the origin of the map.
|
||||
--
|
||||
-- # Setting Parameters
|
||||
--
|
||||
-- Parameters such as the spawn position, heading, country etc. can be set via :Init*XYZ* functions. Note that these functions must be given before the actual spawn command!
|
||||
--
|
||||
-- * @{#SPAWNSTATIC.InitCoordinate}(Coordinate) Sets the coordinate where the static is spawned. Statics are always spawnd on the ground.
|
||||
-- * @{#SPAWNSTATIC.InitHeading}(Heading) sets the orientation of the static.
|
||||
-- * @{#SPAWNSTATIC.InitLivery}(LiveryName) sets the livery of the static. Not all statics support this.
|
||||
-- * @{#SPAWNSTATIC.InitType}(StaticType) sets the type of the static.
|
||||
-- * @{#SPAWNSTATIC.InitShape}(StaticType) sets the shape of the static. Not all statics have this parameter.
|
||||
-- * @{#SPAWNSTATIC.InitNamePrefix}(NamePrefix) sets the name prefix of the spawned statics.
|
||||
-- * @{#SPAWNSTATIC.InitCountry}(CountryID) sets the country and therefore the coalition of the spawned statics.
|
||||
-- * @{#SPAWNSTATIC.InitLinkToUnit}(Unit, OffsetX, OffsetY, OffsetAngle) links the static to a unit, e.g. to an aircraft carrier.
|
||||
--
|
||||
-- ## **Spawn** methods
|
||||
-- # Spawning the Statics
|
||||
--
|
||||
-- Groups can be spawned at different times and methods:
|
||||
-- Once the SPAWNSTATIC object is created and parameters are initialized, the spawn command can be given. There are different methods where some can be used to directly set parameters
|
||||
-- such as position and heading.
|
||||
--
|
||||
-- * @{#SPAWNSTATIC.SpawnFromPointVec2}(): Spawn a new group from a POINT_VEC2 coordinate.
|
||||
-- (The group will be spawned at land height ).
|
||||
-- * @{#SPAWNSTATIC.SpawnFromZone}(): Spawn a new group in a @{Zone}.
|
||||
-- * @{#SPAWNSTATIC.Spawn}(Heading, NewName) spawns the static with the set parameters. Optionally, heading and name can be given. The name **must be unique**!
|
||||
-- * @{#SPAWNSTATIC.SpawnFromCoordinate}(Coordinate, Heading, NewName) spawn the static at the given coordinate. Optionally, heading and name can be given. The name **must be unique**!
|
||||
-- * @{#SPAWNSTATIC.SpawnFromPointVec2}(PointVec2, Heading, NewName) spawns the static at a POINT_VEC2 coordinate. Optionally, heading and name can be given. The name **must be unique**!
|
||||
-- * @{#SPAWNSTATIC.SpawnFromZone}(Zone, Heading, NewName) spawns the static at the center of a @{Zone}. Optionally, heading and name can be given. The name **must be unique**!
|
||||
--
|
||||
-- @field #SPAWNSTATIC SPAWNSTATIC
|
||||
--
|
||||
SPAWNSTATIC = {
|
||||
ClassName = "SPAWNSTATIC",
|
||||
ClassName = "SPAWNSTATIC",
|
||||
SpawnIndex = 0,
|
||||
}
|
||||
|
||||
--- Static template table data.
|
||||
-- @type SPAWNSTATIC.TemplateData
|
||||
-- @field #string name Name of the static.
|
||||
-- @field #string type Type of the static.
|
||||
-- @field #string category Category of the static.
|
||||
-- @field #number x X-coordinate of the static.
|
||||
-- @field #number y Y-coordinate of teh static.
|
||||
-- @field #number heading Heading in rad.
|
||||
-- @field #boolean dead Static is dead if true.
|
||||
-- @field #string livery_id Livery name.
|
||||
-- @field #number unitId Unit ID.
|
||||
-- @field #number groupId Group ID.
|
||||
-- @field #table offsets Offset parameters when linked to a unit.
|
||||
-- @field #number mass Cargo mass in kg.
|
||||
-- @field #boolean canCargo Static can be a cargo.
|
||||
|
||||
--- @type SPAWNSTATIC.SpawnZoneTable
|
||||
-- @list <Core.Zone#ZONE_BASE> SpawnZone
|
||||
|
||||
|
||||
--- Creates the main object to spawn a @{Static} defined in the ME.
|
||||
--- Creates the main object to spawn a @{Static} defined in the mission editor (ME).
|
||||
-- @param #SPAWNSTATIC self
|
||||
-- @param #string SpawnTemplatePrefix is the name of the Group in the ME that defines the Template. Each new group will have the name starting with SpawnTemplatePrefix.
|
||||
-- @param DCS#country.id SpawnCountryID The ID of the country.
|
||||
-- @param DCS#coalition.side SpawnCoalitionID The ID of the coalition.
|
||||
-- @return #SPAWNSTATIC
|
||||
function SPAWNSTATIC:NewFromStatic( SpawnTemplatePrefix, SpawnCountryID, SpawnCoalitionID )
|
||||
local self = BASE:Inherit( self, BASE:New() ) -- #SPAWNSTATIC
|
||||
self:F( { SpawnTemplatePrefix } )
|
||||
|
||||
local TemplateStatic, CoalitionID, CategoryID, CountryID = _DATABASE:GetStaticGroupTemplate( SpawnTemplatePrefix )
|
||||
if TemplateStatic then
|
||||
self.SpawnTemplatePrefix = SpawnTemplatePrefix
|
||||
self.CountryID = SpawnCountryID or CountryID
|
||||
self.CategoryID = CategoryID
|
||||
self.CoalitionID = SpawnCoalitionID or CoalitionID
|
||||
self.SpawnIndex = 0
|
||||
else
|
||||
error( "SPAWNSTATIC:New: There is no static declared in the mission editor with SpawnTemplatePrefix = '" .. SpawnTemplatePrefix .. "'" )
|
||||
end
|
||||
-- @param #string SpawnTemplateName Name of the static object in the ME. Each new static will have the name starting with this prefix.
|
||||
-- @param DCS#country.id SpawnCountryID (Optional) The ID of the country.
|
||||
-- @return #SPAWNSTATIC self
|
||||
function SPAWNSTATIC:NewFromStatic(SpawnTemplateName, SpawnCountryID)
|
||||
|
||||
self:SetEventPriority( 5 )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Creates the main object to spawn a @{Static} based on a type name.
|
||||
-- @param #SPAWNSTATIC self
|
||||
-- @param #string SpawnTypeName is the name of the type.
|
||||
-- @return #SPAWNSTATIC
|
||||
function SPAWNSTATIC:NewFromType( SpawnTypeName, SpawnShapeName, SpawnCategory, SpawnCountryID, SpawnCoalitionID )
|
||||
local self = BASE:Inherit( self, BASE:New() ) -- #SPAWNSTATIC
|
||||
self:F( { SpawnTypeName } )
|
||||
|
||||
self.SpawnTypeName = SpawnTypeName
|
||||
self.CountryID = SpawnCountryID
|
||||
self.CoalitionID = SpawnCoalitionID
|
||||
self.SpawnIndex = 0
|
||||
local TemplateStatic, CoalitionID, CategoryID, CountryID = _DATABASE:GetStaticGroupTemplate(SpawnTemplateName)
|
||||
|
||||
if TemplateStatic then
|
||||
self.SpawnTemplatePrefix = SpawnTemplateName
|
||||
self.TemplateStaticUnit = UTILS.DeepCopy(TemplateStatic.units[1])
|
||||
self.CountryID = SpawnCountryID or CountryID
|
||||
self.CategoryID = CategoryID
|
||||
self.CoalitionID = CoalitionID
|
||||
self.SpawnIndex = 0
|
||||
else
|
||||
error( "SPAWNSTATIC:New: There is no static declared in the mission editor with SpawnTemplatePrefix = '" .. tostring(SpawnTemplateName) .. "'" )
|
||||
end
|
||||
|
||||
self:SetEventPriority( 5 )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Creates a new @{Static} at the original position.
|
||||
--- Creates the main object to spawn a @{Static} given a template table.
|
||||
-- @param #SPAWNSTATIC self
|
||||
-- @param #number Heading The heading of the static, which is a number in degrees from 0 to 360.
|
||||
-- @param #string (optional) The name of the new static.
|
||||
-- @return #SPAWNSTATIC
|
||||
function SPAWNSTATIC:Spawn( Heading, NewName ) --R2.3
|
||||
self:F( { Heading, NewName } )
|
||||
|
||||
local StaticTemplate, CoalitionID, CategoryID, CountryID = _DATABASE:GetStaticGroupTemplate( self.SpawnTemplatePrefix )
|
||||
|
||||
if StaticTemplate then
|
||||
|
||||
local StaticUnitTemplate = StaticTemplate.units[1]
|
||||
|
||||
StaticTemplate.name = NewName or string.format("%s#%05d", self.SpawnTemplatePrefix, self.SpawnIndex )
|
||||
StaticTemplate.heading = ( Heading / 180 ) * math.pi
|
||||
|
||||
_DATABASE:_RegisterStaticTemplate( StaticTemplate, CoalitionID, CategoryID, CountryID )
|
||||
-- @param #table SpawnTemplate Template used for spawning.
|
||||
-- @param DCS#country.id CountryID The ID of the country. Default `country.id.USA`.
|
||||
-- @return #SPAWNSTATIC self
|
||||
function SPAWNSTATIC:NewFromTemplate(SpawnTemplate, CountryID)
|
||||
|
||||
local Static = coalition.addStaticObject( self.CountryID or CountryID, StaticTemplate.units[1] )
|
||||
|
||||
self.SpawnIndex = self.SpawnIndex + 1
|
||||
local self = BASE:Inherit( self, BASE:New() ) -- #SPAWNSTATIC
|
||||
|
||||
return _DATABASE:FindStatic(Static:getName())
|
||||
end
|
||||
self.TemplateStaticUnit = UTILS.DeepCopy(SpawnTemplate)
|
||||
self.SpawnTemplatePrefix = SpawnTemplate.name
|
||||
self.CountryID = CountryID or country.id.USA
|
||||
|
||||
return nil
|
||||
return self
|
||||
end
|
||||
|
||||
--- Creates the main object to spawn a @{Static} from a given type.
|
||||
-- NOTE that you have to init many other parameters as spawn coordinate etc.
|
||||
-- @param #SPAWNSTATIC self
|
||||
-- @param #string StaticType Type of the static.
|
||||
-- @param #string StaticCategory Category of the static, e.g. "Planes".
|
||||
-- @param DCS#country.id CountryID The ID of the country. Default `country.id.USA`.
|
||||
-- @return #SPAWNSTATIC self
|
||||
function SPAWNSTATIC:NewFromType(StaticType, StaticCategory, CountryID)
|
||||
|
||||
local self = BASE:Inherit( self, BASE:New() ) -- #SPAWNSTATIC
|
||||
|
||||
self.InitStaticType=StaticType
|
||||
self.InitStaticCategory=StaticCategory
|
||||
self.CountryID=CountryID or country.id.USA
|
||||
self.SpawnTemplatePrefix=self.InitStaticType
|
||||
|
||||
self.InitStaticCoordinate=COORDINATE:New(0, 0, 0)
|
||||
self.InitStaticHeading=0
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Initialize heading of the spawned static.
|
||||
-- @param #SPAWNSTATIC self
|
||||
-- @param Core.Point#COORDINATE Coordinate Position where the static is spawned.
|
||||
-- @return #SPAWNSTATIC self
|
||||
function SPAWNSTATIC:InitCoordinate(Coordinate)
|
||||
self.InitStaticCoordinate=Coordinate
|
||||
return self
|
||||
end
|
||||
|
||||
--- Initialize heading of the spawned static.
|
||||
-- @param #SPAWNSTATIC self
|
||||
-- @param #number Heading The heading in degrees.
|
||||
-- @return #SPAWNSTATIC self
|
||||
function SPAWNSTATIC:InitHeading(Heading)
|
||||
self.InitStaticHeading=Heading
|
||||
return self
|
||||
end
|
||||
|
||||
--- Initialize livery of the spawned static.
|
||||
-- @param #SPAWNSTATIC self
|
||||
-- @param #string LiveryName Name of the livery to use.
|
||||
-- @return #SPAWNSTATIC self
|
||||
function SPAWNSTATIC:InitLivery(LiveryName)
|
||||
self.InitStaticLivery=LiveryName
|
||||
return self
|
||||
end
|
||||
|
||||
--- Initialize type of the spawned static.
|
||||
-- @param #SPAWNSTATIC self
|
||||
-- @param #string StaticType Type of the static, e.g. "FA-18C_hornet".
|
||||
-- @return #SPAWNSTATIC self
|
||||
function SPAWNSTATIC:InitType(StaticType)
|
||||
self.InitStaticType=StaticType
|
||||
return self
|
||||
end
|
||||
|
||||
--- Initialize shape of the spawned static. Required by some but not all statics.
|
||||
-- @param #SPAWNSTATIC self
|
||||
-- @param #string StaticShape Shape of the static, e.g. "carrier_tech_USA".
|
||||
-- @return #SPAWNSTATIC self
|
||||
function SPAWNSTATIC:InitShape(StaticShape)
|
||||
self.InitStaticShape=StaticShape
|
||||
return self
|
||||
end
|
||||
|
||||
--- Initialize parameters for spawning FARPs.
|
||||
-- @param #SPAWNSTATIC self
|
||||
-- @param #number CallsignID Callsign ID. Default 1 (="London").
|
||||
-- @param #number Frequency Frequency in MHz. Default 127.5 MHz.
|
||||
-- @param #number Modulation Modulation 0=AM, 1=FM.
|
||||
-- @return #SPAWNSTATIC self
|
||||
function SPAWNSTATIC:InitFARP(CallsignID, Frequency, Modulation)
|
||||
self.InitFarp=true
|
||||
self.InitFarpCallsignID=CallsignID or 1
|
||||
self.InitFarpFreq=Frequency or 127.5
|
||||
self.InitFarpModu=Modulation or 0
|
||||
return self
|
||||
end
|
||||
|
||||
--- Initialize cargo mass.
|
||||
-- @param #SPAWNSTATIC self
|
||||
-- @param #number Mass Mass of the cargo in kg.
|
||||
-- @return #SPAWNSTATIC self
|
||||
function SPAWNSTATIC:InitCargoMass(Mass)
|
||||
self.InitStaticCargoMass=Mass
|
||||
return self
|
||||
end
|
||||
|
||||
--- Initialize as cargo.
|
||||
-- @param #SPAWNSTATIC self
|
||||
-- @param #boolean IsCargo If true, this static can act as cargo.
|
||||
-- @return #SPAWNSTATIC self
|
||||
function SPAWNSTATIC: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
|
||||
|
||||
--- Initialize country of the spawned static. This determines the category.
|
||||
-- @param #SPAWNSTATIC self
|
||||
-- @param #string CountryID The country ID, e.g. country.id.USA.
|
||||
-- @return #SPAWNSTATIC self
|
||||
function SPAWNSTATIC:InitCountry(CountryID)
|
||||
self.CountryID=CountryID
|
||||
return self
|
||||
end
|
||||
|
||||
--- Initialize name prefix statics get. This will be appended by "#0001", "#0002" etc.
|
||||
-- @param #SPAWNSTATIC self
|
||||
-- @param #string NamePrefix Name prefix of statics spawned. Will append #0001, etc to the name.
|
||||
-- @return #SPAWNSTATIC self
|
||||
function SPAWNSTATIC:InitNamePrefix(NamePrefix)
|
||||
self.SpawnTemplatePrefix=NamePrefix
|
||||
return self
|
||||
end
|
||||
|
||||
--- Init link to a unit.
|
||||
-- @param #SPAWNSTATIC self
|
||||
-- @param Wrapper.Unit#UNIT Unit The unit to which the static is linked.
|
||||
-- @param #number OffsetX Offset in X.
|
||||
-- @param #number OffsetY Offset in Y.
|
||||
-- @param #number OffsetAngle Offset angle in degrees.
|
||||
-- @return #SPAWNSTATIC self
|
||||
function SPAWNSTATIC:InitLinkToUnit(Unit, OffsetX, OffsetY, OffsetAngle)
|
||||
|
||||
self.InitLinkUnit=Unit
|
||||
self.InitOffsetX=OffsetX or 0
|
||||
self.InitOffsetY=OffsetY or 0
|
||||
self.InitOffsetAngle=OffsetAngle or 0
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Spawn a new STATIC object.
|
||||
-- @param #SPAWNSTATIC self
|
||||
-- @param #number Heading (Optional) The heading of the static, which is a number in degrees from 0 to 360. Default is the heading of the template.
|
||||
-- @param #string NewName (Optional) The name of the new static.
|
||||
-- @return Wrapper.Static#STATIC The static spawned.
|
||||
function SPAWNSTATIC:Spawn(Heading, NewName)
|
||||
|
||||
if Heading then
|
||||
self.InitStaticHeading=Heading
|
||||
end
|
||||
|
||||
if NewName then
|
||||
self.InitStaticName=NewName
|
||||
end
|
||||
|
||||
return self:_SpawnStatic(self.TemplateStaticUnit, self.CountryID)
|
||||
|
||||
end
|
||||
|
||||
--- Creates a new @{Static} from a POINT_VEC2.
|
||||
-- @param #SPAWNSTATIC self
|
||||
-- @param Core.Point#POINT_VEC2 PointVec2 The 2D coordinate where to spawn the static.
|
||||
-- @param #number Heading The heading of the static, which is a number in degrees from 0 to 360.
|
||||
-- @param #string (optional) The name of the new static.
|
||||
-- @return #SPAWNSTATIC
|
||||
function SPAWNSTATIC:SpawnFromPointVec2( PointVec2, Heading, NewName ) --R2.1
|
||||
self:F( { PointVec2, Heading, NewName } )
|
||||
|
||||
local StaticTemplate, CoalitionID, CategoryID, CountryID = _DATABASE:GetStaticGroupTemplate( self.SpawnTemplatePrefix )
|
||||
|
||||
if StaticTemplate then
|
||||
|
||||
local StaticUnitTemplate = StaticTemplate.units[1]
|
||||
|
||||
StaticUnitTemplate.x = PointVec2.x
|
||||
StaticUnitTemplate.y = PointVec2.z
|
||||
|
||||
StaticTemplate.route = nil
|
||||
StaticTemplate.groupId = nil
|
||||
|
||||
StaticTemplate.name = NewName or string.format("%s#%05d", self.SpawnTemplatePrefix, self.SpawnIndex )
|
||||
StaticUnitTemplate.name = StaticTemplate.name
|
||||
StaticUnitTemplate.heading = ( Heading / 180 ) * math.pi
|
||||
|
||||
_DATABASE:_RegisterStaticTemplate( StaticTemplate, CoalitionID, CategoryID, CountryID)
|
||||
|
||||
self:F({StaticTemplate = StaticTemplate})
|
||||
-- @param #string NewName (Optional) The name of the new static.
|
||||
-- @return Wrapper.Static#STATIC The static spawned.
|
||||
function SPAWNSTATIC:SpawnFromPointVec2(PointVec2, Heading, NewName)
|
||||
|
||||
local Static = coalition.addStaticObject( self.CountryID or CountryID, StaticTemplate.units[1] )
|
||||
|
||||
self.SpawnIndex = self.SpawnIndex + 1
|
||||
|
||||
return _DATABASE:FindStatic(Static:getName())
|
||||
end
|
||||
local vec2={x=PointVec2:GetX(), y=PointVec2:GetY()}
|
||||
|
||||
local Coordinate=COORDINATE:NewFromVec2(vec2)
|
||||
|
||||
return nil
|
||||
return self:SpawnFromCoordinate(Coordinate, Heading, NewName)
|
||||
end
|
||||
|
||||
|
||||
--- Respawns the original @{Static}.
|
||||
--- Creates a new @{Static} from a COORDINATE.
|
||||
-- @param #SPAWNSTATIC self
|
||||
-- @return #SPAWNSTATIC
|
||||
function SPAWNSTATIC:ReSpawn()
|
||||
|
||||
local StaticTemplate, CoalitionID, CategoryID, CountryID = _DATABASE:GetStaticGroupTemplate( self.SpawnTemplatePrefix )
|
||||
|
||||
if StaticTemplate then
|
||||
-- @param Core.Point#COORDINATE Coordinate The 3D coordinate where to spawn the static.
|
||||
-- @param #number Heading (Optional) Heading The heading of the static in degrees. Default is 0 degrees.
|
||||
-- @param #string NewName (Optional) The name of the new static.
|
||||
-- @return Wrapper.Static#STATIC The spawned STATIC object.
|
||||
function SPAWNSTATIC:SpawnFromCoordinate(Coordinate, Heading, NewName)
|
||||
|
||||
local StaticUnitTemplate = StaticTemplate.units[1]
|
||||
StaticTemplate.route = nil
|
||||
StaticTemplate.groupId = nil
|
||||
|
||||
local Static = coalition.addStaticObject( self.CountryID or CountryID, StaticTemplate.units[1] )
|
||||
|
||||
return _DATABASE:FindStatic(Static:getName())
|
||||
-- Set up coordinate.
|
||||
self.InitStaticCoordinate=Coordinate
|
||||
|
||||
if Heading then
|
||||
self.InitStaticHeading=Heading
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
--- Creates the original @{Static} at a POINT_VEC2.
|
||||
-- @param #SPAWNSTATIC self
|
||||
-- @param Core.Point#COORDINATE Coordinate The 2D coordinate where to spawn the static.
|
||||
-- @param #number Heading The heading of the static, which is a number in degrees from 0 to 360.
|
||||
-- @return #SPAWNSTATIC
|
||||
function SPAWNSTATIC:ReSpawnAt( Coordinate, Heading )
|
||||
|
||||
local StaticTemplate, CoalitionID, CategoryID, CountryID = _DATABASE:GetStaticGroupTemplate( self.SpawnTemplatePrefix )
|
||||
|
||||
if StaticTemplate then
|
||||
|
||||
local StaticUnitTemplate = StaticTemplate.units[1]
|
||||
|
||||
StaticUnitTemplate.x = Coordinate.x
|
||||
StaticUnitTemplate.y = Coordinate.z
|
||||
|
||||
StaticUnitTemplate.heading = Heading and ( ( Heading / 180 ) * math.pi ) or StaticTemplate.heading
|
||||
|
||||
local Static = coalition.addStaticObject( self.CountryID or CountryID, StaticTemplate.units[1] )
|
||||
|
||||
return _DATABASE:FindStatic(Static:getName())
|
||||
if NewName then
|
||||
self.InitStaticName=NewName
|
||||
end
|
||||
|
||||
return nil
|
||||
|
||||
return self:_SpawnStatic(self.TemplateStaticUnit, self.CountryID)
|
||||
end
|
||||
|
||||
|
||||
--- Creates a new @{Static} from a @{Zone}.
|
||||
-- @param #SPAWNSTATIC self
|
||||
-- @param Core.Zone#ZONE_BASE Zone The Zone where to spawn the static.
|
||||
-- @param #number Heading The heading of the static, which is a number in degrees from 0 to 360.
|
||||
-- @param #string (optional) The name of the new static.
|
||||
-- @return #SPAWNSTATIC
|
||||
function SPAWNSTATIC:SpawnFromZone( Zone, Heading, NewName ) --R2.1
|
||||
self:F( { Zone, Heading, NewName } )
|
||||
-- @param #number Heading (Optional)The heading of the static in degrees. Default is the heading of the template.
|
||||
-- @param #string NewName (Optional) The name of the new static.
|
||||
-- @return Wrapper.Static#STATIC The static spawned.
|
||||
function SPAWNSTATIC:SpawnFromZone(Zone, Heading, NewName)
|
||||
|
||||
-- Spawn the new static at the center of the zone.
|
||||
local Static = self:SpawnFromPointVec2( Zone:GetPointVec2(), Heading, NewName )
|
||||
|
||||
return Static
|
||||
end
|
||||
|
||||
--- Spawns a new static using a given template. Additionally, the country ID needs to be specified, which also determines the coalition of the spawned static.
|
||||
-- @param #SPAWNSTATIC self
|
||||
-- @param #SPAWNSTATIC.TemplateData Template Spawn unit template.
|
||||
-- @param #number CountryID The country ID.
|
||||
-- @return Wrapper.Static#STATIC The static spawned.
|
||||
function SPAWNSTATIC:_SpawnStatic(Template, CountryID)
|
||||
|
||||
Template=Template or {}
|
||||
|
||||
local CountryID=CountryID or self.CountryID
|
||||
|
||||
if self.InitStaticType then
|
||||
Template.type=self.InitStaticType
|
||||
end
|
||||
|
||||
if self.InitStaticCategory then
|
||||
Template.category=self.InitStaticCategory
|
||||
end
|
||||
|
||||
if self.InitStaticCoordinate then
|
||||
Template.x = self.InitStaticCoordinate.x
|
||||
Template.y = self.InitStaticCoordinate.z
|
||||
Template.alt = self.InitStaticCoordinate.y
|
||||
end
|
||||
|
||||
if self.InitStaticHeading then
|
||||
Template.heading = math.rad(self.InitStaticHeading)
|
||||
end
|
||||
|
||||
if self.InitStaticShape then
|
||||
Template.shape_name=self.InitStaticShape
|
||||
end
|
||||
|
||||
if self.InitStaticLivery then
|
||||
Template.livery_id=self.InitStaticLivery
|
||||
end
|
||||
|
||||
if self.InitStaticDead~=nil then
|
||||
Template.dead=self.InitStaticDead
|
||||
end
|
||||
|
||||
if self.InitStaticCargo~=nil then
|
||||
Template.canCargo=self.InitStaticCargo
|
||||
end
|
||||
|
||||
if self.InitStaticCargoMass~=nil then
|
||||
Template.mass=self.InitStaticCargoMass
|
||||
end
|
||||
|
||||
if self.InitLinkUnit then
|
||||
Template.linkUnit=self.InitLinkUnit:GetID()
|
||||
Template.linkOffset=true
|
||||
Template.offsets={}
|
||||
Template.offsets.y=self.InitOffsetY
|
||||
Template.offsets.x=self.InitOffsetX
|
||||
Template.offsets.angle=self.InitOffsetAngle and math.rad(self.InitOffsetAngle) or 0
|
||||
end
|
||||
|
||||
if self.InitFarp then
|
||||
Template.heliport_callsign_id = self.InitFarpCallsignID
|
||||
Template.heliport_frequency = self.InitFarpFreq
|
||||
Template.heliport_modulation = self.InitFarpModu
|
||||
Template.unitId=nil
|
||||
end
|
||||
|
||||
-- Increase spawn index counter.
|
||||
self.SpawnIndex = self.SpawnIndex + 1
|
||||
|
||||
-- Name of the spawned static.
|
||||
Template.name = self.InitStaticName or string.format("%s#%05d", self.SpawnTemplatePrefix, self.SpawnIndex)
|
||||
|
||||
-- Add and register the new static.
|
||||
local mystatic=_DATABASE:AddStatic(Template.name)
|
||||
|
||||
-- Debug output.
|
||||
self:T(Template)
|
||||
|
||||
-- Add static to the game.
|
||||
local Static=nil
|
||||
|
||||
if self.InitFarp then
|
||||
|
||||
local TemplateGroup={}
|
||||
TemplateGroup.units={}
|
||||
TemplateGroup.units[1]=Template
|
||||
|
||||
TemplateGroup.visible=true
|
||||
TemplateGroup.hidden=false
|
||||
TemplateGroup.x=Template.x
|
||||
TemplateGroup.y=Template.y
|
||||
TemplateGroup.name=Template.name
|
||||
|
||||
self:T("Spawning FARP")
|
||||
self:T({Template=Template})
|
||||
self:T({TemplateGroup=TemplateGroup})
|
||||
|
||||
-- ED's dirty way to spawn FARPS.
|
||||
Static=coalition.addGroup(CountryID, -1, TemplateGroup)
|
||||
else
|
||||
self:T("Spawning Static")
|
||||
self:T2({Template=Template})
|
||||
Static=coalition.addStaticObject(CountryID, Template)
|
||||
end
|
||||
|
||||
return mystatic
|
||||
end
|
||||
|
||||
@@ -126,6 +126,39 @@ do
|
||||
-- @param Wrapper.Positionable#POSITIONABLE Target
|
||||
-- @param #number LaserCode Laser code.
|
||||
-- @param #number Duration Duration of lasing in seconds.
|
||||
|
||||
self:AddTransition( "Off", "LaseOnCoordinate", "On" )
|
||||
|
||||
--- LaseOnCoordinate Handler OnBefore for SPOT.
|
||||
-- @function [parent=#SPOT] OnBeforeLaseOnCoordinate
|
||||
-- @param #SPOT self
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @return #boolean
|
||||
|
||||
--- LaseOnCoordinate Handler OnAfter for SPOT.
|
||||
-- @function [parent=#SPOT] OnAfterLaseOnCoordinate
|
||||
-- @param #SPOT self
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
|
||||
--- LaseOnCoordinate Trigger for SPOT.
|
||||
-- @function [parent=#SPOT] LaseOnCoordinate
|
||||
-- @param #SPOT self
|
||||
-- @param Core.Point#COORDINATE Coordinate The coordinate to lase.
|
||||
-- @param #number LaserCode Laser code.
|
||||
-- @param #number Duration Duration of lasing in seconds.
|
||||
|
||||
--- LaseOn Asynchronous Trigger for SPOT
|
||||
-- @function [parent=#SPOT] __LaseOn
|
||||
-- @param #SPOT self
|
||||
-- @param #number Delay
|
||||
-- @param Wrapper.Positionable#POSITIONABLE Target
|
||||
-- @param #number LaserCode Laser code.
|
||||
-- @param #number Duration Duration of lasing in seconds.
|
||||
|
||||
|
||||
|
||||
self:AddTransition( "On", "Lasing", "On" )
|
||||
@@ -194,7 +227,8 @@ do
|
||||
return self
|
||||
end
|
||||
|
||||
--- @param #SPOT self
|
||||
--- On after LaseOn event. Activates the laser spot.
|
||||
-- @param #SPOT self
|
||||
-- @param From
|
||||
-- @param Event
|
||||
-- @param To
|
||||
@@ -226,6 +260,40 @@ do
|
||||
|
||||
self:__Lasing( -1 )
|
||||
end
|
||||
|
||||
|
||||
--- On after LaseOnCoordinate event. Activates the laser spot.
|
||||
-- @param #SPOT self
|
||||
-- @param From
|
||||
-- @param Event
|
||||
-- @param To
|
||||
-- @param Core.Point#COORDINATE Coordinate The coordinate at which the laser is pointing.
|
||||
-- @param #number LaserCode Laser code.
|
||||
-- @param #number Duration Duration of lasing in seconds.
|
||||
function SPOT:onafterLaseOnCoordinate(From, Event, To, Coordinate, LaserCode, Duration)
|
||||
self:F( { "LaseOnCoordinate", Coordinate, LaserCode, Duration } )
|
||||
|
||||
local function StopLase( self )
|
||||
self:LaseOff()
|
||||
end
|
||||
|
||||
self.Target = nil
|
||||
self.TargetCoord=Coordinate
|
||||
self.LaserCode = LaserCode
|
||||
|
||||
self.Lasing = true
|
||||
|
||||
local RecceDcsUnit = self.Recce:GetDCSObject()
|
||||
|
||||
self.SpotIR = Spot.createInfraRed( RecceDcsUnit, { x = 0, y = 1, z = 0 }, Coordinate:GetVec3() )
|
||||
self.SpotLaser = Spot.createLaser( RecceDcsUnit, { x = 0, y = 1, z = 0 }, Coordinate:GetVec3(), LaserCode )
|
||||
|
||||
if Duration then
|
||||
self.ScheduleID = self.LaseScheduler:Schedule( self, StopLase, {self}, Duration )
|
||||
end
|
||||
|
||||
self:__Lasing(-1)
|
||||
end
|
||||
|
||||
--- @param #SPOT self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
@@ -246,10 +314,20 @@ do
|
||||
-- @param To
|
||||
function SPOT:onafterLasing( From, Event, To )
|
||||
|
||||
if self.Target:IsAlive() then
|
||||
if self.Target and self.Target:IsAlive() then
|
||||
self.SpotIR:setPoint( self.Target:GetPointVec3():AddY(1):AddY(math.random(-100,100)/100):AddX(math.random(-100,100)/100):GetVec3() )
|
||||
self.SpotLaser:setPoint( self.Target:GetPointVec3():AddY(1):GetVec3() )
|
||||
self:__Lasing( -0.2 )
|
||||
elseif self.TargetCoord then
|
||||
|
||||
-- Wiggle the IR spot a bit.
|
||||
local irvec3={x=self.TargetCoord.x+math.random(-100,100)/100, y=self.TargetCoord.y+math.random(-100,100)/100, z=self.TargetCoord.z} --#DCS.Vec3
|
||||
local lsvec3={x=self.TargetCoord.x, y=self.TargetCoord.y, z=self.TargetCoord.z} --#DCS.Vec3
|
||||
|
||||
self.SpotIR:setPoint(irvec3)
|
||||
self.SpotLaser:setPoint(lsvec3)
|
||||
|
||||
self:__Lasing(-0.25)
|
||||
else
|
||||
self:F( { "Target is not alive", self.Target:IsAlive() } )
|
||||
end
|
||||
@@ -263,7 +341,7 @@ do
|
||||
-- @return #SPOT
|
||||
function SPOT:onafterLaseOff( From, Event, To )
|
||||
|
||||
self:F( {"Stopped lasing for ", self.Target:GetName() , SpotIR = self.SportIR, SpotLaser = self.SpotLaser } )
|
||||
self:F( {"Stopped lasing for ", self.Target and self.Target:GetName() or "coord", SpotIR = self.SportIR, SpotLaser = self.SpotLaser } )
|
||||
|
||||
self.Lasing = false
|
||||
|
||||
|
||||
292
Moose Development/Moose/Core/Timer.lua
Normal file
292
Moose Development/Moose/Core/Timer.lua
Normal file
@@ -0,0 +1,292 @@
|
||||
--- **Core** - Timer scheduler.
|
||||
--
|
||||
-- **Main Features:**
|
||||
--
|
||||
-- * Delay function calls
|
||||
-- * Easy set up and little overhead
|
||||
-- * Set start, stop and time interval
|
||||
-- * Define max function calls
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **funkyfranky**
|
||||
-- @module Core.Timer
|
||||
-- @image Core_Scheduler.JPG
|
||||
|
||||
|
||||
--- TIMER class.
|
||||
-- @type TIMER
|
||||
-- @field #string ClassName Name of the class.
|
||||
-- @field #string lid Class id string for output to DCS log file.
|
||||
-- @field #number tid Timer ID returned by the DCS API function.
|
||||
-- @field #number uid Unique ID of the timer.
|
||||
-- @field #function func Timer function.
|
||||
-- @field #table para Parameters passed to the timer function.
|
||||
-- @field #number Tstart Relative start time in seconds.
|
||||
-- @field #number Tstop Relative stop time in seconds.
|
||||
-- @field #number dT Time interval between function calls in seconds.
|
||||
-- @field #number ncalls Counter of function calls.
|
||||
-- @field #number ncallsMax Max number of function calls. If reached, timer is stopped.
|
||||
-- @field #boolean isrunning If `true`, timer is running. Else it was not started yet or was stopped.
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
--- *Better three hours too soon than a minute too late.* - William Shakespeare
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- # 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.
|
||||
--
|
||||
-- It provides an easy interface to the DCS [timer.scheduleFunction](https://wiki.hoggitworld.com/view/DCS_func_scheduleFunction).
|
||||
--
|
||||
-- # Construction
|
||||
--
|
||||
-- A new TIMER is created by the @{#TIMER.New}(*func*, *...*) function
|
||||
--
|
||||
-- local mytimer=TIMER:New(myfunction, a, b)
|
||||
--
|
||||
-- The first parameter *func* is the function that is called followed by the necessary comma separeted parameters that are passed to that function.
|
||||
--
|
||||
-- ## Starting the Timer
|
||||
--
|
||||
-- The timer is started by the @{#TIMER.Start}(*Tstart*, *dT*, *Duration*) function
|
||||
--
|
||||
-- mytimer:Start(5, 1, 20)
|
||||
--
|
||||
-- where
|
||||
--
|
||||
-- * *Tstart* is the relative start time in seconds. In the example, the first function call happens after 5 sec.
|
||||
-- * *dT* is the time interval between function calls in seconds. Above, the function is called once per second.
|
||||
-- * *Duration* is the duration in seconds after which the timer is stopped. This is relative to the start time. Here, the timer will run for 20 seconds.
|
||||
--
|
||||
-- Note that
|
||||
--
|
||||
-- * if *Tstart* is not specified (*nil*), the first function call happens immediately, i.e. after one millisecond.
|
||||
-- * if *dT* is not specified (*nil*), the function is called only once.
|
||||
-- * if *Duration* is not specified (*nil*), the timer runs forever or until stopped manually or until the max function calls are reached (see below).
|
||||
--
|
||||
-- For example,
|
||||
--
|
||||
-- mytimer:Start(3) -- Will call the function once after 3 seconds.
|
||||
-- mytimer:Start(nil, 0.5) -- Will call right now and then every 0.5 sec until all eternity.
|
||||
-- mytimer:Start(nil, 2.0, 20) -- Will call right now and then every 2.0 sec for 20 sec.
|
||||
-- mytimer:Start(1.0, nil, 10) -- Does not make sense as the function is only called once anyway.
|
||||
--
|
||||
-- ## Stopping the Timer
|
||||
--
|
||||
-- The timer can be stopped manually by the @{#TIMER.Stop}(*Delay*) function
|
||||
--
|
||||
-- mytimer:Stop()
|
||||
--
|
||||
-- If the optional paramter *Delay* is specified, the timer is stopped after *delay* seconds.
|
||||
--
|
||||
-- ## Limit Function Calls
|
||||
--
|
||||
-- The timer can be stopped after a certain amount of function calles with the @{#TIMER.SetMaxFunctionCalls}(*Nmax*) function
|
||||
--
|
||||
-- mytimer:SetMaxFunctionCalls(20)
|
||||
--
|
||||
-- where *Nmax* is the number of calls after which the timer is stopped, here 20.
|
||||
--
|
||||
-- For example,
|
||||
--
|
||||
-- mytimer:SetMaxFunctionCalls(66):Start(1.0, 0.1)
|
||||
--
|
||||
-- will start the timer after one second and call the function every 0.1 seconds. Once the function has been called 66 times, the timer is stopped.
|
||||
--
|
||||
--
|
||||
-- @field #TIMER
|
||||
TIMER = {
|
||||
ClassName = "TIMER",
|
||||
lid = nil,
|
||||
}
|
||||
|
||||
--- Timer ID.
|
||||
_TIMERID=0
|
||||
|
||||
--- Timer data base.
|
||||
--_TIMERDB={}
|
||||
|
||||
--- TIMER class version.
|
||||
-- @field #string version
|
||||
TIMER.version="0.1.1"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- TODO list
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
-- TODO: A lot.
|
||||
-- TODO: Write docs.
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Constructor
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Create a new TIMER object.
|
||||
-- @param #TIMER self
|
||||
-- @param #function Function The function to call.
|
||||
-- @param ... Parameters passed to the function if any.
|
||||
-- @return #TIMER self
|
||||
function TIMER:New(Function, ...)
|
||||
|
||||
-- Inherit BASE.
|
||||
local self=BASE:Inherit(self, BASE:New()) --#TIMER
|
||||
|
||||
-- Function to call.
|
||||
self.func=Function
|
||||
|
||||
-- Function arguments.
|
||||
self.para=arg or {}
|
||||
|
||||
-- Number of function calls.
|
||||
self.ncalls=0
|
||||
|
||||
-- Not running yet.
|
||||
self.isrunning=false
|
||||
|
||||
-- Increase counter
|
||||
_TIMERID=_TIMERID+1
|
||||
|
||||
-- Set UID.
|
||||
self.uid=_TIMERID
|
||||
|
||||
-- Log id.
|
||||
self.lid=string.format("TIMER UID=%d | ", self.uid)
|
||||
|
||||
-- Add to DB.
|
||||
--_TIMERDB[self.uid]=self
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Create a new TIMER object.
|
||||
-- @param #TIMER self
|
||||
-- @param #number Tstart Relative start time in seconds.
|
||||
-- @param #number dT Interval between function calls in seconds. If not specified `nil`, the function is called only once.
|
||||
-- @param #number Duration Time in seconds for how long the timer is running. If not specified `nil`, the timer runs forever or until stopped manually by the `TIMER:Stop()` function.
|
||||
-- @return #TIMER self
|
||||
function TIMER:Start(Tstart, dT, Duration)
|
||||
|
||||
-- Current time.
|
||||
local Tnow=timer.getTime()
|
||||
|
||||
-- Start time in sec.
|
||||
self.Tstart=Tstart and Tnow+Tstart or Tnow+0.001 -- one millisecond delay if Tstart=nil
|
||||
|
||||
-- Set time interval.
|
||||
self.dT=dT
|
||||
|
||||
-- Stop time.
|
||||
if Duration then
|
||||
self.Tstop=self.Tstart+Duration
|
||||
end
|
||||
|
||||
-- Call DCS timer function.
|
||||
self.tid=timer.scheduleFunction(self._Function, self, self.Tstart)
|
||||
|
||||
-- Set log id.
|
||||
self.lid=string.format("TIMER UID=%d/%d | ", self.uid, self.tid)
|
||||
|
||||
-- Is now running.
|
||||
self.isrunning=true
|
||||
|
||||
-- Debug info.
|
||||
self:T(self.lid..string.format("Starting Timer in %.3f sec, dT=%s, Tstop=%s", self.Tstart-Tnow, tostring(self.dT), tostring(self.Tstop)))
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Stop the timer by removing the timer function.
|
||||
-- @param #TIMER self
|
||||
-- @param #number Delay (Optional) Delay in seconds, before the timer is stopped.
|
||||
-- @return #TIMER self
|
||||
function TIMER:Stop(Delay)
|
||||
|
||||
if Delay and Delay>0 then
|
||||
|
||||
self.Tstop=timer.getTime()+Delay
|
||||
|
||||
else
|
||||
|
||||
if self.tid then
|
||||
|
||||
-- Remove timer function.
|
||||
self:T(self.lid..string.format("Stopping timer by removing timer function after %d calls!", self.ncalls))
|
||||
timer.removeFunction(self.tid)
|
||||
|
||||
-- Not running any more.
|
||||
self.isrunning=false
|
||||
|
||||
-- Remove DB entry.
|
||||
--_TIMERDB[self.uid]=nil
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set max number of function calls. When the function has been called this many times, the TIMER is stopped.
|
||||
-- @param #TIMER self
|
||||
-- @param #number Nmax Set number of max function calls.
|
||||
-- @return #TIMER self
|
||||
function TIMER:SetMaxFunctionCalls(Nmax)
|
||||
self.ncallsMax=Nmax
|
||||
return self
|
||||
end
|
||||
|
||||
--- Check if the timer has been started and was not stopped.
|
||||
-- @param #TIMER self
|
||||
-- @return #boolean If `true`, the timer is running.
|
||||
function TIMER:IsRunning()
|
||||
return self.isrunning
|
||||
end
|
||||
|
||||
--- Call timer function.
|
||||
-- @param #TIMER self
|
||||
-- @param #number time DCS model time in seconds.
|
||||
-- @return #number Time when the function is called again or `nil` if the timer is stopped.
|
||||
function TIMER:_Function(time)
|
||||
|
||||
-- Call function.
|
||||
self.func(unpack(self.para))
|
||||
|
||||
-- Increase number of calls.
|
||||
self.ncalls=self.ncalls+1
|
||||
|
||||
-- Next time.
|
||||
local Tnext=self.dT and time+self.dT or nil
|
||||
|
||||
-- Check if we stop the timer.
|
||||
local stopme=false
|
||||
if Tnext==nil then
|
||||
-- No next time.
|
||||
self:T(self.lid..string.format("No next time as dT=nil ==> Stopping timer after %d function calls", self.ncalls))
|
||||
stopme=true
|
||||
elseif self.Tstop and Tnext>self.Tstop then
|
||||
-- Stop time passed.
|
||||
self:T(self.lid..string.format("Stop time passed %.3f > %.3f ==> Stopping timer after %d function calls", Tnext, self.Tstop, self.ncalls))
|
||||
stopme=true
|
||||
elseif self.ncallsMax and self.ncalls>=self.ncallsMax then
|
||||
-- Number of max function calls reached.
|
||||
self:T(self.lid..string.format("Max function calls Nmax=%d reached ==> Stopping timer after %d function calls", self.ncallsMax, self.ncalls))
|
||||
stopme=true
|
||||
end
|
||||
|
||||
if stopme then
|
||||
-- Remove timer function.
|
||||
self:Stop()
|
||||
return nil
|
||||
else
|
||||
-- Call again in Tnext seconds.
|
||||
return Tnext
|
||||
end
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
@@ -19,6 +19,8 @@
|
||||
do -- UserFlag
|
||||
|
||||
--- @type USERFLAG
|
||||
-- @field #string ClassName Name of the class
|
||||
-- @field #string UserFlagName Name of the flag.
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
|
||||
@@ -30,7 +32,8 @@ do -- UserFlag
|
||||
--
|
||||
-- @field #USERFLAG
|
||||
USERFLAG = {
|
||||
ClassName = "USERFLAG",
|
||||
ClassName = "USERFLAG",
|
||||
UserFlagName = nil,
|
||||
}
|
||||
|
||||
--- USERFLAG Constructor.
|
||||
@@ -46,18 +49,30 @@ do -- UserFlag
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get the userflag name.
|
||||
-- @param #USERFLAG self
|
||||
-- @return #string Name of the user flag.
|
||||
function USERFLAG:GetName()
|
||||
return self.UserFlagName
|
||||
end
|
||||
|
||||
--- Set the userflag to a given Number.
|
||||
-- @param #USERFLAG self
|
||||
-- @param #number Number The number value to be checked if it is the same as the userflag.
|
||||
-- @param #number Delay Delay in seconds, before the flag is set.
|
||||
-- @return #USERFLAG The userflag instance.
|
||||
-- @usage
|
||||
-- local BlueVictory = USERFLAG:New( "VictoryBlue" )
|
||||
-- BlueVictory:Set( 100 ) -- Set the UserFlag VictoryBlue to 100.
|
||||
--
|
||||
function USERFLAG:Set( Number ) --R2.3
|
||||
function USERFLAG:Set( Number, Delay ) --R2.3
|
||||
|
||||
trigger.action.setUserFlag( self.UserFlagName, Number )
|
||||
if Delay and Delay>0 then
|
||||
self:ScheduleOnce(Delay, USERFLAG.Set, self, Number)
|
||||
else
|
||||
--env.info(string.format("Setting flag \"%s\" to %d at T=%.1f", self.UserFlagName, Number, timer.getTime()))
|
||||
trigger.action.setUserFlag( self.UserFlagName, Number )
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
@@ -70,7 +85,7 @@ do -- UserFlag
|
||||
-- local BlueVictory = USERFLAG:New( "VictoryBlue" )
|
||||
-- local BlueVictoryValue = BlueVictory:Get() -- Get the UserFlag VictoryBlue value.
|
||||
--
|
||||
function USERFLAG:Get( Number ) --R2.3
|
||||
function USERFLAG:Get() --R2.3
|
||||
|
||||
return trigger.misc.getUserFlag( self.UserFlagName )
|
||||
end
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
203
Moose Development/Moose/Core/Zone_Detection.lua
Normal file
203
Moose Development/Moose/Core/Zone_Detection.lua
Normal file
@@ -0,0 +1,203 @@
|
||||
|
||||
--- The ZONE_DETECTION class, defined by a zone name, a detection object and a radius.
|
||||
-- @type ZONE_DETECTION
|
||||
-- @field DCS#Vec2 Vec2 The current location of the zone.
|
||||
-- @field DCS#Distance Radius The radius of the zone.
|
||||
-- @extends #ZONE_BASE
|
||||
|
||||
--- The ZONE_DETECTION class defined by a zone name, a location and a radius.
|
||||
-- This class implements the inherited functions from Core.Zone#ZONE_BASE taking into account the own zone format and properties.
|
||||
--
|
||||
-- ## ZONE_DETECTION constructor
|
||||
--
|
||||
-- * @{#ZONE_DETECTION.New}(): Constructor.
|
||||
--
|
||||
-- @field #ZONE_DETECTION
|
||||
ZONE_DETECTION = {
|
||||
ClassName="ZONE_DETECTION",
|
||||
}
|
||||
|
||||
--- Constructor of @{#ZONE_DETECTION}, taking the zone name, the zone location and a radius.
|
||||
-- @param #ZONE_DETECTION self
|
||||
-- @param #string ZoneName Name of the zone.
|
||||
-- @param Functional.Detection#DETECTION_BASE Detection The detection object defining the locations of the central detections.
|
||||
-- @param DCS#Distance Radius The radius around the detections defining the combined zone.
|
||||
-- @return #ZONE_DETECTION self
|
||||
function ZONE_DETECTION:New( ZoneName, Detection, Radius )
|
||||
local self = BASE:Inherit( self, ZONE_BASE:New( ZoneName ) ) -- #ZONE_DETECTION
|
||||
self:F( { ZoneName, Detection, Radius } )
|
||||
|
||||
self.Detection = Detection
|
||||
self.Radius = Radius
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Bounds the zone with tires.
|
||||
-- @param #ZONE_DETECTION self
|
||||
-- @param #number Points (optional) The amount of points in the circle. Default 360.
|
||||
-- @param DCS#country.id CountryID The country id of the tire objects, e.g. country.id.USA for blue or country.id.RUSSIA for red.
|
||||
-- @param #boolean UnBound (Optional) If true the tyres will be destroyed.
|
||||
-- @return #ZONE_DETECTION self
|
||||
function ZONE_DETECTION:BoundZone( Points, CountryID, UnBound )
|
||||
|
||||
local Point = {}
|
||||
local Vec2 = self:GetVec2()
|
||||
|
||||
Points = Points and Points or 360
|
||||
|
||||
local Angle
|
||||
local RadialBase = math.pi*2
|
||||
|
||||
--
|
||||
for Angle = 0, 360, (360 / Points ) do
|
||||
local Radial = Angle * RadialBase / 360
|
||||
Point.x = Vec2.x + math.cos( Radial ) * self:GetRadius()
|
||||
Point.y = Vec2.y + math.sin( Radial ) * self:GetRadius()
|
||||
|
||||
local CountryName = _DATABASE.COUNTRY_NAME[CountryID]
|
||||
|
||||
local Tire = {
|
||||
["country"] = CountryName,
|
||||
["category"] = "Fortifications",
|
||||
["canCargo"] = false,
|
||||
["shape_name"] = "H-tyre_B_WF",
|
||||
["type"] = "Black_Tyre_WF",
|
||||
--["unitId"] = Angle + 10000,
|
||||
["y"] = Point.y,
|
||||
["x"] = Point.x,
|
||||
["name"] = string.format( "%s-Tire #%0d", self:GetName(), Angle ),
|
||||
["heading"] = 0,
|
||||
} -- end of ["group"]
|
||||
|
||||
local Group = coalition.addStaticObject( CountryID, Tire )
|
||||
if UnBound and UnBound == true then
|
||||
Group:destroy()
|
||||
end
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Smokes the zone boundaries in a color.
|
||||
-- @param #ZONE_DETECTION self
|
||||
-- @param Utilities.Utils#SMOKECOLOR SmokeColor The smoke color.
|
||||
-- @param #number Points (optional) The amount of points in the circle.
|
||||
-- @param #number AddHeight (optional) The height to be added for the smoke.
|
||||
-- @param #number AddOffSet (optional) The angle to be added for the smoking start position.
|
||||
-- @return #ZONE_DETECTION self
|
||||
function ZONE_DETECTION:SmokeZone( SmokeColor, Points, AddHeight, AngleOffset )
|
||||
self:F2( SmokeColor )
|
||||
|
||||
local Point = {}
|
||||
local Vec2 = self:GetVec2()
|
||||
|
||||
AddHeight = AddHeight or 0
|
||||
AngleOffset = AngleOffset or 0
|
||||
|
||||
Points = Points and Points or 360
|
||||
|
||||
local Angle
|
||||
local RadialBase = math.pi*2
|
||||
|
||||
for Angle = 0, 360, 360 / Points do
|
||||
local Radial = ( Angle + AngleOffset ) * RadialBase / 360
|
||||
Point.x = Vec2.x + math.cos( Radial ) * self:GetRadius()
|
||||
Point.y = Vec2.y + math.sin( Radial ) * self:GetRadius()
|
||||
POINT_VEC2:New( Point.x, Point.y, AddHeight ):Smoke( SmokeColor )
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Flares the zone boundaries in a color.
|
||||
-- @param #ZONE_DETECTION self
|
||||
-- @param Utilities.Utils#FLARECOLOR FlareColor The flare color.
|
||||
-- @param #number Points (optional) The amount of points in the circle.
|
||||
-- @param DCS#Azimuth Azimuth (optional) Azimuth The azimuth of the flare.
|
||||
-- @param #number AddHeight (optional) The height to be added for the smoke.
|
||||
-- @return #ZONE_DETECTION self
|
||||
function ZONE_DETECTION:FlareZone( FlareColor, Points, Azimuth, AddHeight )
|
||||
self:F2( { FlareColor, Azimuth } )
|
||||
|
||||
local Point = {}
|
||||
local Vec2 = self:GetVec2()
|
||||
|
||||
AddHeight = AddHeight or 0
|
||||
|
||||
Points = Points and Points or 360
|
||||
|
||||
local Angle
|
||||
local RadialBase = math.pi*2
|
||||
|
||||
for Angle = 0, 360, 360 / Points do
|
||||
local Radial = Angle * RadialBase / 360
|
||||
Point.x = Vec2.x + math.cos( Radial ) * self:GetRadius()
|
||||
Point.y = Vec2.y + math.sin( Radial ) * self:GetRadius()
|
||||
POINT_VEC2:New( Point.x, Point.y, AddHeight ):Flare( FlareColor, Azimuth )
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Returns the radius around the detected locations defining the combine zone.
|
||||
-- @param #ZONE_DETECTION self
|
||||
-- @return DCS#Distance The radius.
|
||||
function ZONE_DETECTION:GetRadius()
|
||||
self:F2( self.ZoneName )
|
||||
|
||||
self:T2( { self.Radius } )
|
||||
|
||||
return self.Radius
|
||||
end
|
||||
|
||||
--- Sets the radius around the detected locations defining the combine zone.
|
||||
-- @param #ZONE_DETECTION self
|
||||
-- @param DCS#Distance Radius The radius.
|
||||
-- @return #ZONE_DETECTION self
|
||||
function ZONE_DETECTION:SetRadius( Radius )
|
||||
self:F2( self.ZoneName )
|
||||
|
||||
self.Radius = Radius
|
||||
self:T2( { self.Radius } )
|
||||
|
||||
return self.Radius
|
||||
end
|
||||
|
||||
|
||||
|
||||
--- Returns if a location is within the zone.
|
||||
-- @param #ZONE_DETECTION self
|
||||
-- @param DCS#Vec2 Vec2 The location to test.
|
||||
-- @return #boolean true if the location is within the zone.
|
||||
function ZONE_DETECTION:IsVec2InZone( Vec2 )
|
||||
self:F2( Vec2 )
|
||||
|
||||
local Coordinates = self.Detection:GetDetectedItemCoordinates() -- This returns a list of coordinates that define the (central) locations of the detections.
|
||||
|
||||
for CoordinateID, Coordinate in pairs( Coordinates ) do
|
||||
local ZoneVec2 = Coordinate:GetVec2()
|
||||
if ZoneVec2 then
|
||||
if (( Vec2.x - ZoneVec2.x )^2 + ( Vec2.y - ZoneVec2.y ) ^2 ) ^ 0.5 <= self:GetRadius() then
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
--- Returns if a point is within the zone.
|
||||
-- @param #ZONE_DETECTION self
|
||||
-- @param DCS#Vec3 Vec3 The point to test.
|
||||
-- @return #boolean true if the point is within the zone.
|
||||
function ZONE_DETECTION:IsVec3InZone( Vec3 )
|
||||
self:F2( Vec3 )
|
||||
|
||||
local InZone = self:IsVec2InZone( { x = Vec3.x, y = Vec3.z } )
|
||||
|
||||
return InZone
|
||||
end
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
--- DCS API prototypes
|
||||
-- See [https://wiki.hoggitworld.com/view/Simulator_Scripting_Engine_Documentation](https://wiki.hoggitworld.com/view/Simulator_Scripting_Engine_Documentation)
|
||||
-- for further explanation and examples.
|
||||
--- **DCS API** Prototypes
|
||||
--
|
||||
-- See the [Simulator Scripting Engine Documentation](https://wiki.hoggitworld.com/view/Simulator_Scripting_Engine_Documentation) on Hoggit for further explanation and examples.
|
||||
--
|
||||
-- @module DCS
|
||||
-- @image MOOSE.JPG
|
||||
|
||||
@@ -46,9 +47,13 @@ do -- world
|
||||
-- @field S_EVENT_PLAYER_COMMENT
|
||||
-- @field S_EVENT_SHOOTING_START [https://wiki.hoggitworld.com/view/DCS_event_shooting_start](https://wiki.hoggitworld.com/view/DCS_event_shooting_start)
|
||||
-- @field S_EVENT_SHOOTING_END [https://wiki.hoggitworld.com/view/DCS_event_shooting_end](https://wiki.hoggitworld.com/view/DCS_event_shooting_end)
|
||||
-- @field S_EVENT_MARK ADDED [https://wiki.hoggitworld.com/view/DCS_event_mark_added](https://wiki.hoggitworld.com/view/DCS_event_mark_added)
|
||||
-- @field S_EVENT_MARK CHANGE [https://wiki.hoggitworld.com/view/DCS_event_mark_change](https://wiki.hoggitworld.com/view/DCS_event_mark_change)
|
||||
-- @field S_EVENT_MARK REMOVE [https://wiki.hoggitworld.com/view/DCS_event_mark_remove](https://wiki.hoggitworld.com/view/DCS_event_mark_remove)
|
||||
-- @field S_EVENT_MARK ADDED [https://wiki.hoggitworld.com/view/DCS_event_mark_added](https://wiki.hoggitworld.com/view/DCS_event_mark_added) DCS>=2.5.1
|
||||
-- @field S_EVENT_MARK CHANGE [https://wiki.hoggitworld.com/view/DCS_event_mark_change](https://wiki.hoggitworld.com/view/DCS_event_mark_change) DCS>=2.5.1
|
||||
-- @field S_EVENT_MARK REMOVE [https://wiki.hoggitworld.com/view/DCS_event_mark_remove](https://wiki.hoggitworld.com/view/DCS_event_mark_remove) DCS>=2.5.1
|
||||
-- @field S_EVENT_KILL [https://wiki.hoggitworld.com/view/DCS_event_kill](https://wiki.hoggitworld.com/view/DCS_event_kill) DCS>=2.5.6
|
||||
-- @field S_EVENT_SCORE [https://wiki.hoggitworld.com/view/DCS_event_score](https://wiki.hoggitworld.com/view/DCS_event_score) DCS>=2.5.6
|
||||
-- @field S_EVENT_UNIT_LOST [https://wiki.hoggitworld.com/view/DCS_event_unit_lost](https://wiki.hoggitworld.com/view/DCS_event_unit_lost) DCS>=2.5.6
|
||||
-- @field S_EVENT_LANDING_AFTER_EJECTION [https://wiki.hoggitworld.com/view/DCS_event_landing_after_ejection](https://wiki.hoggitworld.com/view/DCS_event_landing_after_ejection) DCS>=2.5.6
|
||||
-- @field S_EVENT_MAX
|
||||
|
||||
--- The birthplace enumerator is used to define where an aircraft or helicopter has spawned in association with birth events.
|
||||
@@ -90,6 +95,11 @@ do -- world
|
||||
--- Returns a table of mark panels indexed numerically that are present within the mission. See [hoggit](https://wiki.hoggitworld.com/view/DCS_func_getMarkPanels)
|
||||
-- @function [parent=#world] getMarkPanels
|
||||
-- @return #table Table of marks.
|
||||
|
||||
--- Returns a table of DCS airbase objects.
|
||||
-- @function [parent=#world] getAirbases
|
||||
-- @param #number coalitionId The coalition side number ID. Default is all airbases are returned.
|
||||
-- @return #table Table of DCS airbase objects.
|
||||
|
||||
end -- world
|
||||
|
||||
@@ -285,6 +295,17 @@ do -- country
|
||||
-- @field QATAR
|
||||
-- @field OMAN
|
||||
-- @field UNITED_ARAB_EMIRATES
|
||||
-- @field SOUTH_AFRICA
|
||||
-- @field CUBA
|
||||
-- @field PORTUGAL
|
||||
-- @field GDR
|
||||
-- @field LEBANON
|
||||
-- @field CJTF_BLUE
|
||||
-- @field CJTF_RED
|
||||
-- @field UN_PEACEKEEPERS
|
||||
-- @field Argentinia
|
||||
-- @field Cyprus
|
||||
-- @field Slovenia
|
||||
|
||||
country = {} --#country
|
||||
|
||||
@@ -325,9 +346,27 @@ end -- coalition
|
||||
do -- Types
|
||||
|
||||
--- @type Desc
|
||||
-- @field #TypeName typeName type name
|
||||
-- @field #string displayName localized display name
|
||||
-- @field #table attributes object type attributes
|
||||
-- @field #number speedMax0 Max speed in meters/second at zero altitude.
|
||||
-- @field #number massEmpty Empty mass in kg.
|
||||
-- @field #number tankerType Type of refueling system: 0=boom, 1=probe.
|
||||
-- @field #number range Range in km(?).
|
||||
-- @field #table box Bounding box.
|
||||
-- @field #number Hmax Max height in meters.
|
||||
-- @field #number Kmax ?
|
||||
-- @field #number speedMax10K Max speed in meters/second at 10k altitude.
|
||||
-- @field #number NyMin ?
|
||||
-- @field #number NyMax ?
|
||||
-- @field #number fuelMassMax Max fuel mass in kg.
|
||||
-- @field #number speedMax10K Max speed in meters/second.
|
||||
-- @field #number massMax Max mass of unit.
|
||||
-- @field #number RCS ?
|
||||
-- @field #number life Life points.
|
||||
-- @field #number VyMax Max vertical velocity in m/s.
|
||||
-- @field #number Kab ?
|
||||
-- @field #table attributes Table of attributes.
|
||||
-- @field #TypeName typeName Type Name.
|
||||
-- @field #string displayName Localized display name.
|
||||
-- @field #number category Unit category.
|
||||
|
||||
--- A distance type
|
||||
-- @type Distance
|
||||
@@ -337,7 +376,7 @@ do -- Types
|
||||
|
||||
--- Time is given in seconds.
|
||||
-- @type Time
|
||||
-- @extends #number
|
||||
-- @extends #number Time in seconds.
|
||||
|
||||
--- Model time is the time that drives the simulation. Model time may be stopped, accelerated and decelerated relative real time.
|
||||
-- @type ModelTime
|
||||
@@ -345,20 +384,20 @@ do -- Types
|
||||
|
||||
--- Mission time is a model time plus time of the mission start.
|
||||
-- @type MissionTime
|
||||
-- @extends #number
|
||||
-- @extends #number Time in seconds.
|
||||
|
||||
|
||||
--- Distance is given in meters.
|
||||
-- @type Distance
|
||||
-- @extends #number
|
||||
-- @extends #number Distance in meters.
|
||||
|
||||
--- Angle is given in radians.
|
||||
-- @type Angle
|
||||
-- @extends #number
|
||||
-- @extends #number Angle in radians.
|
||||
|
||||
--- Azimuth is an angle of rotation around world axis y counter-clockwise.
|
||||
-- @type Azimuth
|
||||
-- @extends #number
|
||||
-- @extends #number Angle in radians.
|
||||
|
||||
--- Mass is given in kilograms.
|
||||
-- @type Mass
|
||||
@@ -378,15 +417,15 @@ do -- Types
|
||||
|
||||
--- Position is a composite structure. It consists of both coordinate vector and orientation matrix. Position3 (also known as "Pos3" for short) is a table that has following format:
|
||||
-- @type Position3
|
||||
-- @field #Vec3 p
|
||||
-- @field #Vec3 x
|
||||
-- @field #Vec3 y
|
||||
-- @field #Vec3 z
|
||||
-- @field #Vec3 p 3D position vector.
|
||||
-- @field #Vec3 x Orientation component of vector pointing East.
|
||||
-- @field #Vec3 y Orientation component of vector pointing up.
|
||||
-- @field #Vec3 z Orientation component of vector pointing North.
|
||||
|
||||
--- 3-dimensional box.
|
||||
-- @type Box3
|
||||
-- @field #Vec3 min
|
||||
-- @field #Vec3 max
|
||||
-- @field #Vec3 min Min.
|
||||
-- @field #Vec3 max Max
|
||||
|
||||
--- Each object belongs to a type. Object type is a named couple of properties those independent of mission and common for all units of the same type. Name of unit type is a string. Samples of unit type: "Su-27", "KAMAZ" and "M2 Bradley".
|
||||
-- @type TypeName
|
||||
@@ -427,9 +466,15 @@ do -- Types
|
||||
-- @type TaskArray
|
||||
-- @list <#Task>
|
||||
|
||||
---
|
||||
--@type WaypointAir
|
||||
--@field #boolean lateActivated
|
||||
--@field #boolean uncontrolled
|
||||
|
||||
end --
|
||||
|
||||
|
||||
|
||||
do -- Object
|
||||
|
||||
--- [DCS Class Object](https://wiki.hoggitworld.com/view/DCS_Class_Object)
|
||||
@@ -442,8 +487,10 @@ do -- Object
|
||||
-- @field UNIT
|
||||
-- @field WEAPON
|
||||
-- @field STATIC
|
||||
-- @field SCENERY
|
||||
-- @field BASE
|
||||
-- @field SCENERY
|
||||
-- @field CARGO
|
||||
|
||||
|
||||
--- @type Object.Desc
|
||||
-- @extends #Desc
|
||||
@@ -485,7 +532,7 @@ do -- Object
|
||||
--- Returns object coordinates for current time.
|
||||
-- @function [parent=#Object] getPoint
|
||||
-- @param #Object self
|
||||
-- @return #Vec3
|
||||
-- @return #Vec3 3D position vector with x,y,z components.
|
||||
|
||||
--- Returns object position for current time.
|
||||
-- @function [parent=#Object] getPosition
|
||||
@@ -495,7 +542,7 @@ do -- Object
|
||||
--- Returns the unit's velocity vector.
|
||||
-- @function [parent=#Object] getVelocity
|
||||
-- @param #Object self
|
||||
-- @return #Vec3
|
||||
-- @return #Vec3 3D velocity vector.
|
||||
|
||||
--- Returns true if the unit is in air.
|
||||
-- @function [parent=#Object] inAir
|
||||
@@ -527,6 +574,126 @@ do -- CoalitionObject
|
||||
end -- CoalitionObject
|
||||
|
||||
|
||||
do -- Weapon
|
||||
|
||||
--- [DCS Class Weapon](https://wiki.hoggitworld.com/view/DCS_Class_Weapon)
|
||||
-- @type Weapon
|
||||
-- @extends #CoalitionObject
|
||||
-- @field #Weapon.flag flag enum stores weapon flags. Some of them are combination of another flags.
|
||||
-- @field #Weapon.Category Category enum that stores weapon categories.
|
||||
-- @field #Weapon.GuidanceType GuidanceType enum that stores guidance methods. Available only for guided weapon (Weapon.Category.MISSILE and some Weapon.Category.BOMB).
|
||||
-- @field #Weapon.MissileCategory MissileCategory enum that stores missile category. Available only for missiles (Weapon.Category.MISSILE).
|
||||
-- @field #Weapon.WarheadType WarheadType enum that stores warhead types.
|
||||
-- @field #Weapon.Desc Desc The descriptor of a weapon.
|
||||
|
||||
--- enum stores weapon flags. Some of them are combination of another flags.
|
||||
-- @type Weapon.flag
|
||||
-- @field LGB
|
||||
-- @field TvGB
|
||||
-- @field SNSGB
|
||||
-- @field HEBomb
|
||||
-- @field Penetrator
|
||||
-- @field NapalmBomb
|
||||
-- @field FAEBomb
|
||||
-- @field ClusterBomb
|
||||
-- @field Dispencer
|
||||
-- @field CandleBomb
|
||||
-- @field ParachuteBomb
|
||||
-- @field GuidedBomb = LGB + TvGB + SNSGB
|
||||
-- @field AnyUnguidedBomb = HEBomb + Penetrator + NapalmBomb + FAEBomb + ClusterBomb + Dispencer + CandleBomb + ParachuteBomb
|
||||
-- @field AnyBomb = GuidedBomb + AnyUnguidedBomb
|
||||
-- @field LightRocket
|
||||
-- @field MarkerRocket
|
||||
-- @field CandleRocket
|
||||
-- @field HeavyRocket
|
||||
-- @field AnyRocket = LightRocket + HeavyRocket + MarkerRocket + CandleRocket
|
||||
-- @field AntiRadarMissile
|
||||
-- @field AntiShipMissile
|
||||
-- @field AntiTankMissile
|
||||
-- @field FireAndForgetASM
|
||||
-- @field LaserASM
|
||||
-- @field TeleASM
|
||||
-- @field CruiseMissile
|
||||
-- @field GuidedASM = LaserASM + TeleASM
|
||||
-- @field TacticASM = GuidedASM + FireAndForgetASM
|
||||
-- @field AnyASM = AntiRadarMissile + AntiShipMissile + AntiTankMissile + FireAndForgetASM + GuidedASM + CruiseMissile
|
||||
-- @field SRAAM
|
||||
-- @field MRAAM
|
||||
-- @field LRAAM
|
||||
-- @field IR_AAM
|
||||
-- @field SAR_AAM
|
||||
-- @field AR_AAM
|
||||
-- @field AnyAAM = IR_AAM + SAR_AAM + AR_AAM + SRAAM + MRAAM + LRAAM
|
||||
-- @field AnyMissile = AnyASM + AnyAAM
|
||||
-- @field AnyAutonomousMissile = IR_AAM + AntiRadarMissile + AntiShipMissile + FireAndForgetASM + CruiseMissile
|
||||
-- @field GUN_POD
|
||||
-- @field BuiltInCannon
|
||||
-- @field Cannons = GUN_POD + BuiltInCannon
|
||||
-- @field AnyAGWeapon = BuiltInCannon + GUN_POD + AnyBomb + AnyRocket + AnyASM
|
||||
-- @field AnyAAWeapon = BuiltInCannon + GUN_POD + AnyAAM
|
||||
-- @field UnguidedWeapon = Cannons + BuiltInCannon + GUN_POD + AnyUnguidedBomb + AnyRocket
|
||||
-- @field GuidedWeapon = GuidedBomb + AnyASM + AnyAAM
|
||||
-- @field AnyWeapon = AnyBomb + AnyRocket + AnyMissile + Cannons
|
||||
-- @field MarkerWeapon = MarkerRocket + CandleRocket + CandleBomb
|
||||
-- @field ArmWeapon = AnyWeapon - MarkerWeapon
|
||||
|
||||
--- Weapon.Category enum that stores weapon categories.
|
||||
-- @type Weapon.Category
|
||||
-- @field SHELL
|
||||
-- @field MISSILE
|
||||
-- @field ROCKET
|
||||
-- @field BOMB
|
||||
|
||||
|
||||
--- Weapon.GuidanceType enum that stores guidance methods. Available only for guided weapon (Weapon.Category.MISSILE and some Weapon.Category.BOMB).
|
||||
-- @type Weapon.GuidanceType
|
||||
-- @field INS
|
||||
-- @field IR
|
||||
-- @field RADAR_ACTIVE
|
||||
-- @field RADAR_SEMI_ACTIVE
|
||||
-- @field RADAR_PASSIVE
|
||||
-- @field TV
|
||||
-- @field LASER
|
||||
-- @field TELE
|
||||
|
||||
|
||||
--- Weapon.MissileCategory enum that stores missile category. Available only for missiles (Weapon.Category.MISSILE).
|
||||
-- @type Weapon.MissileCategory
|
||||
-- @field AAM
|
||||
-- @field SAM
|
||||
-- @field BM
|
||||
-- @field ANTI_SHIP
|
||||
-- @field CRUISE
|
||||
-- @field OTHER
|
||||
|
||||
--- Weapon.WarheadType enum that stores warhead types.
|
||||
-- @type Weapon.WarheadType
|
||||
-- @field AP
|
||||
-- @field HE
|
||||
-- @field SHAPED_EXPLOSIVE
|
||||
|
||||
--- Returns the unit that launched the weapon.
|
||||
-- @function [parent=#Weapon] getLauncher
|
||||
-- @param #Weapon self
|
||||
-- @return #Unit
|
||||
|
||||
--- returns target of the guided weapon. Unguided weapons and guided weapon that is targeted at the point on the ground will return nil.
|
||||
-- @function [parent=#Weapon] getTarget
|
||||
-- @param #Weapon self
|
||||
-- @return #Object
|
||||
|
||||
--- returns weapon descriptor. Descriptor type depends on weapon category.
|
||||
-- @function [parent=#Weapon] getDesc
|
||||
-- @param #Weapon self
|
||||
-- @return #Weapon.Desc
|
||||
|
||||
|
||||
|
||||
Weapon = {} --#Weapon
|
||||
|
||||
end -- Weapon
|
||||
|
||||
|
||||
do -- Airbase
|
||||
|
||||
--- [DCS Class Airbase](https://wiki.hoggitworld.com/view/DCS_Class_Airbase)
|
||||
@@ -582,7 +749,66 @@ do -- Airbase
|
||||
|
||||
end -- Airbase
|
||||
|
||||
do -- Spot
|
||||
|
||||
--- [DCS Class Spot](https://wiki.hoggitworld.com/view/DCS_Class_Spot)
|
||||
-- Represents a spot from laser or IR-pointer.
|
||||
-- @type Spot
|
||||
-- @field #Spot.Category Category enum that stores spot categories.
|
||||
|
||||
--- Enum that stores spot categories.
|
||||
-- @type Spot.Category
|
||||
-- @field #string INFRA_RED
|
||||
-- @field #string LASER
|
||||
|
||||
|
||||
--- Creates a laser ray emanating from the given object to a point in 3d space.
|
||||
-- @function [parent=#Spot] createLaser
|
||||
-- @param DCS#Object Source The source object of the laser.
|
||||
-- @param DCS#Vec3 LocalRef An optional 3D offset for the source.
|
||||
-- @param DCS#Vec3 Vec3 Target coordinate where the ray is pointing at.
|
||||
-- @param #number LaserCode Any 4 digit number between 1111 and 1788.
|
||||
-- @return #Spot
|
||||
|
||||
--- Creates an infrared ray emanating from the given object to a point in 3d space. Can be seen with night vision goggles.
|
||||
-- @function [parent=#Spot] createInfraRed
|
||||
-- @param DCS#Object Source Source position of the IR ray.
|
||||
-- @param DCS#Vec3 LocalRef An optional 3D offset for the source.
|
||||
-- @param DCS#Vec3 Vec3 Target coordinate where the ray is pointing at.
|
||||
-- @return #Spot
|
||||
|
||||
--- Returns a vec3 table of the x, y, and z coordinates for the position of the given object in 3D space. Coordinates are dependent on the position of the maps origin.
|
||||
-- @function [parent=#Spot] getPoint
|
||||
-- @param #Spot self
|
||||
-- @return DCS#Vec3 Point in 3D, where the beam is pointing at.
|
||||
|
||||
--- Sets the destination point from which the source of the spot is drawn toward.
|
||||
-- @function [parent=#Spot] setPoint
|
||||
-- @param #Spot self
|
||||
-- @param DCS#Vec3 Vec3 Point in 3D, where the beam is pointing at.
|
||||
|
||||
--- Returns the number that is used to define the laser code for which laser designation can track.
|
||||
-- @function [parent=#Spot] getCode
|
||||
-- @param #Spot self
|
||||
-- @return #number Code The laser code used.
|
||||
|
||||
--- Sets the number that is used to define the laser code for which laser designation can track.
|
||||
-- @function [parent=#Spot] setCode
|
||||
-- @param #Spot self
|
||||
-- @param #number Code The laser code. Default value is 1688.
|
||||
|
||||
--- Destroys the spot.
|
||||
-- @function [parent=#Spot] destroy
|
||||
-- @param #Spot self
|
||||
|
||||
--- Gets the category of the spot (laser or IR).
|
||||
-- @function [parent=#Spot] getCategory
|
||||
-- @param #Spot self
|
||||
-- @return #string Category.
|
||||
|
||||
Spot = {} --#Spot
|
||||
|
||||
end -- Spot
|
||||
|
||||
do -- Controller
|
||||
--- Controller is an object that performs A.I.-routines. Other words controller is an instance of A.I.. Controller stores current main task, active enroute tasks and behavior options. Controller performs commands. Please, read DCS A-10C GUI Manual EN.pdf chapter "Task Planning for Unit Groups", page 91 to understand A.I. system of DCS:A-10C.
|
||||
@@ -939,6 +1165,10 @@ do -- Unit
|
||||
-- @param #Unit self
|
||||
-- @return #Unit.Desc
|
||||
|
||||
--- GROUND - Switch on/off radar emissions
|
||||
-- @function [parent=#Unit] enableEmission
|
||||
-- @param #Unit self
|
||||
-- @param #boolean switch
|
||||
|
||||
Unit = {} --#Unit
|
||||
|
||||
@@ -1009,7 +1239,7 @@ do -- Group
|
||||
-- @param #Group self
|
||||
-- @return #number
|
||||
|
||||
--- Returns initial size of the group. If some of the units will be destroyed, initial size of the group will not be changed. Initial size limits the unitNumber parameter for Group.getUnit() function.
|
||||
--- Returns initial size of the group. If some of the units will be destroyed, initial size of the group will not be changed; Initial size limits the unitNumber parameter for Group.getUnit() function.
|
||||
-- @function [parent=#Group] getInitialSize
|
||||
-- @param #Group self
|
||||
-- @return #number
|
||||
@@ -1024,6 +1254,11 @@ do -- Group
|
||||
-- @param #Group self
|
||||
-- @return #Controller
|
||||
|
||||
--- GROUND - Switch on/off radar emissions
|
||||
-- @function [parent=#Group] enableEmission
|
||||
-- @param #Group self
|
||||
-- @param #boolean switch
|
||||
|
||||
Group = {} --#Group
|
||||
|
||||
end -- Group
|
||||
@@ -1082,6 +1317,7 @@ do -- AI
|
||||
-- @field TAKEOFF
|
||||
-- @field TAKEOFF_PARKING
|
||||
-- @field TURNING_POINT
|
||||
-- @field TAKEOFF_PARKING_HOT
|
||||
-- @field LAND
|
||||
|
||||
--- @type AI.Task.TurnMethod
|
||||
@@ -1118,8 +1354,8 @@ do -- AI
|
||||
--- @type AI.Option.Naval
|
||||
-- @field #AI.Option.Naval.id id
|
||||
-- @field #AI.Option.Naval.val val
|
||||
|
||||
--TODO: work on formation
|
||||
|
||||
|
||||
--- @type AI.Option.Air.id
|
||||
-- @field NO_OPTION
|
||||
-- @field ROE
|
||||
@@ -1128,7 +1364,34 @@ do -- AI
|
||||
-- @field FLARE_USING
|
||||
-- @field FORMATION
|
||||
-- @field RTB_ON_BINGO
|
||||
-- @field SILENCE
|
||||
-- @field SILENCE
|
||||
-- @field RTB_ON_OUT_OF_AMMO
|
||||
-- @field ECM_USING
|
||||
-- @field PROHIBIT_AA
|
||||
-- @field PROHIBIT_JETT
|
||||
-- @field PROHIBIT_AB
|
||||
-- @field PROHIBIT_AG
|
||||
-- @field MISSILE_ATTACK
|
||||
-- @field PROHIBIT_WP_PASS_REPORT
|
||||
|
||||
--- @type AI.Option.Air.id.FORMATION
|
||||
-- @field LINE_ABREAST
|
||||
-- @field TRAIL
|
||||
-- @field WEDGE
|
||||
-- @field ECHELON_RIGHT
|
||||
-- @field ECHELON_LEFT
|
||||
-- @field FINGER_FOUR
|
||||
-- @field SPREAD_FOUR
|
||||
-- @field WW2_BOMBER_ELEMENT
|
||||
-- @field WW2_BOMBER_ELEMENT_HEIGHT
|
||||
-- @field WW2_FIGHTER_VIC
|
||||
-- @field HEL_WEDGE
|
||||
-- @field HEL_ECHELON
|
||||
-- @field HEL_FRONT
|
||||
-- @field HEL_COLUMN
|
||||
-- @field COMBAT_BOX
|
||||
-- @field JAVELIN_DOWN
|
||||
|
||||
|
||||
--- @type AI.Option.Air.val
|
||||
-- @field #AI.Option.Air.val.ROE ROE
|
||||
@@ -1161,12 +1424,27 @@ do -- AI
|
||||
-- @field AGAINST_FIRED_MISSILE
|
||||
-- @field WHEN_FLYING_IN_SAM_WEZ
|
||||
-- @field WHEN_FLYING_NEAR_ENEMIES
|
||||
|
||||
--- @type AI.Option.Air.val.ECM_USING
|
||||
-- @field NEVER_USE
|
||||
-- @field USE_IF_ONLY_LOCK_BY_RADAR
|
||||
-- @field USE_IF_DETECTED_LOCK_BY_RADAR
|
||||
-- @field ALWAYS_USE
|
||||
|
||||
--- @type AI.Option.Air.val.MISSILE_ATTACK
|
||||
-- @field MAX_RANGE
|
||||
-- @field NEZ_RANGE
|
||||
-- @field HALF_WAY_RMAX_NEZ
|
||||
-- @field TARGET_THREAT_EST
|
||||
-- @field RANDOM_RANGE
|
||||
|
||||
|
||||
--- @type AI.Option.Ground.id
|
||||
-- @field NO_OPTION
|
||||
-- @field ROE @{#AI.Option.Ground.val.ROE}
|
||||
-- @field DISPERSE_ON_ATTACK true or false
|
||||
-- @field ALARM_STATE @{#AI.Option.Ground.val.ALARM_STATE}
|
||||
-- @field ENGAGE_AIR_WEAPONS
|
||||
|
||||
--- @type AI.Option.Ground.val
|
||||
-- @field #AI.Option.Ground.val.ROE ROE
|
||||
@@ -1197,6 +1475,3 @@ do -- AI
|
||||
AI = {} --#AI
|
||||
|
||||
end -- AI
|
||||
|
||||
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,54 +1,54 @@
|
||||
--- **Functional** -- Keep airbases clean of crashing or colliding airplanes, and kill missiles when being fired at airbases.
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- ## Features:
|
||||
--
|
||||
--
|
||||
--
|
||||
--
|
||||
-- * Try to keep the airbase clean and operational.
|
||||
-- * Prevent airplanes from crashing.
|
||||
-- * Clean up obstructing airplanes from the runway that are standing still for a period of time.
|
||||
-- * Prevent airplanes firing missiles within the airbase zone.
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- ## Missions:
|
||||
--
|
||||
--
|
||||
-- [CLA - CleanUp Airbase](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/CLA%20-%20CleanUp%20Airbase)
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- Specific airbases need to be provided that need to be guarded. Each airbase registered, will be guarded within a zone of 8 km around the airbase.
|
||||
-- Any unit that fires a missile, or shoots within the zone of an airbase, will be monitored by CLEANUP_AIRBASE.
|
||||
-- Within the 8km zone, units cannot fire any missile, which prevents the airbase runway to receive missile or bomb hits.
|
||||
-- Within the 8km zone, units cannot fire any missile, which prevents the airbase runway to receive missile or bomb hits.
|
||||
-- Any airborne or ground unit that is on the runway below 30 meters (default value) will be automatically removed if it is damaged.
|
||||
--
|
||||
--
|
||||
-- This is not a full 100% secure implementation. It is still possible that CLEANUP_AIRBASE cannot prevent (in-time) to keep the airbase clean.
|
||||
-- The following situations may happen that will still stop the runway of an airbase:
|
||||
--
|
||||
--
|
||||
-- * A damaged unit is not removed on time when above the runway, and crashes on the runway.
|
||||
-- * A bomb or missile is still able to dropped on the runway.
|
||||
-- * Units collide on the airbase, and could not be removed on time.
|
||||
--
|
||||
--
|
||||
-- When a unit is within the airbase zone and needs to be monitored,
|
||||
-- its status will be checked every 0.25 seconds! This is required to ensure that the airbase is kept clean.
|
||||
-- But as a result, there is more CPU overload.
|
||||
--
|
||||
--
|
||||
-- So as an advise, I suggest you use the CLEANUP_AIRBASE class with care:
|
||||
--
|
||||
--
|
||||
-- * Only monitor airbases that really need to be monitored!
|
||||
-- * Try not to monitor airbases that are likely to be invaded by enemy troops.
|
||||
-- For these airbases, there is little use to keep them clean, as they will be invaded anyway...
|
||||
--
|
||||
--
|
||||
-- By following the above guidelines, you can add airbase cleanup with acceptable CPU overhead.
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
-- ### Contributions:
|
||||
--
|
||||
-- ### Contributions:
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- @module Functional.CleanUp
|
||||
-- @image CleanUp_Airbases.JPG
|
||||
|
||||
@@ -60,29 +60,29 @@
|
||||
-- @extends #CLEANUP_AIRBASE.__
|
||||
|
||||
--- Keeps airbases clean, and tries to guarantee continuous airbase operations, even under combat.
|
||||
--
|
||||
--
|
||||
-- # 1. CLEANUP_AIRBASE Constructor
|
||||
--
|
||||
--
|
||||
-- Creates the main object which is preventing the airbase to get polluted with debris on the runway, which halts the airbase.
|
||||
--
|
||||
--
|
||||
-- -- Clean these Zones.
|
||||
-- CleanUpAirports = CLEANUP_AIRBASE:New( { AIRBASE.Caucasus.Tbilisi, AIRBASE.Caucasus.Kutaisi )
|
||||
--
|
||||
-- CleanUpAirports = CLEANUP_AIRBASE:New( { AIRBASE.Caucasus.Tbilisi, AIRBASE.Caucasus.Kutaisi } )
|
||||
--
|
||||
-- -- or
|
||||
-- CleanUpTbilisi = CLEANUP_AIRBASE:New( AIRBASE.Caucasus.Tbilisi )
|
||||
-- CleanUpKutaisi = CLEANUP_AIRBASE:New( AIRBASE.Caucasus.Kutaisi )
|
||||
--
|
||||
--
|
||||
-- # 2. Add or Remove airbases
|
||||
--
|
||||
--
|
||||
-- The method @{#CLEANUP_AIRBASE.AddAirbase}() to add an airbase to the cleanup validation process.
|
||||
-- The method @{#CLEANUP_AIRBASE.RemoveAirbase}() removes an airbase from the cleanup validation process.
|
||||
--
|
||||
--
|
||||
-- # 3. Clean missiles and bombs within the airbase zone.
|
||||
--
|
||||
--
|
||||
-- When missiles or bombs hit the runway, the airbase operations stop.
|
||||
-- Use the method @{#CLEANUP_AIRBASE.SetCleanMissiles}() to control the cleaning of missiles, which will prevent airbases to stop.
|
||||
-- Note that this method will not allow anymore airbases to be attacked, so there is a trade-off here to do.
|
||||
--
|
||||
--
|
||||
-- @field #CLEANUP_AIRBASE
|
||||
CLEANUP_AIRBASE = {
|
||||
ClassName = "CLEANUP_AIRBASE",
|
||||
@@ -106,11 +106,11 @@ CLEANUP_AIRBASE.__.Airbases = {}
|
||||
-- or
|
||||
-- CleanUpTbilisi = CLEANUP_AIRBASE:New( AIRBASE.Caucasus.Tbilisi )
|
||||
-- CleanUpKutaisi = CLEANUP_AIRBASE:New( AIRBASE.Caucasus.Kutaisi )
|
||||
function CLEANUP_AIRBASE:New( AirbaseNames )
|
||||
function CLEANUP_AIRBASE:New( AirbaseNames )
|
||||
|
||||
local self = BASE:Inherit( self, BASE:New() ) -- #CLEANUP_AIRBASE
|
||||
self:F( { AirbaseNames } )
|
||||
|
||||
|
||||
if type( AirbaseNames ) == 'table' then
|
||||
for AirbaseID, AirbaseName in pairs( AirbaseNames ) do
|
||||
self:AddAirbase( AirbaseName )
|
||||
@@ -119,9 +119,9 @@ function CLEANUP_AIRBASE:New( AirbaseNames )
|
||||
local AirbaseName = AirbaseNames
|
||||
self:AddAirbase( AirbaseName )
|
||||
end
|
||||
|
||||
|
||||
self:HandleEvent( EVENTS.Birth, self.__.OnEventBirth )
|
||||
|
||||
|
||||
self.__.CleanUpScheduler = SCHEDULER:New( self, self.__.CleanUpSchedule, {}, 1, self.TimeInterval )
|
||||
|
||||
self:HandleEvent( EVENTS.EngineShutdown , self.__.EventAddForCleanUp )
|
||||
@@ -130,7 +130,7 @@ function CLEANUP_AIRBASE:New( AirbaseNames )
|
||||
self:HandleEvent( EVENTS.PilotDead, self.__.OnEventCrash )
|
||||
self:HandleEvent( EVENTS.Dead, self.__.OnEventCrash )
|
||||
self:HandleEvent( EVENTS.Crash, self.__.OnEventCrash )
|
||||
|
||||
|
||||
for UnitName, Unit in pairs( _DATABASE.UNITS ) do
|
||||
local Unit = Unit -- Wrapper.Unit#UNIT
|
||||
if Unit:IsAlive() ~= nil then
|
||||
@@ -144,7 +144,7 @@ function CLEANUP_AIRBASE:New( AirbaseNames )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -155,7 +155,7 @@ end
|
||||
function CLEANUP_AIRBASE:AddAirbase( AirbaseName )
|
||||
self.__.Airbases[AirbaseName] = AIRBASE:FindByName( AirbaseName )
|
||||
self:F({"Airbase:", AirbaseName, self.__.Airbases[AirbaseName]:GetDesc()})
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -197,7 +197,7 @@ function CLEANUP_AIRBASE.__:IsInAirbase( Vec2 )
|
||||
break;
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
return InAirbase
|
||||
end
|
||||
|
||||
@@ -233,7 +233,7 @@ end
|
||||
-- @param DCS#Weapon MissileObject
|
||||
function CLEANUP_AIRBASE.__:DestroyMissile( MissileObject )
|
||||
self:F( { MissileObject } )
|
||||
|
||||
|
||||
if MissileObject and MissileObject:isExist() then
|
||||
MissileObject:destroy()
|
||||
self:T( "MissileObject Destroyed")
|
||||
@@ -245,7 +245,7 @@ end
|
||||
function CLEANUP_AIRBASE.__:OnEventBirth( EventData )
|
||||
self:F( { EventData } )
|
||||
|
||||
if EventData.IniUnit:IsAlive() ~= nil then
|
||||
if EventData and EventData.IniUnit and EventData.IniUnit:IsAlive() ~= nil then
|
||||
if self:IsInAirbase( EventData.IniUnit:GetVec2() ) then
|
||||
self.CleanUpList[EventData.IniDCSUnitName] = {}
|
||||
self.CleanUpList[EventData.IniDCSUnitName].CleanUpUnit = EventData.IniUnit
|
||||
@@ -267,7 +267,7 @@ function CLEANUP_AIRBASE.__:OnEventCrash( Event )
|
||||
|
||||
--TODO: DCS BUG - This stuff is not working due to a DCS bug. Burning units cannot be destroyed.
|
||||
-- self:T("before getGroup")
|
||||
-- local _grp = Unit.getGroup(event.initiator)-- Identify the group that fired
|
||||
-- local _grp = Unit.getGroup(event.initiator)-- Identify the group that fired
|
||||
-- self:T("after getGroup")
|
||||
-- _grp:destroy()
|
||||
-- self:T("after deactivateGroup")
|
||||
@@ -280,7 +280,7 @@ function CLEANUP_AIRBASE.__:OnEventCrash( Event )
|
||||
self.CleanUpList[Event.IniDCSUnitName].CleanUpGroupName = Event.IniDCSGroupName
|
||||
self.CleanUpList[Event.IniDCSUnitName].CleanUpUnitName = Event.IniDCSUnitName
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
--- Detects if a unit shoots a missile.
|
||||
@@ -334,16 +334,16 @@ function CLEANUP_AIRBASE.__:AddForCleanUp( CleanUpUnit, CleanUpUnitName )
|
||||
self.CleanUpList[CleanUpUnitName] = {}
|
||||
self.CleanUpList[CleanUpUnitName].CleanUpUnit = CleanUpUnit
|
||||
self.CleanUpList[CleanUpUnitName].CleanUpUnitName = CleanUpUnitName
|
||||
|
||||
|
||||
local CleanUpGroup = CleanUpUnit:GetGroup()
|
||||
|
||||
|
||||
self.CleanUpList[CleanUpUnitName].CleanUpGroup = CleanUpGroup
|
||||
self.CleanUpList[CleanUpUnitName].CleanUpGroupName = CleanUpGroup:GetName()
|
||||
self.CleanUpList[CleanUpUnitName].CleanUpTime = timer.getTime()
|
||||
self.CleanUpList[CleanUpUnitName].CleanUpMoved = false
|
||||
|
||||
self:T( { "CleanUp: Add to CleanUpList: ", CleanUpGroup:GetName(), CleanUpUnitName } )
|
||||
|
||||
|
||||
end
|
||||
|
||||
--- Detects if the Unit has an S_EVENT_ENGINE_SHUTDOWN or an S_EVENT_HIT within the given AirbaseNames. If this is the case, add the Group to the CLEANUP_AIRBASE List.
|
||||
@@ -369,7 +369,7 @@ function CLEANUP_AIRBASE.__:EventAddForCleanUp( Event )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
|
||||
@@ -380,26 +380,26 @@ function CLEANUP_AIRBASE.__:CleanUpSchedule()
|
||||
local CleanUpCount = 0
|
||||
for CleanUpUnitName, CleanUpListData in pairs( self.CleanUpList ) do
|
||||
CleanUpCount = CleanUpCount + 1
|
||||
|
||||
|
||||
local CleanUpUnit = CleanUpListData.CleanUpUnit -- Wrapper.Unit#UNIT
|
||||
local CleanUpGroupName = CleanUpListData.CleanUpGroupName
|
||||
|
||||
if CleanUpUnit:IsAlive() ~= nil then
|
||||
|
||||
|
||||
if self:IsInAirbase( CleanUpUnit:GetVec2() ) then
|
||||
|
||||
if _DATABASE:GetStatusGroup( CleanUpGroupName ) ~= "ReSpawn" then
|
||||
|
||||
|
||||
local CleanUpCoordinate = CleanUpUnit:GetCoordinate()
|
||||
|
||||
|
||||
self:T( { "CleanUp Scheduler", CleanUpUnitName } )
|
||||
if CleanUpUnit:GetLife() <= CleanUpUnit:GetLife0() * 0.95 then
|
||||
if CleanUpUnit:IsAboveRunway() then
|
||||
if CleanUpUnit:InAir() then
|
||||
|
||||
|
||||
local CleanUpLandHeight = CleanUpCoordinate:GetLandHeight()
|
||||
local CleanUpUnitHeight = CleanUpCoordinate.y - CleanUpLandHeight
|
||||
|
||||
|
||||
if CleanUpUnitHeight < 100 then
|
||||
self:T( { "CleanUp Scheduler", "Destroy " .. CleanUpUnitName .. " because below safe height and damaged." } )
|
||||
self:DestroyUnit( CleanUpUnit )
|
||||
@@ -439,7 +439,6 @@ function CLEANUP_AIRBASE.__:CleanUpSchedule()
|
||||
end
|
||||
end
|
||||
self:T(CleanUpCount)
|
||||
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
@@ -286,9 +286,9 @@ do -- DESIGNATE
|
||||
-- * The status report can be automatically flashed by selecting "Status" -> "Flash Status On".
|
||||
-- * The automatic flashing of the status report can be deactivated by selecting "Status" -> "Flash Status Off".
|
||||
-- * The flashing of the status menu is disabled by default.
|
||||
-- * The method @{#DESIGNATE.FlashStatusMenu}() can be used to enable or disable to flashing of the status menu.
|
||||
-- * The method @{#DESIGNATE.SetFlashStatusMenu}() can be used to enable or disable to flashing of the status menu.
|
||||
--
|
||||
-- Designate:FlashStatusMenu( true )
|
||||
-- Designate:SetFlashStatusMenu( true )
|
||||
--
|
||||
-- The example will activate the flashing of the status menu for this Designate object.
|
||||
--
|
||||
@@ -469,12 +469,12 @@ do -- DESIGNATE
|
||||
self.CC = CC
|
||||
self.Detection = Detection
|
||||
self.AttackSet = AttackSet
|
||||
self.RecceSet = Detection:GetDetectionSetGroup()
|
||||
self.RecceSet = Detection:GetDetectionSet()
|
||||
self.Recces = {}
|
||||
self.Designating = {}
|
||||
self:SetDesignateName()
|
||||
|
||||
self.LaseDuration = 60
|
||||
self:SetLaseDuration() -- Default is 120 seconds.
|
||||
|
||||
self:SetFlashStatusMenu( false )
|
||||
self:SetFlashDetectionMessages( true )
|
||||
@@ -677,6 +677,14 @@ do -- DESIGNATE
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set the lase duration for designations.
|
||||
-- @param #DESIGNATE self
|
||||
-- @param #number LaseDuration The time in seconds a lase will continue to hold on target. The default is 120 seconds.
|
||||
-- @return #DESIGNATE
|
||||
function DESIGNATE:SetLaseDuration( LaseDuration )
|
||||
self.LaseDuration = LaseDuration or 120
|
||||
return self
|
||||
end
|
||||
|
||||
--- Generate an array of possible laser codes.
|
||||
-- Each new lase will select a code from this table.
|
||||
@@ -993,16 +1001,22 @@ do -- DESIGNATE
|
||||
local ID = self.Detection:GetDetectedItemID( DetectedItem )
|
||||
local MenuText = ID --.. ", " .. Coord:ToStringA2G( AttackGroup )
|
||||
|
||||
MenuText = string.format( "(%3s) %s", Designating, MenuText )
|
||||
-- Use injected MenuName from TaskA2GDispatcher if using same Detection Object
|
||||
if DetectedItem.DesignateMenuName then
|
||||
MenuText = string.format( "(%3s) %s", Designating, DetectedItem.DesignateMenuName )
|
||||
else
|
||||
MenuText = string.format( "(%3s) %s", Designating, MenuText )
|
||||
end
|
||||
|
||||
local DetectedMenu = MENU_GROUP_DELAYED:New( AttackGroup, MenuText, MenuDesignate ):SetTime( MenuTime ):SetTag( self.DesignateName )
|
||||
|
||||
-- Build the Lasing menu.
|
||||
if string.find( Designating, "L", 1, true ) == nil then
|
||||
MENU_GROUP_COMMAND_DELAYED:New( AttackGroup, "Search other target", DetectedMenu, self.MenuForget, self, DesignateIndex ):SetTime( MenuTime ):SetTag( self.DesignateName )
|
||||
for LaserCode, MenuText in pairs( self.MenuLaserCodes ) do
|
||||
MENU_GROUP_COMMAND_DELAYED:New( AttackGroup, string.format( MenuText, LaserCode ), DetectedMenu, self.MenuLaseCode, self, DesignateIndex, 60, LaserCode ):SetTime( MenuTime ):SetTag( self.DesignateName )
|
||||
MENU_GROUP_COMMAND_DELAYED:New( AttackGroup, string.format( MenuText, LaserCode ), DetectedMenu, self.MenuLaseCode, self, DesignateIndex, self.LaseDuration, LaserCode ):SetTime( MenuTime ):SetTag( self.DesignateName )
|
||||
end
|
||||
MENU_GROUP_COMMAND_DELAYED:New( AttackGroup, "Lase with random laser code(s)", DetectedMenu, self.MenuLaseOn, self, DesignateIndex, 60 ):SetTime( MenuTime ):SetTag( self.DesignateName )
|
||||
MENU_GROUP_COMMAND_DELAYED:New( AttackGroup, "Lase with random laser code(s)", DetectedMenu, self.MenuLaseOn, self, DesignateIndex, self.LaseDuration ):SetTime( MenuTime ):SetTag( self.DesignateName )
|
||||
else
|
||||
MENU_GROUP_COMMAND_DELAYED:New( AttackGroup, "Stop lasing", DetectedMenu, self.MenuLaseOff, self, DesignateIndex ):SetTime( MenuTime ):SetTag( self.DesignateName )
|
||||
end
|
||||
@@ -1160,10 +1174,10 @@ do -- DESIGNATE
|
||||
|
||||
if string.find( self.Designating[Index], "L", 1, true ) == nil then
|
||||
self.Designating[Index] = self.Designating[Index] .. "L"
|
||||
self.LaseStart = timer.getTime()
|
||||
self.LaseDuration = Duration
|
||||
self:Lasing( Index, Duration, LaserCode )
|
||||
end
|
||||
self.LaseStart = timer.getTime()
|
||||
self.LaseDuration = Duration
|
||||
self:Lasing( Index, Duration, LaserCode )
|
||||
end
|
||||
|
||||
|
||||
@@ -1174,7 +1188,7 @@ do -- DESIGNATE
|
||||
|
||||
|
||||
local DetectedItem = self.Detection:GetDetectedItemByIndex( Index )
|
||||
local TargetSetUnit = self.Detection:GetDetectedSet( DetectedItem )
|
||||
local TargetSetUnit = self.Detection:GetDetectedItemSet( DetectedItem )
|
||||
|
||||
local MarkingCount = 0
|
||||
local MarkedTypes = {}
|
||||
@@ -1322,7 +1336,7 @@ do -- DESIGNATE
|
||||
local MarkedLaserCodesText = ReportLaserCodes:Text(', ')
|
||||
self.CC:GetPositionable():MessageToSetGroup( "Marking " .. MarkingCount .. " x " .. MarkedTypesText .. ", code " .. MarkedLaserCodesText .. ".", 5, self.AttackSet, self.DesignateName )
|
||||
|
||||
self:__Lasing( -30, Index, Duration, LaserCodeRequested )
|
||||
self:__Lasing( -self.LaseDuration, Index, Duration, LaserCodeRequested )
|
||||
|
||||
self:SetDesignateMenu()
|
||||
|
||||
@@ -1344,7 +1358,7 @@ do -- DESIGNATE
|
||||
end
|
||||
|
||||
local DetectedItem = self.Detection:GetDetectedItemByIndex( Index )
|
||||
local TargetSetUnit = self.Detection:GetDetectedSet( DetectedItem )
|
||||
local TargetSetUnit = self.Detection:GetDetectedItemSet( DetectedItem )
|
||||
|
||||
local Recces = self.Recces
|
||||
|
||||
@@ -1369,7 +1383,7 @@ do -- DESIGNATE
|
||||
function DESIGNATE:onafterSmoke( From, Event, To, Index, Color )
|
||||
|
||||
local DetectedItem = self.Detection:GetDetectedItemByIndex( Index )
|
||||
local TargetSetUnit = self.Detection:GetDetectedSet( DetectedItem )
|
||||
local TargetSetUnit = self.Detection:GetDetectedItemSet( DetectedItem )
|
||||
local TargetSetUnitCount = TargetSetUnit:Count()
|
||||
|
||||
local MarkedCount = 0
|
||||
@@ -1385,7 +1399,7 @@ do -- DESIGNATE
|
||||
self:F( "Smoking ..." )
|
||||
|
||||
local RecceGroup = self.RecceSet:FindNearestGroupFromPointVec2(SmokeUnit:GetPointVec2())
|
||||
local RecceUnit = RecceGroup:GetUnit( 1 )
|
||||
local RecceUnit = RecceGroup:GetUnit( 1 ) -- Wrapper.Unit#UNIT
|
||||
|
||||
if RecceUnit then
|
||||
|
||||
@@ -1414,7 +1428,7 @@ do -- DESIGNATE
|
||||
function DESIGNATE:onafterIlluminate( From, Event, To, Index )
|
||||
|
||||
local DetectedItem = self.Detection:GetDetectedItemByIndex( Index )
|
||||
local TargetSetUnit = self.Detection:GetDetectedSet( DetectedItem )
|
||||
local TargetSetUnit = self.Detection:GetDetectedItemSet( DetectedItem )
|
||||
local TargetUnit = TargetSetUnit:GetFirst()
|
||||
|
||||
if TargetUnit then
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
416
Moose Development/Moose/Functional/DetectionZones.lua
Normal file
416
Moose Development/Moose/Functional/DetectionZones.lua
Normal file
@@ -0,0 +1,416 @@
|
||||
do -- DETECTION_ZONES
|
||||
|
||||
--- @type DETECTION_ZONES
|
||||
-- @field DCS#Distance DetectionZoneRange The range till which targets are grouped upon the first detected target.
|
||||
-- @field #DETECTION_BASE.DetectedItems DetectedItems A list of areas containing the set of @{Wrapper.Unit}s, @{Zone}s, the center @{Wrapper.Unit} within the zone, and ID of each area that was detected within a DetectionZoneRange.
|
||||
-- @extends Functional.Detection#DETECTION_BASE
|
||||
|
||||
--- (old, to be revised ) Detect units within the battle zone for a list of @{Core.Zone}s detecting targets following (a) detection method(s),
|
||||
-- and will build a list (table) of @{Core.Set#SET_UNIT}s containing the @{Wrapper.Unit#UNIT}s detected.
|
||||
-- The class is group the detected units within zones given a DetectedZoneRange parameter.
|
||||
-- A set with multiple detected zones will be created as there are groups of units detected.
|
||||
--
|
||||
-- ## 4.1) Retrieve the Detected Unit Sets and Detected Zones
|
||||
--
|
||||
-- The methods to manage the DetectedItems[].Set(s) are implemented in @{Functional.Detection#DECTECTION_BASE} and
|
||||
-- the methods to manage the DetectedItems[].Zone(s) is implemented in @{Functional.Detection#DETECTION_ZONES}.
|
||||
--
|
||||
-- Retrieve the DetectedItems[].Set with the method @{Functional.Detection#DETECTION_BASE.GetDetectedSet}(). A @{Core.Set#SET_UNIT} object will be returned.
|
||||
--
|
||||
-- Retrieve the formed @{Zone@ZONE_UNIT}s as a result of the grouping the detected units within the DetectionZoneRange, use the method @{Functional.Detection#DETECTION_BASE.GetDetectionZones}().
|
||||
-- To understand the amount of zones created, use the method @{Functional.Detection#DETECTION_BASE.GetDetectionZoneCount}().
|
||||
-- If you want to obtain a specific zone from the DetectedZones, use the method @{Functional.Detection#DETECTION_BASE.GetDetectionZone}() with a given index.
|
||||
--
|
||||
-- ## 4.4) Flare or Smoke detected units
|
||||
--
|
||||
-- Use the methods @{Functional.Detection#DETECTION_ZONES.FlareDetectedUnits}() or @{Functional.Detection#DETECTION_ZONES.SmokeDetectedUnits}() to flare or smoke the detected units when a new detection has taken place.
|
||||
--
|
||||
-- ## 4.5) Flare or Smoke or Bound detected zones
|
||||
--
|
||||
-- Use the methods:
|
||||
--
|
||||
-- * @{Functional.Detection#DETECTION_ZONES.FlareDetectedZones}() to flare in a color
|
||||
-- * @{Functional.Detection#DETECTION_ZONES.SmokeDetectedZones}() to smoke in a color
|
||||
-- * @{Functional.Detection#DETECTION_ZONES.SmokeDetectedZones}() to bound with a tire with a white flag
|
||||
--
|
||||
-- the detected zones when a new detection has taken place.
|
||||
--
|
||||
-- @field #DETECTION_ZONES
|
||||
DETECTION_ZONES = {
|
||||
ClassName = "DETECTION_ZONES",
|
||||
DetectionZoneRange = nil,
|
||||
}
|
||||
|
||||
|
||||
--- DETECTION_ZONES constructor.
|
||||
-- @param #DETECTION_ZONES self
|
||||
-- @param Core.Set#SET_ZONE DetectionSetZone The @{Set} of ZONE_RADIUS.
|
||||
-- @param DCS#Coalition.side DetectionCoalition The coalition of the detection.
|
||||
-- @return #DETECTION_ZONES
|
||||
function DETECTION_ZONES:New( DetectionSetZone, DetectionCoalition )
|
||||
|
||||
-- Inherits from DETECTION_BASE
|
||||
local self = BASE:Inherit( self, DETECTION_BASE:New( DetectionSetZone ) ) -- #DETECTION_ZONES
|
||||
|
||||
self.DetectionSetZone = DetectionSetZone -- Core.Set#SET_ZONE
|
||||
self.DetectionCoalition = DetectionCoalition
|
||||
|
||||
self._SmokeDetectedUnits = false
|
||||
self._FlareDetectedUnits = false
|
||||
self._SmokeDetectedZones = false
|
||||
self._FlareDetectedZones = false
|
||||
self._BoundDetectedZones = false
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- @param #DETECTION_ZONES self
|
||||
-- @param #number The amount of alive recce.
|
||||
function DETECTION_ZONES:CountAliveRecce()
|
||||
|
||||
return self.DetectionSetZone:Count()
|
||||
|
||||
end
|
||||
|
||||
--- @param #DETECTION_ZONES self
|
||||
function DETECTION_ZONES:ForEachAliveRecce( IteratorFunction, ... )
|
||||
self:F2( arg )
|
||||
|
||||
self.DetectionSetZone:ForEachZone( IteratorFunction, arg )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Report summary of a detected item using a given numeric index.
|
||||
-- @param #DETECTION_ZONES self
|
||||
-- @param #DETECTION_BASE.DetectedItem DetectedItem The DetectedItem.
|
||||
-- @param Wrapper.Group#GROUP AttackGroup The group to get the settings for.
|
||||
-- @param Core.Settings#SETTINGS Settings (Optional) Message formatting settings to use.
|
||||
-- @return Core.Report#REPORT The report of the detection items.
|
||||
function DETECTION_ZONES:DetectedItemReportSummary( DetectedItem, AttackGroup, Settings )
|
||||
self:F( { DetectedItem = DetectedItem } )
|
||||
|
||||
local DetectedItemID = self:GetDetectedItemID( DetectedItem )
|
||||
|
||||
if DetectedItem then
|
||||
local DetectedSet = self:GetDetectedItemSet( DetectedItem )
|
||||
local ReportSummaryItem
|
||||
|
||||
local DetectedZone = self:GetDetectedItemZone( DetectedItem )
|
||||
local DetectedItemCoordinate = DetectedZone:GetCoordinate()
|
||||
local DetectedItemCoordText = DetectedItemCoordinate:ToString( AttackGroup, Settings )
|
||||
|
||||
local ThreatLevelA2G = self:GetDetectedItemThreatLevel( DetectedItem )
|
||||
local DetectedItemsCount = DetectedSet:Count()
|
||||
local DetectedItemsTypes = DetectedSet:GetTypeNames()
|
||||
|
||||
local Report = REPORT:New()
|
||||
Report:Add(DetectedItemID .. ", " .. DetectedItemCoordText)
|
||||
Report:Add( string.format( "Threat: [%s]", string.rep( "■", ThreatLevelA2G ), string.rep( "□", 10-ThreatLevelA2G ) ) )
|
||||
Report:Add( string.format("Type: %2d of %s", DetectedItemsCount, DetectedItemsTypes ) )
|
||||
Report:Add( string.format("Detected: %s", DetectedItem.IsDetected and "yes" or "no" ) )
|
||||
|
||||
return Report
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
--- Report detailed of a detection result.
|
||||
-- @param #DETECTION_ZONES self
|
||||
-- @param Wrapper.Group#GROUP AttackGroup The group to generate the report for.
|
||||
-- @return #string
|
||||
function DETECTION_ZONES:DetectedReportDetailed( AttackGroup ) --R2.1 Fixed missing report
|
||||
self:F()
|
||||
|
||||
local Report = REPORT:New()
|
||||
for DetectedItemIndex, DetectedItem in pairs( self.DetectedItems ) do
|
||||
local DetectedItem = DetectedItem -- #DETECTION_BASE.DetectedItem
|
||||
local ReportSummary = self:DetectedItemReportSummary( DetectedItem, AttackGroup )
|
||||
Report:SetTitle( "Detected areas:" )
|
||||
Report:Add( ReportSummary:Text() )
|
||||
end
|
||||
|
||||
local ReportText = Report:Text()
|
||||
|
||||
return ReportText
|
||||
end
|
||||
|
||||
|
||||
--- Calculate the optimal intercept point of the DetectedItem.
|
||||
-- @param #DETECTION_ZONES self
|
||||
-- @param #DETECTION_BASE.DetectedItem DetectedItem
|
||||
function DETECTION_ZONES:CalculateIntercept( DetectedItem )
|
||||
|
||||
local DetectedCoord = DetectedItem.Coordinate
|
||||
-- local DetectedSpeed = DetectedCoord:GetVelocity()
|
||||
-- local DetectedHeading = DetectedCoord:GetHeading()
|
||||
--
|
||||
-- if self.Intercept then
|
||||
-- local DetectedSet = DetectedItem.Set
|
||||
-- -- todo: speed
|
||||
--
|
||||
-- local TranslateDistance = DetectedSpeed * self.InterceptDelay
|
||||
--
|
||||
-- local InterceptCoord = DetectedCoord:Translate( TranslateDistance, DetectedHeading )
|
||||
--
|
||||
-- DetectedItem.InterceptCoord = InterceptCoord
|
||||
-- else
|
||||
-- DetectedItem.InterceptCoord = DetectedCoord
|
||||
-- end
|
||||
DetectedItem.InterceptCoord = DetectedCoord
|
||||
end
|
||||
|
||||
|
||||
|
||||
--- Smoke the detected units
|
||||
-- @param #DETECTION_ZONES self
|
||||
-- @return #DETECTION_ZONES self
|
||||
function DETECTION_ZONES:SmokeDetectedUnits()
|
||||
self:F2()
|
||||
|
||||
self._SmokeDetectedUnits = true
|
||||
return self
|
||||
end
|
||||
|
||||
--- Flare the detected units
|
||||
-- @param #DETECTION_ZONES self
|
||||
-- @return #DETECTION_ZONES self
|
||||
function DETECTION_ZONES:FlareDetectedUnits()
|
||||
self:F2()
|
||||
|
||||
self._FlareDetectedUnits = true
|
||||
return self
|
||||
end
|
||||
|
||||
--- Smoke the detected zones
|
||||
-- @param #DETECTION_ZONES self
|
||||
-- @return #DETECTION_ZONES self
|
||||
function DETECTION_ZONES:SmokeDetectedZones()
|
||||
self:F2()
|
||||
|
||||
self._SmokeDetectedZones = true
|
||||
return self
|
||||
end
|
||||
|
||||
--- Flare the detected zones
|
||||
-- @param #DETECTION_ZONES self
|
||||
-- @return #DETECTION_ZONES self
|
||||
function DETECTION_ZONES:FlareDetectedZones()
|
||||
self:F2()
|
||||
|
||||
self._FlareDetectedZones = true
|
||||
return self
|
||||
end
|
||||
|
||||
--- Bound the detected zones
|
||||
-- @param #DETECTION_ZONES self
|
||||
-- @return #DETECTION_ZONES self
|
||||
function DETECTION_ZONES:BoundDetectedZones()
|
||||
self:F2()
|
||||
|
||||
self._BoundDetectedZones = true
|
||||
return self
|
||||
end
|
||||
|
||||
--- Make text documenting the changes of the detected zone.
|
||||
-- @param #DETECTION_ZONES self
|
||||
-- @param #DETECTION_BASE.DetectedItem DetectedItem
|
||||
-- @return #string The Changes text
|
||||
function DETECTION_ZONES:GetChangeText( DetectedItem )
|
||||
self:F( DetectedItem )
|
||||
|
||||
local MT = {}
|
||||
|
||||
for ChangeCode, ChangeData in pairs( DetectedItem.Changes ) do
|
||||
|
||||
if ChangeCode == "AA" then
|
||||
MT[#MT+1] = "Detected new area " .. ChangeData.ID .. ". The center target is a " .. ChangeData.ItemUnitType .. "."
|
||||
end
|
||||
|
||||
if ChangeCode == "RAU" then
|
||||
MT[#MT+1] = "Changed area " .. ChangeData.ID .. ". Removed the center target."
|
||||
end
|
||||
|
||||
if ChangeCode == "AAU" then
|
||||
MT[#MT+1] = "Changed area " .. ChangeData.ID .. ". The new center target is a " .. ChangeData.ItemUnitType .. "."
|
||||
end
|
||||
|
||||
if ChangeCode == "RA" then
|
||||
MT[#MT+1] = "Removed old area " .. ChangeData.ID .. ". No more targets in this area."
|
||||
end
|
||||
|
||||
if ChangeCode == "AU" then
|
||||
local MTUT = {}
|
||||
for ChangeUnitType, ChangeUnitCount in pairs( ChangeData ) do
|
||||
if ChangeUnitType ~= "ID" then
|
||||
MTUT[#MTUT+1] = ChangeUnitCount .. " of " .. ChangeUnitType
|
||||
end
|
||||
end
|
||||
MT[#MT+1] = "Detected for area " .. ChangeData.ID .. " new target(s) " .. table.concat( MTUT, ", " ) .. "."
|
||||
end
|
||||
|
||||
if ChangeCode == "RU" then
|
||||
local MTUT = {}
|
||||
for ChangeUnitType, ChangeUnitCount in pairs( ChangeData ) do
|
||||
if ChangeUnitType ~= "ID" then
|
||||
MTUT[#MTUT+1] = ChangeUnitCount .. " of " .. ChangeUnitType
|
||||
end
|
||||
end
|
||||
MT[#MT+1] = "Removed for area " .. ChangeData.ID .. " invisible or destroyed target(s) " .. table.concat( MTUT, ", " ) .. "."
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
return table.concat( MT, "\n" )
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- Make a DetectionSet table. This function will be overridden in the derived clsses.
|
||||
-- @param #DETECTION_ZONES self
|
||||
-- @return #DETECTION_ZONES self
|
||||
function DETECTION_ZONES:CreateDetectionItems()
|
||||
|
||||
|
||||
self:F( "Checking Detected Items for new Detected Units ..." )
|
||||
|
||||
local DetectedUnits = SET_UNIT:New()
|
||||
|
||||
-- First go through all zones, and check if there are new Zones.
|
||||
-- New Zones become a new DetectedItem.
|
||||
for ZoneName, DetectionZone in pairs( self.DetectionSetZone:GetSet() ) do
|
||||
|
||||
local DetectedItem = self:GetDetectedItemByKey( ZoneName )
|
||||
|
||||
if DetectedItem == nil then
|
||||
DetectedItem = self:AddDetectedItemZone( "ZONE", ZoneName, nil, DetectionZone )
|
||||
end
|
||||
|
||||
local DetectedItemSetUnit = self:GetDetectedItemSet( DetectedItem )
|
||||
|
||||
-- Scan the zone
|
||||
DetectionZone:Scan( { Object.Category.UNIT }, { Unit.Category.GROUND_UNIT } )
|
||||
|
||||
-- For all the units in the zone,
|
||||
-- check if they are of the same coalition to be included.
|
||||
local ZoneUnits = DetectionZone:GetScannedUnits()
|
||||
for DCSUnitID, DCSUnit in pairs( ZoneUnits ) do
|
||||
local UnitName = DCSUnit:getName()
|
||||
local ZoneUnit = UNIT:FindByName( UnitName )
|
||||
local ZoneUnitCoalition = ZoneUnit:GetCoalition()
|
||||
if ZoneUnitCoalition == self.DetectionCoalition then
|
||||
if DetectedItemSetUnit:FindUnit( UnitName ) == nil and DetectedUnits:FindUnit( UnitName ) == nil then
|
||||
self:F( "Adding " .. UnitName )
|
||||
DetectedItemSetUnit:AddUnit( ZoneUnit )
|
||||
DetectedUnits:AddUnit( ZoneUnit )
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- Now all the tests should have been build, now make some smoke and flares...
|
||||
-- We also report here the friendlies within the detected areas.
|
||||
|
||||
for DetectedItemID, DetectedItemData in pairs( self.DetectedItems ) do
|
||||
|
||||
local DetectedItem = DetectedItemData -- #DETECTION_BASE.DetectedItem
|
||||
local DetectedSet = self:GetDetectedItemSet( DetectedItem )
|
||||
local DetectedFirstUnit = DetectedSet:GetFirst()
|
||||
local DetectedZone = self:GetDetectedItemZone( DetectedItem )
|
||||
|
||||
-- Set the last known coordinate to the detection item.
|
||||
local DetectedZoneCoord = DetectedZone:GetCoordinate()
|
||||
self:SetDetectedItemCoordinate( DetectedItem, DetectedZoneCoord, DetectedFirstUnit )
|
||||
|
||||
self:CalculateIntercept( DetectedItem )
|
||||
|
||||
-- We search for friendlies nearby.
|
||||
-- If there weren't any friendlies nearby, and now there are friendlies nearby, we flag the area as "changed".
|
||||
-- If there were friendlies nearby, and now there aren't any friendlies nearby, we flag the area as "changed".
|
||||
-- This is for the A2G dispatcher to detect if there is a change in the tactical situation.
|
||||
local OldFriendliesNearbyGround = self:IsFriendliesNearBy( DetectedItem, Unit.Category.GROUND_UNIT )
|
||||
self:ReportFriendliesNearBy( { DetectedItem = DetectedItem, ReportSetGroup = self.DetectionSetGroup } ) -- Fill the Friendlies table
|
||||
local NewFriendliesNearbyGround = self:IsFriendliesNearBy( DetectedItem, Unit.Category.GROUND_UNIT )
|
||||
if OldFriendliesNearbyGround ~= NewFriendliesNearbyGround then
|
||||
DetectedItem.Changed = true
|
||||
end
|
||||
|
||||
self:SetDetectedItemThreatLevel( DetectedItem ) -- Calculate A2G threat level
|
||||
--self:NearestRecce( DetectedItem )
|
||||
|
||||
|
||||
if DETECTION_ZONES._SmokeDetectedUnits or self._SmokeDetectedUnits then
|
||||
DetectedZone:SmokeZone( SMOKECOLOR.Red, 30 )
|
||||
end
|
||||
|
||||
--DetectedSet:Flush( self )
|
||||
|
||||
DetectedSet:ForEachUnit(
|
||||
--- @param Wrapper.Unit#UNIT DetectedUnit
|
||||
function( DetectedUnit )
|
||||
if DetectedUnit:IsAlive() then
|
||||
--self:T( "Detected Set #" .. DetectedItem.ID .. ":" .. DetectedUnit:GetName() )
|
||||
if DETECTION_ZONES._FlareDetectedUnits or self._FlareDetectedUnits then
|
||||
DetectedUnit:FlareGreen()
|
||||
end
|
||||
if DETECTION_ZONES._SmokeDetectedUnits or self._SmokeDetectedUnits then
|
||||
DetectedUnit:SmokeGreen()
|
||||
end
|
||||
end
|
||||
end
|
||||
)
|
||||
if DETECTION_ZONES._FlareDetectedZones or self._FlareDetectedZones then
|
||||
DetectedZone:FlareZone( SMOKECOLOR.White, 30, math.random( 0,90 ) )
|
||||
end
|
||||
if DETECTION_ZONES._SmokeDetectedZones or self._SmokeDetectedZones then
|
||||
DetectedZone:SmokeZone( SMOKECOLOR.White, 30 )
|
||||
end
|
||||
|
||||
if DETECTION_ZONES._BoundDetectedZones or self._BoundDetectedZones then
|
||||
self.CountryID = DetectedSet:GetFirst():GetCountry()
|
||||
DetectedZone:BoundZone( 12, self.CountryID )
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- @param #DETECTION_ZONES self
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @param Detection The element on which the detection is based.
|
||||
-- @param #number DetectionTimeStamp Time stamp of detection event.
|
||||
function DETECTION_ZONES:onafterDetection( From, Event, To, Detection, DetectionTimeStamp )
|
||||
|
||||
self.DetectionRun = self.DetectionRun + 1
|
||||
if self.DetectionCount > 0 and self.DetectionRun == self.DetectionCount then
|
||||
self:CreateDetectionItems() -- Polymorphic call to Create/Update the DetectionItems list for the DETECTION_ class grouping method.
|
||||
|
||||
for DetectedItemID, DetectedItem in pairs( self.DetectedItems ) do
|
||||
self:UpdateDetectedItemDetection( DetectedItem )
|
||||
self:CleanDetectionItem( DetectedItem, DetectedItemID ) -- Any DetectionItem that has a Set with zero elements in it, must be removed from the DetectionItems list.
|
||||
if DetectedItem then
|
||||
self:__DetectedItem( 0.1, DetectedItem )
|
||||
end
|
||||
end
|
||||
self:__Detect( -self.RefreshTimeInterval )
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- Set IsDetected flag for the DetectedItem, which can have more units.
|
||||
-- @param #DETECTION_ZONES self
|
||||
-- @return #DETECTION_ZONES.DetectedItem DetectedItem
|
||||
-- @return #boolean true if at least one UNIT is detected from the DetectedSet, false if no UNIT was detected from the DetectedSet.
|
||||
function DETECTION_ZONES:UpdateDetectedItemDetection( DetectedItem )
|
||||
|
||||
local IsDetected = true
|
||||
|
||||
DetectedItem.IsDetected = true
|
||||
|
||||
return IsDetected
|
||||
end
|
||||
|
||||
end
|
||||
@@ -872,7 +872,7 @@ function ESCORT:_AttackTarget( DetectedItem )
|
||||
EscortGroup:OptionROTPassiveDefense()
|
||||
EscortGroup:SetState( EscortGroup, "Escort", self )
|
||||
|
||||
local DetectedSet = self.Detection:GetDetectedSet( DetectedItem )
|
||||
local DetectedSet = self.Detection:GetDetectedItemSet( DetectedItem )
|
||||
|
||||
local Tasks = {}
|
||||
|
||||
@@ -895,7 +895,7 @@ function ESCORT:_AttackTarget( DetectedItem )
|
||||
|
||||
else
|
||||
|
||||
local DetectedSet = self.Detection:GetDetectedSet( DetectedItem )
|
||||
local DetectedSet = self.Detection:GetDetectedItemSet( DetectedItem )
|
||||
|
||||
local Tasks = {}
|
||||
|
||||
@@ -934,7 +934,7 @@ function ESCORT:_AssistTarget( EscortGroupAttack, DetectedItem )
|
||||
EscortGroupAttack:OptionROEOpenFire()
|
||||
EscortGroupAttack:OptionROTVertical()
|
||||
|
||||
local DetectedSet = self.Detection:GetDetectedSet( DetectedItem )
|
||||
local DetectedSet = self.Detection:GetDetectedItemSet( DetectedItem )
|
||||
|
||||
local Tasks = {}
|
||||
|
||||
@@ -956,7 +956,7 @@ function ESCORT:_AssistTarget( EscortGroupAttack, DetectedItem )
|
||||
)
|
||||
|
||||
else
|
||||
local DetectedSet = self.Detection:GetDetectedSet( DetectedItem )
|
||||
local DetectedSet = self.Detection:GetDetectedItemSet( DetectedItem )
|
||||
|
||||
local Tasks = {}
|
||||
|
||||
|
||||
1814
Moose Development/Moose/Functional/Fox.lua
Normal file
1814
Moose Development/Moose/Functional/Fox.lua
Normal file
File diff suppressed because it is too large
Load Diff
1376
Moose Development/Moose/Functional/Mantis.lua
Normal file
1376
Moose Development/Moose/Functional/Mantis.lua
Normal file
File diff suppressed because it is too large
Load Diff
@@ -5,7 +5,7 @@
|
||||
-- ## 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 <EFBFBD>
|
||||
-- * 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.
|
||||
|
||||
@@ -81,7 +81,7 @@
|
||||
-- @field #PSEUDOATC
|
||||
PSEUDOATC={
|
||||
ClassName = "PSEUDOATC",
|
||||
player={},
|
||||
group={},
|
||||
Debug=false,
|
||||
mdur=30,
|
||||
mrefresh=120,
|
||||
@@ -98,7 +98,7 @@ PSEUDOATC.id="PseudoATC | "
|
||||
|
||||
--- PSEUDOATC version.
|
||||
-- @field #number version
|
||||
PSEUDOATC.version="0.9.1"
|
||||
PSEUDOATC.version="0.9.2"
|
||||
|
||||
-----------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
@@ -383,16 +383,23 @@ function PSEUDOATC:PlayerEntered(unit)
|
||||
local PlayerName=unit:GetPlayerName()
|
||||
local UnitName=unit:GetName()
|
||||
local CallSign=unit:GetCallsign()
|
||||
local UID=unit:GetDCSObject():getID()
|
||||
|
||||
if not self.group[GID] then
|
||||
self.group[GID]={}
|
||||
self.group[GID].player={}
|
||||
end
|
||||
|
||||
|
||||
-- Init player table.
|
||||
self.player[GID]={}
|
||||
self.player[GID].group=group
|
||||
self.player[GID].unit=unit
|
||||
self.player[GID].groupname=GroupName
|
||||
self.player[GID].unitname=UnitName
|
||||
self.player[GID].playername=PlayerName
|
||||
self.player[GID].callsign=CallSign
|
||||
self.player[GID].waypoints=group:GetTaskRoute()
|
||||
self.group[GID].player[UID]={}
|
||||
self.group[GID].player[UID].group=group
|
||||
self.group[GID].player[UID].unit=unit
|
||||
self.group[GID].player[UID].groupname=GroupName
|
||||
self.group[GID].player[UID].unitname=UnitName
|
||||
self.group[GID].player[UID].playername=PlayerName
|
||||
self.group[GID].player[UID].callsign=CallSign
|
||||
self.group[GID].player[UID].waypoints=group:GetTaskRoute()
|
||||
|
||||
-- Info message.
|
||||
local text=string.format("Player %s entered unit %s of group %s (id=%d).", PlayerName, UnitName, GroupName, GID)
|
||||
@@ -400,19 +407,26 @@ function PSEUDOATC:PlayerEntered(unit)
|
||||
MESSAGE:New(text, 30):ToAllIf(self.Debug)
|
||||
|
||||
-- Create main F10 menu, i.e. "F10/Pseudo ATC"
|
||||
self.player[GID].menu_main=missionCommands.addSubMenuForGroup(GID, "Pseudo ATC")
|
||||
local countPlayerInGroup = 0
|
||||
for _ in pairs(self.group[GID].player) do countPlayerInGroup = countPlayerInGroup + 1 end
|
||||
if countPlayerInGroup <= 1 then
|
||||
self.group[GID].menu_main=missionCommands.addSubMenuForGroup(GID, "Pseudo ATC")
|
||||
end
|
||||
|
||||
-- Create/update custom menu for player
|
||||
self:MenuCreatePlayer(GID,UID)
|
||||
|
||||
-- Create/update list of nearby airports.
|
||||
self:LocalAirports(GID)
|
||||
self:LocalAirports(GID,UID)
|
||||
|
||||
-- Create submenu of local airports.
|
||||
self:MenuAirports(GID)
|
||||
self:MenuAirports(GID,UID)
|
||||
|
||||
-- Create submenu Waypoints.
|
||||
self:MenuWaypoints(GID)
|
||||
self:MenuWaypoints(GID,UID)
|
||||
|
||||
-- Start scheduler to refresh the F10 menues.
|
||||
self.player[GID].scheduler, self.player[GID].schedulerid=SCHEDULER:New(nil, self.MenuRefresh, {self, GID}, self.mrefresh, self.mrefresh)
|
||||
self.group[GID].player[UID].scheduler, self.group[GID].player[UID].schedulerid=SCHEDULER:New(nil, self.MenuRefresh, {self, GID, UID}, self.mrefresh, self.mrefresh)
|
||||
|
||||
end
|
||||
|
||||
@@ -425,24 +439,23 @@ function PSEUDOATC:PlayerLanded(unit, place)
|
||||
|
||||
-- Gather some information.
|
||||
local group=unit:GetGroup()
|
||||
local id=group:GetID()
|
||||
local PlayerName=self.player[id].playername
|
||||
local Callsign=self.player[id].callsign
|
||||
local UnitName=self.player[id].unitname
|
||||
local GroupName=self.player[id].groupname
|
||||
local CallSign=self.player[id].callsign
|
||||
local GID=group:GetID()
|
||||
local UID=unit:GetDCSObject():getID()
|
||||
local PlayerName=self.group[GID].player[UID].playername
|
||||
local UnitName=self.group[GID].player[UID].unitname
|
||||
local GroupName=self.group[GID].player[UID].groupname
|
||||
|
||||
-- Debug message.
|
||||
local text=string.format("Player %s in unit %s of group %s (id=%d) landed at %s.", PlayerName, UnitName, GroupName, id, place)
|
||||
local text=string.format("Player %s in unit %s of group %s (id=%d) landed at %s.", PlayerName, UnitName, GroupName, GID, place)
|
||||
self:T(PSEUDOATC.id..text)
|
||||
MESSAGE:New(text, 30):ToAllIf(self.Debug)
|
||||
|
||||
-- Stop altitude reporting timer if its activated.
|
||||
self:AltitudeTimerStop(id)
|
||||
self:AltitudeTimerStop(GID,UID)
|
||||
|
||||
-- Welcome message.
|
||||
if place and self.chatty then
|
||||
local text=string.format("Touchdown! Welcome to %s. Have a nice day!", place)
|
||||
local text=string.format("Touchdown! Welcome to %s pilot %s. Have a nice day!", place,PlayerName)
|
||||
MESSAGE:New(text, self.mdur):ToGroup(group)
|
||||
end
|
||||
|
||||
@@ -457,15 +470,15 @@ function PSEUDOATC:PlayerTakeOff(unit, place)
|
||||
|
||||
-- Gather some information.
|
||||
local group=unit:GetGroup()
|
||||
local id=group:GetID()
|
||||
local PlayerName=self.player[id].playername
|
||||
local Callsign=self.player[id].callsign
|
||||
local UnitName=self.player[id].unitname
|
||||
local GroupName=self.player[id].groupname
|
||||
local CallSign=self.player[id].callsign
|
||||
local GID=group:GetID()
|
||||
local UID=unit:GetDCSObject():getID()
|
||||
local PlayerName=self.group[GID].player[UID].playername
|
||||
local CallSign=self.group[GID].player[UID].callsign
|
||||
local UnitName=self.group[GID].player[UID].unitname
|
||||
local GroupName=self.group[GID].player[UID].groupname
|
||||
|
||||
-- Debug message.
|
||||
local text=string.format("Player %s in unit %s of group %s (id=%d) took off at %s.", PlayerName, UnitName, GroupName, id, place)
|
||||
local text=string.format("Player %s in unit %s of group %s (id=%d) took off at %s.", PlayerName, UnitName, GroupName, GID, place)
|
||||
self:T(PSEUDOATC.id..text)
|
||||
MESSAGE:New(text, 30):ToAllIf(self.Debug)
|
||||
|
||||
@@ -485,30 +498,44 @@ function PSEUDOATC:PlayerLeft(unit)
|
||||
|
||||
-- Get id.
|
||||
local group=unit:GetGroup()
|
||||
local id=group:GetID()
|
||||
local GID=group:GetID()
|
||||
local UID=unit:GetDCSObject():getID()
|
||||
|
||||
if self.player[id] then
|
||||
if self.group[GID].player[UID] then
|
||||
local PlayerName=self.group[GID].player[UID].playername
|
||||
local CallSign=self.group[GID].player[UID].callsign
|
||||
local UnitName=self.group[GID].player[UID].unitname
|
||||
local GroupName=self.group[GID].player[UID].groupname
|
||||
|
||||
-- Debug message.
|
||||
local text=string.format("Player %s (callsign %s) of group %s just left unit %s.", self.player[id].playername, self.player[id].callsign, self.player[id].groupname, self.player[id].unitname)
|
||||
local text=string.format("Player %s (callsign %s) of group %s just left unit %s.", PlayerName, CallSign, GroupName, UnitName)
|
||||
self:T(PSEUDOATC.id..text)
|
||||
MESSAGE:New(text, 30):ToAllIf(self.Debug)
|
||||
|
||||
-- Stop scheduler for menu updates
|
||||
if self.player[id].schedulerid then
|
||||
self.player[id].scheduler:Stop(self.player[id].schedulerid)
|
||||
if self.group[GID].player[UID].schedulerid then
|
||||
self.group[GID].player[UID].scheduler:Stop(self.group[GID].player[UID].schedulerid)
|
||||
end
|
||||
|
||||
-- Stop scheduler for reporting alt if it runs.
|
||||
self:AltitudeTimerStop(id)
|
||||
self:AltitudeTimerStop(GID,UID)
|
||||
|
||||
-- Remove own menu.
|
||||
if self.group[GID].player[UID].menu_own then
|
||||
missionCommands.removeItemForGroup(GID,self.group[GID].player[UID].menu_own)
|
||||
end
|
||||
-- Remove main menu.
|
||||
if self.player[id].menu_main then
|
||||
missionCommands.removeItem(self.player[id].menu_main)
|
||||
-- WARNING: Remove only if last human element of group
|
||||
|
||||
local countPlayerInGroup = 0
|
||||
for _ in pairs(self.group[GID].player) do countPlayerInGroup = countPlayerInGroup + 1 end
|
||||
|
||||
if self.group[GID].menu_main and countPlayerInGroup==1 then
|
||||
missionCommands.removeItemForGroup(GID,self.group[GID].menu_main)
|
||||
end
|
||||
|
||||
-- Remove player array.
|
||||
self.player[id]=nil
|
||||
self.group[GID].player[UID]=nil
|
||||
|
||||
end
|
||||
end
|
||||
@@ -518,80 +545,94 @@ end
|
||||
|
||||
--- Refreshes all player menues.
|
||||
-- @param #PSEUDOATC self.
|
||||
-- @param #number id Group id of player unit.
|
||||
function PSEUDOATC:MenuRefresh(id)
|
||||
self:F({id=id})
|
||||
|
||||
-- @param #number GID Group id of player unit.
|
||||
-- @param #number UID Unit id of player.
|
||||
function PSEUDOATC:MenuRefresh(GID,UID)
|
||||
self:F({GID=GID,UID=UID})
|
||||
-- Debug message.
|
||||
local text=string.format("Refreshing menues for player %s in group %s.", self.player[id].playername, self.player[id].groupname)
|
||||
local text=string.format("Refreshing menues for player %s in group %s.", self.group[GID].player[UID].playername, self.group[GID].player[UID].groupname)
|
||||
self:T(PSEUDOATC.id..text)
|
||||
MESSAGE:New(text,30):ToAllIf(self.Debug)
|
||||
|
||||
-- Clear menu.
|
||||
self:MenuClear(id)
|
||||
self:MenuClear(GID,UID)
|
||||
|
||||
-- Create list of nearby airports.
|
||||
self:LocalAirports(id)
|
||||
self:LocalAirports(GID,UID)
|
||||
|
||||
-- Create submenu Local Airports.
|
||||
self:MenuAirports(id)
|
||||
self:MenuAirports(GID,UID)
|
||||
|
||||
-- Create submenu Waypoints etc.
|
||||
self:MenuWaypoints(id)
|
||||
self:MenuWaypoints(GID,UID)
|
||||
|
||||
end
|
||||
|
||||
--- Create player menus.
|
||||
-- @param #PSEUDOATC self.
|
||||
-- @param #number GID Group id of player unit.
|
||||
-- @param #number UID Unit id of player.
|
||||
function PSEUDOATC:MenuCreatePlayer(GID,UID)
|
||||
self:F({GID=GID,UID=UID})
|
||||
-- Table for menu entries.
|
||||
local PlayerName=self.group[GID].player[UID].playername
|
||||
self.group[GID].player[UID].menu_own=missionCommands.addSubMenuForGroup(GID, PlayerName, self.group[GID].menu_main)
|
||||
end
|
||||
|
||||
|
||||
|
||||
--- Clear player menus.
|
||||
-- @param #PSEUDOATC self.
|
||||
-- @param #number id Group id of player unit.
|
||||
function PSEUDOATC:MenuClear(id)
|
||||
self:F(id)
|
||||
-- @param #number GID Group id of player unit.
|
||||
-- @param #number UID Unit id of player.
|
||||
function PSEUDOATC:MenuClear(GID,UID)
|
||||
self:F({GID=GID,UID=UID})
|
||||
|
||||
-- Debug message.
|
||||
local text=string.format("Clearing menus for player %s in group %s.", self.player[id].playername, self.player[id].groupname)
|
||||
local text=string.format("Clearing menus for player %s in group %s.", self.group[GID].player[UID].playername, self.group[GID].player[UID].groupname)
|
||||
self:T(PSEUDOATC.id..text)
|
||||
MESSAGE:New(text,30):ToAllIf(self.Debug)
|
||||
|
||||
-- Delete Airports menu.
|
||||
if self.player[id].menu_airports then
|
||||
missionCommands.removeItemForGroup(id, self.player[id].menu_airports)
|
||||
self.player[id].menu_airports=nil
|
||||
if self.group[GID].player[UID].menu_airports then
|
||||
missionCommands.removeItemForGroup(GID, self.group[GID].player[UID].menu_airports)
|
||||
self.group[GID].player[UID].menu_airports=nil
|
||||
else
|
||||
self:T2(PSEUDOATC.id.."No airports to clear menus.")
|
||||
end
|
||||
|
||||
-- Delete waypoints menu.
|
||||
if self.player[id].menu_waypoints then
|
||||
missionCommands.removeItemForGroup(id, self.player[id].menu_waypoints)
|
||||
self.player[id].menu_waypoints=nil
|
||||
if self.group[GID].player[UID].menu_waypoints then
|
||||
missionCommands.removeItemForGroup(GID, self.group[GID].player[UID].menu_waypoints)
|
||||
self.group[GID].player[UID].menu_waypoints=nil
|
||||
end
|
||||
|
||||
-- Delete report alt until touchdown menu command.
|
||||
if self.player[id].menu_reportalt then
|
||||
missionCommands.removeItemForGroup(id, self.player[id].menu_reportalt)
|
||||
self.player[id].menu_reportalt=nil
|
||||
if self.group[GID].player[UID].menu_reportalt then
|
||||
missionCommands.removeItemForGroup(GID, self.group[GID].player[UID].menu_reportalt)
|
||||
self.group[GID].player[UID].menu_reportalt=nil
|
||||
end
|
||||
|
||||
-- Delete request current alt menu command.
|
||||
if self.player[id].menu_requestalt then
|
||||
missionCommands.removeItemForGroup(id, self.player[id].menu_requestalt)
|
||||
self.player[id].menu_requestalt=nil
|
||||
if self.group[GID].player[UID].menu_requestalt then
|
||||
missionCommands.removeItemForGroup(GID, self.group[GID].player[UID].menu_requestalt)
|
||||
self.group[GID].player[UID].menu_requestalt=nil
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Create "F10/Pseudo ATC/Local Airports/Airport Name/" menu items each containing weather report and BR request.
|
||||
-- @param #PSEUDOATC self
|
||||
-- @param #number id Group id of player unit for which menues are created.
|
||||
function PSEUDOATC:MenuAirports(id)
|
||||
self:F(id)
|
||||
-- @param #number GID Group id of player unit.
|
||||
-- @param #number UID Unit id of player.
|
||||
function PSEUDOATC:MenuAirports(GID,UID)
|
||||
self:F({GID=GID,UID=UID})
|
||||
|
||||
-- Table for menu entries.
|
||||
self.player[id].menu_airports=missionCommands.addSubMenuForGroup(id, "Local Airports", self.player[id].menu_main)
|
||||
self.group[GID].player[UID].menu_airports=missionCommands.addSubMenuForGroup(GID, "Local Airports", self.group[GID].player[UID].menu_own)
|
||||
|
||||
local i=0
|
||||
for _,airport in pairs(self.player[id].airports) do
|
||||
for _,airport in pairs(self.group[GID].player[UID].airports) do
|
||||
|
||||
i=i+1
|
||||
if i > 10 then
|
||||
@@ -603,37 +644,38 @@ function PSEUDOATC:MenuAirports(id)
|
||||
local pos=AIRBASE:FindByName(name):GetCoordinate()
|
||||
|
||||
--F10menu_ATC_airports[ID][name] = missionCommands.addSubMenuForGroup(ID, name, F10menu_ATC)
|
||||
local submenu=missionCommands.addSubMenuForGroup(id, name, self.player[id].menu_airports)
|
||||
local submenu=missionCommands.addSubMenuForGroup(GID, name, self.group[GID].player[UID].menu_airports)
|
||||
|
||||
-- Create menu reporting commands
|
||||
missionCommands.addCommandForGroup(id, "Weather Report", submenu, self.ReportWeather, self, id, pos, name)
|
||||
missionCommands.addCommandForGroup(id, "Request BR", submenu, self.ReportBR, self, id, pos, name)
|
||||
missionCommands.addCommandForGroup(GID, "Weather Report", submenu, self.ReportWeather, self, GID, UID, pos, name)
|
||||
missionCommands.addCommandForGroup(GID, "Request BR", submenu, self.ReportBR, self, GID, UID, pos, name)
|
||||
|
||||
-- Debug message.
|
||||
self:T(string.format(PSEUDOATC.id.."Creating airport menu item %s for ID %d", name, id))
|
||||
self:T(string.format(PSEUDOATC.id.."Creating airport menu item %s for ID %d", name, GID))
|
||||
end
|
||||
end
|
||||
|
||||
--- Create "F10/Pseudo ATC/Waypoints/<Waypoint i> menu items.
|
||||
-- @param #PSEUDOATC self
|
||||
-- @param #number id Group id of player unit for which menues are created.
|
||||
function PSEUDOATC:MenuWaypoints(id)
|
||||
self:F(id)
|
||||
-- @param #number GID Group id of player unit.
|
||||
-- @param #number UID Unit id of player.
|
||||
function PSEUDOATC:MenuWaypoints(GID, UID)
|
||||
self:F({GID=GID, UID=UID})
|
||||
|
||||
-- Player unit and callsign.
|
||||
local unit=self.player[id].unit --Wrapper.Unit#UNIT
|
||||
local callsign=self.player[id].callsign
|
||||
-- local unit=self.group[GID].player[UID].unit --Wrapper.Unit#UNIT
|
||||
local callsign=self.group[GID].player[UID].callsign
|
||||
|
||||
-- Debug info.
|
||||
self:T(PSEUDOATC.id..string.format("Creating waypoint menu for %s (ID %d).", callsign, id))
|
||||
self:T(PSEUDOATC.id..string.format("Creating waypoint menu for %s (ID %d).", callsign, GID))
|
||||
|
||||
if #self.player[id].waypoints>0 then
|
||||
if #self.group[GID].player[UID].waypoints>0 then
|
||||
|
||||
-- F10/PseudoATC/Waypoints
|
||||
self.player[id].menu_waypoints=missionCommands.addSubMenuForGroup(id, "Waypoints", self.player[id].menu_main)
|
||||
self.group[GID].player[UID].menu_waypoints=missionCommands.addSubMenuForGroup(GID, "Waypoints", self.group[GID].player[UID].menu_own)
|
||||
|
||||
local j=0
|
||||
for i, wp in pairs(self.player[id].waypoints) do
|
||||
for i, wp in pairs(self.group[GID].player[UID].waypoints) do
|
||||
|
||||
-- Increase counter
|
||||
j=j+1
|
||||
@@ -647,16 +689,16 @@ function PSEUDOATC:MenuWaypoints(id)
|
||||
local name=string.format("Waypoint %d", i-1)
|
||||
|
||||
-- "F10/PseudoATC/Waypoints/Waypoint X"
|
||||
local submenu=missionCommands.addSubMenuForGroup(id, name, self.player[id].menu_waypoints)
|
||||
local submenu=missionCommands.addSubMenuForGroup(GID, name, self.group[GID].player[UID].menu_waypoints)
|
||||
|
||||
-- Menu commands for each waypoint "F10/PseudoATC/My Aircraft (callsign)/Waypoints/Waypoint X/<Commands>"
|
||||
missionCommands.addCommandForGroup(id, "Weather Report", submenu, self.ReportWeather, self, id, pos, name)
|
||||
missionCommands.addCommandForGroup(id, "Request BR", submenu, self.ReportBR, self, id, pos, name)
|
||||
missionCommands.addCommandForGroup(GID, "Weather Report", submenu, self.ReportWeather, self, GID, UID, pos, name)
|
||||
missionCommands.addCommandForGroup(GID, "Request BR", submenu, self.ReportBR, self, GID, UID, pos, name)
|
||||
end
|
||||
end
|
||||
|
||||
self.player[id].menu_reportalt = missionCommands.addCommandForGroup(id, "Talk me down", self.player[id].menu_main, self.AltidudeTimerToggle, self, id)
|
||||
self.player[id].menu_requestalt = missionCommands.addCommandForGroup(id, "Request altitude", self.player[id].menu_main, self.ReportHeight, self, id)
|
||||
self.group[GID].player[UID].menu_reportalt = missionCommands.addCommandForGroup(GID, "Talk me down", self.group[GID].player[UID].menu_own, self.AltidudeTimerToggle, self, GID, UID)
|
||||
self.group[GID].player[UID].menu_requestalt = missionCommands.addCommandForGroup(GID, "Request altitude", self.group[GID].player[UID].menu_own, self.ReportHeight, self, GID, UID)
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------------------------------------------------------------------
|
||||
@@ -664,14 +706,15 @@ end
|
||||
|
||||
--- Weather Report. Report pressure QFE/QNH, temperature, wind at certain location.
|
||||
-- @param #PSEUDOATC self
|
||||
-- @param #number id Group id to which the report is delivered.
|
||||
-- @param #number GID Group id of player unit.
|
||||
-- @param #number UID Unit id of player.
|
||||
-- @param Core.Point#COORDINATE position Coordinates at which the pressure is measured.
|
||||
-- @param #string location Name of the location at which the pressure is measured.
|
||||
function PSEUDOATC:ReportWeather(id, position, location)
|
||||
self:F({id=id, position=position, location=location})
|
||||
function PSEUDOATC:ReportWeather(GID, UID, position, location)
|
||||
self:F({GID=GID, UID=UID, position=position, location=location})
|
||||
|
||||
-- Player unit system settings.
|
||||
local settings=_DATABASE:GetPlayerSettings(self.player[id].playername) or _SETTINGS --Core.Settings#SETTINGS
|
||||
local settings=_DATABASE:GetPlayerSettings(self.group[GID].player[UID].playername) or _SETTINGS --Core.Settings#SETTINGS
|
||||
|
||||
local text=string.format("Local weather at %s:\n", location)
|
||||
|
||||
@@ -699,7 +742,7 @@ function PSEUDOATC:ReportWeather(id, position, location)
|
||||
local T=position:GetTemperature()
|
||||
|
||||
-- Correct unit system.
|
||||
local _T=string.format('%d°F', UTILS.CelciusToFarenheit(T))
|
||||
local _T=string.format('%d°F', UTILS.CelsiusToFahrenheit(T))
|
||||
if settings:IsMetric() then
|
||||
_T=string.format('%d°C', T)
|
||||
end
|
||||
@@ -723,23 +766,24 @@ function PSEUDOATC:ReportWeather(id, position, location)
|
||||
end
|
||||
|
||||
-- Message text.
|
||||
local text=text..string.format("Wind from %s at %s (%s).", Ds, Vs, Bd)
|
||||
local text=text..string.format("%s, Wind from %s at %s (%s).", self.group[GID].player[UID].playername, Ds, Vs, Bd)
|
||||
|
||||
-- Send message
|
||||
self:_DisplayMessageToGroup(self.player[id].unit, text, self.mdur, true)
|
||||
self:_DisplayMessageToGroup(self.group[GID].player[UID].unit, text, self.mdur, true)
|
||||
|
||||
end
|
||||
|
||||
--- Report absolute bearing and range form player unit to airport.
|
||||
-- @param #PSEUDOATC self
|
||||
-- @param #number id Group id to the report is delivered.
|
||||
-- @param #number GID Group id of player unit.
|
||||
-- @param #number UID Unit id of player.
|
||||
-- @param Core.Point#COORDINATE position Coordinates at which the pressure is measured.
|
||||
-- @param #string location Name of the location at which the pressure is measured.
|
||||
function PSEUDOATC:ReportBR(id, position, location)
|
||||
self:F({id=id, position=position, location=location})
|
||||
function PSEUDOATC:ReportBR(GID, UID, position, location)
|
||||
self:F({GID=GID, UID=UID, position=position, location=location})
|
||||
|
||||
-- Current coordinates.
|
||||
local unit=self.player[id].unit --Wrapper.Unit#UNIT
|
||||
local unit=self.group[GID].player[UID].unit --Wrapper.Unit#UNIT
|
||||
local coord=unit:GetCoordinate()
|
||||
|
||||
-- Direction vector from current position (coord) to target (position).
|
||||
@@ -752,7 +796,7 @@ function PSEUDOATC:ReportBR(id, position, location)
|
||||
local Bs=string.format('%03d°', angle)
|
||||
|
||||
-- Settings.
|
||||
local settings=_DATABASE:GetPlayerSettings(self.player[id].playername) or _SETTINGS --Core.Settings#SETTINGS
|
||||
local settings=_DATABASE:GetPlayerSettings(self.group[GID].player[UID].playername) or _SETTINGS --Core.Settings#SETTINGS
|
||||
|
||||
|
||||
local Rs=string.format("%.1f NM", UTILS.MetersToNM(range))
|
||||
@@ -763,18 +807,20 @@ function PSEUDOATC:ReportBR(id, position, location)
|
||||
-- Message text.
|
||||
local text=string.format("%s: Bearing %s, Range %s.", location, Bs, Rs)
|
||||
|
||||
-- Send message to player group.
|
||||
MESSAGE:New(text, self.mdur):ToGroup(self.player[id].group)
|
||||
-- Send message
|
||||
self:_DisplayMessageToGroup(self.group[GID].player[UID].unit, text, self.mdur, true)
|
||||
|
||||
end
|
||||
|
||||
--- Report altitude above ground level of player unit.
|
||||
-- @param #PSEUDOATC self
|
||||
-- @param #number id Group id to the report is delivered.
|
||||
-- @param #number GID Group id of player unit.
|
||||
-- @param #number UID Unit id of player.
|
||||
-- @param #number dt (Optional) Duration the message is displayed.
|
||||
-- @param #boolean _clear (Optional) Clear previouse messages.
|
||||
-- @return #number Altitude above ground.
|
||||
function PSEUDOATC:ReportHeight(id, dt, _clear)
|
||||
self:F({id=id, dt=dt})
|
||||
function PSEUDOATC:ReportHeight(GID, UID, dt, _clear)
|
||||
self:F({GID=GID, UID=UID, dt=dt})
|
||||
|
||||
local dt = dt or self.mdur
|
||||
if _clear==nil then
|
||||
@@ -791,7 +837,7 @@ function PSEUDOATC:ReportHeight(id, dt, _clear)
|
||||
end
|
||||
|
||||
-- Get height AGL.
|
||||
local unit=self.player[id].unit --Wrapper.Unit#UNIT
|
||||
local unit=self.group[GID].player[UID].unit --Wrapper.Unit#UNIT
|
||||
|
||||
if unit and unit:IsAlive() then
|
||||
|
||||
@@ -800,7 +846,7 @@ function PSEUDOATC:ReportHeight(id, dt, _clear)
|
||||
local callsign=unit:GetCallsign()
|
||||
|
||||
-- Settings.
|
||||
local settings=_DATABASE:GetPlayerSettings(self.player[id].playername) or _SETTINGS --Core.Settings#SETTINGS
|
||||
local settings=_DATABASE:GetPlayerSettings(self.group[GID].player[UID].playername) or _SETTINGS --Core.Settings#SETTINGS
|
||||
|
||||
-- Height string.
|
||||
local Hs=string.format("%d ft", UTILS.MetersToFeet(height))
|
||||
@@ -817,7 +863,7 @@ function PSEUDOATC:ReportHeight(id, dt, _clear)
|
||||
end
|
||||
|
||||
-- Send message to player group.
|
||||
self:_DisplayMessageToGroup(self.player[id].unit,_text, dt,_clear)
|
||||
self:_DisplayMessageToGroup(self.group[GID].player[UID].unit,_text, dt,_clear)
|
||||
|
||||
-- Return height
|
||||
return height
|
||||
@@ -830,47 +876,50 @@ end
|
||||
|
||||
--- Toggle report altitude reporting on/off.
|
||||
-- @param #PSEUDOATC self.
|
||||
-- @param #number id Group id of player unit.
|
||||
function PSEUDOATC:AltidudeTimerToggle(id)
|
||||
self:F(id)
|
||||
-- @param #number GID Group id of player unit.
|
||||
-- @param #number UID Unit id of player.
|
||||
function PSEUDOATC:AltidudeTimerToggle(GID,UID)
|
||||
self:F({GID=GID, UID=UID})
|
||||
|
||||
if self.player[id].altimerid then
|
||||
if self.group[GID].player[UID].altimerid then
|
||||
-- If the timer is on, we turn it off.
|
||||
self:AltitudeTimerStop(id)
|
||||
self:AltitudeTimerStop(GID, UID)
|
||||
else
|
||||
-- If the timer is off, we turn it on.
|
||||
self:AltitudeTimeStart(id)
|
||||
self:AltitudeTimeStart(GID, UID)
|
||||
end
|
||||
end
|
||||
|
||||
--- Start altitude reporting scheduler.
|
||||
-- @param #PSEUDOATC self.
|
||||
-- @param #number id Group id of player unit.
|
||||
function PSEUDOATC:AltitudeTimeStart(id)
|
||||
self:F(id)
|
||||
-- @param #number GID Group id of player unit.
|
||||
-- @param #number UID Unit id of player.
|
||||
function PSEUDOATC:AltitudeTimeStart(GID, UID)
|
||||
self:F({GID=GID, UID=UID})
|
||||
|
||||
-- Debug info.
|
||||
self:T(PSEUDOATC.id..string.format("Starting altitude report timer for player ID %d.", id))
|
||||
self:T(PSEUDOATC.id..string.format("Starting altitude report timer for player ID %d.", UID))
|
||||
|
||||
-- Start timer. Altitude is reported every ~3 seconds.
|
||||
self.player[id].altimer, self.player[id].altimerid=SCHEDULER:New(nil, self.ReportHeight, {self, id, 0.1, true}, 1, 3)
|
||||
self.group[GID].player[UID].altimer, self.group[GID].player[UID].altimerid=SCHEDULER:New(nil, self.ReportHeight, {self, GID, UID, 0.1, true}, 1, 3)
|
||||
end
|
||||
|
||||
--- Stop/destroy DCS scheduler function for reporting altitude.
|
||||
-- @param #PSEUDOATC self.
|
||||
-- @param #number id Group id of player unit.
|
||||
function PSEUDOATC:AltitudeTimerStop(id)
|
||||
|
||||
-- @param #number GID Group id of player unit.
|
||||
-- @param #number UID Unit id of player.
|
||||
function PSEUDOATC:AltitudeTimerStop(GID, UID)
|
||||
self:F({GID=GID,UID=UID})
|
||||
-- Debug info.
|
||||
self:T(PSEUDOATC.id..string.format("Stopping altitude report timer for player ID %d.", id))
|
||||
self:T(PSEUDOATC.id..string.format("Stopping altitude report timer for player ID %d.", UID))
|
||||
|
||||
-- Stop timer.
|
||||
if self.player[id].altimerid then
|
||||
self.player[id].altimer:Stop(self.player[id].altimerid)
|
||||
if self.group[GID].player[UID].altimerid then
|
||||
self.group[GID].player[UID].altimer:Stop(self.group[GID].player[UID].altimerid)
|
||||
end
|
||||
|
||||
self.player[id].altimer=nil
|
||||
self.player[id].altimerid=nil
|
||||
self.group[GID].player[UID].altimer=nil
|
||||
self.group[GID].player[UID].altimerid=nil
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
@@ -878,16 +927,17 @@ end
|
||||
|
||||
--- Create list of nearby airports sorted by distance to player unit.
|
||||
-- @param #PSEUDOATC self
|
||||
-- @param #number id Group id of player unit.
|
||||
function PSEUDOATC:LocalAirports(id)
|
||||
self:F(id)
|
||||
-- @param #number GID Group id of player unit.
|
||||
-- @param #number UID Unit id of player.
|
||||
function PSEUDOATC:LocalAirports(GID, UID)
|
||||
self:F({GID=GID, UID=UID})
|
||||
|
||||
-- Airports table.
|
||||
self.player[id].airports=nil
|
||||
self.player[id].airports={}
|
||||
self.group[GID].player[UID].airports=nil
|
||||
self.group[GID].player[UID].airports={}
|
||||
|
||||
-- Current player position.
|
||||
local pos=self.player[id].unit:GetCoordinate()
|
||||
local pos=self.group[GID].player[UID].unit:GetCoordinate()
|
||||
|
||||
-- Loop over coalitions.
|
||||
for i=0,2 do
|
||||
@@ -899,11 +949,14 @@ function PSEUDOATC:LocalAirports(id)
|
||||
for _,airbase in pairs(airports) do
|
||||
|
||||
local name=airbase:getName()
|
||||
local q=AIRBASE:FindByName(name):GetCoordinate()
|
||||
local d=q:Get2DDistance(pos)
|
||||
local a=AIRBASE:FindByName(name)
|
||||
if a then
|
||||
local q=a:GetCoordinate()
|
||||
local d=q:Get2DDistance(pos)
|
||||
|
||||
-- Add to table.
|
||||
table.insert(self.player[id].airports, {distance=d, name=name})
|
||||
-- Add to table.
|
||||
table.insert(self.group[GID].player[UID].airports, {distance=d, name=name})
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@@ -914,7 +967,7 @@ function PSEUDOATC:LocalAirports(id)
|
||||
end
|
||||
|
||||
-- Sort airports table w.r.t. distance to player.
|
||||
table.sort(self.player[id].airports, compare)
|
||||
table.sort(self.group[GID].player[UID].airports, compare)
|
||||
|
||||
end
|
||||
|
||||
@@ -992,3 +1045,5 @@ function PSEUDOATC:_myname(unitname)
|
||||
return string.format("%s (%s)", csign, pname)
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
-- @field #string category Category of aircarft: "plane" or "heli".
|
||||
-- @field #number groupsize Number of aircraft in group.
|
||||
-- @field #string friendly Possible departure/destination airport: all=blue+red+neutral, same=spawn+neutral, spawnonly=spawn, blue=blue+neutral, blueonly=blue, red=red+neutral, redonly=red.
|
||||
-- @field #table ctable Table with the valid coalitons from choice self.friendly.
|
||||
-- @field #table ctable Table with the valid coalitions from choice self.friendly.
|
||||
-- @field #table aircraft Table which holds the basic aircraft properties (speed, range, ...).
|
||||
-- @field #number Vcruisemax Max cruise speed in m/s (250 m/s = 900 km/h = 486 kt) set by user.
|
||||
-- @field #number Vclimb Default climb rate in ft/min.
|
||||
@@ -150,6 +150,8 @@
|
||||
-- @field #number parkingscanradius Radius in meters until which parking spots are scanned for obstacles like other units, statics or scenery.
|
||||
-- @field #boolean parkingscanscenery If true, area around parking spots is scanned for scenery objects. Default is false.
|
||||
-- @field #boolean parkingverysafe If true, parking spots are considered as non-free until a possible aircraft has left and taken off. Default false.
|
||||
-- @field #boolean despawnair If true, aircraft are despawned when they reach their destination zone. Default.
|
||||
-- @field #boolean eplrs If true, turn on EPLSR datalink for the RAT group.
|
||||
-- @extends Core.Spawn#SPAWN
|
||||
|
||||
--- Implements an easy to use way to randomly fill your map with AI aircraft.
|
||||
@@ -240,7 +242,7 @@
|
||||
-- * AIRBASE.TerminalType.OpenMed: Open/Shelter air airplane only.
|
||||
-- * AIRBASE.TerminalType.OpenBig: Open air spawn points. Generally larger but does not guarantee large aircraft are capable of spawning there.
|
||||
-- * AIRBASE.TerminalType.OpenMedOrBig: Combines OpenMed and OpenBig spots.
|
||||
-- * AIRBASE.TerminalType.HelicopterUnsable: Combines HelicopterOnly, OpenMed and OpenBig.
|
||||
-- * AIRBASE.TerminalType.HelicopterUsable: Combines HelicopterOnly, OpenMed and OpenBig.
|
||||
-- * AIRBASE.TerminalType.FighterAircraft: Combines Shelter, OpenMed and OpenBig spots. So effectively all spots usable by fixed wing aircraft.
|
||||
--
|
||||
-- So for example
|
||||
@@ -346,7 +348,7 @@ RAT={
|
||||
category = nil, -- Category of aircarft: "plane" or "heli".
|
||||
groupsize=nil, -- Number of aircraft in the group.
|
||||
friendly = "same", -- Possible departure/destination airport: same=spawn+neutral, spawnonly=spawn, blue=blue+neutral, blueonly=blue, red=red+neutral, redonly=red, neutral.
|
||||
ctable = {}, -- Table with the valid coalitons from choice self.friendly.
|
||||
ctable = {}, -- Table with the valid coalitions from choice self.friendly.
|
||||
aircraft = {}, -- Table which holds the basic aircraft properties (speed, range, ...).
|
||||
Vcruisemax=nil, -- Max cruise speed in set by user.
|
||||
Vclimb=1500, -- Default climb rate in ft/min.
|
||||
@@ -428,6 +430,8 @@ RAT={
|
||||
parkingscanradius=40, -- Scan radius.
|
||||
parkingscanscenery=false, -- Scan parking spots for scenery obstacles.
|
||||
parkingverysafe=false, -- Very safe option.
|
||||
despawnair=true,
|
||||
eplrs=false,
|
||||
}
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
@@ -546,7 +550,7 @@ RAT.id="RAT | "
|
||||
--- RAT version.
|
||||
-- @list version
|
||||
RAT.version={
|
||||
version = "2.3.4",
|
||||
version = "2.3.9",
|
||||
print = true,
|
||||
}
|
||||
|
||||
@@ -653,7 +657,7 @@ end
|
||||
-- @param #RAT self
|
||||
-- @param #number naircraft (Optional) Number of aircraft to spawn. Default is one aircraft.
|
||||
-- @return #boolean True if spawning was successful or nil if nothing was spawned.
|
||||
-- @usage yak:Spawn(5) will spawn five aircraft. By default aircraft will spawn at neutral and red airports if the template group is part of the red coaliton.
|
||||
-- @usage yak:Spawn(5) will spawn five aircraft. By default aircraft will spawn at neutral and red airports if the template group is part of the red coalition.
|
||||
function RAT:Spawn(naircraft)
|
||||
|
||||
-- Make sure that this function is only been called once per RAT object.
|
||||
@@ -717,6 +721,11 @@ function RAT:Spawn(naircraft)
|
||||
self.FLcruise=005*RAT.unit.FL2m
|
||||
end
|
||||
end
|
||||
|
||||
-- Enable helos to go to destinations 100 meters away.
|
||||
if self.category==RAT.cat.heli then
|
||||
self.mindist=50
|
||||
end
|
||||
|
||||
-- Run consistency checks.
|
||||
self:_CheckConsistency()
|
||||
@@ -1093,6 +1102,14 @@ function RAT:SetParkingSpotSafeOFF()
|
||||
return self
|
||||
end
|
||||
|
||||
--- Aircraft that reach their destination zone are not despawned. They will probably go the the nearest airbase and try to land.
|
||||
-- @param #RAT self
|
||||
-- @return #RAT RAT self object.
|
||||
function RAT:SetDespawnAirOFF()
|
||||
self.despawnair=false
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set takeoff type. Starting cold at airport, starting hot at airport, starting at runway, starting in the air.
|
||||
-- Default is "takeoff-coldorhot". So there is a 50% chance that the aircraft starts with cold engines and 50% that it starts with hot engines.
|
||||
-- @param #RAT self
|
||||
@@ -1272,7 +1289,7 @@ end
|
||||
|
||||
--- Include all airports which lie in a zone as possible destinations.
|
||||
-- @param #RAT self
|
||||
-- @param Core.Zone#ZONE zone Zone in which the departure airports lie. Has to be a MOOSE zone.
|
||||
-- @param Core.Zone#ZONE zone Zone in which the destination airports lie. Has to be a MOOSE zone.
|
||||
-- @return #RAT RAT self object.
|
||||
function RAT:SetDestinationsFromZone(zone)
|
||||
self:F2(zone)
|
||||
@@ -1288,7 +1305,7 @@ end
|
||||
|
||||
--- Include all airports which lie in a zone as possible destinations.
|
||||
-- @param #RAT self
|
||||
-- @param Core.Zone#ZONE zone Zone in which the destination airports lie. Has to be a MOOSE zone.
|
||||
-- @param Core.Zone#ZONE zone Zone in which the departure airports lie. Has to be a MOOSE zone.
|
||||
-- @return #RAT RAT self object.
|
||||
function RAT:SetDeparturesFromZone(zone)
|
||||
self:F2(zone)
|
||||
@@ -1627,6 +1644,19 @@ function RAT:Invisible()
|
||||
return self
|
||||
end
|
||||
|
||||
--- Turn EPLRS datalink on/off.
|
||||
-- @param #RAT self
|
||||
-- @param #boolean switch If true (or nil), turn EPLRS on.
|
||||
-- @return #RAT RAT self object.
|
||||
function RAT:SetEPLRS(switch)
|
||||
if switch==nil or switch==true then
|
||||
self.eplrs=true
|
||||
else
|
||||
self.eplrs=false
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Aircraft are immortal.
|
||||
-- @param #RAT self
|
||||
-- @return #RAT RAT self object.
|
||||
@@ -1812,14 +1842,14 @@ function RAT:ATC_Delay(time)
|
||||
end
|
||||
|
||||
--- Set minimum distance between departure and destination. Default is 5 km.
|
||||
-- Minimum distance should not be smaller than maybe ~500 meters to ensure that departure and destination are different.
|
||||
-- Minimum distance should not be smaller than maybe ~100 meters to ensure that departure and destination are different.
|
||||
-- @param #RAT self
|
||||
-- @param #number dist Distance in km.
|
||||
-- @return #RAT RAT self object.
|
||||
function RAT:SetMinDistance(dist)
|
||||
self:F2(dist)
|
||||
-- Distance in meters. Absolute minimum is 500 m.
|
||||
self.mindist=math.max(500, dist*1000)
|
||||
self.mindist=math.max(100, dist*1000)
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -2149,6 +2179,11 @@ function RAT:_SpawnWithRoute(_departure, _destination, _takeoff, _landing, _live
|
||||
self:_CommandImmortal(group, true)
|
||||
end
|
||||
|
||||
-- Set group to be immortal.
|
||||
if self.eplrs then
|
||||
group:CommandEPLRS(true, 1)
|
||||
end
|
||||
|
||||
-- Set ROE, default is "weapon hold".
|
||||
self:_SetROE(group, self.roe)
|
||||
|
||||
@@ -2446,7 +2481,7 @@ function RAT:_SetRoute(takeoff, landing, _departure, _destination, _waypoint)
|
||||
local VxCruiseMax
|
||||
if self.Vcruisemax then
|
||||
-- User input.
|
||||
VxCruiseMax = min(self.Vcruisemax, self.aircraft.Vmax)
|
||||
VxCruiseMax = math.min(self.Vcruisemax, self.aircraft.Vmax)
|
||||
else
|
||||
-- Max cruise speed 90% of Vmax or 900 km/h whichever is lower.
|
||||
VxCruiseMax = math.min(self.aircraft.Vmax*0.90, 250)
|
||||
@@ -3357,11 +3392,19 @@ function RAT:_GetAirportsOfMap()
|
||||
local _name=airbase:getName()
|
||||
local _myab=AIRBASE:FindByName(_name)
|
||||
|
||||
-- Add airport to table.
|
||||
table.insert(self.airports_map, _myab)
|
||||
if _myab then
|
||||
|
||||
local text="MOOSE: Airport ID = ".._myab:GetID().." and Name = ".._myab:GetName()..", Category = ".._myab:GetCategory()..", TypeName = ".._myab:GetTypeName()
|
||||
self:T(RAT.id..text)
|
||||
-- Add airport to table.
|
||||
table.insert(self.airports_map, _myab)
|
||||
|
||||
local text="MOOSE: Airport ID = ".._myab:GetID().." and Name = ".._myab:GetName()..", Category = ".._myab:GetCategory()..", TypeName = ".._myab:GetTypeName()
|
||||
self:T(RAT.id..text)
|
||||
|
||||
else
|
||||
|
||||
self:E(RAT.id..string.format("WARNING: Airbase %s does not exsist as MOOSE object!", tostring(_name)))
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
@@ -3373,7 +3416,7 @@ function RAT:_GetAirportsOfCoalition()
|
||||
for _,coalition in pairs(self.ctable) do
|
||||
for _,_airport in pairs(self.airports_map) do
|
||||
local airport=_airport --Wrapper.Airbase#AIRBASE
|
||||
local category=airport:GetDesc().category
|
||||
local category=airport:GetAirbaseCategory()
|
||||
if airport:GetCoalition()==coalition then
|
||||
-- Planes cannot land on FARPs.
|
||||
--local condition1=self.category==RAT.cat.plane and airport:GetTypeName()=="FARP"
|
||||
@@ -3599,13 +3642,18 @@ function RAT:Status(message, forID)
|
||||
|
||||
local text=string.format("Flight %s will be despawned NOW!", self.alias)
|
||||
self:T(RAT.id..text)
|
||||
-- Despawn old group.
|
||||
|
||||
-- Respawn group
|
||||
if (not self.norespawn) and (not self.respawn_after_takeoff) then
|
||||
local idx=self:GetSpawnIndexFromGroup(group)
|
||||
local coord=group:GetCoordinate()
|
||||
self:_Respawn(idx, coord, 0)
|
||||
end
|
||||
self:_Despawn(group, 0)
|
||||
|
||||
-- Despawn old group.
|
||||
if self.despawnair then
|
||||
self:_Despawn(group, 0)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -3799,7 +3847,7 @@ function RAT:_OnBirth(EventData)
|
||||
|
||||
-- Check if any unit of the group was spawned on top of another unit in the MOOSE data base.
|
||||
local ontop=false
|
||||
if self.checkontop and (_airbase and _airbase:GetDesc().category==Airbase.Category.AIRDROME) then
|
||||
if self.checkontop and (_airbase and _airbase:GetAirbaseCategory()==Airbase.Category.AIRDROME) then
|
||||
ontop=self:_CheckOnTop(SpawnGroup, self.ontopradius)
|
||||
end
|
||||
|
||||
@@ -4409,7 +4457,7 @@ function RAT:_Waypoint(index, description, Type, Coord, Speed, Altitude, Airport
|
||||
|
||||
if (Airport~=nil) and (Type~=RAT.wp.air) then
|
||||
local AirbaseID = Airport:GetID()
|
||||
local AirbaseCategory = Airport:GetDesc().category
|
||||
local AirbaseCategory = Airport:GetAirbaseCategory()
|
||||
if AirbaseCategory == Airbase.Category.SHIP then
|
||||
RoutePoint.linkUnit = AirbaseID
|
||||
RoutePoint.helipadId = AirbaseID
|
||||
@@ -5093,7 +5141,7 @@ function RAT:_ModifySpawnTemplate(waypoints, livery, spawnplace, departure, take
|
||||
local spawnonrunway=false
|
||||
local spawnonairport=false
|
||||
if spawnonground then
|
||||
local AirbaseCategory = departure:GetDesc().category
|
||||
local AirbaseCategory = departure:GetAirbaseCategory()
|
||||
if AirbaseCategory == Airbase.Category.SHIP then
|
||||
spawnonship=true
|
||||
elseif AirbaseCategory == Airbase.Category.HELIPAD then
|
||||
@@ -5125,6 +5173,7 @@ function RAT:_ModifySpawnTemplate(waypoints, livery, spawnplace, departure, take
|
||||
if self.uncontrolled then
|
||||
-- This is used in the SPAWN:SpawnWithIndex() function. Some values are overwritten there!
|
||||
self.SpawnUnControlled=true
|
||||
SpawnTemplate.uncontrolled=true
|
||||
end
|
||||
|
||||
-- Number of units in the group. With grouping this can actually differ from the template group size!
|
||||
@@ -5322,7 +5371,7 @@ function RAT:_ModifySpawnTemplate(waypoints, livery, spawnplace, departure, take
|
||||
|
||||
if spawnonground then
|
||||
|
||||
-- Sh<EFBFBD>ps and FARPS seem to have a build in queue.
|
||||
-- Sh°ps and FARPS seem to have a build in queue.
|
||||
if spawnonship or spawnonfarp or spawnonrunway or automatic then
|
||||
self:T(RAT.id..string.format("RAT group %s spawning at farp, ship or runway %s.", self.alias, departure:GetName()))
|
||||
|
||||
@@ -5435,7 +5484,7 @@ function RAT:_ATCInit(airports_map)
|
||||
if not RAT.ATC.init then
|
||||
local text
|
||||
text="Starting RAT ATC.\nSimultanious = "..RAT.ATC.Nclearance.."\n".."Delay = "..RAT.ATC.delay
|
||||
self:T(RAT.id..text)
|
||||
BASE:T(RAT.id..text)
|
||||
RAT.ATC.init=true
|
||||
for _,ap in pairs(airports_map) do
|
||||
local name=ap:GetName()
|
||||
@@ -5458,7 +5507,7 @@ end
|
||||
-- @param #string name Group name of the flight.
|
||||
-- @param #string dest Name of the destination airport.
|
||||
function RAT:_ATCAddFlight(name, dest)
|
||||
self:T(string.format("%sATC %s: Adding flight %s with destination %s.", RAT.id, dest, name, dest))
|
||||
BASE:T(string.format("%sATC %s: Adding flight %s with destination %s.", RAT.id, dest, name, dest))
|
||||
RAT.ATC.flight[name]={}
|
||||
RAT.ATC.flight[name].destination=dest
|
||||
RAT.ATC.flight[name].Tarrive=-1
|
||||
@@ -5483,7 +5532,7 @@ end
|
||||
-- @param #string name Group name of the flight.
|
||||
-- @param #number time Time the fight first registered.
|
||||
function RAT:_ATCRegisterFlight(name, time)
|
||||
self:T(RAT.id.."Flight ".. name.." registered at ATC for landing clearance.")
|
||||
BASE:T(RAT.id.."Flight ".. name.." registered at ATC for landing clearance.")
|
||||
RAT.ATC.flight[name].Tarrive=time
|
||||
RAT.ATC.flight[name].holding=0
|
||||
end
|
||||
@@ -5514,7 +5563,7 @@ function RAT:_ATCStatus()
|
||||
|
||||
-- Aircraft is holding.
|
||||
local text=string.format("ATC %s: Flight %s is holding for %i:%02d. %s.", dest, name, hold/60, hold%60, busy)
|
||||
self:T(RAT.id..text)
|
||||
BASE:T(RAT.id..text)
|
||||
|
||||
elseif hold==RAT.ATC.onfinal then
|
||||
|
||||
@@ -5522,7 +5571,7 @@ function RAT:_ATCStatus()
|
||||
local Tfinal=Tnow-RAT.ATC.flight[name].Tonfinal
|
||||
|
||||
local text=string.format("ATC %s: Flight %s is on final. Waiting %i:%02d for landing event.", dest, name, Tfinal/60, Tfinal%60)
|
||||
self:T(RAT.id..text)
|
||||
BASE:T(RAT.id..text)
|
||||
|
||||
elseif hold==RAT.ATC.unregistered then
|
||||
|
||||
@@ -5530,7 +5579,7 @@ function RAT:_ATCStatus()
|
||||
--self:T(string.format("ATC %s: Flight %s is not registered yet (hold %d).", dest, name, hold))
|
||||
|
||||
else
|
||||
self:E(RAT.id.."ERROR: Unknown holding time in RAT:_ATCStatus().")
|
||||
BASE:E(RAT.id.."ERROR: Unknown holding time in RAT:_ATCStatus().")
|
||||
end
|
||||
end
|
||||
|
||||
@@ -5572,12 +5621,12 @@ function RAT:_ATCCheck()
|
||||
|
||||
-- Debug message.
|
||||
local text=string.format("ATC %s: Flight %s runway is busy. You are #%d of %d in landing queue. Your holding time is %i:%02d.", name, flight,qID, nqueue, RAT.ATC.flight[flight].holding/60, RAT.ATC.flight[flight].holding%60)
|
||||
self:T(RAT.id..text)
|
||||
BASE:T(RAT.id..text)
|
||||
|
||||
else
|
||||
|
||||
local text=string.format("ATC %s: Flight %s was cleared for landing. Your holding time was %i:%02d.", name, flight, RAT.ATC.flight[flight].holding/60, RAT.ATC.flight[flight].holding%60)
|
||||
self:T(RAT.id..text)
|
||||
BASE:T(RAT.id..text)
|
||||
|
||||
-- Clear flight for landing.
|
||||
RAT:_ATCClearForLanding(name, flight)
|
||||
@@ -5705,12 +5754,7 @@ function RAT:_ATCQueue()
|
||||
for k,v in ipairs(_queue) do
|
||||
table.insert(RAT.ATC.airport[airport].queue, v[1])
|
||||
end
|
||||
|
||||
--fvh
|
||||
--for k,v in ipairs(RAT.ATC.airport[airport].queue) do
|
||||
--print(string.format("queue #%02i flight \"%s\" holding %d seconds",k, v, RAT.ATC.flight[v].holding))
|
||||
--end
|
||||
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
@@ -5742,6 +5786,8 @@ end
|
||||
-- If desired, the @{#RATMANAGER} can be stopped by the @{#RATMANAGER.Stop}(stoptime) function. The parameter "stoptime" specifies the time delay in seconds after which the manager stops.
|
||||
-- When this happens, no new aircraft will be spawned and the population will eventually decrease to zero.
|
||||
--
|
||||
-- When you are using a time intervall like @{#RATMANAGER.dTspawn}(delay), @{#RATMANAGER} will ignore the amount set with @{#RATMANAGER.New}(). @{#RATMANAGER.dTspawn}(delay) will spawn infinite groups.
|
||||
--
|
||||
-- ## Example
|
||||
-- In this example, three different @{#RAT} objects are created (but not spawned manually). The @{#RATMANAGER} takes care that at least five aircraft of each type are alive and that the total number of aircraft
|
||||
-- spawned is 25. The @{#RATMANAGER} is started after 30 seconds and stopped after two hours.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,162 +1,518 @@
|
||||
--- **Functional** -- Make SAM sites execute evasive and defensive behaviour when being fired upon.
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- ## Features:
|
||||
--
|
||||
--
|
||||
-- * When SAM sites are being fired upon, the SAMs will take evasive action will reposition themselves when possible.
|
||||
-- * When SAM sites are being fired upon, the SAMs will take defensive action by shutting down their radars.
|
||||
--
|
||||
-- * SEAD calculates the time it takes for a HARM to reach the target - and will attempt to minimize the shut-down time.
|
||||
-- * Detection and evasion of shots has a random component based on the skill level of the SAM groups.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- ## Missions:
|
||||
--
|
||||
--
|
||||
-- [SEV - SEAD Evasion](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/SEV%20-%20SEAD%20Evasion)
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Authors: **FlightControl**
|
||||
--
|
||||
--
|
||||
-- ### Authors: **FlightControl**, **applevangelist**
|
||||
--
|
||||
-- Last Update: Feb 2022
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- @module Functional.Sead
|
||||
-- @image SEAD.JPG
|
||||
|
||||
--- @type SEAD
|
||||
---
|
||||
-- @type SEAD
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
--- Make SAM sites execute evasive and defensive behaviour when being fired upon.
|
||||
--
|
||||
--
|
||||
-- This class is very easy to use. Just setup a SEAD object by using @{#SEAD.New}() and SAMs will evade and take defensive action when being fired upon.
|
||||
-- Once a HARM attack is detected, SEAD will shut down the radars of the attacked SAM site and take evasive action by moving the SAM
|
||||
-- vehicles around (*if* they are drivable, that is). There's a component of randomness in detection and evasion, which is based on the
|
||||
-- skill set of the SAM set (the higher the skill, the more likely). When a missile is fired from far away, the SAM will stay active for a
|
||||
-- period of time to stay defensive, before it takes evasive actions.
|
||||
--
|
||||
-- # Constructor:
|
||||
--
|
||||
--
|
||||
-- Use the @{#SEAD.New}() constructor to create a new SEAD object.
|
||||
--
|
||||
--
|
||||
-- SEAD_RU_SAM_Defenses = SEAD:New( { 'RU SA-6 Kub', 'RU SA-6 Defenses', 'RU MI-26 Troops', 'RU Attack Gori' } )
|
||||
--
|
||||
--
|
||||
-- @field #SEAD
|
||||
SEAD = {
|
||||
ClassName = "SEAD",
|
||||
TargetSkill = {
|
||||
Average = { Evade = 50, DelayOff = { 10, 25 }, DelayOn = { 10, 30 } } ,
|
||||
Good = { Evade = 30, DelayOff = { 8, 20 }, DelayOn = { 20, 40 } } ,
|
||||
High = { Evade = 15, DelayOff = { 5, 17 }, DelayOn = { 30, 50 } } ,
|
||||
Excellent = { Evade = 10, DelayOff = { 3, 10 }, DelayOn = { 30, 60 } }
|
||||
},
|
||||
SEADGroupPrefixes = {}
|
||||
ClassName = "SEAD",
|
||||
TargetSkill = {
|
||||
Average = { Evade = 30, DelayOn = { 40, 60 } } ,
|
||||
Good = { Evade = 20, DelayOn = { 30, 50 } } ,
|
||||
High = { Evade = 15, DelayOn = { 20, 40 } } ,
|
||||
Excellent = { Evade = 10, DelayOn = { 10, 30 } }
|
||||
},
|
||||
SEADGroupPrefixes = {},
|
||||
SuppressedGroups = {},
|
||||
EngagementRange = 75, -- default 75% engagement range Feature Request #1355
|
||||
Padding = 10,
|
||||
CallBack = nil,
|
||||
UseCallBack = false,
|
||||
debug = false,
|
||||
}
|
||||
|
||||
--- Missile enumerators
|
||||
-- @field Harms
|
||||
SEAD.Harms = {
|
||||
["AGM_88"] = "AGM_88",
|
||||
["AGM_45"] = "AGM_45",
|
||||
["AGM_122"] = "AGM_122",
|
||||
["AGM_84"] = "AGM_84",
|
||||
["AGM_45"] = "AGM_45",
|
||||
["ALARM"] = "ALARM",
|
||||
["LD-10"] = "LD-10",
|
||||
["X_58"] = "X_58",
|
||||
["X_28"] = "X_28",
|
||||
["X_25"] = "X_25",
|
||||
["X_31"] = "X_31",
|
||||
["Kh25"] = "Kh25",
|
||||
["BGM_109"] = "BGM_109",
|
||||
["AGM_154"] = "AGM_154",
|
||||
}
|
||||
|
||||
--- Missile enumerators - from DCS ME and Wikipedia
|
||||
-- @field HarmData
|
||||
SEAD.HarmData = {
|
||||
-- km and mach
|
||||
["AGM_88"] = { 150, 3},
|
||||
["AGM_45"] = { 12, 2},
|
||||
["AGM_122"] = { 16.5, 2.3},
|
||||
["AGM_84"] = { 280, 0.8},
|
||||
["ALARM"] = { 45, 2},
|
||||
["LD-10"] = { 60, 4},
|
||||
["X_58"] = { 70, 4},
|
||||
["X_28"] = { 80, 2.5},
|
||||
["X_25"] = { 25, 0.76},
|
||||
["X_31"] = {150, 3},
|
||||
["Kh25"] = {25, 0.8},
|
||||
["BGM_109"] = {460, 0.705}, --in-game ~465kn
|
||||
["AGM_154"] = {130, 0.61},
|
||||
}
|
||||
|
||||
--- Creates the main object which is handling defensive actions for SA sites or moving SA vehicles.
|
||||
-- When an anti radiation missile is fired (KH-58, KH-31P, KH-31A, KH-25MPU, HARM missiles), the SA will shut down their radars and will take evasive actions...
|
||||
-- Chances are big that the missile will miss.
|
||||
-- @param table{string,...}|string SEADGroupPrefixes which is a table of Prefixes of the SA Groups in the DCSRTE on which evasive actions need to be taken.
|
||||
-- @return SEAD
|
||||
-- @param #SEAD self
|
||||
-- @param #table SEADGroupPrefixes Table of #string entries or single #string, which is a table of Prefixes of the SA Groups in the DCS mission editor on which evasive actions need to be taken.
|
||||
-- @param #number Padding (Optional) Extra number of seconds to add to radar switch-back-on time
|
||||
-- @return #SEAD self
|
||||
-- @usage
|
||||
-- -- CCCP SEAD Defenses
|
||||
-- -- Defends the Russian SA installations from SEAD attacks.
|
||||
-- SEAD_RU_SAM_Defenses = SEAD:New( { 'RU SA-6 Kub', 'RU SA-6 Defenses', 'RU MI-26 Troops', 'RU Attack Gori' } )
|
||||
function SEAD:New( SEADGroupPrefixes )
|
||||
local self = BASE:Inherit( self, BASE:New() )
|
||||
self:F( SEADGroupPrefixes )
|
||||
if type( SEADGroupPrefixes ) == 'table' then
|
||||
for SEADGroupPrefixID, SEADGroupPrefix in pairs( SEADGroupPrefixes ) do
|
||||
self.SEADGroupPrefixes[SEADGroupPrefix] = SEADGroupPrefix
|
||||
end
|
||||
else
|
||||
self.SEADGroupNames[SEADGroupPrefixes] = SEADGroupPrefixes
|
||||
end
|
||||
|
||||
self:HandleEvent( EVENTS.Shot )
|
||||
|
||||
return self
|
||||
function SEAD:New( SEADGroupPrefixes, Padding )
|
||||
|
||||
local self = BASE:Inherit( self, FSM:New() )
|
||||
self:T( SEADGroupPrefixes )
|
||||
|
||||
if type( SEADGroupPrefixes ) == 'table' then
|
||||
for SEADGroupPrefixID, SEADGroupPrefix in pairs( SEADGroupPrefixes ) do
|
||||
self.SEADGroupPrefixes[SEADGroupPrefix] = SEADGroupPrefix
|
||||
end
|
||||
else
|
||||
self.SEADGroupPrefixes[SEADGroupPrefixes] = SEADGroupPrefixes
|
||||
end
|
||||
|
||||
local padding = Padding or 10
|
||||
if padding < 10 then padding = 10 end
|
||||
self.Padding = padding
|
||||
self.UseEmissionsOnOff = true
|
||||
|
||||
self.debug = false
|
||||
|
||||
self.CallBack = nil
|
||||
self.UseCallBack = false
|
||||
|
||||
self:HandleEvent( EVENTS.Shot, self.HandleEventShot )
|
||||
|
||||
-- Start State.
|
||||
self:SetStartState("Running")
|
||||
self:AddTransition("*", "ManageEvasion", "*")
|
||||
self:AddTransition("*", "CalculateHitZone", "*")
|
||||
|
||||
self:I("*** SEAD - Started Version 0.4.3")
|
||||
return self
|
||||
end
|
||||
|
||||
--- Detects if an SA site was shot with an anti radiation missile. In this case, take evasive actions based on the skill level set within the ME.
|
||||
-- @see SEAD
|
||||
-- @param #SEAD
|
||||
--- Update the active SEAD Set (while running)
|
||||
-- @param #SEAD self
|
||||
-- @param #table SEADGroupPrefixes The prefixes to add, note: can also be a single #string
|
||||
-- @return #SEAD self
|
||||
function SEAD:UpdateSet( SEADGroupPrefixes )
|
||||
|
||||
self:T( SEADGroupPrefixes )
|
||||
|
||||
if type( SEADGroupPrefixes ) == 'table' then
|
||||
for SEADGroupPrefixID, SEADGroupPrefix in pairs( SEADGroupPrefixes ) do
|
||||
self.SEADGroupPrefixes[SEADGroupPrefix] = SEADGroupPrefix
|
||||
end
|
||||
else
|
||||
self.SEADGroupPrefixes[SEADGroupPrefixes] = SEADGroupPrefixes
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets the engagement range of the SAMs. Defaults to 75% to make it more deadly. Feature Request #1355
|
||||
-- @param #SEAD self
|
||||
-- @param #number range Set the engagement range in percent, e.g. 55 (default 75)
|
||||
-- @return #SEAD self
|
||||
function SEAD:SetEngagementRange(range)
|
||||
self:T( { range } )
|
||||
range = range or 75
|
||||
if range < 0 or range > 100 then
|
||||
range = 75
|
||||
end
|
||||
self.EngagementRange = range
|
||||
self:T(string.format("*** SEAD - Engagement range set to %s",range))
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set the padding in seconds, which extends the radar off time calculated by SEAD
|
||||
-- @param #SEAD self
|
||||
-- @param #number Padding Extra number of seconds to add for the switch-on (default 10 seconds)
|
||||
-- @return #SEAD self
|
||||
function SEAD:SetPadding(Padding)
|
||||
self:T( { Padding } )
|
||||
local padding = Padding or 10
|
||||
if padding < 10 then padding = 10 end
|
||||
self.Padding = padding
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set SEAD to use emissions on/off in addition to alarm state.
|
||||
-- @param #SEAD self
|
||||
-- @param #boolean Switch True for on, false for off.
|
||||
-- @return #SEAD self
|
||||
function SEAD:SwitchEmissions(Switch)
|
||||
self:T({Switch})
|
||||
self.UseEmissionsOnOff = Switch
|
||||
return self
|
||||
end
|
||||
|
||||
--- Add an object to call back when going evasive.
|
||||
-- @param #SEAD self
|
||||
-- @param #table Object The object to call. Needs to have object functions as follows:
|
||||
-- `:SeadSuppressionPlanned(Group, Name, SuppressionStartTime, SuppressionEndTime)`
|
||||
-- `:SeadSuppressionStart(Group, Name)`,
|
||||
-- `:SeadSuppressionEnd(Group, Name)`,
|
||||
-- @return #SEAD self
|
||||
function SEAD:AddCallBack(Object)
|
||||
self:T({Class=Object.ClassName})
|
||||
self.CallBack = Object
|
||||
self.UseCallBack = true
|
||||
return self
|
||||
end
|
||||
|
||||
--- (Internal) Check if a known HARM was fired
|
||||
-- @param #SEAD self
|
||||
-- @param #string WeaponName
|
||||
-- @return #boolean Returns true for a match
|
||||
-- @return #string name Name of hit in table
|
||||
function SEAD:_CheckHarms(WeaponName)
|
||||
self:T( { WeaponName } )
|
||||
local hit = false
|
||||
local name = ""
|
||||
for _,_name in pairs (SEAD.Harms) do
|
||||
if string.find(WeaponName,_name,1,true) then
|
||||
hit = true
|
||||
name = _name
|
||||
break
|
||||
end
|
||||
end
|
||||
return hit, name
|
||||
end
|
||||
|
||||
--- (Internal) Return distance in meters between two coordinates or -1 on error.
|
||||
-- @param #SEAD self
|
||||
-- @param Core.Point#COORDINATE _point1 Coordinate one
|
||||
-- @param Core.Point#COORDINATE _point2 Coordinate two
|
||||
-- @return #number Distance in meters
|
||||
function SEAD:_GetDistance(_point1, _point2)
|
||||
self:T("_GetDistance")
|
||||
if _point1 and _point2 then
|
||||
local distance1 = _point1:Get2DDistance(_point2)
|
||||
local distance2 = _point1:DistanceFromPointVec2(_point2)
|
||||
--self:T({dist1=distance1, dist2=distance2})
|
||||
if distance1 and type(distance1) == "number" then
|
||||
return distance1
|
||||
elseif distance2 and type(distance2) == "number" then
|
||||
return distance2
|
||||
else
|
||||
self:E("*****Cannot calculate distance!")
|
||||
self:E({_point1,_point2})
|
||||
return -1
|
||||
end
|
||||
else
|
||||
self:E("******Cannot calculate distance!")
|
||||
self:E({_point1,_point2})
|
||||
return -1
|
||||
end
|
||||
end
|
||||
|
||||
--- (Internal) Calculate hit zone of an AGM-88
|
||||
-- @param #SEAD self
|
||||
-- @param #table SEADWeapon DCS.Weapon object
|
||||
-- @param Core.Point#COORDINATE pos0 Position of the plane when it fired
|
||||
-- @param #number height Height when the missile was fired
|
||||
-- @param Wrapper.Group#GROUP SEADGroup Attacker group
|
||||
-- @param #string SEADWeaponName Weapon Name
|
||||
-- @return #SEAD self
|
||||
function SEAD:onafterCalculateHitZone(From,Event,To,SEADWeapon,pos0,height,SEADGroup,SEADWeaponName)
|
||||
self:T("**** Calculating hit zone for " .. (SEADWeaponName or "None"))
|
||||
if SEADWeapon and SEADWeapon:isExist() then
|
||||
--local pos = SEADWeapon:getPoint()
|
||||
|
||||
-- postion and height
|
||||
local position = SEADWeapon:getPosition()
|
||||
local mheight = height
|
||||
-- heading
|
||||
local wph = math.atan2(position.x.z, position.x.x)
|
||||
if wph < 0 then
|
||||
wph=wph+2*math.pi
|
||||
end
|
||||
wph=math.deg(wph)
|
||||
|
||||
-- velocity
|
||||
local wpndata = SEAD.HarmData["AGM_88"]
|
||||
if string.find(SEADWeaponName,"154",1) then
|
||||
wpndata = SEAD.HarmData["AGM_154"]
|
||||
end
|
||||
local mveloc = math.floor(wpndata[2] * 340.29)
|
||||
local c1 = (2*mheight*9.81)/(mveloc^2)
|
||||
local c2 = (mveloc^2) / 9.81
|
||||
local Ropt = c2 * math.sqrt(c1+1)
|
||||
if height <= 5000 then
|
||||
Ropt = Ropt * 0.72
|
||||
elseif height <= 7500 then
|
||||
Ropt = Ropt * 0.82
|
||||
elseif height <= 10000 then
|
||||
Ropt = Ropt * 0.87
|
||||
elseif height <= 12500 then
|
||||
Ropt = Ropt * 0.98
|
||||
end
|
||||
|
||||
-- look at a couple of zones across the trajectory
|
||||
for n=1,3 do
|
||||
local dist = Ropt - ((n-1)*20000)
|
||||
local predpos= pos0:Translate(dist,wph)
|
||||
if predpos then
|
||||
|
||||
local targetzone = ZONE_RADIUS:New("Target Zone",predpos:GetVec2(),20000)
|
||||
|
||||
if self.debug then
|
||||
predpos:MarkToAll(string.format("height=%dm | heading=%d | velocity=%ddeg | Ropt=%dm",mheight,wph,mveloc,Ropt),false)
|
||||
targetzone:DrawZone(coalition.side.BLUE,{0,0,1},0.2,nil,nil,3,true)
|
||||
end
|
||||
|
||||
local seadset = SET_GROUP:New():FilterPrefixes(self.SEADGroupPrefixes):FilterZones({targetzone}):FilterOnce()
|
||||
local tgtcoord = targetzone:GetRandomPointVec2()
|
||||
--if tgtcoord and tgtcoord.ClassName == "COORDINATE" then
|
||||
--local tgtgrp = seadset:FindNearestGroupFromPointVec2(tgtcoord)
|
||||
local tgtgrp = seadset:GetRandom()
|
||||
local _targetgroup = nil
|
||||
local _targetgroupname = "none"
|
||||
local _targetskill = "Random"
|
||||
if tgtgrp and tgtgrp:IsAlive() then
|
||||
_targetgroup = tgtgrp
|
||||
_targetgroupname = tgtgrp:GetName() -- group name
|
||||
_targetskill = tgtgrp:GetUnit(1):GetSkill()
|
||||
self:T("*** Found Target = ".. _targetgroupname)
|
||||
self:ManageEvasion(_targetskill,_targetgroup,pos0,"AGM_88",SEADGroup, 20)
|
||||
end
|
||||
--end
|
||||
end
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- (Internal) Handle Evasion
|
||||
-- @param #SEAD self
|
||||
-- @param #string _targetskill
|
||||
-- @param Wrapper.Group#GROUP _targetgroup
|
||||
-- @param Core.Point#COORDINATE SEADPlanePos
|
||||
-- @param #string SEADWeaponName
|
||||
-- @param Wrapper.Group#GROUP SEADGroup Attacker Group
|
||||
-- @param #number timeoffset Offset for tti calc
|
||||
-- @return #SEAD self
|
||||
function SEAD:onafterManageEvasion(From,Event,To,_targetskill,_targetgroup,SEADPlanePos,SEADWeaponName,SEADGroup,timeoffset)
|
||||
local timeoffset = timeoffset or 0
|
||||
if _targetskill == "Random" then -- when skill is random, choose a skill
|
||||
local Skills = { "Average", "Good", "High", "Excellent" }
|
||||
_targetskill = Skills[ math.random(1,4) ]
|
||||
end
|
||||
--self:T( _targetskill )
|
||||
if self.TargetSkill[_targetskill] then
|
||||
local _evade = math.random (1,100) -- random number for chance of evading action
|
||||
if (_evade > self.TargetSkill[_targetskill].Evade) then
|
||||
self:T("*** SEAD - Evading")
|
||||
-- calculate distance of attacker
|
||||
local _targetpos = _targetgroup:GetCoordinate()
|
||||
local _distance = self:_GetDistance(SEADPlanePos, _targetpos)
|
||||
-- weapon speed
|
||||
local hit, data = self:_CheckHarms(SEADWeaponName)
|
||||
local wpnspeed = 666 -- ;)
|
||||
local reach = 10
|
||||
if hit then
|
||||
local wpndata = SEAD.HarmData[data]
|
||||
reach = wpndata[1] * 1,1
|
||||
local mach = wpndata[2]
|
||||
wpnspeed = math.floor(mach * 340.29)
|
||||
end
|
||||
-- time to impact
|
||||
local _tti = math.floor(_distance / wpnspeed) - timeoffset -- estimated impact time
|
||||
if _distance > 0 then
|
||||
_distance = math.floor(_distance / 1000) -- km
|
||||
else
|
||||
_distance = 0
|
||||
end
|
||||
|
||||
self:T( string.format("*** SEAD - target skill %s, distance %dkm, reach %dkm, tti %dsec", _targetskill, _distance,reach,_tti ))
|
||||
|
||||
if reach >= _distance then
|
||||
self:T("*** SEAD - Shot in Reach")
|
||||
|
||||
local function SuppressionStart(args)
|
||||
self:T(string.format("*** SEAD - %s Radar Off & Relocating",args[2]))
|
||||
local grp = args[1] -- Wrapper.Group#GROUP
|
||||
local name = args[2] -- #string Group Name
|
||||
local attacker = args[3] -- Wrapper.Group#GROUP
|
||||
if self.UseEmissionsOnOff then
|
||||
grp:EnableEmission(false)
|
||||
end
|
||||
grp:OptionAlarmStateGreen() -- needed else we cannot move around
|
||||
grp:RelocateGroundRandomInRadius(20,300,false,false,"Diamond")
|
||||
if self.UseCallBack then
|
||||
local object = self.CallBack
|
||||
object:SeadSuppressionStart(grp,name,attacker)
|
||||
end
|
||||
end
|
||||
|
||||
local function SuppressionStop(args)
|
||||
self:T(string.format("*** SEAD - %s Radar On",args[2]))
|
||||
local grp = args[1] -- Wrapper.Group#GROUP
|
||||
local name = args[2] -- #string Group Nam
|
||||
if self.UseEmissionsOnOff then
|
||||
grp:EnableEmission(true)
|
||||
end
|
||||
grp:OptionAlarmStateRed()
|
||||
grp:OptionEngageRange(self.EngagementRange)
|
||||
self.SuppressedGroups[name] = false
|
||||
if self.UseCallBack then
|
||||
local object = self.CallBack
|
||||
object:SeadSuppressionEnd(grp,name)
|
||||
end
|
||||
end
|
||||
|
||||
-- randomize switch-on time
|
||||
local delay = math.random(self.TargetSkill[_targetskill].DelayOn[1], self.TargetSkill[_targetskill].DelayOn[2])
|
||||
if delay > _tti then delay = delay / 2 end -- speed up
|
||||
if _tti > 600 then delay = _tti - 90 end -- shot from afar, 600 is default shorad ontime
|
||||
|
||||
local SuppressionStartTime = timer.getTime() + delay
|
||||
local SuppressionEndTime = timer.getTime() + _tti + self.Padding
|
||||
local _targetgroupname = _targetgroup:GetName()
|
||||
if not self.SuppressedGroups[_targetgroupname] then
|
||||
self:T(string.format("*** SEAD - %s | Parameters TTI %ds | Switch-Off in %ds",_targetgroupname,_tti,delay))
|
||||
timer.scheduleFunction(SuppressionStart,{_targetgroup,_targetgroupname, SEADGroup},SuppressionStartTime)
|
||||
timer.scheduleFunction(SuppressionStop,{_targetgroup,_targetgroupname},SuppressionEndTime)
|
||||
self.SuppressedGroups[_targetgroupname] = true
|
||||
if self.UseCallBack then
|
||||
local object = self.CallBack
|
||||
object:SeadSuppressionPlanned(_targetgroup,_targetgroupname,SuppressionStartTime,SuppressionEndTime, SEADGroup)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- (Internal) Detects if an SAM site was shot with an anti radiation missile. In this case, take evasive actions based on the skill level set within the ME.
|
||||
-- @param #SEAD self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function SEAD:OnEventShot( EventData )
|
||||
self:F( { EventData } )
|
||||
-- @return #SEAD self
|
||||
function SEAD:HandleEventShot( EventData )
|
||||
self:T( { EventData.id } )
|
||||
local SEADPlane = EventData.IniUnit -- Wrapper.Unit#UNIT
|
||||
local SEADGroup = EventData.IniGroup -- Wrapper.Group#GROUP
|
||||
local SEADPlanePos = SEADPlane:GetCoordinate() -- Core.Point#COORDINATE
|
||||
local SEADUnit = EventData.IniDCSUnit
|
||||
local SEADUnitName = EventData.IniDCSUnitName
|
||||
local SEADWeapon = EventData.Weapon -- Identify the weapon fired
|
||||
local SEADWeaponName = EventData.WeaponName -- return weapon type
|
||||
|
||||
local SEADUnit = EventData.IniDCSUnit
|
||||
local SEADUnitName = EventData.IniDCSUnitName
|
||||
local SEADWeapon = EventData.Weapon -- Identify the weapon fired
|
||||
local SEADWeaponName = EventData.WeaponName -- return weapon type
|
||||
-- Start of the 2nd loop
|
||||
self:T( "Missile Launched = " .. SEADWeaponName )
|
||||
if SEADWeaponName == "KH-58" or SEADWeaponName == "KH-25MPU" or SEADWeaponName == "AGM-88" or SEADWeaponName == "KH-31A" or SEADWeaponName == "KH-31P" then -- Check if the missile is a SEAD
|
||||
local _evade = math.random (1,100) -- random number for chance of evading action
|
||||
local _targetMim = EventData.Weapon:getTarget() -- Identify target
|
||||
local _targetMimname = Unit.getName(_targetMim)
|
||||
local _targetMimgroup = Unit.getGroup(Weapon.getTarget(SEADWeapon))
|
||||
local _targetMimgroupName = _targetMimgroup:getName()
|
||||
local _targetMimcont= _targetMimgroup:getController()
|
||||
local _targetskill = _DATABASE.Templates.Units[_targetMimname].Template.skill
|
||||
self:T( self.SEADGroupPrefixes )
|
||||
self:T( _targetMimgroupName )
|
||||
local SEADGroupFound = false
|
||||
for SEADGroupPrefixID, SEADGroupPrefix in pairs( self.SEADGroupPrefixes ) do
|
||||
if string.find( _targetMimgroupName, SEADGroupPrefix, 1, true ) then
|
||||
SEADGroupFound = true
|
||||
self:T( 'Group Found' )
|
||||
break
|
||||
end
|
||||
end
|
||||
if SEADGroupFound == true then
|
||||
if _targetskill == "Random" then -- when skill is random, choose a skill
|
||||
local Skills = { "Average", "Good", "High", "Excellent" }
|
||||
_targetskill = Skills[ math.random(1,4) ]
|
||||
end
|
||||
self:T( _targetskill )
|
||||
if self.TargetSkill[_targetskill] then
|
||||
if (_evade > self.TargetSkill[_targetskill].Evade) then
|
||||
self:T( string.format("Evading, target skill " ..string.format(_targetskill)) )
|
||||
local _targetMim = Weapon.getTarget(SEADWeapon)
|
||||
local _targetMimname = Unit.getName(_targetMim)
|
||||
local _targetMimgroup = Unit.getGroup(Weapon.getTarget(SEADWeapon))
|
||||
local _targetMimcont= _targetMimgroup:getController()
|
||||
routines.groupRandomDistSelf(_targetMimgroup,300,'Diamond',250,20) -- move randomly
|
||||
local SuppressedGroups1 = {} -- unit suppressed radar off for a random time
|
||||
local function SuppressionEnd1(id)
|
||||
id.ctrl:setOption(AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.GREEN)
|
||||
SuppressedGroups1[id.groupName] = nil
|
||||
end
|
||||
local id = {
|
||||
groupName = _targetMimgroup,
|
||||
ctrl = _targetMimcont
|
||||
}
|
||||
local delay1 = math.random(self.TargetSkill[_targetskill].DelayOff[1], self.TargetSkill[_targetskill].DelayOff[2])
|
||||
if SuppressedGroups1[id.groupName] == nil then
|
||||
SuppressedGroups1[id.groupName] = {
|
||||
SuppressionEndTime1 = timer.getTime() + delay1,
|
||||
SuppressionEndN1 = SuppressionEndCounter1 --Store instance of SuppressionEnd() scheduled function
|
||||
}
|
||||
Controller.setOption(_targetMimcont, AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.GREEN)
|
||||
timer.scheduleFunction(SuppressionEnd1, id, SuppressedGroups1[id.groupName].SuppressionEndTime1) --Schedule the SuppressionEnd() function
|
||||
--trigger.action.outText( string.format("Radar Off " ..string.format(delay1)), 20)
|
||||
end
|
||||
|
||||
local SuppressedGroups = {}
|
||||
local function SuppressionEnd(id)
|
||||
id.ctrl:setOption(AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.RED)
|
||||
SuppressedGroups[id.groupName] = nil
|
||||
end
|
||||
local id = {
|
||||
groupName = _targetMimgroup,
|
||||
ctrl = _targetMimcont
|
||||
}
|
||||
local delay = math.random(self.TargetSkill[_targetskill].DelayOn[1], self.TargetSkill[_targetskill].DelayOn[2])
|
||||
if SuppressedGroups[id.groupName] == nil then
|
||||
SuppressedGroups[id.groupName] = {
|
||||
SuppressionEndTime = timer.getTime() + delay,
|
||||
SuppressionEndN = SuppressionEndCounter --Store instance of SuppressionEnd() scheduled function
|
||||
}
|
||||
timer.scheduleFunction(SuppressionEnd, id, SuppressedGroups[id.groupName].SuppressionEndTime) --Schedule the SuppressionEnd() function
|
||||
--trigger.action.outText( string.format("Radar On " ..string.format(delay)), 20)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
self:T( "*** SEAD - Missile Launched = " .. SEADWeaponName)
|
||||
--self:T({ SEADWeapon })
|
||||
|
||||
if self:_CheckHarms(SEADWeaponName) then
|
||||
self:T( '*** SEAD - Weapon Match' )
|
||||
local _targetskill = "Random"
|
||||
local _targetgroupname = "none"
|
||||
local _target = EventData.Weapon:getTarget() -- Identify target
|
||||
if not _target or self.debug then -- AGM-88 or 154 w/o target data
|
||||
self:E("***** SEAD - No target data for " .. (SEADWeaponName or "None"))
|
||||
if string.find(SEADWeaponName,"AGM_88",1,true) or string.find(SEADWeaponName,"AGM_154",1,true) then
|
||||
self:I("**** Tracking AGM-88/154 with no target data.")
|
||||
local pos0 = SEADPlane:GetCoordinate()
|
||||
local fheight = SEADPlane:GetHeight()
|
||||
self:__CalculateHitZone(20,SEADWeapon,pos0,fheight,SEADGroup,SEADWeaponName)
|
||||
end
|
||||
return self
|
||||
end
|
||||
local targetcat = _target:getCategory() -- Identify category
|
||||
local _targetUnit = nil -- Wrapper.Unit#UNIT
|
||||
local _targetgroup = nil -- Wrapper.Group#GROUP
|
||||
self:T(string.format("*** Targetcat = %d",targetcat))
|
||||
if targetcat == Object.Category.UNIT then -- UNIT
|
||||
self:T("*** Target Category UNIT")
|
||||
_targetUnit = UNIT:Find(_target) -- Wrapper.Unit#UNIT
|
||||
if _targetUnit and _targetUnit:IsAlive() then
|
||||
_targetgroup = _targetUnit:GetGroup()
|
||||
_targetgroupname = _targetgroup:GetName() -- group name
|
||||
local _targetUnitName = _targetUnit:GetName()
|
||||
_targetUnit:GetSkill()
|
||||
_targetskill = _targetUnit:GetSkill()
|
||||
end
|
||||
elseif targetcat == Object.Category.STATIC then
|
||||
self:T("*** Target Category STATIC")
|
||||
local seadset = SET_GROUP:New():FilterPrefixes(self.SEADGroupPrefixes):FilterOnce()
|
||||
local targetpoint = _target:getPoint() or {x=0,y=0,z=0}
|
||||
local tgtcoord = COORDINATE:NewFromVec3(targetpoint)
|
||||
local tgtgrp = seadset:FindNearestGroupFromPointVec2(tgtcoord)
|
||||
if tgtgrp and tgtgrp:IsAlive() then
|
||||
_targetgroup = tgtgrp
|
||||
_targetgroupname = tgtgrp:GetName() -- group name
|
||||
_targetskill = tgtgrp:GetUnit(1):GetSkill()
|
||||
self:T("*** Found Target = ".. _targetgroupname)
|
||||
end
|
||||
end
|
||||
-- see if we are shot at
|
||||
local SEADGroupFound = false
|
||||
for SEADGroupPrefixID, SEADGroupPrefix in pairs( self.SEADGroupPrefixes ) do
|
||||
self:T("Target = ".. _targetgroupname .. " | Prefix = " .. SEADGroupPrefix )
|
||||
if string.find( _targetgroupname, SEADGroupPrefix,1,true ) then
|
||||
SEADGroupFound = true
|
||||
self:T( '*** SEAD - Group Match Found' )
|
||||
break
|
||||
end
|
||||
end
|
||||
if SEADGroupFound == true then -- yes we are being attacked
|
||||
self:ManageEvasion(_targetskill,_targetgroup,SEADPlanePos,SEADWeaponName,SEADGroup)
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
548
Moose Development/Moose/Functional/Shorad.lua
Normal file
548
Moose Development/Moose/Functional/Shorad.lua
Normal file
@@ -0,0 +1,548 @@
|
||||
--- **Functional** -- Short Range Air Defense System
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- **SHORAD** - Short Range Air Defense System
|
||||
-- Controls a network of short range air/missile defense groups.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ## Missions:
|
||||
--
|
||||
-- ### [SHORAD - Short Range Air Defense](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/SRD%20-%20SHORAD%20Defense)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author : **applevangelist **
|
||||
--
|
||||
-- @module Functional.Shorad
|
||||
-- @image Functional.Shorad.jpg
|
||||
--
|
||||
-- Date: July 2021
|
||||
|
||||
-------------------------------------------------------------------------
|
||||
--- **SHORAD** class, extends Core.Base#BASE
|
||||
-- @type SHORAD
|
||||
-- @field #string ClassName
|
||||
-- @field #string name Name of this Shorad
|
||||
-- @field #boolean debug Set the debug state
|
||||
-- @field #string Prefixes String to be used to build the @{#Core.Set#SET_GROUP}
|
||||
-- @field #number Radius Shorad defense radius in meters
|
||||
-- @field Core.Set#SET_GROUP Groupset The set of Shorad groups
|
||||
-- @field Core.Set#SET_GROUP Samset The set of SAM groups to defend
|
||||
-- @field #string Coalition The coalition of this Shorad
|
||||
-- @field #number ActiveTimer How long a Shorad stays active after wake-up in seconds
|
||||
-- @field #table ActiveGroups Table for the timer function
|
||||
-- @field #string lid The log ID for the dcs.log
|
||||
-- @field #boolean DefendHarms Default true, intercept incoming HARMS
|
||||
-- @field #boolean DefendMavs Default true, intercept incoming AG-Missiles
|
||||
-- @field #number DefenseLowProb Default 70, minimum detection limit
|
||||
-- @field #number DefenseHighProb Default 90, maximim detection limit
|
||||
-- @field #boolean UseEmOnOff Decide if we are using Emission on/off (default) or AlarmState red/green.
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
--- *Good friends are worth defending.* Mr Tushman, Wonder (the Movie)
|
||||
--
|
||||
-- Simple Class for a more intelligent Short Range Air Defense System
|
||||
--
|
||||
-- #SHORAD
|
||||
-- Moose derived missile intercepting short range defense system.
|
||||
-- Protects a network of SAM sites. Uses events to switch on the defense groups closest to the enemy.
|
||||
-- Easily integrated with @{Functional.Mantis#MANTIS} to complete the defensive system setup.
|
||||
--
|
||||
-- ## Usage
|
||||
--
|
||||
-- Set up a #SET_GROUP for the SAM sites to be protected:
|
||||
--
|
||||
-- `local SamSet = SET_GROUP:New():FilterPrefixes("Red SAM"):FilterCoalitions("red"):FilterStart()`
|
||||
--
|
||||
-- By default, SHORAD will defense against both HARMs and AG-Missiles with short to medium range. The default defense probability is 70-90%.
|
||||
-- When a missile is detected, SHORAD will activate defense groups in the given radius around the target for 10 minutes. It will *not* react to friendly fire.
|
||||
--
|
||||
-- ### Start a new SHORAD system, parameters are:
|
||||
--
|
||||
-- * Name: Name of this SHORAD.
|
||||
-- * ShoradPrefix: Filter for the Shorad #SET_GROUP.
|
||||
-- * Samset: The #SET_GROUP of SAM sites to defend.
|
||||
-- * Radius: Defense radius in meters.
|
||||
-- * ActiveTimer: Determines how many seconds the systems stay on red alert after wake-up call.
|
||||
-- * Coalition: Coalition, i.e. "blue", "red", or "neutral".*
|
||||
--
|
||||
-- `myshorad = SHORAD:New("RedShorad", "Red SHORAD", SamSet, 25000, 600, "red")`
|
||||
--
|
||||
-- ## Customize options
|
||||
--
|
||||
-- * SHORAD:SwitchDebug(debug)
|
||||
-- * SHORAD:SwitchHARMDefense(onoff)
|
||||
-- * SHORAD:SwitchAGMDefense(onoff)
|
||||
-- * SHORAD:SetDefenseLimits(low,high)
|
||||
-- * SHORAD:SetActiveTimer(seconds)
|
||||
-- * SHORAD:SetDefenseRadius(meters)
|
||||
--
|
||||
-- @field #SHORAD
|
||||
SHORAD = {
|
||||
ClassName = "SHORAD",
|
||||
name = "MyShorad",
|
||||
debug = false,
|
||||
Prefixes = "",
|
||||
Radius = 20000,
|
||||
Groupset = nil,
|
||||
Samset = nil,
|
||||
Coalition = nil,
|
||||
ActiveTimer = 600, --stay on 10 mins
|
||||
ActiveGroups = {},
|
||||
lid = "",
|
||||
DefendHarms = true,
|
||||
DefendMavs = true,
|
||||
DefenseLowProb = 70,
|
||||
DefenseHighProb = 90,
|
||||
UseEmOnOff = false,
|
||||
}
|
||||
|
||||
-----------------------------------------------------------------------
|
||||
-- SHORAD System
|
||||
-----------------------------------------------------------------------
|
||||
|
||||
do
|
||||
-- TODO Complete list?
|
||||
--- Missile enumerators
|
||||
-- @field Harms
|
||||
SHORAD.Harms = {
|
||||
["AGM_88"] = "AGM_88",
|
||||
["AGM_45"] = "AGM_45",
|
||||
["AGM_122"] = "AGM_122",
|
||||
["AGM_84"] = "AGM_84",
|
||||
["AGM_45"] = "AGM_45",
|
||||
["ALARM"] = "ALARM",
|
||||
["LD-10"] = "LD-10",
|
||||
["X_58"] = "X_58",
|
||||
["X_28"] = "X_28",
|
||||
["X_25"] = "X_25",
|
||||
["X_31"] = "X_31",
|
||||
["Kh25"] = "Kh25",
|
||||
}
|
||||
|
||||
--- TODO complete list?
|
||||
-- @field Mavs
|
||||
SHORAD.Mavs = {
|
||||
["AGM"] = "AGM",
|
||||
["C-701"] = "C-701",
|
||||
["Kh25"] = "Kh25",
|
||||
["Kh29"] = "Kh29",
|
||||
["Kh31"] = "Kh31",
|
||||
["Kh66"] = "Kh66",
|
||||
}
|
||||
|
||||
--- Instantiates a new SHORAD object
|
||||
-- @param #SHORAD self
|
||||
-- @param #string Name Name of this SHORAD
|
||||
-- @param #string ShoradPrefix Filter for the Shorad #SET_GROUP
|
||||
-- @param Core.Set#SET_GROUP Samset The #SET_GROUP of SAM sites to defend
|
||||
-- @param #number Radius Defense radius in meters, used to switch on groups
|
||||
-- @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() )
|
||||
self:T({Name, ShoradPrefix, Samset, Radius, ActiveTimer, Coalition})
|
||||
|
||||
local GroupSet = SET_GROUP:New():FilterPrefixes(ShoradPrefix):FilterCoalitions(Coalition):FilterCategoryGround():FilterStart()
|
||||
|
||||
self.name = Name or "MyShorad"
|
||||
self.Prefixes = ShoradPrefix or "SAM SHORAD"
|
||||
self.Radius = Radius or 20000
|
||||
self.Coalition = Coalition or "blue"
|
||||
self.Samset = Samset or GroupSet
|
||||
self.ActiveTimer = ActiveTimer or 600
|
||||
self.ActiveGroups = {}
|
||||
self.Groupset = GroupSet
|
||||
self.DefendHarms = true
|
||||
self.DefendMavs = true
|
||||
self.DefenseLowProb = 70 -- probability to detect a missile shot, low margin
|
||||
self.DefenseHighProb = 90 -- probability to detect a missile shot, high margin
|
||||
self.UseEmOnOff = UseEmOnOff or false -- Decide if we are using Emission on/off (default) or AlarmState red/green
|
||||
self:I("*** SHORAD - Started Version 0.2.8")
|
||||
-- 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)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Initially set all groups to alarm state GREEN
|
||||
-- @param #SHORAD self
|
||||
function SHORAD:_InitState()
|
||||
self:T(self.lid .. " _InitState")
|
||||
local table = {}
|
||||
local set = self.Groupset
|
||||
self:T({set = set})
|
||||
local aliveset = set:GetAliveSet() --#table
|
||||
for _,_group in pairs (aliveset) do
|
||||
if self.UseEmOnOff then
|
||||
--_group:SetAIOff()
|
||||
_group:EnableEmission(false)
|
||||
_group:OptionAlarmStateRed() --Wrapper.Group#GROUP
|
||||
else
|
||||
_group:OptionAlarmStateGreen() --Wrapper.Group#GROUP
|
||||
end
|
||||
_group:OptionDisperseOnAttack(30)
|
||||
end
|
||||
-- gather entropy
|
||||
for i=1,100 do
|
||||
math.random()
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Switch debug state on
|
||||
-- @param #SHORAD self
|
||||
-- @param #boolean debug Switch debug on (true) or off (false)
|
||||
function SHORAD:SwitchDebug(onoff)
|
||||
self:T( { onoff } )
|
||||
if onoff then
|
||||
self:SwitchDebugOn()
|
||||
else
|
||||
self.SwitchDebugOff()
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Switch debug state on
|
||||
-- @param #SHORAD self
|
||||
function SHORAD:SwitchDebugOn()
|
||||
self.debug = true
|
||||
--tracing
|
||||
BASE:TraceOn()
|
||||
BASE:TraceClass("SHORAD")
|
||||
return self
|
||||
end
|
||||
|
||||
--- Switch debug state off
|
||||
-- @param #SHORAD self
|
||||
function SHORAD:SwitchDebugOff()
|
||||
self.debug = false
|
||||
BASE:TraceOff()
|
||||
return self
|
||||
end
|
||||
|
||||
--- Switch defense for HARMs
|
||||
-- @param #SHORAD self
|
||||
-- @param #boolean onoff
|
||||
function SHORAD:SwitchHARMDefense(onoff)
|
||||
self:T( { onoff } )
|
||||
local onoff = onoff or true
|
||||
self.DefendHarms = onoff
|
||||
return self
|
||||
end
|
||||
|
||||
--- Switch defense for AGMs
|
||||
-- @param #SHORAD self
|
||||
-- @param #boolean onoff
|
||||
function SHORAD:SwitchAGMDefense(onoff)
|
||||
self:T( { onoff } )
|
||||
local onoff = onoff or true
|
||||
self.DefendMavs = onoff
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set defense probability limits
|
||||
-- @param #SHORAD self
|
||||
-- @param #number low Minimum detection limit, integer 1-100
|
||||
-- @param #number high Maximum detection limit integer 1-100
|
||||
function SHORAD:SetDefenseLimits(low,high)
|
||||
self:T( { low, high } )
|
||||
local low = low or 70
|
||||
local high = high or 90
|
||||
if (low < 0) or (low > 100) or (low > high) then
|
||||
low = 70
|
||||
end
|
||||
if (high < 0) or (high > 100) or (high < low ) then
|
||||
high = 90
|
||||
end
|
||||
self.DefenseLowProb = low
|
||||
self.DefenseHighProb = high
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set the number of seconds a SHORAD site will stay active
|
||||
-- @param #SHORAD self
|
||||
-- @param #number seconds Number of seconds systems stay active
|
||||
function SHORAD:SetActiveTimer(seconds)
|
||||
self:T(self.lid .. " SetActiveTimer")
|
||||
local timer = seconds or 600
|
||||
if timer < 0 then
|
||||
timer = 600
|
||||
end
|
||||
self.ActiveTimer = timer
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set the number of meters for the SHORAD defense zone
|
||||
-- @param #SHORAD self
|
||||
-- @param #number meters Radius of the defense search zone in meters. #SHORADs in this range around a targeted group will go active
|
||||
function SHORAD:SetDefenseRadius(meters)
|
||||
self:T(self.lid .. " SetDefenseRadius")
|
||||
local radius = meters or 20000
|
||||
if radius < 0 then
|
||||
radius = 20000
|
||||
end
|
||||
self.Radius = radius
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set using Emission on/off instead of changing alarm state
|
||||
-- @param #SHORAD self
|
||||
-- @param #boolean switch Decide if we are changing alarm state or AI state
|
||||
function SHORAD:SetUsingEmOnOff(switch)
|
||||
self:T(self.lid .. " SetUsingEmOnOff")
|
||||
self.UseEmOnOff = switch or false
|
||||
return self
|
||||
end
|
||||
|
||||
--- Check if a HARM was fired
|
||||
-- @param #SHORAD self
|
||||
-- @param #string WeaponName
|
||||
-- @return #boolean Returns true for a match
|
||||
function SHORAD:_CheckHarms(WeaponName)
|
||||
self:T(self.lid .. " _CheckHarms")
|
||||
self:T( { WeaponName } )
|
||||
local hit = false
|
||||
if self.DefendHarms then
|
||||
for _,_name in pairs (SHORAD.Harms) do
|
||||
if string.find(WeaponName,_name,1) then hit = true end
|
||||
end
|
||||
end
|
||||
return hit
|
||||
end
|
||||
|
||||
--- Check if an AGM was fired
|
||||
-- @param #SHORAD self
|
||||
-- @param #string WeaponName
|
||||
-- @return #boolean Returns true for a match
|
||||
function SHORAD:_CheckMavs(WeaponName)
|
||||
self:T(self.lid .. " _CheckMavs")
|
||||
self:T( { WeaponName } )
|
||||
local hit = false
|
||||
if self.DefendMavs then
|
||||
for _,_name in pairs (SHORAD.Mavs) do
|
||||
if string.find(WeaponName,_name,1) then hit = true end
|
||||
end
|
||||
end
|
||||
return hit
|
||||
end
|
||||
|
||||
--- Check the coalition of the attacker
|
||||
-- @param #SHORAD self
|
||||
-- @param #string Coalition name
|
||||
-- @return #boolean Returns false for a match
|
||||
function SHORAD:_CheckCoalition(Coalition)
|
||||
self:T(self.lid .. " _CheckCoalition")
|
||||
local owncoalition = self.Coalition
|
||||
local othercoalition = ""
|
||||
if Coalition == 0 then
|
||||
othercoalition = "neutral"
|
||||
elseif Coalition == 1 then
|
||||
othercoalition = "red"
|
||||
else
|
||||
othercoalition = "blue"
|
||||
end
|
||||
self:T({owncoalition = owncoalition, othercoalition = othercoalition})
|
||||
if owncoalition ~= othercoalition then
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
--- Check if the missile is aimed at a SHORAD
|
||||
-- @param #SHORAD self
|
||||
-- @param #string TargetGroupName Name of the target group
|
||||
-- @return #boolean Returns true for a match, else false
|
||||
function SHORAD:_CheckShotAtShorad(TargetGroupName)
|
||||
self:T(self.lid .. " _CheckShotAtShorad")
|
||||
local tgtgrp = TargetGroupName
|
||||
local shorad = self.Groupset
|
||||
local shoradset = shorad:GetAliveSet() --#table
|
||||
local returnname = false
|
||||
for _,_groups in pairs (shoradset) do
|
||||
local groupname = _groups:GetName()
|
||||
if string.find(groupname, tgtgrp, 1) then
|
||||
returnname = true
|
||||
--_groups:RelocateGroundRandomInRadius(7,100,false,false) -- be a bit evasive
|
||||
end
|
||||
end
|
||||
return returnname
|
||||
end
|
||||
|
||||
--- Check if the missile is aimed at a SAM site
|
||||
-- @param #SHORAD self
|
||||
-- @param #string TargetGroupName Name of the target group
|
||||
-- @return #boolean Returns true for a match, else false
|
||||
function SHORAD:_CheckShotAtSams(TargetGroupName)
|
||||
self:T(self.lid .. " _CheckShotAtSams")
|
||||
local tgtgrp = TargetGroupName
|
||||
local shorad = self.Samset
|
||||
--local shoradset = shorad:GetAliveSet() --#table
|
||||
local shoradset = shorad:GetSet() --#table
|
||||
local returnname = false
|
||||
for _,_groups in pairs (shoradset) do
|
||||
local groupname = _groups:GetName()
|
||||
if string.find(groupname, tgtgrp, 1) then
|
||||
returnname = true
|
||||
end
|
||||
end
|
||||
return returnname
|
||||
end
|
||||
|
||||
--- Calculate if the missile shot is detected
|
||||
-- @param #SHORAD self
|
||||
-- @return #boolean Returns true for a detection, else false
|
||||
function SHORAD:_ShotIsDetected()
|
||||
self:T(self.lid .. " _ShotIsDetected")
|
||||
local IsDetected = false
|
||||
local DetectionProb = math.random(self.DefenseLowProb, self.DefenseHighProb) -- reference value
|
||||
local ActualDetection = math.random(1,100) -- value for this shot
|
||||
if ActualDetection <= DetectionProb then
|
||||
IsDetected = true
|
||||
end
|
||||
return IsDetected
|
||||
end
|
||||
|
||||
--- Wake up #SHORADs in a zone with diameter Radius for ActiveTimer seconds
|
||||
-- @param #SHORAD self
|
||||
-- @param #string TargetGroup Name of the target group used to build the #ZONE
|
||||
-- @param #number Radius Radius of the #ZONE
|
||||
-- @param #number ActiveTimer Number of seconds to stay active
|
||||
-- @param #number TargetCat (optional) Category, i.e. Object.Category.UNIT or Object.Category.STATIC
|
||||
-- @usage Use this function to integrate with other systems, example
|
||||
--
|
||||
-- local SamSet = SET_GROUP:New():FilterPrefixes("Blue SAM"):FilterCoalitions("blue"):FilterStart()
|
||||
-- myshorad = SHORAD:New("BlueShorad", "Blue SHORAD", SamSet, 22000, 600, "blue")
|
||||
-- myshorad:SwitchDebug(true)
|
||||
-- mymantis = MANTIS:New("BlueMantis","Blue SAM","Blue EWR",nil,"blue",false,"Blue Awacs")
|
||||
-- mymantis:AddShorad(myshorad,720)
|
||||
-- mymantis:Start()
|
||||
function SHORAD:WakeUpShorad(TargetGroup, Radius, ActiveTimer, TargetCat)
|
||||
self:T(self.lid .. " WakeUpShorad")
|
||||
self:T({TargetGroup, Radius, ActiveTimer, TargetCat})
|
||||
local targetcat = TargetCat or Object.Category.UNIT
|
||||
local targetgroup = TargetGroup
|
||||
local targetvec2 = nil
|
||||
if targetcat == Object.Category.UNIT then
|
||||
targetvec2 = GROUP:FindByName(targetgroup):GetVec2()
|
||||
elseif targetcat == Object.Category.STATIC then
|
||||
targetvec2 = STATIC:FindByName(targetgroup,false):GetVec2()
|
||||
else
|
||||
local samset = self.Samset
|
||||
local sam = samset:GetRandom()
|
||||
targetvec2 = sam:GetVec2()
|
||||
end
|
||||
local targetzone = ZONE_RADIUS:New("Shorad",targetvec2,Radius) -- create a defense zone to check
|
||||
local groupset = self.Groupset --Core.Set#SET_GROUP
|
||||
local shoradset = groupset:GetAliveSet() --#table
|
||||
-- local function to switch off shorad again
|
||||
local function SleepShorad(group)
|
||||
local groupname = group:GetName()
|
||||
self.ActiveGroups[groupname] = nil
|
||||
if self.UseEmOnOff then
|
||||
group:EnableEmission(false)
|
||||
--group:SetAIOff()
|
||||
else
|
||||
group:OptionAlarmStateGreen()
|
||||
end
|
||||
local text = string.format("Sleeping SHORAD %s", group:GetName())
|
||||
self:T(text)
|
||||
local m = MESSAGE:New(text,10,"SHORAD"):ToAllIf(self.debug)
|
||||
end
|
||||
-- go through set and find the one(s) to activate
|
||||
for _,_group in pairs (shoradset) do
|
||||
if _group:IsAnyInZone(targetzone) then
|
||||
local text = string.format("Waking up SHORAD %s", _group:GetName())
|
||||
self:T(text)
|
||||
local m = MESSAGE:New(text,10,"SHORAD"):ToAllIf(self.debug)
|
||||
if self.UseEmOnOff then
|
||||
--_group:SetAIOn()
|
||||
_group:EnableEmission(true)
|
||||
end
|
||||
_group:OptionAlarmStateRed()
|
||||
local groupname = _group:GetName()
|
||||
if self.ActiveGroups[groupname] == nil then -- no timer yet for this group
|
||||
self.ActiveGroups[groupname] = { Timing = ActiveTimer }
|
||||
local endtime = timer.getTime() + (ActiveTimer * math.random(75,100) / 100 ) -- randomize wakeup a bit
|
||||
timer.scheduleFunction(SleepShorad, _group, endtime)
|
||||
end
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Main function - work on the EventData
|
||||
-- @param #SHORAD self
|
||||
-- @param Core.Event#EVENTDATA EventData The event details table data set
|
||||
function SHORAD:HandleEventShot( EventData )
|
||||
self:T( { EventData } )
|
||||
self:T(self.lid .. " HandleEventShot")
|
||||
--local ShootingUnit = EventData.IniDCSUnit
|
||||
--local ShootingUnitName = EventData.IniDCSUnitName
|
||||
local ShootingWeapon = EventData.Weapon -- Identify the weapon fired
|
||||
local ShootingWeaponName = EventData.WeaponName -- return weapon type
|
||||
-- get firing coalition
|
||||
local weaponcoalition = EventData.IniGroup:GetCoalition()
|
||||
-- get detection probability
|
||||
if self:_CheckCoalition(weaponcoalition) then --avoid overhead on friendly fire
|
||||
local IsDetected = self:_ShotIsDetected()
|
||||
-- convert to text
|
||||
local DetectedText = "false"
|
||||
if IsDetected then
|
||||
DetectedText = "true"
|
||||
end
|
||||
local text = string.format("%s Missile Launched = %s | Detected probability state is %s", self.lid, ShootingWeaponName, DetectedText)
|
||||
self:T( text )
|
||||
local m = MESSAGE:New(text,10,"Info"):ToAllIf(self.debug)
|
||||
--
|
||||
if (self:_CheckHarms(ShootingWeaponName) or self:_CheckMavs(ShootingWeaponName)) and IsDetected then
|
||||
-- get target data
|
||||
local targetdata = EventData.Weapon:getTarget() -- Identify target
|
||||
local targetcat = targetdata:getCategory() -- Identify category
|
||||
self:T(string.format("Target Category (3=STATIC, 1=UNIT)= %s",tostring(targetcat)))
|
||||
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)
|
||||
end
|
||||
--local targetunitname = Unit.getName(targetdata) -- Unit name
|
||||
if targetunit and targetunit:IsAlive() then
|
||||
local targetunitname = targetunit:GetName()
|
||||
--local targetgroup = Unit.getGroup(Weapon.getTarget(ShootingWeapon)) --targeted group
|
||||
local targetgroup = nil
|
||||
local targetgroupname = "none"
|
||||
if targetcat == Object.Category.UNIT then
|
||||
targetgroup = targetunit:GetGroup()
|
||||
targetgroupname = targetgroup:GetName() -- group name
|
||||
elseif targetcat == Object.Category.STATIC then
|
||||
targetgroup = targetunit
|
||||
targetgroupname = targetunitname
|
||||
end
|
||||
local text = string.format("%s Missile Target = %s", self.lid, tostring(targetgroupname))
|
||||
self:T( text )
|
||||
local m = MESSAGE:New(text,10,"Info"):ToAllIf(self.debug)
|
||||
-- check if we or a SAM site are the target
|
||||
--local TargetGroup = EventData.TgtGroup -- Wrapper.Group#GROUP
|
||||
local shotatus = self:_CheckShotAtShorad(targetgroupname) --#boolean
|
||||
local shotatsams = self:_CheckShotAtSams(targetgroupname) --#boolean
|
||||
-- if being shot at, find closest SHORADs to activate
|
||||
if shotatsams or shotatus then
|
||||
self:T({shotatsams=shotatsams,shotatus=shotatus})
|
||||
self:WakeUpShorad(targetgroupname, self.Radius, self.ActiveTimer, targetcat)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
--
|
||||
end
|
||||
-----------------------------------------------------------------------
|
||||
-- SHORAD end
|
||||
-----------------------------------------------------------------------
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -39,7 +39,7 @@
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
-- ### Contributions: **Millertime** - Concept
|
||||
-- ### Contributions: **Millertime** - Concept, **funkyfranky**
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
@@ -49,6 +49,15 @@
|
||||
do -- ZONE_CAPTURE_COALITION
|
||||
|
||||
--- @type ZONE_CAPTURE_COALITION
|
||||
-- @field #string ClassName Name of the class.
|
||||
-- @field #number MarkBlue ID of blue F10 mark.
|
||||
-- @field #number MarkRed ID of red F10 mark.
|
||||
-- @field #number StartInterval Time in seconds after the status monitor is started.
|
||||
-- @field #number RepeatInterval Time in seconds after which the zone status is updated.
|
||||
-- @field #boolean HitsOn If true, hit events are monitored and trigger the "Attack" event when a defending unit is hit.
|
||||
-- @field #number HitTimeLast Time stamp in seconds when the last unit inside the zone was hit.
|
||||
-- @field #number HitTimeAttackOver Time interval in seconds before the zone goes from "Attacked" to "Guarded" state after the last hit.
|
||||
-- @field #boolean MarkOn If true, create marks of zone status on F10 map.
|
||||
-- @extends Functional.ZoneGoalCoalition#ZONE_GOAL_COALITION
|
||||
|
||||
|
||||
@@ -197,8 +206,7 @@ do -- ZONE_CAPTURE_COALITION
|
||||
--
|
||||
-- ### IMPORTANT
|
||||
--
|
||||
-- **Each capture zone object must have the monitoring process started specifically.
|
||||
-- The monitoring process is NOT started by default!!!**
|
||||
-- **Each capture zone object must have the monitoring process started specifically. The monitoring process is NOT started by default!**
|
||||
--
|
||||
--
|
||||
-- # Full Example
|
||||
@@ -338,29 +346,48 @@ do -- ZONE_CAPTURE_COALITION
|
||||
--
|
||||
-- @field #ZONE_CAPTURE_COALITION
|
||||
ZONE_CAPTURE_COALITION = {
|
||||
ClassName = "ZONE_CAPTURE_COALITION",
|
||||
ClassName = "ZONE_CAPTURE_COALITION",
|
||||
MarkBlue = nil,
|
||||
MarkRed = nil,
|
||||
StartInterval = nil,
|
||||
RepeatInterval = nil,
|
||||
HitsOn = nil,
|
||||
HitTimeLast = nil,
|
||||
HitTimeAttackOver = nil,
|
||||
MarkOn = nil,
|
||||
}
|
||||
|
||||
--- @field #table ZONE_CAPTURE_COALITION.States
|
||||
ZONE_CAPTURE_COALITION.States = {}
|
||||
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Constructor and Start/Stop Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- ZONE_CAPTURE_COALITION Constructor.
|
||||
-- @param #ZONE_CAPTURE_COALITION self
|
||||
-- @param Core.Zone#ZONE Zone A @{Zone} object with the goal to be achieved.
|
||||
-- @param DCSCoalition.DCSCoalition#coalition Coalition The initial coalition owning the zone.
|
||||
-- @param #table UnitCategories Table of unit categories. See [DCS Class Unit](https://wiki.hoggitworld.com/view/DCS_Class_Unit). Default {Unit.Category.GROUND_UNIT}.
|
||||
-- @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_CAPTURE_COALITION
|
||||
-- @usage
|
||||
--
|
||||
-- AttackZone = ZONE:New( "AttackZone" )
|
||||
--
|
||||
-- ZoneCaptureCoalition = ZONE_CAPTURE_COALITION:New( AttackZone, coalition.side.RED ) -- Create a new ZONE_CAPTURE_COALITION object of zone AttackZone with ownership RED coalition.
|
||||
-- ZoneCaptureCoalition = ZONE_CAPTURE_COALITION:New( AttackZone, coalition.side.RED, {UNITS ) -- Create a new ZONE_CAPTURE_COALITION object of zone AttackZone with ownership RED coalition.
|
||||
-- ZoneCaptureCoalition:__Guard( 1 ) -- Start the Guarding of the AttackZone.
|
||||
--
|
||||
function ZONE_CAPTURE_COALITION:New( Zone, Coalition )
|
||||
function ZONE_CAPTURE_COALITION:New( Zone, Coalition, UnitCategories, ObjectCategories )
|
||||
|
||||
local self = BASE:Inherit( self, ZONE_GOAL_COALITION:New( Zone, Coalition ) ) -- #ZONE_CAPTURE_COALITION
|
||||
|
||||
self:F( { Zone = Zone, Coalition = Coalition } )
|
||||
local self = BASE:Inherit( self, ZONE_GOAL_COALITION:New( Zone, Coalition, UnitCategories ) ) -- #ZONE_CAPTURE_COALITION
|
||||
self:F( { Zone = Zone, Coalition = Coalition, UnitCategories = UnitCategories, ObjectCategories = ObjectCategories } )
|
||||
|
||||
self:SetObjectCategories(ObjectCategories)
|
||||
|
||||
-- Default is no smoke.
|
||||
self:SetSmokeZone(false)
|
||||
-- Default is F10 marks ON.
|
||||
self:SetMarkZone(true)
|
||||
-- Start in state "Empty".
|
||||
self:SetStartState("Empty")
|
||||
|
||||
do
|
||||
|
||||
@@ -545,174 +572,12 @@ do -- ZONE_CAPTURE_COALITION
|
||||
-- @param #ZONE_CAPTURE_COALITION self
|
||||
-- @param #number Delay
|
||||
|
||||
-- ZoneGoal objects are added to the _DATABASE.ZONES_GOAL and SET_ZONE_GOAL sets.
|
||||
_EVENTDISPATCHER:CreateEventNewZoneGoal(self)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- @param #ZONE_CAPTURE_COALITION self
|
||||
function ZONE_CAPTURE_COALITION:onenterCaptured()
|
||||
|
||||
self:GetParent( self, ZONE_CAPTURE_COALITION ).onenterCaptured( self )
|
||||
|
||||
self.Goal:Achieved()
|
||||
end
|
||||
|
||||
|
||||
function ZONE_CAPTURE_COALITION:IsGuarded()
|
||||
|
||||
local IsGuarded = self.Zone:IsAllInZoneOfCoalition( self.Coalition )
|
||||
self:F( { IsGuarded = IsGuarded } )
|
||||
return IsGuarded
|
||||
end
|
||||
|
||||
|
||||
function ZONE_CAPTURE_COALITION:IsEmpty()
|
||||
|
||||
local IsEmpty = self.Zone:IsNoneInZone()
|
||||
self:F( { IsEmpty = IsEmpty } )
|
||||
return IsEmpty
|
||||
end
|
||||
|
||||
|
||||
function ZONE_CAPTURE_COALITION:IsCaptured()
|
||||
|
||||
local IsCaptured = self.Zone:IsAllInZoneOfOtherCoalition( self.Coalition )
|
||||
self:F( { IsCaptured = IsCaptured } )
|
||||
return IsCaptured
|
||||
end
|
||||
|
||||
|
||||
function ZONE_CAPTURE_COALITION:IsAttacked()
|
||||
|
||||
local IsAttacked = self.Zone:IsSomeInZoneOfCoalition( self.Coalition )
|
||||
self:F( { IsAttacked = IsAttacked } )
|
||||
return IsAttacked
|
||||
end
|
||||
|
||||
|
||||
|
||||
--- Mark.
|
||||
-- @param #ZONE_CAPTURE_COALITION self
|
||||
function ZONE_CAPTURE_COALITION:Mark()
|
||||
|
||||
local Coord = self.Zone:GetCoordinate()
|
||||
local ZoneName = self:GetZoneName()
|
||||
local State = self:GetState()
|
||||
|
||||
if self.MarkRed and self.MarkBlue then
|
||||
self:F( { MarkRed = self.MarkRed, MarkBlue = self.MarkBlue } )
|
||||
Coord:RemoveMark( self.MarkRed )
|
||||
Coord:RemoveMark( self.MarkBlue )
|
||||
end
|
||||
|
||||
if self.Coalition == coalition.side.BLUE then
|
||||
self.MarkBlue = Coord:MarkToCoalitionBlue( "Coalition: Blue\nGuard Zone: " .. ZoneName .. "\nStatus: " .. State )
|
||||
self.MarkRed = Coord:MarkToCoalitionRed( "Coalition: Blue\nCapture Zone: " .. ZoneName .. "\nStatus: " .. State )
|
||||
else
|
||||
self.MarkRed = Coord:MarkToCoalitionRed( "Coalition: Red\nGuard Zone: " .. ZoneName .. "\nStatus: " .. State )
|
||||
self.MarkBlue = Coord:MarkToCoalitionBlue( "Coalition: Red\nCapture Zone: " .. ZoneName .. "\nStatus: " .. State )
|
||||
end
|
||||
end
|
||||
|
||||
--- Bound.
|
||||
-- @param #ZONE_CAPTURE_COALITION self
|
||||
function ZONE_CAPTURE_COALITION:onenterGuarded()
|
||||
|
||||
--self:GetParent( self ):onenterGuarded()
|
||||
|
||||
if self.Coalition == coalition.side.BLUE then
|
||||
--elf.ProtectZone:BoundZone( 12, country.id.USA )
|
||||
else
|
||||
--self.ProtectZone:BoundZone( 12, country.id.RUSSIA )
|
||||
end
|
||||
|
||||
self:Mark()
|
||||
|
||||
end
|
||||
|
||||
function ZONE_CAPTURE_COALITION:onenterCaptured()
|
||||
|
||||
--self:GetParent( self ):onenterCaptured()
|
||||
|
||||
local NewCoalition = self.Zone:GetScannedCoalition()
|
||||
self:F( { NewCoalition = NewCoalition } )
|
||||
self:SetCoalition( NewCoalition )
|
||||
|
||||
self:Mark()
|
||||
end
|
||||
|
||||
|
||||
function ZONE_CAPTURE_COALITION:onenterEmpty()
|
||||
|
||||
--self:GetParent( self ):onenterEmpty()
|
||||
|
||||
self:Mark()
|
||||
end
|
||||
|
||||
|
||||
function ZONE_CAPTURE_COALITION:onenterAttacked()
|
||||
|
||||
--self:GetParent( self ):onenterAttacked()
|
||||
|
||||
self:Mark()
|
||||
end
|
||||
|
||||
|
||||
--- When started, check the Coalition status.
|
||||
-- @param #ZONE_CAPTURE_COALITION self
|
||||
function ZONE_CAPTURE_COALITION:onafterGuard()
|
||||
|
||||
--self:F({BASE:GetParent( self )})
|
||||
--BASE:GetParent( self ).onafterGuard( self )
|
||||
|
||||
if not self.SmokeScheduler then
|
||||
self.SmokeScheduler = self:ScheduleRepeat( 1, 1, 0.1, nil, self.StatusSmoke, self )
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function ZONE_CAPTURE_COALITION:IsCaptured()
|
||||
|
||||
local IsCaptured = self.Zone:IsAllInZoneOfOtherCoalition( self.Coalition )
|
||||
self:F( { IsCaptured = IsCaptured } )
|
||||
return IsCaptured
|
||||
end
|
||||
|
||||
|
||||
function ZONE_CAPTURE_COALITION:IsAttacked()
|
||||
|
||||
local IsAttacked = self.Zone:IsSomeInZoneOfCoalition( self.Coalition )
|
||||
self:F( { IsAttacked = IsAttacked } )
|
||||
return IsAttacked
|
||||
end
|
||||
|
||||
|
||||
--- Check status Coalition ownership.
|
||||
-- @param #ZONE_CAPTURE_COALITION self
|
||||
function ZONE_CAPTURE_COALITION:StatusZone()
|
||||
|
||||
local State = self:GetState()
|
||||
self:F( { State = self:GetState() } )
|
||||
|
||||
self:GetParent( self, ZONE_CAPTURE_COALITION ).StatusZone( self )
|
||||
|
||||
if State ~= "Guarded" and self:IsGuarded() then
|
||||
self:Guard()
|
||||
end
|
||||
|
||||
if State ~= "Empty" and self:IsEmpty() then
|
||||
self:Empty()
|
||||
end
|
||||
|
||||
if State ~= "Attacked" and self:IsAttacked() then
|
||||
self:Attack()
|
||||
end
|
||||
|
||||
if State ~= "Captured" and self:IsCaptured() then
|
||||
self:Capture()
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Starts the zone capturing monitoring process.
|
||||
-- This process can be CPU intensive, ensure that you specify reasonable time intervals for the monitoring process.
|
||||
@@ -723,6 +588,7 @@ do -- ZONE_CAPTURE_COALITION
|
||||
-- @param #ZONE_CAPTURE_COALITION self
|
||||
-- @param #number StartInterval (optional) Specifies the start time interval in seconds when the zone state will be checked for the first time.
|
||||
-- @param #number RepeatInterval (optional) Specifies the repeat time interval in seconds when the zone state will be checked repeatedly.
|
||||
-- @return #ZONE_CAPTURE_COALITION self
|
||||
-- @usage
|
||||
--
|
||||
-- -- Setup the zone.
|
||||
@@ -737,13 +603,23 @@ do -- ZONE_CAPTURE_COALITION
|
||||
--
|
||||
function ZONE_CAPTURE_COALITION:Start( StartInterval, RepeatInterval )
|
||||
|
||||
StartInterval = StartInterval or 15
|
||||
RepeatInterval = RepeatInterval or 15
|
||||
self.StartInterval = StartInterval or 1
|
||||
self.RepeatInterval = RepeatInterval or 15
|
||||
|
||||
if self.ScheduleStatusZone then
|
||||
self:ScheduleStop( self.ScheduleStatusZone )
|
||||
end
|
||||
self.ScheduleStatusZone = self:ScheduleRepeat( StartInterval, RepeatInterval, 0.1, nil, self.StatusZone, self )
|
||||
|
||||
-- Start Status scheduler.
|
||||
self.ScheduleStatusZone = self:ScheduleRepeat( self.StartInterval, self.RepeatInterval, 0.1, nil, self.StatusZone, self )
|
||||
|
||||
-- We check if a unit within the zone is hit. If it is, then we must move the zone to attack state.
|
||||
self:HandleEvent(EVENTS.Hit, self.OnEventHit)
|
||||
|
||||
-- Create mark on F10 map.
|
||||
self:Mark()
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
@@ -785,9 +661,283 @@ do -- ZONE_CAPTURE_COALITION
|
||||
function ZONE_CAPTURE_COALITION:Stop()
|
||||
|
||||
if self.ScheduleStatusZone then
|
||||
self:ScheduleStop( self.ScheduleStatusZone )
|
||||
self:ScheduleStop(self.ScheduleStatusZone)
|
||||
end
|
||||
|
||||
if self.SmokeScheduler then
|
||||
self:ScheduleStop(self.SmokeScheduler)
|
||||
end
|
||||
|
||||
self:UnHandleEvent(EVENTS.Hit)
|
||||
|
||||
end
|
||||
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- User API Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Set whether hit events of defending units are monitored and trigger "Attack" events.
|
||||
-- @param #ZONE_CAPTURE_COALITION self
|
||||
-- @param #boolean Switch If *true*, hit events are monitored. If *false* or *nil*, hit events are not monitored.
|
||||
-- @param #number TimeAttackOver (Optional) Time in seconds after an attack is over after the last hit and the zone state goes to "Guarded". Default is 300 sec = 5 min.
|
||||
-- @return #ZONE_CAPTURE_COALITION self
|
||||
function ZONE_CAPTURE_COALITION:SetMonitorHits(Switch, TimeAttackOver)
|
||||
self.HitsOn=Switch
|
||||
self.HitTimeAttackOver=TimeAttackOver or 5*60
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set whether marks on the F10 map are shown, which display the current zone status.
|
||||
-- @param #ZONE_CAPTURE_COALITION self
|
||||
-- @param #boolean Switch If *true* or *nil*, marks are shown. If *false*, marks are not displayed.
|
||||
-- @return #ZONE_CAPTURE_COALITION self
|
||||
function ZONE_CAPTURE_COALITION:SetMarkZone(Switch)
|
||||
if Switch==nil or Switch==true then
|
||||
self.MarkOn=true
|
||||
else
|
||||
self.MarkOn=false
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- DCS Event Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
--- Monitor hit events.
|
||||
-- @param #ZONE_CAPTURE_COALITION self
|
||||
-- @param Core.Event#EVENTDATA EventData The event data.
|
||||
function ZONE_CAPTURE_COALITION:OnEventHit( EventData )
|
||||
|
||||
if self.HitsOn then
|
||||
|
||||
local UnitHit = EventData.TgtUnit
|
||||
|
||||
if UnitHit.ClassName ~= "SCENERY" then
|
||||
-- Check if unit is inside the capture zone and that it is of the defending coalition.
|
||||
if UnitHit and UnitHit:IsInZone(self) and UnitHit:GetCoalition()==self.Coalition then
|
||||
|
||||
-- Update last hit time.
|
||||
self.HitTimeLast=timer.getTime()
|
||||
|
||||
-- Only trigger attacked event if not already in state "Attacked".
|
||||
if self:GetState()~="Attacked" then
|
||||
self:F2("Hit ==> Attack")
|
||||
self:Attack()
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- FSM Event Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- On after "Guard" event.
|
||||
-- @param #ZONE_CAPTURE_COALITION self
|
||||
function ZONE_CAPTURE_COALITION:onafterGuard()
|
||||
self:F2("After Guard")
|
||||
|
||||
if self.SmokeZone and not self.SmokeScheduler then
|
||||
self.SmokeScheduler = self:ScheduleRepeat( self.StartInterval, self.RepeatInterval, 0.1, nil, self.StatusSmoke, self )
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
--- On enter "Guarded" state.
|
||||
-- @param #ZONE_CAPTURE_COALITION self
|
||||
function ZONE_CAPTURE_COALITION:onenterGuarded()
|
||||
self:F2("Enter Guarded")
|
||||
self:Mark()
|
||||
end
|
||||
|
||||
--- On enter "Captured" state.
|
||||
-- @param #ZONE_CAPTURE_COALITION self
|
||||
function ZONE_CAPTURE_COALITION:onenterCaptured()
|
||||
self:F2("Enter Captured")
|
||||
|
||||
-- Get new coalition.
|
||||
local NewCoalition = self:GetScannedCoalition()
|
||||
self:F( { NewCoalition = NewCoalition } )
|
||||
|
||||
-- Set new owner of zone.
|
||||
self:SetCoalition(NewCoalition)
|
||||
|
||||
-- Update mark.
|
||||
self:Mark()
|
||||
|
||||
-- Goal achieved.
|
||||
self.Goal:Achieved()
|
||||
end
|
||||
|
||||
--- On enter "Empty" state.
|
||||
-- @param #ZONE_CAPTURE_COALITION self
|
||||
function ZONE_CAPTURE_COALITION:onenterEmpty()
|
||||
self:F2("Enter Empty")
|
||||
self:Mark()
|
||||
end
|
||||
|
||||
--- On enter "Attacked" state.
|
||||
-- @param #ZONE_CAPTURE_COALITION self
|
||||
function ZONE_CAPTURE_COALITION:onenterAttacked()
|
||||
self:F2("Enter Attacked")
|
||||
self:Mark()
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Status Check Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Check if zone is "Empty".
|
||||
-- @param #ZONE_CAPTURE_COALITION self
|
||||
-- @return #boolean self:IsNoneInZone()
|
||||
function ZONE_CAPTURE_COALITION:IsEmpty()
|
||||
|
||||
local IsEmpty = self:IsNoneInZone()
|
||||
self:F( { IsEmpty = IsEmpty } )
|
||||
|
||||
return IsEmpty
|
||||
end
|
||||
|
||||
--- Check if zone is "Guarded", i.e. only one (the defending) coalition is present inside the zone.
|
||||
-- @param #ZONE_CAPTURE_COALITION self
|
||||
-- @return #boolean self:IsAllInZoneOfCoalition( self.Coalition )
|
||||
function ZONE_CAPTURE_COALITION:IsGuarded()
|
||||
|
||||
local IsGuarded = self:IsAllInZoneOfCoalition( self.Coalition )
|
||||
self:F( { IsGuarded = IsGuarded } )
|
||||
|
||||
return IsGuarded
|
||||
end
|
||||
|
||||
--- Check if zone is "Captured", i.e. another coalition took control over the zone and is the only one present.
|
||||
-- @param #ZONE_CAPTURE_COALITION self
|
||||
-- @return #boolean self:IsAllInZoneOfOtherCoalition( self.Coalition )
|
||||
function ZONE_CAPTURE_COALITION:IsCaptured()
|
||||
|
||||
local IsCaptured = self:IsAllInZoneOfOtherCoalition( self.Coalition )
|
||||
self:F( { IsCaptured = IsCaptured } )
|
||||
|
||||
return IsCaptured
|
||||
end
|
||||
|
||||
--- Check if zone is "Attacked", i.e. another coalition entered the zone.
|
||||
-- @param #ZONE_CAPTURE_COALITION self
|
||||
-- @return #boolean self:IsSomeInZoneOfCoalition( self.Coalition )
|
||||
function ZONE_CAPTURE_COALITION:IsAttacked()
|
||||
|
||||
local IsAttacked = self:IsSomeInZoneOfCoalition( self.Coalition )
|
||||
self:F( { IsAttacked = IsAttacked } )
|
||||
|
||||
return IsAttacked
|
||||
end
|
||||
|
||||
|
||||
--- Check status Coalition ownership.
|
||||
-- @param #ZONE_CAPTURE_COALITION self
|
||||
function ZONE_CAPTURE_COALITION:StatusZone()
|
||||
|
||||
-- Get FSM state.
|
||||
local State = self:GetState()
|
||||
|
||||
-- Scan zone in parent class ZONE_GOAL_COALITION
|
||||
self:GetParent( self, ZONE_CAPTURE_COALITION ).StatusZone( self )
|
||||
|
||||
local Tnow=timer.getTime()
|
||||
|
||||
-- Check if zone is guarded.
|
||||
if State ~= "Guarded" and self:IsGuarded() then
|
||||
|
||||
-- Check that there was a sufficient amount of time after the last hit before going back to "Guarded".
|
||||
if self.HitTimeLast==nil or Tnow>=self.HitTimeLast+self.HitTimeAttackOver then
|
||||
self:Guard()
|
||||
self.HitTimeLast=nil
|
||||
end
|
||||
end
|
||||
|
||||
-- Check if zone is empty.
|
||||
if State ~= "Empty" and self:IsEmpty() then
|
||||
self:Empty()
|
||||
end
|
||||
|
||||
-- Check if zone is attacked.
|
||||
if State ~= "Attacked" and self:IsAttacked() then
|
||||
self:Attack()
|
||||
end
|
||||
|
||||
-- Check if zone is captured.
|
||||
if State ~= "Captured" and self:IsCaptured() then
|
||||
self:Capture()
|
||||
end
|
||||
|
||||
-- Count stuff in zone.
|
||||
local unitset=self:GetScannedSetUnit()
|
||||
local nRed=0
|
||||
local nBlue=0
|
||||
for _,object in pairs(unitset:GetSet()) do
|
||||
local coal=object:GetCoalition()
|
||||
if object:IsAlive() then
|
||||
if coal==coalition.side.RED then
|
||||
nRed=nRed+1
|
||||
elseif coal==coalition.side.BLUE then
|
||||
nBlue=nBlue+1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Status text.
|
||||
if false then
|
||||
local text=string.format("CAPTURE ZONE %s: Owner=%s (Previous=%s): #blue=%d, #red=%d, Status %s", self:GetZoneName(), self:GetCoalitionName(), UTILS.GetCoalitionName(self:GetPreviousCoalition()), nBlue, nRed, State)
|
||||
local NewState = self:GetState()
|
||||
if NewState~=State then
|
||||
text=text..string.format(" --> %s", NewState)
|
||||
end
|
||||
self:I(text)
|
||||
end
|
||||
|
||||
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)
|
||||
end
|
||||
if self.MarkBlue then
|
||||
Coord:RemoveMark(self.MarkBlue)
|
||||
end
|
||||
|
||||
-- Create new marks for each coalition.
|
||||
if self.Coalition == coalition.side.BLUE then
|
||||
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.MarkBlue = Coord:MarkToCoalitionBlue( "Coalition: Red\nCapture Zone: " .. ZoneName .. "\nStatus: " .. State )
|
||||
else
|
||||
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
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
-- ### Contributions: **funkyfranky**
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
@@ -17,10 +18,16 @@
|
||||
do -- Zone
|
||||
|
||||
--- @type ZONE_GOAL
|
||||
-- @extends Core.Fsm#FSM
|
||||
-- @field #string ClassName Name of the class.
|
||||
-- @field Core.Goal#GOAL Goal The goal object.
|
||||
-- @field #number SmokeTime Time stamp in seconds when the last smoke of the zone was triggered.
|
||||
-- @field Core.Scheduler#SCHEDULER SmokeScheduler Scheduler responsible for smoking the zone.
|
||||
-- @field #number SmokeColor Color of the smoke.
|
||||
-- @field #boolean SmokeZone If true, smoke zone.
|
||||
-- @extends Core.Zone#ZONE_RADIUS
|
||||
|
||||
|
||||
-- Models processes that have a Goal with a defined achievement involving a Zone.
|
||||
--- Models processes that have a Goal with a defined achievement involving a Zone.
|
||||
-- Derived classes implement the ways how the achievements can be realized.
|
||||
--
|
||||
-- ## 1. ZONE_GOAL constructor
|
||||
@@ -39,24 +46,41 @@ do -- Zone
|
||||
--
|
||||
-- @field #ZONE_GOAL
|
||||
ZONE_GOAL = {
|
||||
ClassName = "ZONE_GOAL",
|
||||
ClassName = "ZONE_GOAL",
|
||||
Goal = nil,
|
||||
SmokeTime = nil,
|
||||
SmokeScheduler = nil,
|
||||
SmokeColor = nil,
|
||||
SmokeZone = nil,
|
||||
}
|
||||
|
||||
--- ZONE_GOAL Constructor.
|
||||
-- @param #ZONE_GOAL self
|
||||
-- @param Core.Zone#ZONE_BASE Zone A @{Zone} object with the goal to be achieved.
|
||||
-- @param Core.Zone#ZONE_RADIUS Zone A @{Zone} object with the goal to be achieved.
|
||||
-- @return #ZONE_GOAL
|
||||
function ZONE_GOAL:New( Zone )
|
||||
|
||||
local self = BASE:Inherit( self, FSM:New() ) -- #ZONE_GOAL
|
||||
local self = BASE:Inherit( self, ZONE_RADIUS:New( Zone:GetName(), Zone:GetVec2(), Zone:GetRadius() ) ) -- #ZONE_GOAL
|
||||
self:F( { Zone = Zone } )
|
||||
|
||||
self.Zone = Zone -- Core.Zone#ZONE_BASE
|
||||
-- Goal object.
|
||||
self.Goal = GOAL:New()
|
||||
|
||||
self.SmokeTime = nil
|
||||
|
||||
-- Set smoke ON.
|
||||
self:SetSmokeZone(true)
|
||||
|
||||
self:AddTransition( "*", "DestroyedUnit", "*" )
|
||||
|
||||
--- DestroyedUnit event.
|
||||
-- @function [parent=#ZONE_GOAL] DestroyedUnit
|
||||
-- @param #ZONE_GOAL self
|
||||
|
||||
--- DestroyedUnit delayed event
|
||||
-- @function [parent=#ZONE_GOAL] __DestroyedUnit
|
||||
-- @param #ZONE_GOAL self
|
||||
-- @param #number delay Delay in seconds.
|
||||
|
||||
--- DestroyedUnit Handler OnAfter for ZONE_GOAL
|
||||
-- @function [parent=#ZONE_GOAL] OnAfterDestroyedUnit
|
||||
@@ -70,52 +94,64 @@ do -- Zone
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get the Zone
|
||||
--- Get the Zone.
|
||||
-- @param #ZONE_GOAL self
|
||||
-- @return Core.Zone#ZONE_BASE
|
||||
-- @return #ZONE_GOAL
|
||||
function ZONE_GOAL:GetZone()
|
||||
return self.Zone
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Get the name of the ProtectZone
|
||||
--- Get the name of the Zone.
|
||||
-- @param #ZONE_GOAL self
|
||||
-- @return #string
|
||||
function ZONE_GOAL:GetZoneName()
|
||||
return self.Zone:GetName()
|
||||
return self:GetName()
|
||||
end
|
||||
|
||||
|
||||
--- Smoke the center of theh zone.
|
||||
--- Activate smoking of zone with the color or the current owner.
|
||||
-- @param #ZONE_GOAL self
|
||||
-- @param #SMOKECOLOR.Color SmokeColor
|
||||
function ZONE_GOAL:Smoke( SmokeColor )
|
||||
|
||||
-- @param #boolean switch If *true* or *nil* activate smoke. If *false* or *nil*, no smoke.
|
||||
-- @return #ZONE_GOAL
|
||||
function ZONE_GOAL:SetSmokeZone(switch)
|
||||
self.SmokeZone=switch
|
||||
--[[
|
||||
if switch==nil or switch==true then
|
||||
self.SmokeZone=true
|
||||
else
|
||||
self.SmokeZone=false
|
||||
end
|
||||
]]
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set the smoke color.
|
||||
-- @param #ZONE_GOAL self
|
||||
-- @param DCS#SMOKECOLOR.Color SmokeColor
|
||||
function ZONE_GOAL:Smoke( SmokeColor )
|
||||
self:F( { SmokeColor = SmokeColor} )
|
||||
|
||||
self.SmokeColor = SmokeColor
|
||||
end
|
||||
|
||||
|
||||
--- Flare the center of the zone.
|
||||
--- Flare the zone boundary.
|
||||
-- @param #ZONE_GOAL self
|
||||
-- @param #SMOKECOLOR.Color FlareColor
|
||||
-- @param DCS#SMOKECOLOR.Color FlareColor
|
||||
function ZONE_GOAL:Flare( FlareColor )
|
||||
self.Zone:FlareZone( FlareColor, math.random( 1, 360 ) )
|
||||
self:FlareZone( FlareColor, 30)
|
||||
end
|
||||
|
||||
|
||||
--- When started, check the Smoke and the Zone status.
|
||||
-- @param #ZONE_GOAL self
|
||||
function ZONE_GOAL:onafterGuard()
|
||||
|
||||
--self:GetParent( self ):onafterStart()
|
||||
|
||||
self:F("Guard")
|
||||
|
||||
--self:ScheduleRepeat( 15, 15, 0.1, nil, self.StatusZone, self )
|
||||
if not self.SmokeScheduler then
|
||||
self.SmokeScheduler = self:ScheduleRepeat( 1, 1, 0.1, nil, self.StatusSmoke, self )
|
||||
|
||||
-- Start smoke
|
||||
if self.SmokeZone and not self.SmokeScheduler then
|
||||
self.SmokeScheduler = self:ScheduleRepeat(1, 1, 0.1, nil, self.StatusSmoke, self)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -123,44 +159,54 @@ do -- Zone
|
||||
--- Check status Smoke.
|
||||
-- @param #ZONE_GOAL self
|
||||
function ZONE_GOAL:StatusSmoke()
|
||||
|
||||
self:F({self.SmokeTime, self.SmokeColor})
|
||||
|
||||
local CurrentTime = timer.getTime()
|
||||
|
||||
if self.SmokeTime == nil or self.SmokeTime + 300 <= CurrentTime then
|
||||
if self.SmokeColor then
|
||||
self.Zone:GetCoordinate():Smoke( self.SmokeColor )
|
||||
--self.SmokeColor = nil
|
||||
self.SmokeTime = CurrentTime
|
||||
if self.SmokeZone then
|
||||
|
||||
-- Current time.
|
||||
local CurrentTime = timer.getTime()
|
||||
|
||||
-- Restart smoke every 5 min.
|
||||
if self.SmokeTime == nil or self.SmokeTime + 300 <= CurrentTime then
|
||||
if self.SmokeColor then
|
||||
self:GetCoordinate():Smoke( self.SmokeColor )
|
||||
self.SmokeTime = CurrentTime
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- @param #ZONE_GOAL self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
-- @param Core.Event#EVENTDATA EventData Event data table.
|
||||
function ZONE_GOAL:__Destroyed( EventData )
|
||||
self:F( { "EventDead", EventData } )
|
||||
|
||||
self:F( { EventData.IniUnit } )
|
||||
|
||||
local Vec3 = EventData.IniDCSUnit:getPosition().p
|
||||
self:F( { Vec3 = Vec3 } )
|
||||
local ZoneGoal = self:GetZone()
|
||||
self:F({ZoneGoal})
|
||||
|
||||
if EventData.IniDCSUnit then
|
||||
if ZoneGoal:IsVec3InZone(Vec3) then
|
||||
|
||||
local Vec3 = EventData.IniDCSUnit:getPosition().p
|
||||
self:F( { Vec3 = Vec3 } )
|
||||
|
||||
if Vec3 and self:IsVec3InZone(Vec3) then
|
||||
|
||||
local PlayerHits = _DATABASE.HITS[EventData.IniUnitName]
|
||||
|
||||
if PlayerHits then
|
||||
|
||||
for PlayerName, PlayerHit in pairs( PlayerHits.Players or {} ) do
|
||||
self.Goal:AddPlayerContribution( PlayerName )
|
||||
self:DestroyedUnit( EventData.IniUnitName, PlayerName )
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
|
||||
@@ -1,73 +1,123 @@
|
||||
--- **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
|
||||
|
||||
do -- ZoneGoal
|
||||
|
||||
--- @type ZONE_GOAL_COALITION
|
||||
-- @field #string ClassName Name of the Class.
|
||||
-- @field #number Coalition The current coalition ID of the zone owner.
|
||||
-- @field #number PreviousCoalition The previous owner of the zone.
|
||||
-- @field #table UnitCategories Table of unit categories that are able to capture and hold the zone. Default is only GROUND units.
|
||||
-- @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,
|
||||
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.
|
||||
-- @param DCSCoalition.DCSCoalition#coalition Coalition The initial coalition owning the zone.
|
||||
-- @param DCSCoalition.DCSCoalition#coalition Coalition The initial coalition owning the zone. Default coalition.side.NEUTRAL.
|
||||
-- @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 )
|
||||
|
||||
function ZONE_GOAL_COALITION:New( Zone, Coalition, UnitCategories )
|
||||
|
||||
if not Zone then
|
||||
BASE:E( "ERROR: No Zone specified in ZONE_GOAL_COALITION!" )
|
||||
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 } )
|
||||
|
||||
self:SetCoalition( Coalition )
|
||||
-- Set initial owner.
|
||||
self:SetCoalition( Coalition or coalition.side.NEUTRAL )
|
||||
|
||||
-- Set default unit and object categories for the zone scan.
|
||||
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
|
||||
-- @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.Coalition = Coalition
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
|
||||
--- Set the owning coalition of the zone.
|
||||
-- @param #ZONE_GOAL_COALITION self
|
||||
-- @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 }
|
||||
end
|
||||
|
||||
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 }
|
||||
end
|
||||
|
||||
self.ObjectCategories = ObjectCategories or { Object.Category.UNIT, Object.Category.STATIC }
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get the owning coalition of the zone.
|
||||
-- @param #ZONE_GOAL_COALITION self
|
||||
-- @return DCSCoalition.DCSCoalition#coalition Coalition.
|
||||
@@ -75,38 +125,37 @@ do -- ZoneGoal
|
||||
return self.Coalition
|
||||
end
|
||||
|
||||
|
||||
--- Get the previous coalition, 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()
|
||||
|
||||
if self.Coalition == coalition.side.BLUE then
|
||||
return "Blue"
|
||||
end
|
||||
|
||||
if self.Coalition == coalition.side.RED then
|
||||
return "Red"
|
||||
end
|
||||
|
||||
if self.Coalition == coalition.side.NEUTRAL then
|
||||
return "Neutral"
|
||||
end
|
||||
|
||||
return ""
|
||||
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()
|
||||
self:F( { State = self:GetState() } )
|
||||
|
||||
self.Zone:Scan( { Object.Category.UNIT, Object.Category.STATIC } )
|
||||
|
||||
|
||||
-- Debug 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
|
||||
|
||||
|
||||
46
Moose Development/Moose/Globals.lua
Normal file
46
Moose Development/Moose/Globals.lua
Normal file
@@ -0,0 +1,46 @@
|
||||
--- GLOBALS: The order of the declarations is important here. Don't touch it.
|
||||
|
||||
--- Declare the event dispatcher based on the EVENT class
|
||||
_EVENTDISPATCHER = EVENT:New() -- Core.Event#EVENT
|
||||
|
||||
--- Declare the timer dispatcher based on the SCHEDULEDISPATCHER class
|
||||
_SCHEDULEDISPATCHER = SCHEDULEDISPATCHER:New() -- Core.ScheduleDispatcher#SCHEDULEDISPATCHER
|
||||
|
||||
--- Declare the main database object, which is used internally by the MOOSE classes.
|
||||
_DATABASE = DATABASE:New() -- Core.Database#DATABASE
|
||||
|
||||
--- Settings
|
||||
_SETTINGS = SETTINGS:Set()
|
||||
_SETTINGS:SetPlayerMenuOn()
|
||||
|
||||
--- Register cargos.
|
||||
_DATABASE:_RegisterCargos()
|
||||
|
||||
--- Register zones.
|
||||
_DATABASE:_RegisterZones()
|
||||
_DATABASE:_RegisterAirbases()
|
||||
|
||||
--- Check if os etc is available.
|
||||
BASE:I("Checking de-sanitization of os, io and lfs:")
|
||||
local __na=false
|
||||
if os then
|
||||
BASE:I("- os available")
|
||||
else
|
||||
BASE:I("- os NOT available! Some functions may not work.")
|
||||
__na=true
|
||||
end
|
||||
if io then
|
||||
BASE:I("- io available")
|
||||
else
|
||||
BASE:I("- io NOT available! Some functions may not work.")
|
||||
__na=true
|
||||
end
|
||||
if lfs then
|
||||
BASE:I("- lfs available")
|
||||
else
|
||||
BASE:I("- lfs NOT available! Some functions may not work.")
|
||||
__na=true
|
||||
end
|
||||
if __na then
|
||||
BASE:I("Check <DCS install folder>/Scripts/MissionScripting.lua and comment out the lines with sanitizeModule(''). Use at your own risk!)")
|
||||
end
|
||||
141
Moose Development/Moose/Modules.lua
Normal file
141
Moose Development/Moose/Modules.lua
Normal file
@@ -0,0 +1,141 @@
|
||||
__Moose.Include( 'Scripts/Moose/Utilities/Enums.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Utilities/Routines.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Utilities/Utils.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Utilities/Profiler.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Utilities/Templates.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Utilities/STTS.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Utilities/FiFo.lua' )
|
||||
|
||||
__Moose.Include( 'Scripts/Moose/Core/Base.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/Beacon.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/UserFlag.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/Report.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/Scheduler.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/ScheduleDispatcher.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/Event.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/Settings.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/Menu.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/Zone.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/Zone_Detection.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/Database.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/Set.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/Point.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/Velocity.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/Message.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/Fsm.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/Spawn.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/SpawnStatic.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/Timer.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/Goal.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/Spot.lua' )
|
||||
|
||||
__Moose.Include( 'Scripts/Moose/Wrapper/Object.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Wrapper/Identifiable.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Wrapper/Positionable.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Wrapper/Controllable.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Wrapper/Group.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Wrapper/Unit.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Wrapper/Client.lua' )
|
||||
__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/Cargo/Cargo.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Cargo/CargoUnit.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Cargo/CargoSlingload.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Cargo/CargoCrate.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Cargo/CargoGroup.lua' )
|
||||
|
||||
__Moose.Include( 'Scripts/Moose/Functional/Scoring.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Functional/CleanUp.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Functional/Movement.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Functional/Sead.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Functional/Escort.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Functional/MissileTrainer.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Functional/ATC_Ground.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Functional/Detection.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Functional/DetectionZones.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Functional/Designate.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Functional/RAT.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Functional/Range.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Functional/ZoneGoal.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Functional/ZoneGoalCoalition.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Functional/ZoneCaptureCoalition.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Functional/Artillery.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Functional/Suppression.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Functional/PseudoATC.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Functional/Warehouse.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Functional/Fox.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Functional/Mantis.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Functional/Shorad.lua' )
|
||||
|
||||
__Moose.Include( 'Scripts/Moose/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/CSAR.lua' )
|
||||
|
||||
__Moose.Include( 'Scripts/Moose/AI/AI_Balancer.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/AI/AI_Air.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/AI/AI_Air_Patrol.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/AI/AI_Air_Engage.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/AI/AI_A2A_Patrol.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/AI/AI_A2A_Cap.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/AI/AI_A2A_Gci.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/AI/AI_A2A_Dispatcher.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/AI/AI_A2G_BAI.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/AI/AI_A2G_CAS.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/AI/AI_A2G_SEAD.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/AI/AI_A2G_Dispatcher.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/AI/AI_Patrol.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/AI/AI_Cap.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/AI/AI_Cas.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/AI/AI_Bai.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/AI/AI_Formation.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/AI/AI_Escort.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/AI/AI_Escort_Request.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/AI/AI_Escort_Dispatcher.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/AI/AI_Escort_Dispatcher_Request.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/AI/AI_Cargo.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/AI/AI_Cargo_APC.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/AI/AI_Cargo_Helicopter.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/AI/AI_Cargo_Airplane.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/AI/AI_Cargo_Ship.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/AI/AI_Cargo_Dispatcher.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/AI/AI_Cargo_Dispatcher_APC.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/AI/AI_Cargo_Dispatcher_Helicopter.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/AI/AI_Cargo_Dispatcher_Airplane.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/AI/AI_Cargo_Dispatcher_Ship.lua' )
|
||||
|
||||
__Moose.Include( 'Scripts/Moose/Actions/Act_Assign.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Actions/Act_Route.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Actions/Act_Account.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Actions/Act_Assist.lua' )
|
||||
|
||||
__Moose.Include( 'Scripts/Moose/Sound/UserSound.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Sound/SoundOutput.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Sound/Radio.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Sound/RadioQueue.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Sound/RadioSpeech.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Sound/SRS.lua' )
|
||||
|
||||
__Moose.Include( 'Scripts/Moose/Tasking/CommandCenter.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Tasking/Mission.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Tasking/Task.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Tasking/TaskInfo.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Tasking/Task_Manager.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Tasking/DetectionManager.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Tasking/Task_A2G_Dispatcher.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Tasking/Task_A2G.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Tasking/Task_A2A_Dispatcher.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Tasking/Task_A2A.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Tasking/Task_Cargo.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Tasking/Task_Cargo_Transport.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Tasking/Task_Cargo_CSAR.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Tasking/Task_Cargo_Dispatcher.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Tasking/Task_Capture_Zone.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Tasking/Task_Capture_Dispatcher.lua' )
|
||||
|
||||
__Moose.Include( 'Scripts/Moose/Globals.lua' )
|
||||
@@ -1,18 +0,0 @@
|
||||
-- The order of the declarations is important here. Don't touch it.
|
||||
|
||||
|
||||
--- Declare the event dispatcher based on the EVENT class
|
||||
_EVENTDISPATCHER = EVENT:New() -- Core.Event#EVENT
|
||||
|
||||
--- Declare the timer dispatcher based on the SCHEDULEDISPATCHER class
|
||||
_SCHEDULEDISPATCHER = SCHEDULEDISPATCHER:New() -- Core.Timer#SCHEDULEDISPATCHER
|
||||
|
||||
--- Declare the main database object, which is used internally by the MOOSE classes.
|
||||
_DATABASE = DATABASE:New() -- Core.Database#DATABASE
|
||||
|
||||
_SETTINGS = SETTINGS:Set()
|
||||
_SETTINGS:SetPlayerMenuOn()
|
||||
|
||||
_DATABASE:_RegisterCargos()
|
||||
_DATABASE:_RegisterZones()
|
||||
|
||||
2583
Moose Development/Moose/Ops/ATIS.lua
Normal file
2583
Moose Development/Moose/Ops/ATIS.lua
Normal file
File diff suppressed because it is too large
Load Diff
17787
Moose Development/Moose/Ops/Airboss.lua
Normal file
17787
Moose Development/Moose/Ops/Airboss.lua
Normal file
File diff suppressed because it is too large
Load Diff
2311
Moose Development/Moose/Ops/CSAR.lua
Normal file
2311
Moose Development/Moose/Ops/CSAR.lua
Normal file
File diff suppressed because it is too large
Load Diff
5468
Moose Development/Moose/Ops/CTLD.lua
Normal file
5468
Moose Development/Moose/Ops/CTLD.lua
Normal file
File diff suppressed because it is too large
Load Diff
1696
Moose Development/Moose/Ops/RecoveryTanker.lua
Normal file
1696
Moose Development/Moose/Ops/RecoveryTanker.lua
Normal file
File diff suppressed because it is too large
Load Diff
1296
Moose Development/Moose/Ops/RescueHelo.lua
Normal file
1296
Moose Development/Moose/Ops/RescueHelo.lua
Normal file
File diff suppressed because it is too large
Load Diff
394
Moose Development/Moose/Sound/Radio.lua
Normal file
394
Moose Development/Moose/Sound/Radio.lua
Normal file
@@ -0,0 +1,394 @@
|
||||
--- **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,
|
||||
-- you won't hear/be able to use the TACAN beacon informations.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Authors: Hugues "Grey_Echo" Bousquet, funkyfranky
|
||||
--
|
||||
-- @module Sound.Radio
|
||||
-- @image Core_Radio.JPG
|
||||
|
||||
|
||||
--- *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.
|
||||
--
|
||||
-- @type RADIO
|
||||
-- @field Wrapper.Controllable#CONTROLLABLE Positionable The @{#CONTROLLABLE} that will transmit the radio calls.
|
||||
-- @field #string FileName Name of the sound file played.
|
||||
-- @field #number Frequency Frequency of the transmission in Hz.
|
||||
-- @field #number Modulation Modulation of the transmission (either radio.modulation.AM or radio.modulation.FM).
|
||||
-- @field #string Subtitle Subtitle of the transmission.
|
||||
-- @field #number SubtitleDuration Duration of the Subtitle in seconds.
|
||||
-- @field #number Power Power of the antenna is Watts.
|
||||
-- @field #boolean Loop Transmission is repeated (default true).
|
||||
-- @field #string alias Name of the radio transmitter.
|
||||
-- @extends Core.Base#BASE
|
||||
RADIO = {
|
||||
ClassName = "RADIO",
|
||||
FileName = "",
|
||||
Frequency = 0,
|
||||
Modulation = radio.modulation.AM,
|
||||
Subtitle = "",
|
||||
SubtitleDuration = 0,
|
||||
Power = 100,
|
||||
Loop = false,
|
||||
alias = nil,
|
||||
}
|
||||
|
||||
--- Create a new RADIO Object. This doesn't broadcast a transmission, though, use @{#RADIO.Broadcast} to actually broadcast.
|
||||
-- If you want to create a RADIO, you probably should use @{Wrapper.Positionable#POSITIONABLE.GetRadio}() instead.
|
||||
-- @param #RADIO self
|
||||
-- @param Wrapper.Positionable#POSITIONABLE Positionable The @{Positionable} that will receive radio capabilities.
|
||||
-- @return #RADIO The RADIO object or #nil if Positionable is invalid.
|
||||
function RADIO:New(Positionable)
|
||||
|
||||
-- Inherit base
|
||||
local self = BASE:Inherit( self, BASE:New() ) -- Core.Radio#RADIO
|
||||
self:F(Positionable)
|
||||
|
||||
if Positionable:GetPointVec2() then -- It's stupid, but the only way I found to make sure positionable is valid
|
||||
self.Positionable = Positionable
|
||||
return self
|
||||
end
|
||||
|
||||
self:E({error="The passed positionable is invalid, no RADIO created!", positionable=Positionable})
|
||||
return nil
|
||||
end
|
||||
|
||||
--- Set alias of the transmitter.
|
||||
-- @param #RADIO self
|
||||
-- @param #string alias Name of the radio transmitter.
|
||||
-- @return #RADIO self
|
||||
function RADIO:SetAlias(alias)
|
||||
self.alias=tostring(alias)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get alias of the transmitter.
|
||||
-- @param #RADIO self
|
||||
-- @return #string Name of the transmitter.
|
||||
function RADIO:GetAlias()
|
||||
return tostring(self.alias)
|
||||
end
|
||||
|
||||
--- Set the file name for the radio transmission.
|
||||
-- @param #RADIO self
|
||||
-- @param #string FileName File name of the sound file (i.e. "Noise.ogg")
|
||||
-- @return #RADIO self
|
||||
function RADIO:SetFileName(FileName)
|
||||
self:F2(FileName)
|
||||
|
||||
if type(FileName) == "string" then
|
||||
|
||||
if FileName:find(".ogg") or FileName:find(".wav") then
|
||||
if not FileName:find("l10n/DEFAULT/") then
|
||||
FileName = "l10n/DEFAULT/" .. FileName
|
||||
end
|
||||
|
||||
self.FileName = FileName
|
||||
return self
|
||||
end
|
||||
end
|
||||
|
||||
self:E({"File name invalid. Maybe something wrong with the extension?", FileName})
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set the frequency for the radio transmission.
|
||||
-- If the transmitting positionable is a unit or group, this also set the command "SetFrequency" with the defined frequency and modulation.
|
||||
-- @param #RADIO self
|
||||
-- @param #number Frequency Frequency in MHz.
|
||||
-- @return #RADIO self
|
||||
function RADIO:SetFrequency(Frequency)
|
||||
self:F2(Frequency)
|
||||
|
||||
if type(Frequency) == "number" then
|
||||
|
||||
-- If frequency is in range
|
||||
-- if (Frequency >= 30 and Frequency <= 87.995) or (Frequency >= 108 and Frequency <= 173.995) or (Frequency >= 225 and Frequency <= 399.975) then
|
||||
|
||||
-- Convert frequency from MHz to Hz
|
||||
self.Frequency = Frequency * 1000000
|
||||
|
||||
-- If the RADIO is attached to a UNIT or a GROUP, we need to send the DCS Command "SetFrequency" to change the UNIT or GROUP frequency
|
||||
if self.Positionable.ClassName == "UNIT" or self.Positionable.ClassName == "GROUP" then
|
||||
|
||||
local commandSetFrequency={
|
||||
id = "SetFrequency",
|
||||
params = {
|
||||
frequency = self.Frequency,
|
||||
modulation = self.Modulation,
|
||||
}
|
||||
}
|
||||
|
||||
self:T2(commandSetFrequency)
|
||||
self.Positionable:SetCommand(commandSetFrequency)
|
||||
end
|
||||
|
||||
return self
|
||||
-- end
|
||||
end
|
||||
|
||||
self:E({"Frequency is not a number. Frequency unchanged.", Frequency})
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set AM or FM modulation of the radio transmitter.
|
||||
-- @param #RADIO self
|
||||
-- @param #number Modulation Modulation is either radio.modulation.AM or radio.modulation.FM.
|
||||
-- @return #RADIO self
|
||||
function RADIO:SetModulation(Modulation)
|
||||
self:F2(Modulation)
|
||||
if type(Modulation) == "number" then
|
||||
if Modulation == radio.modulation.AM or Modulation == radio.modulation.FM then --TODO Maybe make this future proof if ED decides to add an other modulation ?
|
||||
self.Modulation = Modulation
|
||||
return self
|
||||
end
|
||||
end
|
||||
self:E({"Modulation is invalid. Use DCS's enum radio.modulation. Modulation unchanged.", self.Modulation})
|
||||
return self
|
||||
end
|
||||
|
||||
--- Check validity of the power passed and sets RADIO.Power
|
||||
-- @param #RADIO self
|
||||
-- @param #number Power Power in W.
|
||||
-- @return #RADIO self
|
||||
function RADIO:SetPower(Power)
|
||||
self:F2(Power)
|
||||
|
||||
if type(Power) == "number" then
|
||||
self.Power = math.floor(math.abs(Power)) --TODO Find what is the maximum power allowed by DCS and limit power to that
|
||||
else
|
||||
self:E({"Power is invalid. Power unchanged.", self.Power})
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set message looping on or off.
|
||||
-- @param #RADIO self
|
||||
-- @param #boolean Loop If true, message is repeated indefinitely.
|
||||
-- @return #RADIO self
|
||||
function RADIO:SetLoop(Loop)
|
||||
self:F2(Loop)
|
||||
if type(Loop) == "boolean" then
|
||||
self.Loop = Loop
|
||||
return self
|
||||
end
|
||||
self:E({"Loop is invalid. Loop unchanged.", self.Loop})
|
||||
return self
|
||||
end
|
||||
|
||||
--- Check validity of the subtitle and the subtitleDuration passed and sets RADIO.subtitle and RADIO.subtitleDuration
|
||||
-- Both parameters are mandatory, since it wouldn't make much sense to change the Subtitle and not its duration
|
||||
-- @param #RADIO self
|
||||
-- @param #string Subtitle
|
||||
-- @param #number SubtitleDuration in s
|
||||
-- @return #RADIO self
|
||||
-- @usage
|
||||
-- -- create the broadcaster and attaches it a RADIO
|
||||
-- local MyUnit = UNIT:FindByName("MyUnit")
|
||||
-- local MyUnitRadio = MyUnit:GetRadio()
|
||||
--
|
||||
-- -- add a subtitle for the next transmission, which will be up for 10s
|
||||
-- MyUnitRadio:SetSubtitle("My Subtitle, 10)
|
||||
function RADIO:SetSubtitle(Subtitle, SubtitleDuration)
|
||||
self:F2({Subtitle, SubtitleDuration})
|
||||
if type(Subtitle) == "string" then
|
||||
self.Subtitle = Subtitle
|
||||
else
|
||||
self.Subtitle = ""
|
||||
self:E({"Subtitle is invalid. Subtitle reset.", self.Subtitle})
|
||||
end
|
||||
if type(SubtitleDuration) == "number" then
|
||||
self.SubtitleDuration = SubtitleDuration
|
||||
else
|
||||
self.SubtitleDuration = 0
|
||||
self:E({"SubtitleDuration is invalid. SubtitleDuration reset.", self.SubtitleDuration})
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Create a new transmission, that is to say, populate the RADIO with relevant data
|
||||
-- In this function the data is especially relevant if the broadcaster is anything but a UNIT or a GROUP,
|
||||
-- but it will work with a UNIT or a GROUP anyway.
|
||||
-- Only the #RADIO and the Filename are mandatory
|
||||
-- @param #RADIO self
|
||||
-- @param #string FileName Name of the sound file that will be transmitted.
|
||||
-- @param #number Frequency Frequency in MHz.
|
||||
-- @param #number Modulation Modulation of frequency, which is either radio.modulation.AM or radio.modulation.FM.
|
||||
-- @param #number Power Power in W.
|
||||
-- @return #RADIO self
|
||||
function RADIO:NewGenericTransmission(FileName, Frequency, Modulation, Power, Loop)
|
||||
self:F({FileName, Frequency, Modulation, Power})
|
||||
|
||||
self:SetFileName(FileName)
|
||||
if Frequency then self:SetFrequency(Frequency) end
|
||||
if Modulation then self:SetModulation(Modulation) end
|
||||
if Power then self:SetPower(Power) end
|
||||
if Loop then self:SetLoop(Loop) end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Create a new transmission, that is to say, populate the RADIO with relevant data
|
||||
-- In this function the data is especially relevant if the broadcaster is a UNIT or a GROUP,
|
||||
-- but it will work for any @{Wrapper.Positionable#POSITIONABLE}.
|
||||
-- Only the RADIO and the Filename are mandatory.
|
||||
-- @param #RADIO self
|
||||
-- @param #string FileName Name of sound file.
|
||||
-- @param #string Subtitle Subtitle to be displayed with sound file.
|
||||
-- @param #number SubtitleDuration Duration of subtitle display in seconds.
|
||||
-- @param #number Frequency Frequency in MHz.
|
||||
-- @param #number Modulation Modulation which can be either radio.modulation.AM or radio.modulation.FM
|
||||
-- @param #boolean Loop If true, loop message.
|
||||
-- @return #RADIO self
|
||||
function RADIO:NewUnitTransmission(FileName, Subtitle, SubtitleDuration, Frequency, Modulation, Loop)
|
||||
self:F({FileName, Subtitle, SubtitleDuration, Frequency, Modulation, Loop})
|
||||
|
||||
-- Set file name.
|
||||
self:SetFileName(FileName)
|
||||
|
||||
-- Set modulation AM/FM.
|
||||
if Modulation then
|
||||
self:SetModulation(Modulation)
|
||||
end
|
||||
|
||||
-- Set frequency.
|
||||
if Frequency then
|
||||
self:SetFrequency(Frequency)
|
||||
end
|
||||
|
||||
-- Set subtitle.
|
||||
if Subtitle then
|
||||
self:SetSubtitle(Subtitle, SubtitleDuration or 0)
|
||||
end
|
||||
|
||||
-- Set Looping.
|
||||
if Loop then
|
||||
self:SetLoop(Loop)
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Broadcast the transmission.
|
||||
-- * The Radio has to be populated with the new transmission before broadcasting.
|
||||
-- * Please use RADIO setters or either @{#RADIO.NewGenericTransmission} or @{#RADIO.NewUnitTransmission}
|
||||
-- * This class is in fact pretty smart, it determines the right DCS function to use depending on the type of POSITIONABLE
|
||||
-- * If the POSITIONABLE is not a UNIT or a GROUP, we use the generic (but limited) trigger.action.radioTransmission()
|
||||
-- * If the POSITIONABLE is a UNIT or a GROUP, we use the "TransmitMessage" Command
|
||||
-- * If your POSITIONABLE is a UNIT or a GROUP, the Power is ignored.
|
||||
-- * If your POSITIONABLE is not a UNIT or a GROUP, the Subtitle, SubtitleDuration are ignored
|
||||
-- @param #RADIO self
|
||||
-- @param #boolean viatrigger Use trigger.action.radioTransmission() in any case, i.e. also for UNITS and GROUPS.
|
||||
-- @return #RADIO self
|
||||
function RADIO:Broadcast(viatrigger)
|
||||
self:F({viatrigger=viatrigger})
|
||||
|
||||
-- If the POSITIONABLE is actually a UNIT or a GROUP, use the more complicated DCS command system.
|
||||
if (self.Positionable.ClassName=="UNIT" or self.Positionable.ClassName=="GROUP") and (not viatrigger) then
|
||||
self:T("Broadcasting from a UNIT or a GROUP")
|
||||
|
||||
local commandTransmitMessage={
|
||||
id = "TransmitMessage",
|
||||
params = {
|
||||
file = self.FileName,
|
||||
duration = self.SubtitleDuration,
|
||||
subtitle = self.Subtitle,
|
||||
loop = self.Loop,
|
||||
}}
|
||||
|
||||
self:T3(commandTransmitMessage)
|
||||
self.Positionable:SetCommand(commandTransmitMessage)
|
||||
else
|
||||
-- If the POSITIONABLE is anything else, we revert to the general singleton function
|
||||
-- I need to give it a unique name, so that the transmission can be stopped later. I use the class ID
|
||||
self:T("Broadcasting from a POSITIONABLE")
|
||||
trigger.action.radioTransmission(self.FileName, self.Positionable:GetPositionVec3(), self.Modulation, self.Loop, self.Frequency, self.Power, tostring(self.ID))
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
|
||||
--- Stops a transmission
|
||||
-- This function is especially usefull to stop the broadcast of looped transmissions
|
||||
-- @param #RADIO self
|
||||
-- @return #RADIO self
|
||||
function RADIO:StopBroadcast()
|
||||
self:F()
|
||||
-- If the POSITIONABLE is a UNIT or a GROUP, stop the transmission with the DCS "StopTransmission" command
|
||||
if self.Positionable.ClassName == "UNIT" or self.Positionable.ClassName == "GROUP" then
|
||||
|
||||
local commandStopTransmission={id="StopTransmission", params={}}
|
||||
|
||||
self.Positionable:SetCommand(commandStopTransmission)
|
||||
else
|
||||
-- Else, we use the appropriate singleton funciton
|
||||
trigger.action.stopRadioTransmission(tostring(self.ID))
|
||||
end
|
||||
return self
|
||||
end
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user