mirror of
https://github.com/FlightControl-Master/MOOSE.git
synced 2025-08-15 10:47:21 +00:00
Compare commits
643 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0971f8ffa7 | ||
|
|
9739f94428 | ||
|
|
450f4eaec0 | ||
|
|
b5186fa328 | ||
|
|
526dc8363d | ||
|
|
242d735d8c | ||
|
|
9fedd32e78 | ||
|
|
a146003a6a | ||
|
|
d17df74191 | ||
|
|
d23403383f | ||
|
|
877b36f8a8 | ||
|
|
299820162b | ||
|
|
5d20a59d92 | ||
|
|
c1ab3b5476 | ||
|
|
0076130483 | ||
|
|
a5194b1999 | ||
|
|
b53b1f083f | ||
|
|
994a36001f | ||
|
|
26b91e3f66 | ||
|
|
f42aedd485 | ||
|
|
a73818a615 | ||
|
|
7c22e9fe69 | ||
|
|
c58e91b956 | ||
|
|
a61059242c | ||
|
|
cea9437e66 | ||
|
|
3f052ef1da | ||
|
|
c137a4b06d | ||
|
|
6c46dafc59 | ||
|
|
1207417894 | ||
|
|
7c717c219c | ||
|
|
a9fad53255 | ||
|
|
372cc021bd | ||
|
|
6820774266 | ||
|
|
a0c189a349 | ||
|
|
1060ff16d8 | ||
|
|
1255f48645 | ||
|
|
7ef69208d4 | ||
|
|
5277cca4e1 | ||
|
|
aaf77815ca | ||
|
|
3f488cc091 | ||
|
|
bdbbdfe60e | ||
|
|
d15c2be2d0 | ||
|
|
73994914eb | ||
|
|
63b0dae794 | ||
|
|
003e865ff7 | ||
|
|
e3c03287b7 | ||
|
|
73493c3a23 | ||
|
|
562a3f6208 | ||
|
|
3c5f3d6c37 | ||
|
|
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 |
@@ -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"
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -18,6 +18,8 @@ local.properties
|
||||
# External tool builders
|
||||
.externalToolBuilders/
|
||||
|
||||
# AppVeyor
|
||||
.appveyor/
|
||||
|
||||
# CDT-specific
|
||||
.cproject
|
||||
|
||||
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.
|
||||
}
|
||||
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
|
||||
17
Moose Development/Moose/.vscode/settings.json
vendored
Normal file
17
Moose Development/Moose/.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"Lua.workspace.preloadFileSize": 1000,
|
||||
"Lua.diagnostics.disable": [
|
||||
"undefined-doc-name"
|
||||
],
|
||||
"Lua.diagnostics.globals": [
|
||||
"BASE",
|
||||
"lfs",
|
||||
"__Moose",
|
||||
"trigger",
|
||||
"coord",
|
||||
"missionCommands"
|
||||
],
|
||||
"Lua.completion.displayContext": 5,
|
||||
"Lua.runtime.version": "Lua 5.1",
|
||||
"Lua.completion.callSnippet": "Both"
|
||||
}
|
||||
@@ -40,8 +40,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.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
@@ -71,7 +71,7 @@
|
||||
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detected}**: The AI has detected new targets.
|
||||
-- * **@{#AI_A2A_CAP.Destroy}**: The AI has destroyed a bogey @{Wrapper.Unit}.
|
||||
-- * **@{#AI_A2A_CAP.Destroyed}**: The AI has destroyed all bogeys @{Wrapper.Unit}s assigned in the CAS task.
|
||||
-- * **Status** ( Group ): The AI is checking status (fuel and damage). When the 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
|
||||
--
|
||||
@@ -143,7 +143,7 @@ end
|
||||
-- @return #AI_A2A_CAP
|
||||
function AI_A2A_CAP:New( AICap, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, EngageMinSpeed, EngageMaxSpeed, PatrolAltType )
|
||||
|
||||
return self:New2( AICap, EngageMinSpeed, EngageMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolZone, PatrolMinSpeed, PatrolMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolAltType, PatrolAltType )
|
||||
return self:New2( AICap, EngageMinSpeed, EngageMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolAltType, PatrolZone, PatrolMinSpeed, PatrolMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolAltType )
|
||||
|
||||
end
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -42,8 +42,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.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
@@ -73,7 +73,7 @@
|
||||
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detected}**: The AI has detected new targets.
|
||||
-- * **@{#AI_A2A_GCI.Destroy}**: The AI has destroyed a bogey @{Wrapper.Unit}.
|
||||
-- * **@{#AI_A2A_GCI.Destroyed}**: The AI has destroyed all bogeys @{Wrapper.Unit}s assigned in the CAS task.
|
||||
-- * **Status** ( Group ): The AI is checking status (fuel and damage). When the 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
|
||||
--
|
||||
|
||||
@@ -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.
|
||||
--
|
||||
-- ===
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -42,8 +42,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.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
|
||||
@@ -253,6 +253,9 @@ function AI_AIR:New( AIGroup )
|
||||
|
||||
self.IdleCount = 0
|
||||
|
||||
self.RTBSpeedMaxFactor = 0.6
|
||||
self.RTBSpeedMinFactor = 0.5
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -370,11 +373,11 @@ end
|
||||
|
||||
|
||||
--- When the AI is out of fuel, it is required that a new AI is started, before the old AI can return to the home base.
|
||||
-- Therefore, with a parameter and a calculation of the distance to the home base, the fuel treshold is calculated.
|
||||
-- When the fuel treshold is reached, the AI will continue for a given time its patrol task in orbit, while a new AIControllable is targetted to the AI_AIR.
|
||||
-- Therefore, with a parameter and a calculation of the distance to the home base, the fuel threshold is calculated.
|
||||
-- When the fuel threshold is reached, the AI will continue for a given time its patrol task in orbit, while a new AIControllable is targetted to the AI_AIR.
|
||||
-- Once the time is finished, the old AI will return to the base.
|
||||
-- @param #AI_AIR self
|
||||
-- @param #number FuelThresholdPercentage The treshold in percentage (between 0 and 1) when the AIControllable is considered to get out of fuel.
|
||||
-- @param #number FuelThresholdPercentage The threshold in percentage (between 0 and 1) when the AIControllable is considered to get out of fuel.
|
||||
-- @param #number OutOfFuelOrbitTime The amount of seconds the out of fuel AIControllable will orbit before returning to the base.
|
||||
-- @return #AI_AIR self
|
||||
function AI_AIR:SetFuelThreshold( FuelThresholdPercentage, OutOfFuelOrbitTime )
|
||||
@@ -387,14 +390,14 @@ function AI_AIR:SetFuelThreshold( FuelThresholdPercentage, OutOfFuelOrbitTime )
|
||||
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_AIR 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_AIR self
|
||||
function AI_AIR:SetDamageThreshold( PatrolDamageThreshold )
|
||||
|
||||
@@ -476,7 +479,7 @@ function AI_AIR:onafterStatus()
|
||||
|
||||
local Fuel = self.Controllable:GetFuelMin()
|
||||
|
||||
-- If the fuel in the controllable is below the treshold percentage,
|
||||
-- If the fuel in the controllable is below the threshold percentage,
|
||||
-- then send for refuel in case of a tanker, otherwise RTB.
|
||||
if Fuel < self.FuelThresholdPercentage then
|
||||
|
||||
@@ -576,6 +579,19 @@ function AI_AIR.RTBHold( AIGroup, Fsm )
|
||||
|
||||
end
|
||||
|
||||
--- Set the min and max factors on RTB speed. Use this, if your planes are heading back to base too fast. Default values are 0.5 and 0.6.
|
||||
-- The RTB speed is calculated as the max speed of the unit multiplied by MinFactor (lower bracket) and multiplied by MaxFactor (upper bracket).
|
||||
-- A random value in this bracket is then applied in the waypoint routing generation.
|
||||
-- @param #AI_AIR self
|
||||
-- @param #number MinFactor Lower bracket factor. Defaults to 0.5.
|
||||
-- @param #number MaxFactor Upper bracket factor. Defaults to 0.6.
|
||||
-- @return #AI_AIR self
|
||||
function AI_AIR:SetRTBSpeedFactors(MinFactor,MaxFactor)
|
||||
self.RTBSpeedMaxFactor = MaxFactor or 0.6
|
||||
self.RTBSpeedMinFactor = MinFactor or 0.5
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- @param #AI_AIR self
|
||||
-- @param Wrapper.Group#GROUP AIGroup
|
||||
@@ -585,29 +601,38 @@ function AI_AIR:onafterRTB( AIGroup, From, Event, To )
|
||||
|
||||
if AIGroup and AIGroup:IsAlive() then
|
||||
|
||||
self:I( "Group " .. AIGroup:GetName() .. " ... RTB! ( " .. self:GetState() .. " )" )
|
||||
self:T( "Group " .. AIGroup:GetName() .. " ... RTB! ( " .. self:GetState() .. " )" )
|
||||
|
||||
self:ClearTargetDistance()
|
||||
--AIGroup:ClearTasks()
|
||||
|
||||
|
||||
AIGroup:OptionProhibitAfterburner(true)
|
||||
|
||||
local EngageRoute = {}
|
||||
|
||||
--- Calculate the target route point.
|
||||
|
||||
local FromCoord = AIGroup:GetCoordinate()
|
||||
local ToTargetCoord = self.HomeAirbase:GetCoordinate()
|
||||
|
||||
if not self.RTBMinSpeed and not self.RTBMaxSpeed then
|
||||
local ToTargetCoord = self.HomeAirbase:GetCoordinate() -- coordinate is on land height(!)
|
||||
local ToTargetVec3 = ToTargetCoord:GetVec3()
|
||||
ToTargetVec3.y = ToTargetCoord:GetLandHeight()+3000 -- let's set this 1000m/3000 feet above ground
|
||||
local ToTargetCoord2 = COORDINATE:NewFromVec3( ToTargetVec3 )
|
||||
|
||||
if not self.RTBMinSpeed or not self.RTBMaxSpeed then
|
||||
local RTBSpeedMax = AIGroup:GetSpeedMax()
|
||||
self:SetRTBSpeed( RTBSpeedMax * 0.25, RTBSpeedMax * 0.25 )
|
||||
local RTBSpeedMaxFactor = self.RTBSpeedMaxFactor or 0.6
|
||||
local RTBSpeedMinFactor = self.RTBSpeedMinFactor or 0.5
|
||||
self:SetRTBSpeed( RTBSpeedMax * RTBSpeedMinFactor, RTBSpeedMax * RTBSpeedMaxFactor)
|
||||
end
|
||||
|
||||
local RTBSpeed = math.random( self.RTBMinSpeed, self.RTBMaxSpeed )
|
||||
local ToAirbaseAngle = FromCoord:GetAngleDegrees( FromCoord:GetDirectionVec3( ToTargetCoord ) )
|
||||
--local ToAirbaseAngle = FromCoord:GetAngleDegrees( FromCoord:GetDirectionVec3( ToTargetCoord2 ) )
|
||||
|
||||
local Distance = FromCoord:Get2DDistance( ToTargetCoord )
|
||||
local Distance = FromCoord:Get2DDistance( ToTargetCoord2 )
|
||||
|
||||
local ToAirbaseCoord = FromCoord:Translate( 5000, ToAirbaseAngle )
|
||||
--local ToAirbaseCoord = FromCoord:Translate( 5000, ToAirbaseAngle )
|
||||
local ToAirbaseCoord = ToTargetCoord2
|
||||
|
||||
if Distance < 5000 then
|
||||
self:I( "RTB and near the airbase!" )
|
||||
self:Home()
|
||||
|
||||
@@ -10,11 +10,11 @@
|
||||
-- * Setup (CAS) Controlled Air Support squadrons, to attack closeby enemy ground units near friendly installations.
|
||||
-- * Setup (BAI) Battleground Air Interdiction squadrons to attack remote enemy ground units and targets.
|
||||
-- * Define and use a detection network controlled by recce.
|
||||
-- * Define AIR defense squadrons at airbases, farps and carriers.
|
||||
-- * Define AIR defense squadrons at airbases, FARPs and carriers.
|
||||
-- * Enable airbases for AIR defenses.
|
||||
-- * Add different planes and helicopter templates to squadrons.
|
||||
-- * Assign squadrons to execute a specific engagement type depending on threat level of the detected ground enemy unit composition.
|
||||
-- * Add multiple squadrons to different airbases, farps or carriers.
|
||||
-- * Add multiple squadrons to different airbases, FARPs or carriers.
|
||||
-- * Define different ranges to engage upon.
|
||||
-- * Establish an automatic in air refuel process for planes using refuel tankers.
|
||||
-- * Setup default settings for all squadrons and AIR defenses.
|
||||
@@ -40,7 +40,7 @@
|
||||
--
|
||||
-- AI_AIR_DISPATCHER is the main AIR defense class that models the AIR defense system.
|
||||
--
|
||||
-- Before you start using the AI_AIR_DISPATCHER, ask youself the following questions.
|
||||
-- Before you start using the AI_AIR_DISPATCHER, ask yourself the following questions.
|
||||
--
|
||||
--
|
||||
-- ## 1. Which coalition am I modeling an AIR defense system for? blue or red?
|
||||
@@ -128,7 +128,7 @@
|
||||
-- Depending on the defense type, different payloads will be needed. See further points on squadron definition.
|
||||
--
|
||||
--
|
||||
-- ## 7. Where will the Squadrons be located? On Airbases? On Carrier Ships? On Farps?
|
||||
-- ## 7. Where will the Squadrons be located? On Airbases? On Carrier Ships? On FARPs?
|
||||
--
|
||||
-- Squadrons are placed at the **home base** on an **airfield**, **carrier** or **farp**.
|
||||
-- Carefully plan where each Squadron will be located as part of the defense system required for mission effective defenses.
|
||||
@@ -354,7 +354,7 @@ do -- AI_AIR_DISPATCHER
|
||||
-- **DetectionSetGroup** is then calling `FilterStart()`, which is starting the dynamic filtering or inclusion of these groups.
|
||||
-- Note that any destroy or new spawn of a group having a name, starting with the above prefix, will be removed or added to the set.
|
||||
--
|
||||
-- Then a new detection object is created from the class `DETECTION_AREAS`. A grouping radius of 1000 meters (1km) is choosen.
|
||||
-- Then a new detection object is created from the class `DETECTION_AREAS`. A grouping radius of 1000 meters (1km) is chosen.
|
||||
--
|
||||
-- The `Detection` object is then passed to the @{#AI_AIR_DISPATCHER.New}() method to indicate the reconnaissance network
|
||||
-- configuration and setup the AIR defense detection mechanism.
|
||||
@@ -647,7 +647,7 @@ do -- AI_AIR_DISPATCHER
|
||||
-- * @{#AI_AIR_DISPATCHER.SetSquadronLandingAtRunway}() will despawn the returning aircraft directly after landing at the runway.
|
||||
-- * @{#AI_AIR_DISPATCHER.SetSquadronLandingAtEngineShutdown}() will despawn the returning aircraft when the aircraft has returned to its parking spot and has turned off its engines.
|
||||
--
|
||||
-- You can use these methods to minimize the airbase coodination overhead and to increase the airbase efficiency.
|
||||
-- You can use these methods to minimize the airbase coordination overhead and to increase the airbase efficiency.
|
||||
-- When there are lots of aircraft returning for landing, at the same airbase, the takeoff process will be halted, which can cause a complete failure of the
|
||||
-- A2A defense system, as no new CAP or GCI planes can takeoff.
|
||||
-- Note that the method @{#AI_AIR_DISPATCHER.SetSquadronLandingNearAirbase}() will only work for returning aircraft, not for damaged or out of fuel aircraft.
|
||||
@@ -724,13 +724,13 @@ do -- AI_AIR_DISPATCHER
|
||||
--
|
||||
-- Use the method @{#AI_AIR_DISPATCHER.SetSquadronEngageLimit}() to limit the amount of aircraft that will engage with the enemy, per squadron.
|
||||
--
|
||||
-- ## 4. Set the **fuel treshold**.
|
||||
-- ## 4. Set the **fuel threshold**.
|
||||
--
|
||||
-- When aircraft get **out of fuel** to a certain %-tage, which is by default **15% (0.15)**, there are two possible actions that can be taken:
|
||||
-- When aircraft get **out of fuel** to a certain %, which is by default **15% (0.15)**, there are two possible actions that can be taken:
|
||||
-- - The aircraft will go RTB, and will be replaced with a new aircraft if possible.
|
||||
-- - The aircraft will refuel at a tanker, if a tanker has been specified for the squadron.
|
||||
--
|
||||
-- Use the method @{#AI_AIR_DISPATCHER.SetSquadronFuelThreshold}() to set the **squadron fuel treshold** of the aircraft for all squadrons.
|
||||
-- Use the method @{#AI_AIR_DISPATCHER.SetSquadronFuelThreshold}() to set the **squadron fuel threshold** of the aircraft for all squadrons.
|
||||
--
|
||||
-- ## 6. Other configuration options
|
||||
--
|
||||
@@ -786,17 +786,17 @@ do -- AI_AIR_DISPATCHER
|
||||
--
|
||||
-- Use the method @{#AI_AIR_DISPATCHER.SetDefaultGrouping}() to set the **default grouping** of spawned airplanes for all squadrons.
|
||||
--
|
||||
-- ## 10.5. Default RTB fuel treshold.
|
||||
-- ## 10.5. Default RTB fuel threshold.
|
||||
--
|
||||
-- When an airplane gets **out of fuel** to a certain %-tage, which is **15% (0.15)**, it will go RTB, and will be replaced with a new airplane when applicable.
|
||||
-- When an airplane gets **out of fuel** to a certain %, which is **15% (0.15)**, it will go RTB, and will be replaced with a new airplane when applicable.
|
||||
--
|
||||
-- Use the method @{#AI_AIR_DISPATCHER.SetDefaultFuelThreshold}() to set the **default fuel treshold** of spawned airplanes for all squadrons.
|
||||
-- Use the method @{#AI_AIR_DISPATCHER.SetDefaultFuelThreshold}() to set the **default fuel threshold** of spawned airplanes for all squadrons.
|
||||
--
|
||||
-- ## 10.6. Default RTB damage treshold.
|
||||
-- ## 10.6. Default RTB damage threshold.
|
||||
--
|
||||
-- When an airplane is **damaged** to a certain %-tage, which is **40% (0.40)**, it will go RTB, and will be replaced with a new airplane when applicable.
|
||||
-- When an airplane is **damaged** to a certain %, which is **40% (0.40)**, it will go RTB, and will be replaced with a new airplane when applicable.
|
||||
--
|
||||
-- Use the method @{#AI_AIR_DISPATCHER.SetDefaultDamageThreshold}() to set the **default damage treshold** of spawned airplanes for all squadrons.
|
||||
-- Use the method @{#AI_AIR_DISPATCHER.SetDefaultDamageThreshold}() to set the **default damage threshold** of spawned airplanes for all squadrons.
|
||||
--
|
||||
-- ## 10.7. Default settings for **patrol**.
|
||||
--
|
||||
@@ -829,7 +829,7 @@ do -- AI_AIR_DISPATCHER
|
||||
--
|
||||
-- In the mission editor, setup a group with task Refuelling. A tanker unit of the correct coalition will be automatically selected.
|
||||
-- Then, use the method @{#AI_AIR_DISPATCHER.SetDefaultTanker}() to set the tanker for the dispatcher.
|
||||
-- Use the method @{#AI_AIR_DISPATCHER.SetDefaultFuelThreshold}() to set the %-tage left in the defender airplane tanks when a refuel action is needed.
|
||||
-- Use the method @{#AI_AIR_DISPATCHER.SetDefaultFuelThreshold}() to set the % left in the defender airplane tanks when a refuel action is needed.
|
||||
--
|
||||
-- When the tanker specified is alive and in the air, the tanker will be used for refuelling.
|
||||
--
|
||||
@@ -843,7 +843,7 @@ do -- AI_AIR_DISPATCHER
|
||||
-- A2ADispatcher:SetSquadronCapInterval("Sochi", 2, 30, 600, 1 )
|
||||
-- A2ADispatcher:SetSquadronGci( "Sochi", 900, 1200 )
|
||||
--
|
||||
-- -- Set the default tanker for refuelling to "Tanker", when the default fuel treshold has reached 90% fuel left.
|
||||
-- -- Set the default tanker for refuelling to "Tanker", when the default fuel threshold has reached 90% fuel left.
|
||||
-- A2ADispatcher:SetDefaultFuelThreshold( 0.9 )
|
||||
-- A2ADispatcher:SetDefaultTanker( "Tanker" )
|
||||
--
|
||||
@@ -882,10 +882,7 @@ do -- AI_AIR_DISPATCHER
|
||||
-- As a result, the GCI and CAP will stop!
|
||||
-- However, the squadron will still stay alive. Any airplane that is airborne will continue its operations until all airborne airplanes
|
||||
-- of the squadron will be destroyed. This to keep consistency of air operations not to confuse the players.
|
||||
--
|
||||
--
|
||||
--
|
||||
--
|
||||
--
|
||||
-- @field #AI_AIR_DISPATCHER
|
||||
AI_AIR_DISPATCHER = {
|
||||
ClassName = "AI_AIR_DISPATCHER",
|
||||
@@ -914,10 +911,10 @@ do -- AI_AIR_DISPATCHER
|
||||
--- Enumerator for spawns at airbases
|
||||
-- @type AI_AIR_DISPATCHER.Takeoff
|
||||
-- @extends Wrapper.Group#GROUP.Takeoff
|
||||
|
||||
|
||||
--- @field #AI_AIR_DISPATCHER.Takeoff Takeoff
|
||||
AI_AIR_DISPATCHER.Takeoff = GROUP.Takeoff
|
||||
|
||||
|
||||
--- Defnes Landing location.
|
||||
-- @field #AI_AIR_DISPATCHER.Landing
|
||||
AI_AIR_DISPATCHER.Landing = {
|
||||
@@ -925,7 +922,7 @@ do -- AI_AIR_DISPATCHER
|
||||
AtRunway = 2,
|
||||
AtEngineShutdown = 3,
|
||||
}
|
||||
|
||||
|
||||
--- A defense queue item description
|
||||
-- @type AI_AIR_DISPATCHER.DefenseQueueItem
|
||||
-- @field Squadron
|
||||
@@ -936,7 +933,7 @@ do -- AI_AIR_DISPATCHER
|
||||
-- @field Functional.Detection#DETECTION_BASE AttackerDetection
|
||||
-- @field DefenderGrouping
|
||||
-- @field #string SquadronName The name of the squadron.
|
||||
|
||||
|
||||
--- Queue of planned defenses to be launched.
|
||||
-- This queue exists because defenses must be launched on FARPS, or in the air, or on an airbase, or on carriers.
|
||||
-- And some of these platforms have very limited amount of "launching" platforms.
|
||||
@@ -945,40 +942,39 @@ do -- AI_AIR_DISPATCHER
|
||||
-- This guarantees that launched defenders are also directly existing ...
|
||||
-- @type AI_AIR_DISPATCHER.DefenseQueue
|
||||
-- @list<#AI_AIR_DISPATCHER.DefenseQueueItem> DefenseQueueItem A list of all defenses being queued ...
|
||||
|
||||
|
||||
--- @field #AI_AIR_DISPATCHER.DefenseQueue DefenseQueue
|
||||
AI_AIR_DISPATCHER.DefenseQueue = {}
|
||||
|
||||
|
||||
|
||||
--- Defense approach types
|
||||
-- @type #AI_AIR_DISPATCHER.DefenseApproach
|
||||
AI_AIR_DISPATCHER.DefenseApproach = {
|
||||
Random = 1,
|
||||
Distance = 2,
|
||||
}
|
||||
|
||||
|
||||
--- AI_AIR_DISPATCHER constructor.
|
||||
-- This is defining the AIR DISPATCHER for one coaliton.
|
||||
-- This is defining the AIR DISPATCHER for one coalition.
|
||||
-- The Dispatcher works with a @{Functional.Detection#DETECTION_BASE} object that is taking of the detection of targets using the EWR units.
|
||||
-- The Detection object is polymorphic, depending on the type of detection object choosen, the detection will work differently.
|
||||
-- The Detection object is polymorphic, depending on the type of detection object chosen, the detection will work differently.
|
||||
-- @param #AI_AIR_DISPATCHER self
|
||||
-- @param Functional.Detection#DETECTION_BASE Detection The DETECTION object that will detects targets using the the Early Warning Radar network.
|
||||
-- @return #AI_AIR_DISPATCHER self
|
||||
-- @usage
|
||||
--
|
||||
-- -- Setup the Detection, using DETECTION_AREAS.
|
||||
-- -- First define the SET of GROUPs that are defining the EWR network.
|
||||
-- -- Here with prefixes DF CCCP AWACS, DF CCCP EWR.
|
||||
-- DetectionSetGroup = SET_GROUP:New()
|
||||
-- DetectionSetGroup:FilterPrefixes( { "DF CCCP AWACS", "DF CCCP EWR" } )
|
||||
-- DetectionSetGroup:FilterStart()
|
||||
--
|
||||
-- -- Define the DETECTION_AREAS, using the DetectionSetGroup, with a 30km grouping radius.
|
||||
-- Detection = DETECTION_AREAS:New( DetectionSetGroup, 30000 )
|
||||
--
|
||||
-- -- Now Setup the AIR dispatcher, and initialize it using the Detection object.
|
||||
-- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection ) --
|
||||
--
|
||||
--
|
||||
-- -- Setup the Detection, using DETECTION_AREAS.
|
||||
-- -- First define the SET of GROUPs that are defining the EWR network.
|
||||
-- -- Here with prefixes DF CCCP AWACS, DF CCCP EWR.
|
||||
-- DetectionSetGroup = SET_GROUP:New()
|
||||
-- DetectionSetGroup:FilterPrefixes( { "DF CCCP AWACS", "DF CCCP EWR" } )
|
||||
-- DetectionSetGroup:FilterStart()
|
||||
--
|
||||
-- -- Define the DETECTION_AREAS, using the DetectionSetGroup, with a 30km grouping radius.
|
||||
-- Detection = DETECTION_AREAS:New( DetectionSetGroup, 30000 )
|
||||
--
|
||||
-- -- Now Setup the AIR dispatcher, and initialize it using the Detection object.
|
||||
-- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection )
|
||||
--
|
||||
function AI_AIR_DISPATCHER:New( Detection )
|
||||
|
||||
-- Inherits from DETECTION_MANAGER
|
||||
@@ -1435,17 +1431,17 @@ do -- AI_AIR_DISPATCHER
|
||||
end
|
||||
|
||||
|
||||
--- Set the default damage treshold when defenders will RTB.
|
||||
-- The default damage treshold is by default set to 40%, which means that when the airplane is 40% damaged, it will go RTB.
|
||||
--- Set the default damage threshold when defenders will RTB.
|
||||
-- The default damage threshold is by default set to 40%, which means that when the airplane is 40% damaged, it will go RTB.
|
||||
-- @param #AI_AIR_DISPATCHER self
|
||||
-- @param #number DamageThreshold A decimal number between 0 and 1, that expresses the %-tage of the damage treshold before going RTB.
|
||||
-- @param #number DamageThreshold A decimal number between 0 and 1, that expresses the % of the damage threshold before going RTB.
|
||||
-- @return #AI_AIR_DISPATCHER
|
||||
-- @usage
|
||||
--
|
||||
-- -- Now Setup the AIR dispatcher, and initialize it using the Detection object.
|
||||
-- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection )
|
||||
--
|
||||
-- -- Now Setup the default damage treshold.
|
||||
-- -- Now Setup the default damage threshold.
|
||||
-- AIRDispatcher:SetDefaultDamageThreshold( 0.90 ) -- Go RTB when the airplane 90% damaged.
|
||||
--
|
||||
function AI_AIR_DISPATCHER:SetDefaultDamageThreshold( DamageThreshold )
|
||||
@@ -1989,7 +1985,7 @@ do -- AI_AIR_DISPATCHER
|
||||
|
||||
--- Defines the default amount of extra planes that will take-off as part of the defense system.
|
||||
-- @param #AI_AIR_DISPATCHER self
|
||||
-- @param #number Overhead The %-tage of Units that dispatching command will allocate to intercept in surplus of detected amount of units.
|
||||
-- @param #number Overhead The % of Units that dispatching command will allocate to intercept in surplus of detected amount of units.
|
||||
-- The default overhead is 1, so equal balance. The @{#AI_AIR_DISPATCHER.SetOverhead}() method can be used to tweak the defense strength,
|
||||
-- taking into account the plane types of the squadron. For example, a MIG-31 with full long-distance AIR missiles payload, may still be less effective than a F-15C with short missiles...
|
||||
-- So in this case, one may want to use the Overhead method to allocate more defending planes as the amount of detected attacking planes.
|
||||
@@ -2028,7 +2024,7 @@ do -- AI_AIR_DISPATCHER
|
||||
--- Defines the amount of extra planes that will take-off as part of the defense system.
|
||||
-- @param #AI_AIR_DISPATCHER self
|
||||
-- @param #string SquadronName The name of the squadron.
|
||||
-- @param #number Overhead The %-tage of Units that dispatching command will allocate to intercept in surplus of detected amount of units.
|
||||
-- @param #number Overhead The % of Units that dispatching command will allocate to intercept in surplus of detected amount of units.
|
||||
-- The default overhead is 1, so equal balance. The @{#AI_AIR_DISPATCHER.SetOverhead}() method can be used to tweak the defense strength,
|
||||
-- taking into account the plane types of the squadron. For example, a MIG-31 with full long-distance AIR missiles payload, may still be less effective than a F-15C with short missiles...
|
||||
-- So in this case, one may want to use the Overhead method to allocate more defending planes as the amount of detected attacking planes.
|
||||
@@ -2068,7 +2064,7 @@ do -- AI_AIR_DISPATCHER
|
||||
--- Gets the overhead of planes as part of the defense system, in comparison with the attackers.
|
||||
-- @param #AI_AIR_DISPATCHER self
|
||||
-- @param #string SquadronName The name of the squadron.
|
||||
-- @return #number The %-tage of Units that dispatching command will allocate to intercept in surplus of detected amount of units.
|
||||
-- @return #number The % of Units that dispatching command will allocate to intercept in surplus of detected amount of units.
|
||||
-- The default overhead is 1, so equal balance. The @{#AI_AIR_DISPATCHER.SetOverhead}() method can be used to tweak the defense strength,
|
||||
-- taking into account the plane types of the squadron. For example, a MIG-31 with full long-distance AIR missiles payload, may still be less effective than a F-15C with short missiles...
|
||||
-- So in this case, one may want to use the Overhead method to allocate more defending planes as the amount of detected attacking planes.
|
||||
@@ -2674,17 +2670,17 @@ do -- AI_AIR_DISPATCHER
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set the default fuel treshold when defenders will RTB or Refuel in the air.
|
||||
-- The fuel treshold is by default set to 15%, which means that an airplane will stay in the air until 15% of its fuel has been consumed.
|
||||
--- Set the default fuel threshold when defenders will RTB or Refuel in the air.
|
||||
-- The fuel threshold is by default set to 15%, which means that an airplane will stay in the air until 15% of its fuel has been consumed.
|
||||
-- @param #AI_AIR_DISPATCHER self
|
||||
-- @param #number FuelThreshold A decimal number between 0 and 1, that expresses the %-tage of the treshold of fuel remaining in the tank when the plane will go RTB or Refuel.
|
||||
-- @param #number FuelThreshold A decimal number between 0 and 1, that expresses the % of the threshold of fuel remaining in the tank when the plane will go RTB or Refuel.
|
||||
-- @return #AI_AIR_DISPATCHER
|
||||
-- @usage
|
||||
--
|
||||
-- -- Now Setup the AIR dispatcher, and initialize it using the Detection object.
|
||||
-- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection )
|
||||
--
|
||||
-- -- Now Setup the default fuel treshold.
|
||||
-- -- Now Setup the default fuel threshold.
|
||||
-- AIRDispatcher:SetDefaultFuelThreshold( 0.30 ) -- Go RTB when only 30% of fuel remaining in the tank.
|
||||
--
|
||||
function AI_AIR_DISPATCHER:SetDefaultFuelThreshold( FuelThreshold )
|
||||
@@ -2695,18 +2691,18 @@ do -- AI_AIR_DISPATCHER
|
||||
end
|
||||
|
||||
|
||||
--- Set the fuel treshold for the squadron when defenders will RTB or Refuel in the air.
|
||||
-- The fuel treshold is by default set to 15%, which means that an airplane will stay in the air until 15% of its fuel has been consumed.
|
||||
--- Set the fuel threshold for the squadron when defenders will RTB or Refuel in the air.
|
||||
-- The fuel threshold is by default set to 15%, which means that an airplane will stay in the air until 15% of its fuel has been consumed.
|
||||
-- @param #AI_AIR_DISPATCHER self
|
||||
-- @param #string SquadronName The name of the squadron.
|
||||
-- @param #number FuelThreshold A decimal number between 0 and 1, that expresses the %-tage of the treshold of fuel remaining in the tank when the plane will go RTB or Refuel.
|
||||
-- @param #number FuelThreshold A decimal number between 0 and 1, that expresses the % of the threshold of fuel remaining in the tank when the plane will go RTB or Refuel.
|
||||
-- @return #AI_AIR_DISPATCHER
|
||||
-- @usage
|
||||
--
|
||||
-- -- Now Setup the AIR dispatcher, and initialize it using the Detection object.
|
||||
-- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection )
|
||||
--
|
||||
-- -- Now Setup the default fuel treshold.
|
||||
-- -- Now Setup the default fuel threshold.
|
||||
-- AIRDispatcher:SetSquadronRefuelThreshold( "SquadronName", 0.30 ) -- Go RTB when only 30% of fuel remaining in the tank.
|
||||
--
|
||||
function AI_AIR_DISPATCHER:SetSquadronFuelThreshold( SquadronName, FuelThreshold )
|
||||
@@ -2726,7 +2722,7 @@ do -- AI_AIR_DISPATCHER
|
||||
-- -- Now Setup the AIR dispatcher, and initialize it using the Detection object.
|
||||
-- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection )
|
||||
--
|
||||
-- -- Now Setup the default fuel treshold.
|
||||
-- -- Now Setup the default fuel threshold.
|
||||
-- AIRDispatcher:SetDefaultFuelThreshold( 0.30 ) -- Go RTB when only 30% of fuel remaining in the tank.
|
||||
--
|
||||
-- -- Now Setup the default tanker.
|
||||
@@ -2749,7 +2745,7 @@ do -- AI_AIR_DISPATCHER
|
||||
-- -- Now Setup the AIR dispatcher, and initialize it using the Detection object.
|
||||
-- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection )
|
||||
--
|
||||
-- -- Now Setup the squadron fuel treshold.
|
||||
-- -- Now Setup the squadron fuel threshold.
|
||||
-- AIRDispatcher:SetSquadronRefuelThreshold( "SquadronName", 0.30 ) -- Go RTB when only 30% of fuel remaining in the tank.
|
||||
--
|
||||
-- -- Now Setup the squadron tanker.
|
||||
@@ -2847,7 +2843,7 @@ do -- AI_AIR_DISPATCHER
|
||||
-- @param #AI_AIR_DISPATCHER self
|
||||
function AI_AIR_DISPATCHER:CountDefendersEngaged( AttackerDetection, AttackerCount )
|
||||
|
||||
-- First, count the active AIGroups Units, targetting the DetectedSet
|
||||
-- First, count the active AIGroups Units, targeting the DetectedSet
|
||||
local DefendersEngaged = 0
|
||||
local DefendersTotal = 0
|
||||
|
||||
|
||||
@@ -42,8 +42,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.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
@@ -418,6 +418,7 @@ end
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @param Core.Set#SET_UNIT AttackSetUnit Unit set to be attacked.
|
||||
function AI_AIR_ENGAGE:onafterEngageRoute( DefenderGroup, From, Event, To, AttackSetUnit )
|
||||
self:I( { DefenderGroup, From, Event, To, AttackSetUnit } )
|
||||
|
||||
@@ -425,7 +426,7 @@ function AI_AIR_ENGAGE:onafterEngageRoute( DefenderGroup, From, Event, To, Attac
|
||||
|
||||
self.AttackSetUnit = AttackSetUnit -- Kept in memory in case of resume from refuel in air!
|
||||
|
||||
local AttackCount = AttackSetUnit:Count()
|
||||
local AttackCount = AttackSetUnit:CountAlive()
|
||||
|
||||
if AttackCount > 0 then
|
||||
|
||||
@@ -510,6 +511,7 @@ end
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
-- @param Core.Set#SET_UNIT AttackSetUnit Set of units to be attacked.
|
||||
function AI_AIR_ENGAGE:onafterEngage( DefenderGroup, From, Event, To, AttackSetUnit )
|
||||
self:F( { DefenderGroup, From, Event, To, AttackSetUnit} )
|
||||
|
||||
@@ -517,8 +519,8 @@ function AI_AIR_ENGAGE:onafterEngage( DefenderGroup, From, Event, To, AttackSetU
|
||||
|
||||
self.AttackSetUnit = AttackSetUnit -- Kept in memory in case of resume from refuel in air!
|
||||
|
||||
local AttackCount = AttackSetUnit:Count()
|
||||
self:I({AttackCount = AttackCount})
|
||||
local AttackCount = AttackSetUnit:CountAlive()
|
||||
self:T({AttackCount = AttackCount})
|
||||
|
||||
if AttackCount > 0 then
|
||||
|
||||
@@ -531,6 +533,10 @@ function AI_AIR_ENGAGE:onafterEngage( DefenderGroup, From, Event, To, AttackSetU
|
||||
DefenderCoord:SetY( EngageAltitude ) -- Ground targets don't have an altitude.
|
||||
|
||||
local TargetCoord = AttackSetUnit:GetFirst():GetPointVec3()
|
||||
if not TargetCoord then
|
||||
self:Return()
|
||||
return
|
||||
end
|
||||
TargetCoord:SetY( EngageAltitude ) -- Ground targets don't have an altitude.
|
||||
|
||||
local TargetDistance = DefenderCoord:Get2DDistance( TargetCoord )
|
||||
|
||||
@@ -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.
|
||||
--
|
||||
-- 
|
||||
--
|
||||
@@ -70,7 +70,7 @@
|
||||
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detected}**: The AI has detected new targets.
|
||||
-- * **@{#AI_AIR_PATROL.Destroy}**: The AI has destroyed a bogey @{Wrapper.Unit}.
|
||||
-- * **@{#AI_AIR_PATROL.Destroyed}**: The AI has destroyed all bogeys @{Wrapper.Unit}s assigned in the CAS task.
|
||||
-- * **Status** ( Group ): The AI is checking status (fuel and damage). When the 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
|
||||
--
|
||||
|
||||
@@ -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)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
|
||||
@@ -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
|
||||
--
|
||||
@@ -428,8 +428,10 @@ function AI_CAP_ZONE:onafterEngage( Controllable, From, Event, To )
|
||||
--- Calculate the current route point.
|
||||
local CurrentVec2 = self.Controllable:GetVec2()
|
||||
|
||||
--TODO: Create GetAltitude function for GROUP, and delete GetUnit(1).
|
||||
local CurrentAltitude = self.Controllable:GetUnit(1):GetAltitude()
|
||||
if not CurrentVec2 then return self end
|
||||
|
||||
--DONE: Create GetAltitude function for GROUP, and delete GetUnit(1).
|
||||
local CurrentAltitude = self.Controllable:GetAltitude()
|
||||
local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y )
|
||||
local ToEngageZoneSpeed = self.PatrolMaxSpeed
|
||||
local CurrentRoutePoint = CurrentPointVec3:WaypointAir(
|
||||
|
||||
@@ -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.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
@@ -459,8 +459,8 @@ function AI_CAS_ZONE:onafterEngage( Controllable, From, Event, To,
|
||||
--- Calculate the current route point.
|
||||
local CurrentVec2 = self.Controllable:GetVec2()
|
||||
|
||||
--TODO: Create GetAltitude function for GROUP, and delete GetUnit(1).
|
||||
local CurrentAltitude = self.Controllable:GetUnit(1):GetAltitude()
|
||||
--DONE: Create GetAltitude function for GROUP, and delete GetUnit(1).
|
||||
local CurrentAltitude = self.Controllable:GetAltitude()
|
||||
local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y )
|
||||
local ToEngageZoneSpeed = self.PatrolMaxSpeed
|
||||
local CurrentRoutePoint = CurrentPointVec3:WaypointAir(
|
||||
@@ -520,7 +520,7 @@ function AI_CAS_ZONE:onafterEngage( Controllable, From, Event, To,
|
||||
|
||||
self:SetRefreshTimeInterval( 2 )
|
||||
self:SetDetectionActivated()
|
||||
self:__Target( -2 ) -- Start 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
|
||||
@@ -580,4 +582,3 @@ function AI_CARGO:onafterDeployed( Carrier, From, Event, To, DeployZone, Defend
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
--- **AI** -- (R2.4) - Models the intelligent transportation of infantry and other cargo.
|
||||
--- **AI** - Models the intelligent transportation of cargo using ground vehicles.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
@@ -98,7 +98,8 @@ function AI_CARGO_APC:New( APC, CargoSet, CombatRadius )
|
||||
self:AddTransition( "*", "Guard", "Unloaded" )
|
||||
self:AddTransition( "*", "Home", "*" )
|
||||
self:AddTransition( "*", "Reload", "Boarding" )
|
||||
|
||||
self:AddTransition( "*", "Deployed", "*" )
|
||||
self:AddTransition( "*", "PickedUp", "*" )
|
||||
self:AddTransition( "*", "Destroyed", "Destroyed" )
|
||||
|
||||
self:SetCombatRadius( CombatRadius )
|
||||
@@ -157,6 +158,45 @@ function AI_CARGO_APC:SetCarrier( CargoCarrier )
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set whether or not the carrier will use roads to *pickup* and *deploy* the cargo.
|
||||
-- @param #AI_CARGO_APC self
|
||||
-- @param #boolean Offroad If true, carrier will not use roads. If `nil` or `false` the carrier will use roads when available.
|
||||
-- @param #number Formation Offroad formation used. Default is `ENUMS.Formation.Vehicle.Offroad`.
|
||||
-- @return #AI_CARGO_APC self
|
||||
function AI_CARGO_APC:SetOffRoad(Offroad, Formation)
|
||||
|
||||
self:SetPickupOffRoad(Offroad, Formation)
|
||||
self:SetDeployOffRoad(Offroad, Formation)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set whether the carrier will *not* use roads to *pickup* the cargo.
|
||||
-- @param #AI_CARGO_APC self
|
||||
-- @param #boolean Offroad If true, carrier will not use roads.
|
||||
-- @param #number Formation Offroad formation used. Default is `ENUMS.Formation.Vehicle.Offroad`.
|
||||
-- @return #AI_CARGO_APC self
|
||||
function AI_CARGO_APC:SetPickupOffRoad(Offroad, Formation)
|
||||
|
||||
self.pickupOffroad=Offroad
|
||||
self.pickupFormation=Formation or ENUMS.Formation.Vehicle.OffRoad
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set whether the carrier will *not* use roads to *deploy* the cargo.
|
||||
-- @param #AI_CARGO_APC self
|
||||
-- @param #boolean Offroad If true, carrier will not use roads.
|
||||
-- @param #number Formation Offroad formation used. Default is `ENUMS.Formation.Vehicle.Offroad`.
|
||||
-- @return #AI_CARGO_APC self
|
||||
function AI_CARGO_APC:SetDeployOffRoad(Offroad, Formation)
|
||||
|
||||
self.deployOffroad=Offroad
|
||||
self.deployFormation=Formation or ENUMS.Formation.Vehicle.OffRoad
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Find a free Carrier within a radius.
|
||||
-- @param #AI_CARGO_APC self
|
||||
@@ -350,10 +390,13 @@ function AI_CARGO_APC:onafterFollow( APC, From, Event, To )
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- @param #AI_CARGO_APC
|
||||
-- @param Wrapper.Group#GROUP APC
|
||||
function AI_CARGO_APC._Pickup( APC, self, Coordinate, Speed, PickupZone )
|
||||
--- Pickup task function. Triggers Load event.
|
||||
-- @param Wrapper.Group#GROUP APC The cargo carrier group.
|
||||
-- @param #AI_CARGO_APC sel `AI_CARGO_APC` class.
|
||||
-- @param Core.Point#COORDINATE Coordinate. The coordinate (not used).
|
||||
-- @param #number Speed Speed (not used).
|
||||
-- @param Core.Zone#ZONE PickupZone Pickup zone.
|
||||
function AI_CARGO_APC._Pickup(APC, self, Coordinate, Speed, PickupZone)
|
||||
|
||||
APC:F( { "AI_CARGO_APC._Pickup:", APC:GetName() } )
|
||||
|
||||
@@ -362,8 +405,12 @@ function AI_CARGO_APC._Pickup( APC, self, Coordinate, Speed, PickupZone )
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function AI_CARGO_APC._Deploy( APC, self, Coordinate, DeployZone )
|
||||
--- Deploy task function. Triggers Unload event.
|
||||
-- @param Wrapper.Group#GROUP APC The cargo carrier group.
|
||||
-- @param #AI_CARGO_APC self `AI_CARGO_APC` class.
|
||||
-- @param Core.Point#COORDINATE Coordinate. The coordinate (not used).
|
||||
-- @param Core.Zone#ZONE DeployZone Deploy zone.
|
||||
function AI_CARGO_APC._Deploy(APC, self, Coordinate, DeployZone)
|
||||
|
||||
APC:F( { "AI_CARGO_APC._Deploy:", APC } )
|
||||
|
||||
@@ -392,12 +439,20 @@ function AI_CARGO_APC:onafterPickup( APC, From, Event, To, Coordinate, Speed, He
|
||||
self.RoutePickup = true
|
||||
|
||||
local _speed=Speed or APC:GetSpeedMax()*0.5
|
||||
|
||||
-- Route on road.
|
||||
local Waypoints = {}
|
||||
|
||||
if self.pickupOffroad then
|
||||
Waypoints[1]=APC:GetCoordinate():WaypointGround(Speed, self.pickupFormation)
|
||||
Waypoints[2]=Coordinate:WaypointGround(_speed, self.pickupFormation, DCSTasks)
|
||||
else
|
||||
Waypoints=APC:TaskGroundOnRoad(Coordinate, _speed, ENUMS.Formation.Vehicle.OffRoad, true)
|
||||
end
|
||||
|
||||
local Waypoints = APC:TaskGroundOnRoad( Coordinate, _speed, "Line abreast", true )
|
||||
|
||||
local TaskFunction = APC:TaskFunction( "AI_CARGO_APC._Pickup", self, Coordinate, Speed, PickupZone )
|
||||
|
||||
self:F({Waypoints = Waypoints})
|
||||
|
||||
local Waypoint = Waypoints[#Waypoints]
|
||||
APC:SetTaskWaypoint( Waypoint, TaskFunction ) -- Set for the given Route at Waypoint 2 the TaskRouteToZone.
|
||||
|
||||
@@ -428,18 +483,34 @@ function AI_CARGO_APC:onafterDeploy( APC, From, Event, To, Coordinate, Speed, He
|
||||
|
||||
self.RouteDeploy = true
|
||||
|
||||
local _speed=Speed or APC:GetSpeedMax()*0.5
|
||||
|
||||
local Waypoints = APC:TaskGroundOnRoad( Coordinate, _speed, "Line abreast", true )
|
||||
-- Set speed in km/h.
|
||||
local speedmax=APC:GetSpeedMax()
|
||||
local _speed=Speed or speedmax*0.5
|
||||
_speed=math.min(_speed, speedmax)
|
||||
|
||||
-- Route on road.
|
||||
local Waypoints = {}
|
||||
|
||||
if self.deployOffroad then
|
||||
Waypoints[1]=APC:GetCoordinate():WaypointGround(Speed, self.deployFormation)
|
||||
Waypoints[2]=Coordinate:WaypointGround(_speed, self.deployFormation, DCSTasks)
|
||||
else
|
||||
Waypoints=APC:TaskGroundOnRoad(Coordinate, _speed, ENUMS.Formation.Vehicle.OffRoad, true)
|
||||
end
|
||||
|
||||
-- Task function
|
||||
local TaskFunction = APC:TaskFunction( "AI_CARGO_APC._Deploy", self, Coordinate, DeployZone )
|
||||
|
||||
self:F({Waypoints = Waypoints})
|
||||
-- Last waypoint
|
||||
local Waypoint = Waypoints[#Waypoints]
|
||||
APC:SetTaskWaypoint( Waypoint, TaskFunction ) -- Set for the given Route at Waypoint 2 the TaskRouteToZone.
|
||||
|
||||
-- Set task function
|
||||
APC:SetTaskWaypoint(Waypoint, TaskFunction) -- Set for the given Route at Waypoint 2 the TaskRouteToZone.
|
||||
|
||||
-- Route group
|
||||
APC:Route( Waypoints, 1 ) -- Move after a random seconds to the Route. See the Route method for details.
|
||||
|
||||
-- Call parent function.
|
||||
self:GetParent( self, AI_CARGO_APC ).onafterDeploy( self, APC, From, Event, To, Coordinate, Speed, Height, DeployZone )
|
||||
|
||||
end
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
--- **AI** -- (R2.4) - Models the intelligent transportation of infantry (cargo).
|
||||
--- **AI** - Models the intelligent transportation of cargo using airplanes.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
@@ -408,9 +408,6 @@ function AI_CARGO_AIRPLANE:onafterUnload( Airplane, From, Event, To, DeployZone
|
||||
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
--- Route the airplane from one airport or it's current position to another airbase.
|
||||
-- @param #AI_CARGO_AIRPLANE self
|
||||
-- @param Wrapper.Group#GROUP Airplane Airplane group to be routed.
|
||||
@@ -438,14 +435,10 @@ function AI_CARGO_AIRPLANE:Route( Airplane, Airbase, Speed, Height, Uncontrolled
|
||||
|
||||
-- To point.
|
||||
local AirbasePointVec2 = Airbase:GetPointVec2()
|
||||
local ToWaypoint = AirbasePointVec2:WaypointAir(
|
||||
POINT_VEC3.RoutePointAltType.BARO,
|
||||
"Land",
|
||||
"Landing",
|
||||
Speed or Airplane:GetSpeedMax()*0.8
|
||||
)
|
||||
ToWaypoint["airdromeId"] = Airbase:GetID()
|
||||
ToWaypoint["speed_locked"] = true
|
||||
local ToWaypoint = AirbasePointVec2:WaypointAir(POINT_VEC3.RoutePointAltType.BARO, "Land", "Landing", Speed or Airplane:GetSpeedMax()*0.8, true, Airbase)
|
||||
|
||||
--ToWaypoint["airdromeId"] = Airbase:GetID()
|
||||
--ToWaypoint["speed_locked"] = true
|
||||
|
||||
|
||||
-- If self.Airbase~=nil then group is currently at an airbase, where it should be respawned.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
--- **AI** -- (R2.4) - Models the intelligent transportation of infantry and other cargo.
|
||||
--- **AI** - Models the intelligent transportation of infantry and other cargo.
|
||||
--
|
||||
-- ## Features:
|
||||
--
|
||||
@@ -1104,7 +1104,7 @@ function AI_CARGO_DISPATCHER:onafterMonitor()
|
||||
-- The Pickup sequence ...
|
||||
-- Check if this Carrier need to go and Pickup something...
|
||||
-- So, if the cargo bay is not full yet with cargo to be loaded ...
|
||||
self:I( { Carrier = CarrierGroupName, IsRelocating = AI_Cargo:IsRelocating(), IsTransporting = AI_Cargo:IsTransporting() } )
|
||||
self:T( { Carrier = CarrierGroupName, IsRelocating = AI_Cargo:IsRelocating(), IsTransporting = AI_Cargo:IsTransporting() } )
|
||||
if AI_Cargo:IsRelocating() == false and AI_Cargo:IsTransporting() == false then
|
||||
-- ok, so there is a free Carrier
|
||||
-- now find the first cargo that is Unloaded
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
--- **AI** -- (2.4) - Models the intelligent transportation of infantry and other cargo using APCs.
|
||||
--- **AI** - Models the intelligent transportation of infantry and other cargo using APCs.
|
||||
--
|
||||
-- ## Features:
|
||||
--
|
||||
@@ -181,25 +181,36 @@ function AI_CARGO_DISPATCHER_APC:New( APCSet, CargoSet, PickupZoneSet, DeployZon
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- AI cargo
|
||||
-- @param #AI_CARGO_DISPATCHER_APC self
|
||||
-- @param Wrapper.Group#GROUP APC The APC carrier.
|
||||
-- @param Core.Set#SET_CARGO CargoSet Cargo set.
|
||||
-- @return AI.AI_Cargo_APC#AI_CARGO_DISPATCHER_APC AI cargo APC object.
|
||||
function AI_CARGO_DISPATCHER_APC:AICargo( APC, CargoSet )
|
||||
|
||||
return AI_CARGO_APC:New( APC, CargoSet, self.CombatRadius )
|
||||
local aicargoapc=AI_CARGO_APC:New(APC, CargoSet, self.CombatRadius)
|
||||
|
||||
aicargoapc:SetDeployOffRoad(self.deployOffroad, self.deployFormation)
|
||||
aicargoapc:SetPickupOffRoad(self.pickupOffroad, self.pickupFormation)
|
||||
|
||||
return aicargoapc
|
||||
end
|
||||
|
||||
--- Enable/Disable unboarding of cargo (infantry) when enemies are nearby (to help defend the carrier).
|
||||
-- This is only valid for APCs and trucks etc, thus ground vehicles.
|
||||
-- @param #AI_CARGO_DISPATCHER_APC self
|
||||
-- @param #number CombatRadius Provide the combat radius to defend the carrier by unboarding the cargo when enemies are nearby.
|
||||
-- When the combat radius is 0, no defense will happen of the carrier.
|
||||
-- When the combat radius is 0 (default), no defense will happen of the carrier.
|
||||
-- When the combat radius is not provided, no defense will happen!
|
||||
-- @return #AI_CARGO_DISPATCHER_APC
|
||||
-- @usage
|
||||
--
|
||||
-- -- Disembark the infantry when the carrier is under attack.
|
||||
-- AICargoDispatcher:SetCombatRadius( true )
|
||||
-- AICargoDispatcher:SetCombatRadius( 500 )
|
||||
--
|
||||
-- -- Keep the cargo in the carrier when the carrier is under attack.
|
||||
-- AICargoDispatcher:SetCombatRadius( false )
|
||||
-- AICargoDispatcher:SetCombatRadius( 0 )
|
||||
function AI_CARGO_DISPATCHER_APC:SetCombatRadius( CombatRadius )
|
||||
|
||||
self.CombatRadius = CombatRadius or 0
|
||||
@@ -207,3 +218,41 @@ function AI_CARGO_DISPATCHER_APC:SetCombatRadius( CombatRadius )
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set whether the carrier will *not* use roads to *pickup* and *deploy* the cargo.
|
||||
-- @param #AI_CARGO_DISPATCHER_APC self
|
||||
-- @param #boolean Offroad If true, carrier will not use roads.
|
||||
-- @param #number Formation Offroad formation used. Default is `ENUMS.Formation.Vehicle.Offroad`.
|
||||
-- @return #AI_CARGO_DISPATCHER_APC self
|
||||
function AI_CARGO_DISPATCHER_APC:SetOffRoad(Offroad, Formation)
|
||||
|
||||
self:SetPickupOffRoad(Offroad, Formation)
|
||||
self:SetDeployOffRoad(Offroad, Formation)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set whether the carrier will *not* use roads to *pickup* the cargo.
|
||||
-- @param #AI_CARGO_DISPATCHER_APC self
|
||||
-- @param #boolean Offroad If true, carrier will not use roads.
|
||||
-- @param #number Formation Offroad formation used. Default is `ENUMS.Formation.Vehicle.Offroad`.
|
||||
-- @return #AI_CARGO_DISPATCHER_APC self
|
||||
function AI_CARGO_DISPATCHER_APC:SetPickupOffRoad(Offroad, Formation)
|
||||
|
||||
self.pickupOffroad=Offroad
|
||||
self.pickupFormation=Formation or ENUMS.Formation.Vehicle.OffRoad
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set whether the carrier will *not* use roads to *deploy* the cargo.
|
||||
-- @param #AI_CARGO_DISPATCHER_APC self
|
||||
-- @param #boolean Offroad If true, carrier will not use roads.
|
||||
-- @param #number Formation Offroad formation used. Default is `ENUMS.Formation.Vehicle.Offroad`.
|
||||
-- @return #AI_CARGO_DISPATCHER_APC self
|
||||
function AI_CARGO_DISPATCHER_APC:SetDeployOffRoad(Offroad, Formation)
|
||||
|
||||
self.deployOffroad=Offroad
|
||||
self.deployFormation=Formation or ENUMS.Formation.Vehicle.OffRoad
|
||||
|
||||
return self
|
||||
end
|
||||
@@ -174,8 +174,8 @@ function AI_CARGO_DISPATCHER_HELICOPTER:New( HelicopterSet, CargoSet, PickupZone
|
||||
self:SetPickupSpeed( 350, 150 )
|
||||
self:SetDeploySpeed( 350, 150 )
|
||||
|
||||
self:SetPickupRadius( 0, 0 )
|
||||
self:SetDeployRadius( 0, 0 )
|
||||
self:SetPickupRadius( 40, 12 )
|
||||
self:SetDeployRadius( 40, 12 )
|
||||
|
||||
self:SetPickupHeight( 500, 200 )
|
||||
self:SetDeployHeight( 500, 200 )
|
||||
@@ -186,6 +186,9 @@ end
|
||||
|
||||
function AI_CARGO_DISPATCHER_HELICOPTER:AICargo( Helicopter, CargoSet )
|
||||
|
||||
return AI_CARGO_HELICOPTER:New( Helicopter, CargoSet )
|
||||
local dispatcher = AI_CARGO_HELICOPTER:New( Helicopter, CargoSet )
|
||||
dispatcher:SetLandingSpeedAndHeight(27, 6)
|
||||
return dispatcher
|
||||
|
||||
end
|
||||
|
||||
|
||||
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_Dispatcher.JPG
|
||||
|
||||
--- @type AI_CARGO_DISPATCHER_SHIP
|
||||
-- @extends AI.AI_Cargo_Dispatcher#AI_CARGO_DISPATCHER
|
||||
|
||||
|
||||
--- A dynamic cargo transportation capability for AI groups.
|
||||
--
|
||||
-- Naval vessels can be mobilized to semi-intelligently transport cargo within the simulation.
|
||||
--
|
||||
-- The AI_CARGO_DISPATCHER_SHIP module is derived from the AI_CARGO_DISPATCHER module.
|
||||
--
|
||||
-- ## Note! In order to fully understand the mechanisms of the AI_CARGO_DISPATCHER_SHIP class, it is recommended that you first consult and READ the documentation of the @{AI.AI_Cargo_Dispatcher} module!!!
|
||||
--
|
||||
-- This will be particularly helpful in order to determine how to **Tailor the different cargo handling events**.
|
||||
--
|
||||
-- The AI_CARGO_DISPATCHER_SHIP class uses the @{Cargo.Cargo} capabilities within the MOOSE framwork.
|
||||
-- Also ensure that you fully understand how to declare and setup Cargo objects within the MOOSE framework before using this class.
|
||||
-- CARGO derived objects must generally be declared within the mission to make the AI_CARGO_DISPATCHER_SHIP object recognize the cargo.
|
||||
--
|
||||
--
|
||||
-- # 1) AI_CARGO_DISPATCHER_SHIP constructor.
|
||||
--
|
||||
-- * @{AI_CARGO_DISPATCHER_SHIP.New}(): Creates a new AI_CARGO_DISPATCHER_SHIP object.
|
||||
--
|
||||
-- ---
|
||||
--
|
||||
-- # 2) AI_CARGO_DISPATCHER_SHIP is a Finite State Machine.
|
||||
--
|
||||
-- This section must be read as follows... Each of the rows indicate a state transition, triggered through an event, and with an ending state of the event was executed.
|
||||
-- The first column is the **From** state, the second column the **Event**, and the third column the **To** state.
|
||||
--
|
||||
-- So, each of the rows have the following structure.
|
||||
--
|
||||
-- * **From** => **Event** => **To**
|
||||
--
|
||||
-- Important to know is that an event can only be executed if the **current state** is the **From** state.
|
||||
-- This, when an **Event** that is being triggered has a **From** state that is equal to the **Current** state of the state machine, the event will be executed,
|
||||
-- and the resulting state will be the **To** state.
|
||||
--
|
||||
-- These are the different possible state transitions of this state machine implementation:
|
||||
--
|
||||
-- * Idle => Start => Monitoring
|
||||
-- * Monitoring => Monitor => Monitoring
|
||||
-- * Monitoring => Stop => Idle
|
||||
--
|
||||
-- * Monitoring => Pickup => Monitoring
|
||||
-- * Monitoring => Load => Monitoring
|
||||
-- * Monitoring => Loading => Monitoring
|
||||
-- * Monitoring => Loaded => Monitoring
|
||||
-- * Monitoring => PickedUp => Monitoring
|
||||
-- * Monitoring => Deploy => Monitoring
|
||||
-- * Monitoring => Unload => Monitoring
|
||||
-- * Monitoring => Unloaded => Monitoring
|
||||
-- * Monitoring => Deployed => Monitoring
|
||||
-- * Monitoring => Home => Monitoring
|
||||
--
|
||||
--
|
||||
-- ## 2.1) AI_CARGO_DISPATCHER States.
|
||||
--
|
||||
-- * **Monitoring**: The process is dispatching.
|
||||
-- * **Idle**: The process is idle.
|
||||
--
|
||||
-- ## 2.2) AI_CARGO_DISPATCHER Events.
|
||||
--
|
||||
-- * **Start**: Start the transport process.
|
||||
-- * **Stop**: Stop the transport process.
|
||||
-- * **Monitor**: Monitor and take action.
|
||||
--
|
||||
-- * **Pickup**: Pickup cargo.
|
||||
-- * **Load**: Load the cargo.
|
||||
-- * **Loading**: The dispatcher is coordinating the loading of a cargo.
|
||||
-- * **Loaded**: Flag that the cargo is loaded.
|
||||
-- * **PickedUp**: The dispatcher has loaded all requested cargo into the CarrierGroup.
|
||||
-- * **Deploy**: Deploy cargo to a location.
|
||||
-- * **Unload**: Unload the cargo.
|
||||
-- * **Unloaded**: Flag that the cargo is unloaded.
|
||||
-- * **Deployed**: All cargo is unloaded from the carriers in the group.
|
||||
-- * **Home**: A Carrier is going home.
|
||||
--
|
||||
-- ## 2.3) Enhance your mission scripts with **Tailored** Event Handling!
|
||||
--
|
||||
-- Within your mission, you can capture these events when triggered, and tailor the events with your own code!
|
||||
-- Check out the @{AI.AI_Cargo_Dispatcher#AI_CARGO_DISPATCHER} class at chapter 3 for details on the different event handlers that are available and how to use them.
|
||||
--
|
||||
-- **There are a lot of templates available that allows you to quickly setup an event handler for a specific event type!**
|
||||
--
|
||||
-- ---
|
||||
--
|
||||
-- # 3) Set the pickup parameters.
|
||||
--
|
||||
-- Several parameters can be set to pickup cargo:
|
||||
--
|
||||
-- * @{#AI_CARGO_DISPATCHER_SHIP.SetPickupRadius}(): Sets or randomizes the pickup location for the Ship around the cargo coordinate in a radius defined an outer and optional inner radius.
|
||||
-- * @{#AI_CARGO_DISPATCHER_SHIP.SetPickupSpeed}(): Set the speed or randomizes the speed in km/h to pickup the cargo.
|
||||
--
|
||||
-- # 4) Set the deploy parameters.
|
||||
--
|
||||
-- Several parameters can be set to deploy cargo:
|
||||
--
|
||||
-- * @{#AI_CARGO_DISPATCHER_SHIP.SetDeployRadius}(): Sets or randomizes the deploy location for the Ship around the cargo coordinate in a radius defined an outer and an optional inner radius.
|
||||
-- * @{#AI_CARGO_DISPATCHER_SHIP.SetDeploySpeed}(): Set the speed or randomizes the speed in km/h to deploy the cargo.
|
||||
--
|
||||
-- # 5) Set the home zone when there isn't any more cargo to pickup.
|
||||
--
|
||||
-- A home zone can be specified to where the Ship will move when there isn't any cargo left for pickup.
|
||||
-- Use @{#AI_CARGO_DISPATCHER_SHIP.SetHomeZone}() to specify the home zone.
|
||||
--
|
||||
-- If no home zone is specified, the Ship will wait near the deploy zone for a new pickup command.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @field #AI_CARGO_DISPATCHER_SHIP
|
||||
AI_CARGO_DISPATCHER_SHIP = {
|
||||
ClassName = "AI_CARGO_DISPATCHER_SHIP"
|
||||
}
|
||||
|
||||
--- Creates a new AI_CARGO_DISPATCHER_SHIP object.
|
||||
-- @param #AI_CARGO_DISPATCHER_SHIP self
|
||||
-- @param Core.Set#SET_GROUP ShipSet The set of @{Wrapper.Group#GROUP} objects of Ships that will transport the cargo
|
||||
-- @param Core.Set#SET_CARGO CargoSet The set of @{Cargo.Cargo#CARGO} objects, which can be CARGO_GROUP, CARGO_CRATE, or CARGO_SLINGLOAD objects.
|
||||
-- @param Core.Set#SET_ZONE PickupZoneSet The set of pickup zones which are used to determine from where the cargo can be picked up by the Ship.
|
||||
-- @param Core.Set#SET_ZONE DeployZoneSet The set of deploy zones which determine where the cargo will be deployed by the Ship.
|
||||
-- @param #table ShippingLane Table containing list of Shipping Lanes to be used
|
||||
-- @return #AI_CARGO_DISPATCHER_SHIP
|
||||
-- @usage
|
||||
--
|
||||
-- -- An AI dispatcher object for a naval group, moving cargo from pickup zones to deploy zones via a predetermined Shipping Lane
|
||||
--
|
||||
-- local SetCargoInfantry = SET_CARGO:New():FilterTypes( "Infantry" ):FilterStart()
|
||||
-- local SetShip = SET_GROUP:New():FilterPrefixes( "Ship" ):FilterStart()
|
||||
-- local SetPickupZones = SET_ZONE:New():FilterPrefixes( "Pickup" ):FilterStart()
|
||||
-- local SetDeployZones = SET_ZONE:New():FilterPrefixes( "Deploy" ):FilterStart()
|
||||
-- NEED MORE THOUGHT - ShippingLane is part of Warehouse.......
|
||||
-- local ShippingLane = GROUP:New():FilterPrefixes( "ShippingLane" ):FilterStart()
|
||||
--
|
||||
-- AICargoDispatcherShip = AI_CARGO_DISPATCHER_SHIP:New( SetShip, SetCargoInfantry, SetPickupZones, SetDeployZones, ShippingLane )
|
||||
-- AICargoDispatcherShip:Start()
|
||||
--
|
||||
function AI_CARGO_DISPATCHER_SHIP:New( ShipSet, CargoSet, PickupZoneSet, DeployZoneSet, ShippingLane )
|
||||
|
||||
local self = BASE:Inherit( self, AI_CARGO_DISPATCHER:New( ShipSet, CargoSet, PickupZoneSet, DeployZoneSet ) )
|
||||
|
||||
self:SetPickupSpeed( 60, 10 )
|
||||
self:SetDeploySpeed( 60, 10 )
|
||||
|
||||
self:SetPickupRadius( 500, 6000 )
|
||||
self:SetDeployRadius( 500, 6000 )
|
||||
|
||||
self:SetPickupHeight( 0, 0 )
|
||||
self:SetDeployHeight( 0, 0 )
|
||||
|
||||
self:SetShippingLane( ShippingLane )
|
||||
|
||||
self:SetMonitorTimeInterval( 600 )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function AI_CARGO_DISPATCHER_SHIP:SetShippingLane( ShippingLane )
|
||||
self.ShippingLane = ShippingLane
|
||||
|
||||
return self
|
||||
|
||||
end
|
||||
|
||||
function AI_CARGO_DISPATCHER_SHIP:AICargo( Ship, CargoSet )
|
||||
|
||||
return AI_CARGO_SHIP:New( Ship, CargoSet, 0, self.ShippingLane )
|
||||
end
|
||||
@@ -1,4 +1,4 @@
|
||||
--- **AI** -- (R2.4) - Models the intelligent transportation of infantry (cargo).
|
||||
--- **AI** - Models the intelligent transportation of cargo using helicopters.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
@@ -33,7 +33,7 @@
|
||||
--
|
||||
-- ## Infantry health.
|
||||
--
|
||||
-- When infantry is unboarded from the APCs, the infantry is actually respawned into the battlefield.
|
||||
-- When infantry is unboarded from the helicopters, the infantry is actually respawned into the battlefield.
|
||||
-- As a result, the unboarding infantry is very _healthy_ every time it unboards.
|
||||
-- This is due to the limitation of the DCS simulator, which is not able to specify the health of new spawned units as a parameter.
|
||||
-- However, infantry that was destroyed when unboarded, won't be respawned again. Destroyed is destroyed.
|
||||
@@ -64,25 +64,24 @@ function AI_CARGO_HELICOPTER:New( Helicopter, CargoSet )
|
||||
self.Zone = ZONE_GROUP:New( Helicopter:GetName(), Helicopter, 300 )
|
||||
|
||||
self:SetStartState( "Unloaded" )
|
||||
-- Boarding
|
||||
self:AddTransition( "Unloaded", "Pickup", "Unloaded" )
|
||||
self:AddTransition( "*", "Landed", "*" )
|
||||
self:AddTransition( "*", "Load", "*" )
|
||||
self:AddTransition( "*", "Loaded", "Loaded" )
|
||||
self:AddTransition( "Loaded", "PickedUp", "Loaded" )
|
||||
|
||||
self:AddTransition( "Unloaded", "Pickup", "*" )
|
||||
self:AddTransition( "Loaded", "Deploy", "*" )
|
||||
-- Unboarding
|
||||
self:AddTransition( "Loaded", "Deploy", "*" )
|
||||
self:AddTransition( "*", "Queue", "*" )
|
||||
self:AddTransition( "*", "Orbit" , "*" )
|
||||
self:AddTransition( "*", "Destroyed", "*" )
|
||||
self:AddTransition( "*", "Unload", "*" )
|
||||
self:AddTransition( "*", "Unloaded", "Unloaded" )
|
||||
self:AddTransition( "Unloaded", "Deployed", "Unloaded" )
|
||||
|
||||
self:AddTransition( { "Unloaded", "Loading" }, "Load", "Boarding" )
|
||||
self:AddTransition( "Boarding", "Board", "Boarding" )
|
||||
self:AddTransition( "Boarding", "Loaded", "Boarding" )
|
||||
self:AddTransition( "Boarding", "PickedUp", "Loaded" )
|
||||
self:AddTransition( "Loaded", "Unload", "Unboarding" )
|
||||
self:AddTransition( "Unboarding", "Unboard", "Unboarding" )
|
||||
self:AddTransition( "Unboarding", "Unloaded", "Unboarding" )
|
||||
self:AddTransition( "Unboarding", "Deployed", "Unloaded" )
|
||||
|
||||
self:AddTransition( "*", "Landed", "*" )
|
||||
self:AddTransition( "*", "Queue", "*" )
|
||||
self:AddTransition( "*", "Orbit" , "*" )
|
||||
-- RTB
|
||||
self:AddTransition( "*", "Home" , "*" )
|
||||
|
||||
self:AddTransition( "*", "Destroyed", "Destroyed" )
|
||||
|
||||
--- Pickup Handler OnBefore for AI_CARGO_HELICOPTER
|
||||
-- @function [parent=#AI_CARGO_HELICOPTER] OnBeforePickup
|
||||
@@ -100,13 +99,31 @@ function AI_CARGO_HELICOPTER:New( Helicopter, CargoSet )
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param Core.Point#COORDINATE Coordinate
|
||||
-- @param #number Speed Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
-- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
|
||||
--- PickedUp Handler OnAfter for AI_CARGO_HELICOPTER - Cargo set has been picked up, ready to deploy
|
||||
-- @function [parent=#AI_CARGO_HELICOPTER] OnAfterPickedUp
|
||||
-- @param #AI_CARGO_HELICOPTER self
|
||||
-- @param Wrapper.Group#GROUP Helicopter The helicopter #GROUP object
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param Wrapper.Unit#UNIT Unit The helicopter #UNIT object
|
||||
|
||||
--- Unloaded Handler OnAfter for AI_CARGO_HELICOPTER - Cargo unloaded, carrier is empty
|
||||
-- @function [parent=#AI_CARGO_HELICOPTER] OnAfterUnloaded
|
||||
-- @param #AI_CARGO_HELICOPTER self
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param Cargo.CargoGroup#CARGO_GROUP Cargo The #CARGO_GROUP object.
|
||||
-- @param Wrapper.Unit#UNIT Unit The helicopter #UNIT object
|
||||
|
||||
--- Pickup Trigger for AI_CARGO_HELICOPTER
|
||||
-- @function [parent=#AI_CARGO_HELICOPTER] Pickup
|
||||
-- @param #AI_CARGO_HELICOPTER self
|
||||
-- @param Core.Point#COORDINATE Coordinate
|
||||
-- @param #number Speed Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
-- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
|
||||
--- Pickup Asynchronous Trigger for AI_CARGO_HELICOPTER
|
||||
-- @function [parent=#AI_CARGO_HELICOPTER] __Pickup
|
||||
@@ -122,7 +139,7 @@ function AI_CARGO_HELICOPTER:New( Helicopter, CargoSet )
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param Core.Point#COORDINATE Coordinate Place at which cargo is deployed.
|
||||
-- @param #number Speed Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
-- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
-- @return #boolean
|
||||
|
||||
--- Deploy Handler OnAfter for AI_CARGO_HELICOPTER
|
||||
@@ -132,21 +149,42 @@ function AI_CARGO_HELICOPTER:New( Helicopter, CargoSet )
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param Core.Point#COORDINATE Coordinate
|
||||
-- @param #number Speed Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
-- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
|
||||
--- Deployed Handler OnAfter for AI_CARGO_HELICOPTER
|
||||
-- @function [parent=#AI_CARGO_HELICOPTER] OnAfterDeployed
|
||||
-- @param #AI_CARGO_HELICOPTER self
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
|
||||
--- Deploy Trigger for AI_CARGO_HELICOPTER
|
||||
-- @function [parent=#AI_CARGO_HELICOPTER] Deploy
|
||||
-- @param #AI_CARGO_HELICOPTER self
|
||||
-- @param Core.Point#COORDINATE Coordinate Place at which the cargo is deployed.
|
||||
-- @param #number Speed Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
-- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
|
||||
--- Deploy Asynchronous Trigger for AI_CARGO_HELICOPTER
|
||||
-- @function [parent=#AI_CARGO_HELICOPTER] __Deploy
|
||||
-- @param #number Delay Delay in seconds.
|
||||
-- @param #AI_CARGO_HELICOPTER self
|
||||
-- @param Core.Point#COORDINATE Coordinate Place at which the cargo is deployed.
|
||||
-- @param #number Speed Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
-- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
|
||||
--- Home Trigger for AI_CARGO_HELICOPTER
|
||||
-- @function [parent=#AI_CARGO_HELICOPTER] Home
|
||||
-- @param #AI_CARGO_HELICOPTER self
|
||||
-- @param Core.Point#COORDINATE Coordinate Place to which the helicopter will go.
|
||||
-- @param #number Speed (optional) Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
-- @param #number Height (optional) Height the Helicopter should be flying at.
|
||||
|
||||
--- Home Asynchronous Trigger for AI_CARGO_HELICOPTER
|
||||
-- @function [parent=#AI_CARGO_HELICOPTER] __Home
|
||||
-- @param #number Delay Delay in seconds.
|
||||
-- @param #AI_CARGO_HELICOPTER self
|
||||
-- @param Core.Point#COORDINATE Coordinate Place to which the helicopter will go.
|
||||
-- @param #number Speed (optional) Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
-- @param #number Height (optional) Height the Helicopter should be flying at.
|
||||
|
||||
-- We need to capture the Crash events for the helicopters.
|
||||
-- The helicopter reference is used in the semaphore AI_CARGO_QUEUE.
|
||||
@@ -173,6 +211,9 @@ function AI_CARGO_HELICOPTER:New( Helicopter, CargoSet )
|
||||
|
||||
self:SetCarrier( Helicopter )
|
||||
|
||||
self.landingspeed = 15 -- kph
|
||||
self.landingheight = 5.5 -- meter
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -221,6 +262,25 @@ function AI_CARGO_HELICOPTER:SetCarrier( Helicopter )
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set landingspeed and -height for helicopter landings. Adjust after tracing if your helis get stuck after landing.
|
||||
-- @param #AI_CARGO_HELICOPTER self
|
||||
-- @param #number speed Landing speed in kph(!), e.g. 15
|
||||
-- @param #number height Landing height in meters(!), e.g. 5.5
|
||||
-- @return #AI_CARGO_HELICOPTER self
|
||||
-- @usage If your choppers get stuck, add tracing to your script to determine if they hit the right parameters like so:
|
||||
--
|
||||
-- BASE:TraceOn()
|
||||
-- BASE:TraceClass("AI_CARGO_HELICOPTER")
|
||||
--
|
||||
-- Watch the DCS.log for entries stating `Helicopter:<name>, Height = Helicopter:<number>, Velocity = Helicopter:<number>`
|
||||
-- Adjust if necessary.
|
||||
function AI_CARGO_HELICOPTER:SetLandingSpeedAndHeight(speed, height)
|
||||
local _speed = speed or 15
|
||||
local _height = height or 5.5
|
||||
self.landingheight = _height
|
||||
self.landingspeed = _speed
|
||||
return self
|
||||
end
|
||||
|
||||
--- @param #AI_CARGO_HELICOPTER self
|
||||
-- @param Wrapper.Group#GROUP Helicopter
|
||||
@@ -228,7 +288,7 @@ end
|
||||
-- @param Event
|
||||
-- @param To
|
||||
function AI_CARGO_HELICOPTER:onafterLanded( Helicopter, From, Event, To )
|
||||
|
||||
self:F({From, Event, To})
|
||||
Helicopter:F( { Name = Helicopter:GetName() } )
|
||||
|
||||
if Helicopter and Helicopter:IsAlive() then
|
||||
@@ -237,13 +297,13 @@ function AI_CARGO_HELICOPTER:onafterLanded( Helicopter, From, Event, To )
|
||||
-- 1 - When the helo lands normally on the ground.
|
||||
-- 2 - when the helo is hit and goes RTB or even when it is destroyed.
|
||||
-- For point 2, this is an issue, the infantry may not unload in this case!
|
||||
-- So we check if the helo is on the ground, and velocity< 5.
|
||||
-- So we check if the helo is on the ground, and velocity< 15.
|
||||
-- Only then the infantry can unload (and load too, for consistency)!
|
||||
|
||||
self:F( { Helicopter:GetName(), Height = Helicopter:GetHeight( true ), Velocity = Helicopter:GetVelocityKMH() } )
|
||||
self:T( { Helicopter:GetName(), Height = Helicopter:GetHeight( true ), Velocity = Helicopter:GetVelocityKMH() } )
|
||||
|
||||
if self.RoutePickup == true then
|
||||
if Helicopter:GetHeight( true ) <= 5 and Helicopter:GetVelocityKMH() < 10 then
|
||||
if Helicopter:GetHeight( true ) <= self.landingheight then --and Helicopter:GetVelocityKMH() < self.landingspeed then
|
||||
--self:Load( Helicopter:GetPointVec2() )
|
||||
self:Load( self.PickupZone )
|
||||
self.RoutePickup = false
|
||||
@@ -251,7 +311,7 @@ function AI_CARGO_HELICOPTER:onafterLanded( Helicopter, From, Event, To )
|
||||
end
|
||||
|
||||
if self.RouteDeploy == true then
|
||||
if Helicopter:GetHeight( true ) <= 5 and Helicopter:GetVelocityKMH() < 10 then
|
||||
if Helicopter:GetHeight( true ) <= self.landingheight then --and Helicopter:GetVelocityKMH() < self.landingspeed then
|
||||
self:Unload( self.DeployZone )
|
||||
self.RouteDeploy = false
|
||||
end
|
||||
@@ -269,7 +329,7 @@ end
|
||||
-- @param Core.Point#COORDINATE Coordinate
|
||||
-- @param #number Speed
|
||||
function AI_CARGO_HELICOPTER:onafterQueue( Helicopter, From, Event, To, Coordinate, Speed, DeployZone )
|
||||
|
||||
self:F({From, Event, To, Coordinate, Speed, DeployZone})
|
||||
local HelicopterInZone = false
|
||||
|
||||
if Helicopter and Helicopter:IsAlive() == true then
|
||||
@@ -308,7 +368,11 @@ function AI_CARGO_HELICOPTER:onafterQueue( Helicopter, From, Event, To, Coordina
|
||||
-- true
|
||||
-- )
|
||||
-- Route[#Route+1] = WaypointFrom
|
||||
local CoordinateTo = Coordinate
|
||||
local CoordinateTo = Coordinate
|
||||
|
||||
local landheight = CoordinateTo:GetLandHeight() -- get target height
|
||||
CoordinateTo.y = landheight + 50 -- flight height should be 50m above ground
|
||||
|
||||
local WaypointTo = CoordinateTo:WaypointAir(
|
||||
"RADIO",
|
||||
POINT_VEC3.RoutePointType.TurningPoint,
|
||||
@@ -348,28 +412,17 @@ end
|
||||
-- @param Core.Point#COORDINATE Coordinate
|
||||
-- @param #number Speed
|
||||
function AI_CARGO_HELICOPTER:onafterOrbit( Helicopter, From, Event, To, Coordinate )
|
||||
|
||||
self:F({From, Event, To, Coordinate})
|
||||
|
||||
if Helicopter and Helicopter:IsAlive() then
|
||||
|
||||
local Route = {}
|
||||
|
||||
-- local CoordinateFrom = Helicopter:GetCoordinate()
|
||||
-- local WaypointFrom = CoordinateFrom:WaypointAir(
|
||||
-- "RADIO",
|
||||
-- POINT_VEC3.RoutePointType.TurningPoint,
|
||||
-- POINT_VEC3.RoutePointAction.TurningPoint,
|
||||
-- Speed,
|
||||
-- true
|
||||
-- )
|
||||
-- Route[#Route+1] = WaypointFrom
|
||||
local CoordinateTo = Coordinate
|
||||
local WaypointTo = CoordinateTo:WaypointAir(
|
||||
"RADIO",
|
||||
POINT_VEC3.RoutePointType.TurningPoint,
|
||||
POINT_VEC3.RoutePointAction.TurningPoint,
|
||||
50,
|
||||
true
|
||||
)
|
||||
local CoordinateTo = Coordinate
|
||||
local landheight = CoordinateTo:GetLandHeight() -- get target height
|
||||
CoordinateTo.y = landheight + 50 -- flight height should be 50m above ground
|
||||
|
||||
local WaypointTo = CoordinateTo:WaypointAir("RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, 50, true)
|
||||
Route[#Route+1] = WaypointTo
|
||||
|
||||
local Tasks = {}
|
||||
@@ -379,7 +432,7 @@ function AI_CARGO_HELICOPTER:onafterOrbit( Helicopter, From, Event, To, Coordina
|
||||
Route[#Route+1] = WaypointTo
|
||||
|
||||
-- Now route the helicopter
|
||||
Helicopter:Route( Route, 0 )
|
||||
Helicopter:Route(Route, 0)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -395,7 +448,7 @@ end
|
||||
-- @param #boolean Deployed Cargo is deployed.
|
||||
-- @return #boolean True if all cargo has been unloaded.
|
||||
function AI_CARGO_HELICOPTER:onafterDeployed( Helicopter, From, Event, To, DeployZone )
|
||||
self:F( { Helicopter, From, Event, To, DeployZone = DeployZone } )
|
||||
self:F( { From, Event, To, DeployZone = DeployZone } )
|
||||
|
||||
self:Orbit( Helicopter:GetCoordinate(), 50 )
|
||||
|
||||
@@ -408,7 +461,6 @@ function AI_CARGO_HELICOPTER:onafterDeployed( Helicopter, From, Event, To, Deplo
|
||||
|
||||
self:GetParent( self, AI_CARGO_HELICOPTER ).onafterDeployed( self, Helicopter, From, Event, To, DeployZone )
|
||||
|
||||
|
||||
end
|
||||
|
||||
--- On after Pickup event.
|
||||
@@ -418,11 +470,12 @@ end
|
||||
-- @param Event
|
||||
-- @param To
|
||||
-- @param Core.Point#COORDINATE Coordinate Pickup place.
|
||||
-- @param #number Speed Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
-- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
-- @param #number Height Height in meters to move to the pickup coordinate. This parameter is ignored for APCs.
|
||||
-- @param Core.Zone#ZONE PickupZone (optional) The zone where the cargo will be picked up. The PickupZone can be nil, if there wasn't any PickupZoneSet provided.
|
||||
function AI_CARGO_HELICOPTER:onafterPickup( Helicopter, From, Event, To, Coordinate, Speed, Height, PickupZone )
|
||||
|
||||
self:F({Coordinate, Speed, Height, PickupZone })
|
||||
|
||||
if Helicopter and Helicopter:IsAlive() ~= nil then
|
||||
|
||||
Helicopter:Activate()
|
||||
@@ -436,25 +489,16 @@ function AI_CARGO_HELICOPTER:onafterPickup( Helicopter, From, Event, To, Coordin
|
||||
|
||||
--- Calculate the target route point.
|
||||
local CoordinateFrom = Helicopter:GetCoordinate()
|
||||
local CoordinateTo = Coordinate
|
||||
|
||||
--- Create a route point of type air.
|
||||
local WaypointFrom = CoordinateFrom:WaypointAir(
|
||||
"RADIO",
|
||||
POINT_VEC3.RoutePointType.TurningPoint,
|
||||
POINT_VEC3.RoutePointAction.TurningPoint,
|
||||
_speed,
|
||||
true
|
||||
)
|
||||
local WaypointFrom = CoordinateFrom:WaypointAir("RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, _speed, true)
|
||||
|
||||
--- Create a route point of type air.
|
||||
local WaypointTo = CoordinateTo:WaypointAir(
|
||||
"RADIO",
|
||||
POINT_VEC3.RoutePointType.TurningPoint,
|
||||
POINT_VEC3.RoutePointAction.TurningPoint,
|
||||
_speed,
|
||||
true
|
||||
)
|
||||
local CoordinateTo = Coordinate
|
||||
local landheight = CoordinateTo:GetLandHeight() -- get target height
|
||||
CoordinateTo.y = landheight + 50 -- flight height should be 50m above ground
|
||||
|
||||
local WaypointTo = CoordinateTo:WaypointAir("RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint,_speed, true)
|
||||
|
||||
Route[#Route+1] = WaypointFrom
|
||||
Route[#Route+1] = WaypointTo
|
||||
@@ -495,10 +539,10 @@ end
|
||||
-- @param Event
|
||||
-- @param To
|
||||
-- @param Core.Point#COORDINATE Coordinate Place at which the cargo is deployed.
|
||||
-- @param #number Speed Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
-- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
-- @param #number Height Height in meters to move to the deploy coordinate.
|
||||
function AI_CARGO_HELICOPTER:onafterDeploy( Helicopter, From, Event, To, Coordinate, Speed, Height, DeployZone )
|
||||
|
||||
self:F({From, Event, To, Coordinate, Speed, Height, DeployZone})
|
||||
if Helicopter and Helicopter:IsAlive() ~= nil then
|
||||
|
||||
self.RouteDeploy = true
|
||||
@@ -514,25 +558,17 @@ function AI_CARGO_HELICOPTER:onafterDeploy( Helicopter, From, Event, To, Coordin
|
||||
|
||||
--- Create a route point of type air.
|
||||
local CoordinateFrom = Helicopter:GetCoordinate()
|
||||
local WaypointFrom = CoordinateFrom:WaypointAir(
|
||||
"RADIO",
|
||||
POINT_VEC3.RoutePointType.TurningPoint,
|
||||
POINT_VEC3.RoutePointAction.TurningPoint,
|
||||
_speed,
|
||||
true
|
||||
)
|
||||
local WaypointFrom = CoordinateFrom:WaypointAir("RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, _speed, true)
|
||||
Route[#Route+1] = WaypointFrom
|
||||
Route[#Route+1] = WaypointFrom
|
||||
|
||||
--- Create a route point of type air.
|
||||
local CoordinateTo = Coordinate
|
||||
local WaypointTo = CoordinateTo:WaypointAir(
|
||||
"RADIO",
|
||||
POINT_VEC3.RoutePointType.TurningPoint,
|
||||
POINT_VEC3.RoutePointAction.TurningPoint,
|
||||
_speed,
|
||||
true
|
||||
)
|
||||
|
||||
local CoordinateTo = Coordinate
|
||||
local landheight = CoordinateTo:GetLandHeight() -- get target height
|
||||
CoordinateTo.y = landheight + 50 -- flight height should be 50m above ground
|
||||
|
||||
local WaypointTo = CoordinateTo:WaypointAir("RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, _speed, true)
|
||||
|
||||
Route[#Route+1] = WaypointTo
|
||||
Route[#Route+1] = WaypointTo
|
||||
@@ -542,7 +578,9 @@ function AI_CARGO_HELICOPTER:onafterDeploy( Helicopter, From, Event, To, Coordin
|
||||
|
||||
local Tasks = {}
|
||||
|
||||
-- The _Deploy function does not exist.
|
||||
Tasks[#Tasks+1] = Helicopter:TaskFunction( "AI_CARGO_HELICOPTER._Deploy", self, Coordinate, DeployZone )
|
||||
|
||||
Tasks[#Tasks+1] = Helicopter:TaskOrbitCircle( math.random( 30, 100 ), _speed, CoordinateTo:GetRandomCoordinateInRadius( 800, 500 ) )
|
||||
|
||||
--Tasks[#Tasks+1] = Helicopter:TaskLandAtVec2( CoordinateTo:GetVec2() )
|
||||
@@ -566,11 +604,12 @@ end
|
||||
-- @param Event
|
||||
-- @param To
|
||||
-- @param Core.Point#COORDINATE Coordinate Home place.
|
||||
-- @param #number Speed Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
-- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go.
|
||||
-- @param #number Height Height in meters to move to the home coordinate.
|
||||
-- @param Core.Zone#ZONE HomeZone The zone wherein the carrier will return when all cargo has been transported. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE.
|
||||
function AI_CARGO_HELICOPTER:onafterHome( Helicopter, From, Event, To, Coordinate, Speed, Height, HomeZone )
|
||||
|
||||
self:F({From, Event, To, Coordinate, Speed, Height})
|
||||
|
||||
if Helicopter and Helicopter:IsAlive() ~= nil then
|
||||
|
||||
self.RouteHome = true
|
||||
@@ -579,30 +618,23 @@ function AI_CARGO_HELICOPTER:onafterHome( Helicopter, From, Event, To, Coordinat
|
||||
|
||||
--- Calculate the target route point.
|
||||
|
||||
Coordinate.y = Height
|
||||
--Coordinate.y = Height
|
||||
Height = Height or 50
|
||||
|
||||
Speed = Speed or Helicopter:GetSpeedMax()*0.5
|
||||
|
||||
--- Create a route point of type air.
|
||||
local CoordinateFrom = Helicopter:GetCoordinate()
|
||||
local WaypointFrom = CoordinateFrom:WaypointAir(
|
||||
"RADIO",
|
||||
POINT_VEC3.RoutePointType.TurningPoint,
|
||||
POINT_VEC3.RoutePointAction.TurningPoint,
|
||||
Speed ,
|
||||
true
|
||||
)
|
||||
|
||||
local WaypointFrom = CoordinateFrom:WaypointAir("RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, Speed, true)
|
||||
Route[#Route+1] = WaypointFrom
|
||||
|
||||
--- Create a route point of type air.
|
||||
local CoordinateTo = Coordinate
|
||||
local WaypointTo = CoordinateTo:WaypointAir(
|
||||
"RADIO",
|
||||
POINT_VEC3.RoutePointType.TurningPoint,
|
||||
POINT_VEC3.RoutePointAction.TurningPoint,
|
||||
Speed ,
|
||||
true
|
||||
)
|
||||
local CoordinateTo = Coordinate
|
||||
local landheight = CoordinateTo:GetLandHeight() -- get target height
|
||||
CoordinateTo.y = landheight + Height -- flight height should be 50m above ground
|
||||
|
||||
local WaypointTo = CoordinateTo:WaypointAir("RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, Speed, true)
|
||||
|
||||
Route[#Route+1] = WaypointTo
|
||||
|
||||
@@ -613,12 +645,11 @@ function AI_CARGO_HELICOPTER:onafterHome( Helicopter, From, Event, To, Coordinat
|
||||
|
||||
Tasks[#Tasks+1] = Helicopter:TaskLandAtVec2( CoordinateTo:GetVec2() )
|
||||
Route[#Route].task = Helicopter:TaskCombo( Tasks )
|
||||
|
||||
|
||||
Route[#Route+1] = WaypointTo
|
||||
|
||||
-- Now route the helicopter
|
||||
Helicopter:Route( Route, 0 )
|
||||
|
||||
Helicopter:Route(Route, 0)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
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_Dispatcher.JPG
|
||||
|
||||
--- @type AI_CARGO_SHIP
|
||||
-- @extends AI.AI_Cargo#AI_CARGO
|
||||
|
||||
--- Brings a dynamic cargo handling capability for an AI naval group.
|
||||
--
|
||||
-- Naval ships can be utilized to transport cargo around the map following naval shipping lanes.
|
||||
-- The AI_CARGO_SHIP class uses the @{Cargo.Cargo} capabilities within the MOOSE framework.
|
||||
-- @{Cargo.Cargo} must be declared within the mission or warehouse to make the AI_CARGO_SHIP recognize the cargo.
|
||||
-- Please consult the @{Cargo.Cargo} module for more information.
|
||||
--
|
||||
-- ## Cargo loading.
|
||||
--
|
||||
-- The module will automatically load cargo when the Ship is within boarding or loading radius.
|
||||
-- The boarding or loading radius is specified when the cargo is created in the simulation and depends on the type of
|
||||
-- cargo and the specified boarding radius.
|
||||
--
|
||||
-- ## Defending the Ship when enemies are nearby
|
||||
-- This is not supported for naval cargo because most tanks don't float. Protect your transports...
|
||||
--
|
||||
-- ## Infantry or cargo **health**.
|
||||
-- When cargo is unboarded from the Ship, the cargo is actually respawned into the battlefield.
|
||||
-- As a result, the unboarding cargo is very _healthy_ every time it unboards.
|
||||
-- This is due to the limitation of the DCS simulator, which is not able to specify the health of newly spawned units as a parameter.
|
||||
-- However, cargo that was destroyed when unboarded and following the Ship won't be respawned again (this is likely not a thing for
|
||||
-- naval cargo due to the lack of support for defending the Ship mentioned above). Destroyed is destroyed.
|
||||
-- As a result, there is some additional strength that is gained when an unboarding action happens, but in terms of simulation balance
|
||||
-- this has marginal impact on the overall battlefield simulation. Given the relatively short duration of DCS missions and the somewhat
|
||||
-- lengthy naval transport times, most units entering the Ship as cargo will be freshly en route to an amphibious landing or transporting
|
||||
-- between warehouses.
|
||||
--
|
||||
-- ## Control the Ships on the map.
|
||||
--
|
||||
-- Currently, naval transports can only be controlled via scripts due to their reliance upon predefined Shipping Lanes created in the Mission
|
||||
-- Editor. An interesting future enhancement could leverage new pathfinding functionality for ships in the Ops module.
|
||||
--
|
||||
-- ## Cargo deployment.
|
||||
--
|
||||
-- Using the @{AI_CARGO_SHIP.Deploy}() method, you are able to direct the Ship towards a Deploy zone to unboard/unload the cargo at the
|
||||
-- specified coordinate. The Ship will follow the Shipping Lane to ensure consistent cargo transportation within the simulation environment.
|
||||
--
|
||||
-- ## Cargo pickup.
|
||||
--
|
||||
-- Using the @{AI_CARGO_SHIP.Pickup}() method, you are able to direct the Ship towards a Pickup zone to board/load the cargo at the specified
|
||||
-- coordinate. The Ship will follow the Shipping Lane to ensure consistent cargo transportation within the simulation environment.
|
||||
--
|
||||
--
|
||||
-- @field #AI_CARGO_SHIP
|
||||
AI_CARGO_SHIP = {
|
||||
ClassName = "AI_CARGO_SHIP",
|
||||
Coordinate = nil -- Core.Point#COORDINATE
|
||||
}
|
||||
|
||||
--- Creates a new AI_CARGO_SHIP object.
|
||||
-- @param #AI_CARGO_SHIP self
|
||||
-- @param Wrapper.Group#GROUP Ship The carrier Ship group
|
||||
-- @param Core.Set#SET_CARGO CargoSet The set of cargo to be transported
|
||||
-- @param #number CombatRadius Provide the combat radius to defend the carrier by unboarding the cargo when enemies are nearby. When CombatRadius is 0, no defense will occur.
|
||||
-- @param #table ShippingLane Table containing list of Shipping Lanes to be used
|
||||
-- @return #AI_CARGO_SHIP
|
||||
function AI_CARGO_SHIP:New( Ship, CargoSet, CombatRadius, ShippingLane )
|
||||
|
||||
local self = BASE:Inherit( self, AI_CARGO:New( Ship, CargoSet ) ) -- #AI_CARGO_SHIP
|
||||
|
||||
self:AddTransition( "*", "Monitor", "*" )
|
||||
self:AddTransition( "*", "Destroyed", "Destroyed" )
|
||||
self:AddTransition( "*", "Home", "*" )
|
||||
|
||||
self:SetCombatRadius( 0 ) -- Don't want to deploy cargo in middle of water to defend Ship, so set CombatRadius to 0
|
||||
self:SetShippingLane ( ShippingLane )
|
||||
|
||||
self:SetCarrier( Ship )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set the Carrier
|
||||
-- @param #AI_CARGO_SHIP self
|
||||
-- @param Wrapper.Group#GROUP CargoCarrier
|
||||
-- @return #AI_CARGO_SHIP
|
||||
function AI_CARGO_SHIP:SetCarrier( CargoCarrier )
|
||||
self.CargoCarrier = CargoCarrier -- Wrapper.Group#GROUIP
|
||||
self.CargoCarrier:SetState( self.CargoCarrier, "AI_CARGO_SHIP", self )
|
||||
|
||||
CargoCarrier:HandleEvent( EVENTS.Dead )
|
||||
|
||||
function CargoCarrier:OnEventDead( EventData )
|
||||
self:F({"dead"})
|
||||
local AICargoTroops = self:GetState( self, "AI_CARGO_SHIP" )
|
||||
self:F({AICargoTroops=AICargoTroops})
|
||||
if AICargoTroops then
|
||||
self:F({})
|
||||
if not AICargoTroops:Is( "Loaded" ) then
|
||||
-- Better hope they can swim!
|
||||
AICargoTroops:Destroyed()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
self.Zone = ZONE_UNIT:New( self.CargoCarrier:GetName() .. "-Zone", self.CargoCarrier, self.CombatRadius )
|
||||
self.Coalition = self.CargoCarrier:GetCoalition()
|
||||
|
||||
self:SetControllable( CargoCarrier )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- FInd a free Carrier within a radius
|
||||
-- @param #AI_CARGO_SHIP self
|
||||
-- @param Core.Point#COORDINATE Coordinate
|
||||
-- @param #number Radius
|
||||
-- @return Wrapper.Group#GROUP NewCarrier
|
||||
function AI_CARGO_SHIP:FindCarrier( Coordinate, Radius )
|
||||
|
||||
local CoordinateZone = ZONE_RADIUS:New( "Zone", Coordinate:GetVec2(), Radius )
|
||||
CoordinateZone:Scan( { Object.Category.UNIT } )
|
||||
for _, DCSUnit in pairs( CoordinateZone:GetScannedUnits() ) do
|
||||
local NearUnit = UNIT:Find( DCSUnit )
|
||||
self:F({NearUnit=NearUnit})
|
||||
if not NearUnit:GetState( NearUnit, "AI_CARGO_SHIP" ) then
|
||||
local Attributes = NearUnit:GetDesc()
|
||||
self:F({Desc=Attributes})
|
||||
if NearUnit:HasAttributes( "Trucks" ) then
|
||||
return NearUnit:GetGroup()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
function AI_CARGO_SHIP:SetShippingLane( ShippingLane )
|
||||
self.ShippingLane = ShippingLane
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function AI_CARGO_SHIP:SetCombatRadius( CombatRadius )
|
||||
self.CombatRadius = CombatRadius or 0
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Follow Infantry to the Carrier
|
||||
-- @param #AI_CARGO_SHIP self
|
||||
-- @param #AI_CARGO_SHIP Me
|
||||
-- @param Wrapper.Unit#UNIT ShipUnit
|
||||
-- @param Cargo.CargoGroup#CARGO_GROUP Cargo
|
||||
-- @return #AI_CARGO_SHIP
|
||||
function AI_CARGO_SHIP:FollowToCarrier( Me, ShipUnit, CargoGroup )
|
||||
|
||||
local InfantryGroup = CargoGroup:GetGroup()
|
||||
|
||||
self:F( { self=self:GetClassNameAndID(), InfantryGroup = InfantryGroup:GetName() } )
|
||||
|
||||
if ShipUnit:IsAlive() then
|
||||
-- Check if the Cargo is near the CargoCarrier
|
||||
if InfantryGroup:IsPartlyInZone( ZONE_UNIT:New( "Radius", ShipUnit, 1000 ) ) then
|
||||
|
||||
-- Cargo does not need to navigate to Carrier
|
||||
Me:Guard()
|
||||
else
|
||||
|
||||
self:F( { InfantryGroup = InfantryGroup:GetName() } )
|
||||
if InfantryGroup:IsAlive() then
|
||||
|
||||
self:F( { InfantryGroup = InfantryGroup:GetName() } )
|
||||
local Waypoints = {}
|
||||
|
||||
-- Calculate new route
|
||||
local FromCoord = InfantryGroup:GetCoordinate()
|
||||
local FromGround = FromCoord:WaypointGround( 10, "Diamond" )
|
||||
self:F({FromGround=FromGround})
|
||||
table.insert( Waypoints, FromGround )
|
||||
|
||||
local ToCoord = ShipUnit:GetCoordinate():GetRandomCoordinateInRadius( 10, 5 )
|
||||
local ToGround = ToCoord:WaypointGround( 10, "Diamond" )
|
||||
self:F({ToGround=ToGround})
|
||||
table.insert( Waypoints, ToGround )
|
||||
|
||||
local TaskRoute = InfantryGroup:TaskFunction( "AI_CARGO_SHIP.FollowToCarrier", Me, ShipUnit, CargoGroup )
|
||||
|
||||
self:F({Waypoints=Waypoints})
|
||||
local Waypoint = Waypoints[#Waypoints]
|
||||
InfantryGroup:SetTaskWaypoint( Waypoint, TaskRoute ) -- Set for the given Route at Waypoint 2 the TaskRouteToZone
|
||||
|
||||
InfantryGroup:Route( Waypoints, 1 ) -- Move after a random number of seconds to the Route. See Route method for details
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function AI_CARGO_SHIP:onafterMonitor( Ship, From, Event, To )
|
||||
self:F( { Ship, From, Event, To, IsTransporting = self:IsTransporting() } )
|
||||
|
||||
if self.CombatRadius > 0 then
|
||||
-- We really shouldn't find ourselves in here for Ships since the CombatRadius should always be 0.
|
||||
-- This is to avoid Unloading the Ship in the middle of the sea.
|
||||
if Ship and Ship:IsAlive() then
|
||||
if self.CarrierCoordinate then
|
||||
if self:IsTransporting() == true then
|
||||
local Coordinate = Ship:GetCoordinate()
|
||||
if self:Is( "Unloaded" ) or self:Is( "Loaded" ) then
|
||||
self.Zone:Scan( { Object.Category.UNIT } )
|
||||
if self.Zone:IsAllInZoneOfCoalition( self.Coalition ) then
|
||||
if self:Is( "Unloaded" ) then
|
||||
-- There are no enemies within combat radius. Reload the CargoCarrier.
|
||||
self:Reload()
|
||||
end
|
||||
else
|
||||
if self:Is( "Loaded" ) then
|
||||
-- There are enemies within combat radius. Unload the CargoCarrier.
|
||||
self:__Unload( 1, nil, true ) -- The 2nd parameter is true, which means that the unload is for defending the carrier, not to deploy!
|
||||
else
|
||||
if self:Is( "Unloaded" ) then
|
||||
--self:Follow()
|
||||
end
|
||||
self:F( "I am here" .. self:GetCurrentState() )
|
||||
if self:Is( "Following" ) then
|
||||
for Cargo, ShipUnit in pairs( self.Carrier_Cargo ) do
|
||||
local Cargo = Cargo -- Cargo.Cargo#CARGO
|
||||
local ShipUnit = ShipUnit -- Wrapper.Unit#UNIT
|
||||
if Cargo:IsAlive() then
|
||||
if not Cargo:IsNear( ShipUnit, 40 ) then
|
||||
ShipUnit:RouteStop()
|
||||
self.CarrierStopped = true
|
||||
else
|
||||
if self.CarrierStopped then
|
||||
if Cargo:IsNear( ShipUnit, 25 ) then
|
||||
ShipUnit:RouteResume()
|
||||
self.CarrierStopped = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
self.CarrierCoordinate = Ship:GetCoordinate()
|
||||
end
|
||||
self:__Monitor( -5 )
|
||||
end
|
||||
end
|
||||
|
||||
--- Check if cargo ship is alive and trigger Load event
|
||||
-- @param Wrapper.Group#Group Ship
|
||||
-- @param #AI_CARGO_SHIP self
|
||||
function AI_CARGO_SHIP._Pickup( Ship, self, Coordinate, Speed, PickupZone )
|
||||
|
||||
Ship:F( { "AI_CARGO_Ship._Pickup:", Ship:GetName() } )
|
||||
|
||||
if Ship:IsAlive() then
|
||||
self:Load( PickupZone )
|
||||
end
|
||||
end
|
||||
|
||||
--- Check if cargo ship is alive and trigger Unload event. Good time to remind people that Lua is case sensitive and Unload != UnLoad
|
||||
-- @param Wrapper.Group#GROUP Ship
|
||||
-- @param #AI_CARGO_SHIP self
|
||||
function AI_CARGO_SHIP._Deploy( Ship, self, Coordinate, DeployZone )
|
||||
Ship:F( { "AI_CARGO_Ship._Deploy:", Ship } )
|
||||
|
||||
if Ship:IsAlive() then
|
||||
self:Unload( DeployZone )
|
||||
end
|
||||
end
|
||||
|
||||
--- on after Pickup event.
|
||||
-- @param AI_CARGO_SHIP Ship
|
||||
-- @param From
|
||||
-- @param Event
|
||||
-- @param To
|
||||
-- @param Core.Point#COORDINATE Coordinate of the pickup point
|
||||
-- @param #number Speed Speed in km/h to sail to the pickup coordinate. Default is 50% of max speed for the unit
|
||||
-- @param #number Height Altitude in meters to move to the pickup coordinate. This parameter is ignored for Ships
|
||||
-- @param Core.Zone#ZONE PickupZone (optional) The zone where the cargo will be picked up. The PickupZone can be nil if there was no PickupZoneSet provided
|
||||
function AI_CARGO_SHIP:onafterPickup( Ship, From, Event, To, Coordinate, Speed, Height, PickupZone )
|
||||
|
||||
if Ship and Ship:IsAlive() then
|
||||
AI_CARGO_SHIP._Pickup( Ship, self, Coordinate, Speed, PickupZone )
|
||||
self:GetParent( self, AI_CARGO_SHIP ).onafterPickup( self, Ship, From, Event, To, Coordinate, Speed, Height, PickupZone )
|
||||
end
|
||||
end
|
||||
|
||||
--- On after Deploy event.
|
||||
-- @param #AI_CARGO_SHIP self
|
||||
-- @param Wrapper.Group#GROUP SHIP
|
||||
-- @param From
|
||||
-- @param Event
|
||||
-- @param To
|
||||
-- @param Core.Point#COORDINATE Coordinate Coordinate of the deploy point
|
||||
-- @param #number Speed Speed in km/h to sail to the deploy coordinate. Default is 50% of max speed for the unit
|
||||
-- @param #number Height Altitude in meters to move to the deploy coordinate. This parameter is ignored for Ships
|
||||
-- @param Core.Zone#ZONE DeployZone The zone where the cargo will be deployed.
|
||||
function AI_CARGO_SHIP:onafterDeploy( Ship, From, Event, To, Coordinate, Speed, Height, DeployZone )
|
||||
|
||||
if Ship and Ship:IsAlive() then
|
||||
|
||||
Speed = Speed or Ship:GetSpeedMax()*0.8
|
||||
local lane = self.ShippingLane
|
||||
|
||||
if lane then
|
||||
local Waypoints = {}
|
||||
|
||||
for i=1, #lane do
|
||||
local coord = lane[i]
|
||||
local Waypoint = coord:WaypointGround(_speed)
|
||||
table.insert(Waypoints, Waypoint)
|
||||
end
|
||||
|
||||
local TaskFunction = Ship:TaskFunction( "AI_CARGO_SHIP._Deploy", self, Coordinate, DeployZone )
|
||||
local Waypoint = Waypoints[#Waypoints]
|
||||
Ship:SetTaskWaypoint( Waypoint, TaskFunction )
|
||||
Ship:Route(Waypoints, 1)
|
||||
self:GetParent( self, AI_CARGO_SHIP ).onafterDeploy( self, Ship, From, Event, To, Coordinate, Speed, Height, DeployZone )
|
||||
else
|
||||
self:E(self.lid.."ERROR: No shipping lane defined for Naval Transport!")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- On after Unload event.
|
||||
-- @param #AI_CARGO_SHIP self
|
||||
-- @param Wrapper.Group#GROUP Ship
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE.
|
||||
function AI_CARGO_SHIP:onafterUnload( Ship, From, Event, To, DeployZone, Defend )
|
||||
self:F( { Ship, From, Event, To, DeployZone, Defend = Defend } )
|
||||
|
||||
local UnboardInterval = 5
|
||||
local UnboardDelay = 5
|
||||
|
||||
if Ship and Ship:IsAlive() then
|
||||
for _, ShipUnit in pairs( Ship:GetUnits() ) do
|
||||
local ShipUnit = ShipUnit -- Wrapper.Unit#UNIT
|
||||
Ship:RouteStop()
|
||||
for _, Cargo in pairs( ShipUnit:GetCargo() ) do
|
||||
self:F( { Cargo = Cargo:GetName(), Isloaded = Cargo:IsLoaded() } )
|
||||
if Cargo:IsLoaded() then
|
||||
local unboardCoord = DeployZone:GetRandomPointVec2()
|
||||
Cargo:__UnBoard( UnboardDelay, unboardCoord, 1000)
|
||||
UnboardDelay = UnboardDelay + Cargo:GetCount() * UnboardInterval
|
||||
self:__Unboard( UnboardDelay, Cargo, ShipUnit, DeployZone, Defend )
|
||||
if not Defend == true then
|
||||
Cargo:SetDeployed( true )
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function AI_CARGO_SHIP:onafterHome( Ship, From, Event, To, Coordinate, Speed, Height, HomeZone )
|
||||
if Ship and Ship:IsAlive() then
|
||||
|
||||
self.RouteHome = true
|
||||
Speed = Speed or Ship:GetSpeedMax()*0.8
|
||||
local lane = self.ShippingLane
|
||||
|
||||
if lane then
|
||||
local Waypoints = {}
|
||||
|
||||
-- Need to find a more generalized way to do this instead of reversing the shipping lane.
|
||||
-- This only works if the Source/Dest route waypoints are numbered 1..n and not n..1
|
||||
for i=#lane, 1, -1 do
|
||||
local coord = lane[i]
|
||||
local Waypoint = coord:WaypointGround(_speed)
|
||||
table.insert(Waypoints, Waypoint)
|
||||
end
|
||||
|
||||
local Waypoint = Waypoints[#Waypoints]
|
||||
Ship:Route(Waypoints, 1)
|
||||
|
||||
else
|
||||
self:E(self.lid.."ERROR: No shipping lane defined for Naval Transport!")
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -164,7 +164,7 @@ AI_FORMATION.__Enum.ReportType = {
|
||||
--- MENUPARAM type
|
||||
-- @type MENUPARAM
|
||||
-- @field #AI_FORMATION ParamSelf
|
||||
-- @field #Distance ParamDistance
|
||||
-- @field #number ParamDistance
|
||||
-- @field #function ParamFunction
|
||||
-- @field #string ParamMessage
|
||||
|
||||
@@ -184,11 +184,13 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin
|
||||
|
||||
self.FollowGroupSet:ForEachGroup(
|
||||
function( FollowGroup )
|
||||
self:E("Following")
|
||||
--self:E("Following")
|
||||
FollowGroup:SetState( self, "Mode", self.__Enum.Mode.Formation )
|
||||
end
|
||||
)
|
||||
|
||||
self:SetFlightModeFormation()
|
||||
|
||||
self:SetFlightRandomization( 2 )
|
||||
|
||||
self:SetStartState( "None" )
|
||||
@@ -207,9 +209,9 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin
|
||||
-- @param #string To
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group.
|
||||
-- @param #nubmer YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #nubmer YSpace The space between groups on the Y-axis in meters for each sequent group.
|
||||
-- @param #nubmer ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group.
|
||||
-- @param #number ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group.
|
||||
-- @return #boolean
|
||||
|
||||
@@ -222,9 +224,9 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin
|
||||
-- @param #string To
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group.
|
||||
-- @param #nubmer YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #nubmer YSpace The space between groups on the Y-axis in meters for each sequent group.
|
||||
-- @param #nubmer ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group.
|
||||
-- @param #number ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group.
|
||||
|
||||
--- FormationLine Trigger for AI_FORMATION
|
||||
@@ -232,9 +234,9 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin
|
||||
-- @param #AI_FORMATION self
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group.
|
||||
-- @param #nubmer YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #nubmer YSpace The space between groups on the Y-axis in meters for each sequent group.
|
||||
-- @param #nubmer ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group.
|
||||
-- @param #number ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group.
|
||||
|
||||
--- FormationLine Asynchronous Trigger for AI_FORMATION
|
||||
@@ -243,9 +245,9 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin
|
||||
-- @param #number Delay
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group.
|
||||
-- @param #nubmer YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #nubmer YSpace The space between groups on the Y-axis in meters for each sequent group.
|
||||
-- @param #nubmer ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group.
|
||||
-- @param #number ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group.
|
||||
|
||||
self:AddTransition( "*", "FormationTrail", "*" )
|
||||
@@ -257,7 +259,7 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin
|
||||
-- @param #string To
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group.
|
||||
-- @param #nubmer YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @return #boolean
|
||||
|
||||
--- FormationTrail Handler OnAfter for AI_FORMATION
|
||||
@@ -268,14 +270,14 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin
|
||||
-- @param #string To
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group.
|
||||
-- @param #nubmer YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
|
||||
--- FormationTrail Trigger for AI_FORMATION
|
||||
-- @function [parent=#AI_FORMATION] FormationTrail
|
||||
-- @param #AI_FORMATION self
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group.
|
||||
-- @param #nubmer YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
|
||||
--- FormationTrail Asynchronous Trigger for AI_FORMATION
|
||||
-- @function [parent=#AI_FORMATION] __FormationTrail
|
||||
@@ -283,7 +285,7 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin
|
||||
-- @param #number Delay
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group.
|
||||
-- @param #nubmer YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
|
||||
self:AddTransition( "*", "FormationStack", "*" )
|
||||
--- FormationStack Handler OnBefore for AI_FORMATION
|
||||
@@ -294,7 +296,7 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin
|
||||
-- @param #string To
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group.
|
||||
-- @param #nubmer YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group.
|
||||
-- @return #boolean
|
||||
|
||||
@@ -306,7 +308,7 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin
|
||||
-- @param #string To
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group.
|
||||
-- @param #nubmer YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group.
|
||||
|
||||
--- FormationStack Trigger for AI_FORMATION
|
||||
@@ -314,7 +316,7 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin
|
||||
-- @param #AI_FORMATION self
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group.
|
||||
-- @param #nubmer YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group.
|
||||
|
||||
--- FormationStack Asynchronous Trigger for AI_FORMATION
|
||||
@@ -323,7 +325,7 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin
|
||||
-- @param #number Delay
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group.
|
||||
-- @param #nubmer YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group.
|
||||
|
||||
self:AddTransition( "*", "FormationLeftLine", "*" )
|
||||
@@ -335,8 +337,8 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #nubmer YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #nubmer ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group.
|
||||
-- @return #boolean
|
||||
|
||||
@@ -348,16 +350,16 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #nubmer YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #nubmer ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group.
|
||||
|
||||
--- FormationLeftLine Trigger for AI_FORMATION
|
||||
-- @function [parent=#AI_FORMATION] FormationLeftLine
|
||||
-- @param #AI_FORMATION self
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #nubmer YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #nubmer ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group.
|
||||
|
||||
--- FormationLeftLine Asynchronous Trigger for AI_FORMATION
|
||||
@@ -365,8 +367,8 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin
|
||||
-- @param #AI_FORMATION self
|
||||
-- @param #number Delay
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #nubmer YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #nubmer ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group.
|
||||
|
||||
self:AddTransition( "*", "FormationRightLine", "*" )
|
||||
@@ -378,8 +380,8 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #nubmer YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #nubmer ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group.
|
||||
-- @return #boolean
|
||||
|
||||
@@ -391,16 +393,16 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #nubmer YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #nubmer ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group.
|
||||
|
||||
--- FormationRightLine Trigger for AI_FORMATION
|
||||
-- @function [parent=#AI_FORMATION] FormationRightLine
|
||||
-- @param #AI_FORMATION self
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #nubmer YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #nubmer ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group.
|
||||
|
||||
--- FormationRightLine Asynchronous Trigger for AI_FORMATION
|
||||
@@ -408,8 +410,8 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin
|
||||
-- @param #AI_FORMATION self
|
||||
-- @param #number Delay
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #nubmer YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #nubmer ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group.
|
||||
|
||||
self:AddTransition( "*", "FormationLeftWing", "*" )
|
||||
@@ -422,8 +424,8 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin
|
||||
-- @param #string To
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group.
|
||||
-- @param #nubmer YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #nubmer ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group.
|
||||
-- @return #boolean
|
||||
|
||||
@@ -436,8 +438,8 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin
|
||||
-- @param #string To
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group.
|
||||
-- @param #nubmer YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #nubmer ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group.
|
||||
|
||||
--- FormationLeftWing Trigger for AI_FORMATION
|
||||
@@ -445,8 +447,8 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin
|
||||
-- @param #AI_FORMATION self
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group.
|
||||
-- @param #nubmer YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #nubmer ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group.
|
||||
|
||||
--- FormationLeftWing Asynchronous Trigger for AI_FORMATION
|
||||
@@ -455,8 +457,8 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin
|
||||
-- @param #number Delay
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group.
|
||||
-- @param #nubmer YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #nubmer ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group.
|
||||
|
||||
self:AddTransition( "*", "FormationRightWing", "*" )
|
||||
@@ -469,8 +471,8 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin
|
||||
-- @param #string To
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group.
|
||||
-- @param #nubmer YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #nubmer ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group.
|
||||
-- @return #boolean
|
||||
|
||||
@@ -483,8 +485,8 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin
|
||||
-- @param #string To
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group.
|
||||
-- @param #nubmer YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #nubmer ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group.
|
||||
|
||||
--- FormationRightWing Trigger for AI_FORMATION
|
||||
@@ -492,8 +494,8 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin
|
||||
-- @param #AI_FORMATION self
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group.
|
||||
-- @param #nubmer YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #nubmer ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group.
|
||||
|
||||
--- FormationRightWing Asynchronous Trigger for AI_FORMATION
|
||||
@@ -502,8 +504,8 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin
|
||||
-- @param #number Delay
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group.
|
||||
-- @param #nubmer YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #nubmer ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group.
|
||||
|
||||
self:AddTransition( "*", "FormationCenterWing", "*" )
|
||||
@@ -516,9 +518,9 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin
|
||||
-- @param #string To
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group.
|
||||
-- @param #nubmer YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group.
|
||||
-- @param #nubmer ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group.
|
||||
-- @return #boolean
|
||||
|
||||
@@ -531,9 +533,9 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin
|
||||
-- @param #string To
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group.
|
||||
-- @param #nubmer YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group.
|
||||
-- @param #nubmer ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group.
|
||||
|
||||
--- FormationCenterWing Trigger for AI_FORMATION
|
||||
@@ -541,9 +543,9 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin
|
||||
-- @param #AI_FORMATION self
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group.
|
||||
-- @param #nubmer YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group.
|
||||
-- @param #nubmer ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group.
|
||||
|
||||
--- FormationCenterWing Asynchronous Trigger for AI_FORMATION
|
||||
@@ -552,9 +554,9 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin
|
||||
-- @param #number Delay
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group.
|
||||
-- @param #nubmer YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group.
|
||||
-- @param #nubmer ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group.
|
||||
|
||||
self:AddTransition( "*", "FormationVic", "*" )
|
||||
@@ -566,9 +568,9 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin
|
||||
-- @param #string To
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group.
|
||||
-- @param #nubmer YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group.
|
||||
-- @param #nubmer ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group.
|
||||
-- @return #boolean
|
||||
|
||||
@@ -580,9 +582,9 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin
|
||||
-- @param #string To
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group.
|
||||
-- @param #nubmer YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group.
|
||||
-- @param #nubmer ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group.
|
||||
|
||||
--- FormationVic Trigger for AI_FORMATION
|
||||
@@ -590,9 +592,9 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin
|
||||
-- @param #AI_FORMATION self
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group.
|
||||
-- @param #nubmer YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group.
|
||||
-- @param #nubmer ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group.
|
||||
|
||||
--- FormationVic Asynchronous Trigger for AI_FORMATION
|
||||
@@ -601,9 +603,9 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin
|
||||
-- @param #number Delay
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group.
|
||||
-- @param #nubmer YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group.
|
||||
-- @param #nubmer ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group.
|
||||
|
||||
self:AddTransition( "*", "FormationBox", "*" )
|
||||
@@ -615,9 +617,9 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin
|
||||
-- @param #string To
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group.
|
||||
-- @param #nubmer YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group.
|
||||
-- @param #nubmer ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group.
|
||||
-- @param #number ZLevels The amount of levels on the Z-axis.
|
||||
-- @return #boolean
|
||||
@@ -630,9 +632,9 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin
|
||||
-- @param #string To
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group.
|
||||
-- @param #nubmer YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group.
|
||||
-- @param #nubmer ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group.
|
||||
-- @param #number ZLevels The amount of levels on the Z-axis.
|
||||
|
||||
@@ -641,9 +643,9 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin
|
||||
-- @param #AI_FORMATION self
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group.
|
||||
-- @param #nubmer YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group.
|
||||
-- @param #nubmer ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group.
|
||||
-- @param #number ZLevels The amount of levels on the Z-axis.
|
||||
|
||||
@@ -653,9 +655,9 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin
|
||||
-- @param #number Delay
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group.
|
||||
-- @param #nubmer YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group.
|
||||
-- @param #nubmer ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group.
|
||||
-- @param #number ZLevels The amount of levels on the Z-axis.
|
||||
|
||||
@@ -704,9 +706,9 @@ end
|
||||
-- @param #string To
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group.
|
||||
-- @param #nubmer YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #nubmer YSpace The space between groups on the Y-axis in meters for each sequent group.
|
||||
-- @param #nubmer ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group.
|
||||
-- @param #number ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group.
|
||||
-- @return #AI_FORMATION
|
||||
function AI_FORMATION:onafterFormationLine( FollowGroupSet, From , Event , To, XStart, XSpace, YStart, YSpace, ZStart, ZSpace, Formation ) --R2.1
|
||||
@@ -751,7 +753,7 @@ end
|
||||
-- @param #string To
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group.
|
||||
-- @param #nubmer YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @return #AI_FORMATION
|
||||
function AI_FORMATION:onafterFormationTrail( FollowGroupSet, From , Event , To, XStart, XSpace, YStart ) --R2.1
|
||||
|
||||
@@ -769,7 +771,7 @@ end
|
||||
-- @param #string To
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group.
|
||||
-- @param #nubmer YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group.
|
||||
-- @return #AI_FORMATION
|
||||
function AI_FORMATION:onafterFormationStack( FollowGroupSet, From , Event , To, XStart, XSpace, YStart, YSpace ) --R2.1
|
||||
@@ -789,8 +791,8 @@ end
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #nubmer YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #nubmer ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group.
|
||||
-- @return #AI_FORMATION
|
||||
function AI_FORMATION:onafterFormationLeftLine( FollowGroupSet, From , Event , To, XStart, YStart, ZStart, ZSpace ) --R2.1
|
||||
@@ -808,8 +810,8 @@ end
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #nubmer YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #nubmer ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group.
|
||||
-- @return #AI_FORMATION
|
||||
function AI_FORMATION:onafterFormationRightLine( FollowGroupSet, From , Event , To, XStart, YStart, ZStart, ZSpace ) --R2.1
|
||||
@@ -828,8 +830,8 @@ end
|
||||
-- @param #string To
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group.
|
||||
-- @param #nubmer YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #nubmer ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group.
|
||||
function AI_FORMATION:onafterFormationLeftWing( FollowGroupSet, From , Event , To, XStart, XSpace, YStart, ZStart, ZSpace ) --R2.1
|
||||
|
||||
@@ -848,8 +850,8 @@ end
|
||||
-- @param #string To
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group.
|
||||
-- @param #nubmer YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #nubmer ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group.
|
||||
function AI_FORMATION:onafterFormationRightWing( FollowGroupSet, From , Event , To, XStart, XSpace, YStart, ZStart, ZSpace ) --R2.1
|
||||
|
||||
@@ -867,9 +869,9 @@ end
|
||||
-- @param #string To
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group.
|
||||
-- @param #nubmer YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group.
|
||||
-- @param #nubmer ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group.
|
||||
function AI_FORMATION:onafterFormationCenterWing( FollowGroupSet, From , Event , To, XStart, XSpace, YStart, YSpace, ZStart, ZSpace ) --R2.1
|
||||
|
||||
@@ -905,9 +907,9 @@ end
|
||||
-- @param #string To
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group.
|
||||
-- @param #nubmer YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group.
|
||||
-- @param #nubmer ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group.
|
||||
-- @return #AI_FORMATION
|
||||
function AI_FORMATION:onafterFormationVic( FollowGroupSet, From , Event , To, XStart, XSpace, YStart, YSpace, ZStart, ZSpace ) --R2.1
|
||||
@@ -924,9 +926,9 @@ end
|
||||
-- @param #string To
|
||||
-- @param #number XStart The start position on the X-axis in meters for the first group.
|
||||
-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group.
|
||||
-- @param #nubmer YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number YStart The start position on the Y-axis in meters for the first group.
|
||||
-- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group.
|
||||
-- @param #nubmer ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZStart The start position on the Z-axis in meters for the first group.
|
||||
-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group.
|
||||
-- @param #number ZLevels The amount of levels on the Z-axis.
|
||||
-- @return #AI_FORMATION
|
||||
@@ -997,7 +999,7 @@ function AI_FORMATION:SetFlightModeMission( FollowGroup )
|
||||
FollowGroup:SetState( FollowGroup, "PreviousMode", FollowGroup:GetState( FollowGroup, "Mode" ) )
|
||||
FollowGroup:SetState( FollowGroup, "Mode", self.__Enum.Mode.Mission )
|
||||
else
|
||||
self.EscortGroupSet:ForSomeGroupAlive(
|
||||
self.FollowGroupSet:ForSomeGroupAlive(
|
||||
--- @param Core.Group#GROUP EscortGroup
|
||||
function( FollowGroup )
|
||||
FollowGroup:SetState( FollowGroup, "PreviousMode", FollowGroup:GetState( FollowGroup, "Mode" ) )
|
||||
@@ -1021,7 +1023,7 @@ function AI_FORMATION:SetFlightModeAttack( FollowGroup )
|
||||
FollowGroup:SetState( FollowGroup, "PreviousMode", FollowGroup:GetState( FollowGroup, "Mode" ) )
|
||||
FollowGroup:SetState( FollowGroup, "Mode", self.__Enum.Mode.Attack )
|
||||
else
|
||||
self.EscortGroupSet:ForSomeGroupAlive(
|
||||
self.FollowGroupSet:ForSomeGroupAlive(
|
||||
--- @param Core.Group#GROUP EscortGroup
|
||||
function( FollowGroup )
|
||||
FollowGroup:SetState( FollowGroup, "PreviousMode", FollowGroup:GetState( FollowGroup, "Mode" ) )
|
||||
@@ -1045,7 +1047,7 @@ function AI_FORMATION:SetFlightModeFormation( FollowGroup )
|
||||
FollowGroup:SetState( FollowGroup, "PreviousMode", FollowGroup:GetState( FollowGroup, "Mode" ) )
|
||||
FollowGroup:SetState( FollowGroup, "Mode", self.__Enum.Mode.Formation )
|
||||
else
|
||||
self.EscortGroupSet:ForSomeGroupAlive(
|
||||
self.FollowGroupSet:ForSomeGroupAlive(
|
||||
--- @param Core.Group#GROUP EscortGroup
|
||||
function( FollowGroup )
|
||||
FollowGroup:SetState( FollowGroup, "PreviousMode", FollowGroup:GetState( FollowGroup, "Mode" ) )
|
||||
@@ -1065,7 +1067,7 @@ end
|
||||
-- @param Core.Set#SET_GROUP FollowGroupSet The following set of groups.
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @pram #string To The to state.
|
||||
-- @param #string To The to state.
|
||||
function AI_FORMATION:onafterStop(FollowGroupSet, From, Event, To) --R2.1
|
||||
self:E("Stopping formation.")
|
||||
end
|
||||
@@ -1075,7 +1077,7 @@ end
|
||||
-- @param Core.Set#SET_GROUP FollowGroupSet The following set of groups.
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @pram #string To The to state.
|
||||
-- @param #string To The to state.
|
||||
function AI_FORMATION:onbeforeFollow( FollowGroupSet, From, Event, To ) --R2.1
|
||||
if From=="Stopped" then
|
||||
return false -- Deny transition.
|
||||
@@ -1083,7 +1085,12 @@ function AI_FORMATION:onbeforeFollow( FollowGroupSet, From, Event, To ) --R2.1
|
||||
return true
|
||||
end
|
||||
|
||||
--- @param #AI_FORMATION self
|
||||
--- Enter following state.
|
||||
-- @param #AI_FORMATION self
|
||||
-- @param Core.Set#SET_GROUP FollowGroupSet The following set of groups.
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To The to state.
|
||||
function AI_FORMATION:onenterFollowing( FollowGroupSet ) --R2.1
|
||||
|
||||
if self.FollowUnit:IsAlive() then
|
||||
@@ -1093,153 +1100,184 @@ function AI_FORMATION:onenterFollowing( FollowGroupSet ) --R2.1
|
||||
local CT1, CT2, CV1, CV2
|
||||
CT1 = ClientUnit:GetState( self, "CT1" )
|
||||
|
||||
local CuVec3=ClientUnit:GetVec3()
|
||||
|
||||
if CT1 == nil or CT1 == 0 then
|
||||
ClientUnit:SetState( self, "CV1", ClientUnit:GetPointVec3() )
|
||||
ClientUnit:SetState( self, "CV1", CuVec3)
|
||||
ClientUnit:SetState( self, "CT1", timer.getTime() )
|
||||
else
|
||||
CT1 = ClientUnit:GetState( self, "CT1" )
|
||||
CT2 = timer.getTime()
|
||||
CV1 = ClientUnit:GetState( self, "CV1" )
|
||||
CV2 = ClientUnit:GetPointVec3()
|
||||
CV2 = CuVec3
|
||||
|
||||
ClientUnit:SetState( self, "CT1", CT2 )
|
||||
ClientUnit:SetState( self, "CV1", CV2 )
|
||||
end
|
||||
|
||||
FollowGroupSet:ForEachGroupAlive(
|
||||
--- @param Wrapper.Group#GROUP FollowGroup
|
||||
-- @param Wrapper.Unit#UNIT ClientUnit
|
||||
function( FollowGroup, Formation, ClientUnit, CT1, CV1, CT2, CV2 )
|
||||
|
||||
if FollowGroup:GetState( FollowGroup, "Mode" ) == self.__Enum.Mode.Formation then
|
||||
|
||||
self:T({Mode=FollowGroup:GetState( FollowGroup, "Mode" )})
|
||||
|
||||
FollowGroup:OptionROTEvadeFire()
|
||||
FollowGroup:OptionROEReturnFire()
|
||||
|
||||
local GroupUnit = FollowGroup:GetUnit( 1 )
|
||||
local FollowFormation = FollowGroup:GetState( self, "FormationVec3" )
|
||||
if FollowFormation then
|
||||
local FollowDistance = FollowFormation.x
|
||||
|
||||
local GT1 = GroupUnit:GetState( self, "GT1" )
|
||||
|
||||
if CT1 == nil or CT1 == 0 or GT1 == nil or GT1 == 0 then
|
||||
GroupUnit:SetState( self, "GV1", GroupUnit:GetPointVec3() )
|
||||
GroupUnit:SetState( self, "GT1", timer.getTime() )
|
||||
else
|
||||
local CD = ( ( CV2.x - CV1.x )^2 + ( CV2.y - CV1.y )^2 + ( CV2.z - CV1.z )^2 ) ^ 0.5
|
||||
local CT = CT2 - CT1
|
||||
|
||||
local CS = ( 3600 / CT ) * ( CD / 1000 ) / 3.6
|
||||
|
||||
local CDv = { x = CV2.x - CV1.x, y = CV2.y - CV1.y, z = CV2.z - CV1.z }
|
||||
local Ca = math.atan2( CDv.x, CDv.z )
|
||||
|
||||
local GT1 = GroupUnit:GetState( self, "GT1" )
|
||||
local GT2 = timer.getTime()
|
||||
local GV1 = GroupUnit:GetState( self, "GV1" )
|
||||
local GV2 = GroupUnit:GetPointVec3()
|
||||
GV2:AddX( math.random( -Formation.FlightRandomization / 2, Formation.FlightRandomization / 2 ) )
|
||||
GV2:AddY( math.random( -Formation.FlightRandomization / 2, Formation.FlightRandomization / 2 ) )
|
||||
GV2:AddZ( math.random( -Formation.FlightRandomization / 2, Formation.FlightRandomization / 2 ) )
|
||||
GroupUnit:SetState( self, "GT1", GT2 )
|
||||
GroupUnit:SetState( self, "GV1", GV2 )
|
||||
|
||||
|
||||
local GD = ( ( GV2.x - GV1.x )^2 + ( GV2.y - GV1.y )^2 + ( GV2.z - GV1.z )^2 ) ^ 0.5
|
||||
local GT = GT2 - GT1
|
||||
|
||||
|
||||
-- Calculate the distance
|
||||
local GDv = { x = GV2.x - CV1.x, y = GV2.y - CV1.y, z = GV2.z - CV1.z }
|
||||
local Alpha_T = math.atan2( GDv.x, GDv.z ) - math.atan2( CDv.x, CDv.z )
|
||||
local Alpha_R = ( Alpha_T < 0 ) and Alpha_T + 2 * math.pi or Alpha_T
|
||||
local Position = math.cos( Alpha_R )
|
||||
local GD = ( ( GDv.x )^2 + ( GDv.z )^2 ) ^ 0.5
|
||||
local Distance = GD * Position + - CS * 0.5
|
||||
|
||||
-- Calculate the group direction vector
|
||||
local GV = { x = GV2.x - CV2.x, y = GV2.y - CV2.y, z = GV2.z - CV2.z }
|
||||
|
||||
-- Calculate GH2, GH2 with the same height as CV2.
|
||||
local GH2 = { x = GV2.x, y = CV2.y + FollowFormation.y, z = GV2.z }
|
||||
|
||||
-- Calculate the angle of GV to the orthonormal plane
|
||||
local alpha = math.atan2( GV.x, GV.z )
|
||||
|
||||
local GVx = FollowFormation.z * math.cos( Ca ) + FollowFormation.x * math.sin( Ca )
|
||||
local GVz = FollowFormation.x * math.cos( Ca ) - FollowFormation.z * math.sin( Ca )
|
||||
|
||||
|
||||
-- Now we calculate the intersecting vector between the circle around CV2 with radius FollowDistance and GH2.
|
||||
-- From the GeoGebra model: CVI = (x(CV2) + FollowDistance cos(alpha), y(GH2) + FollowDistance sin(alpha), z(CV2))
|
||||
local Inclination = ( Distance + FollowFormation.x ) / 10
|
||||
if Inclination < -30 then
|
||||
Inclination = - 30
|
||||
end
|
||||
local CVI = { x = CV2.x + CS * 10 * math.sin(Ca),
|
||||
y = GH2.y + Inclination, -- + FollowFormation.y,
|
||||
y = GH2.y,
|
||||
z = CV2.z + CS * 10 * math.cos(Ca),
|
||||
}
|
||||
|
||||
-- Calculate the direction vector DV of the escort group. We use CVI as the base and CV2 as the direction.
|
||||
local DV = { x = CV2.x - CVI.x, y = CV2.y - CVI.y, z = CV2.z - CVI.z }
|
||||
|
||||
-- We now calculate the unary direction vector DVu, so that we can multiply DVu with the speed, which is expressed in meters / s.
|
||||
-- We need to calculate this vector to predict the point the escort group needs to fly to according its speed.
|
||||
-- The distance of the destination point should be far enough not to have the aircraft starting to swipe left to right...
|
||||
local DVu = { x = DV.x / FollowDistance, y = DV.y, z = DV.z / FollowDistance }
|
||||
|
||||
-- Now we can calculate the group destination vector GDV.
|
||||
local GDV = { x = CVI.x, y = CVI.y, z = CVI.z }
|
||||
|
||||
local ADDx = FollowFormation.x * math.cos(alpha) - FollowFormation.z * math.sin(alpha)
|
||||
local ADDz = FollowFormation.z * math.cos(alpha) + FollowFormation.x * math.sin(alpha)
|
||||
|
||||
local GDV_Formation = {
|
||||
x = GDV.x - GVx,
|
||||
y = GDV.y,
|
||||
z = GDV.z - GVz
|
||||
}
|
||||
|
||||
if self.SmokeDirectionVector == true then
|
||||
trigger.action.smoke( GDV, trigger.smokeColor.Green )
|
||||
trigger.action.smoke( GDV_Formation, trigger.smokeColor.White )
|
||||
end
|
||||
|
||||
|
||||
|
||||
local Time = 120
|
||||
|
||||
local Speed = - ( Distance + FollowFormation.x ) / Time
|
||||
|
||||
if Distance > -10000 then
|
||||
Speed = - ( Distance + FollowFormation.x ) / 60
|
||||
end
|
||||
|
||||
if Distance > -2500 then
|
||||
Speed = - ( Distance + FollowFormation.x ) / 20
|
||||
end
|
||||
|
||||
local GS = Speed + CS
|
||||
|
||||
self:F( { Distance = Distance, Speed = Speed, CS = CS, GS = GS } )
|
||||
|
||||
|
||||
-- Now route the escort to the desired point with the desired speed.
|
||||
FollowGroup:RouteToVec3( GDV_Formation, GS ) -- DCS models speed in Mps (Miles per second)
|
||||
end
|
||||
end
|
||||
end
|
||||
end,
|
||||
self, ClientUnit, CT1, CV1, CT2, CV2
|
||||
)
|
||||
--FollowGroupSet:ForEachGroupAlive( bla, self, ClientUnit, CT1, CV1, CT2, CV2)
|
||||
|
||||
for _,_group in pairs(FollowGroupSet:GetSet()) do
|
||||
local group=_group --Wrapper.Group#GROUP
|
||||
if group and group:IsAlive() then
|
||||
self:FollowMe(group, ClientUnit, CT1, CV1, CT2, CV2)
|
||||
end
|
||||
end
|
||||
|
||||
self:__Follow( -self.dtFollow )
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- Follow me.
|
||||
-- @param #AI_FORMATION self
|
||||
-- @param Wrapper.Group#GROUP FollowGroup Follow group.
|
||||
-- @param Wrapper.Unit#UNIT ClientUnit Client Unit.
|
||||
-- @param DCS#Time CT1 Time
|
||||
-- @param DCS#Vec3 CV1 Vec3
|
||||
-- @param DCS#Time CT2 Time
|
||||
-- @param DCS#Vec3 CV2 Vec3
|
||||
function AI_FORMATION:FollowMe(FollowGroup, ClientUnit, CT1, CV1, CT2, CV2)
|
||||
|
||||
if FollowGroup:GetState( FollowGroup, "Mode" ) == self.__Enum.Mode.Formation and not self:Is("Stopped") then
|
||||
|
||||
self:T({Mode=FollowGroup:GetState( FollowGroup, "Mode" )})
|
||||
|
||||
FollowGroup:OptionROTEvadeFire()
|
||||
FollowGroup:OptionROEReturnFire()
|
||||
|
||||
local GroupUnit = FollowGroup:GetUnit( 1 )
|
||||
|
||||
local GuVec3=GroupUnit:GetVec3()
|
||||
|
||||
local FollowFormation = FollowGroup:GetState( self, "FormationVec3" )
|
||||
|
||||
if FollowFormation then
|
||||
local FollowDistance = FollowFormation.x
|
||||
|
||||
local GT1 = GroupUnit:GetState( self, "GT1" )
|
||||
|
||||
if CT1 == nil or CT1 == 0 or GT1 == nil or GT1 == 0 then
|
||||
GroupUnit:SetState( self, "GV1", GuVec3)
|
||||
GroupUnit:SetState( self, "GT1", timer.getTime() )
|
||||
else
|
||||
local CD = ( ( CV2.x - CV1.x )^2 + ( CV2.y - CV1.y )^2 + ( CV2.z - CV1.z )^2 ) ^ 0.5
|
||||
local CT = CT2 - CT1
|
||||
|
||||
local CS = ( 3600 / CT ) * ( CD / 1000 ) / 3.6
|
||||
|
||||
local CDv = { x = CV2.x - CV1.x, y = CV2.y - CV1.y, z = CV2.z - CV1.z }
|
||||
local Ca = math.atan2( CDv.x, CDv.z )
|
||||
|
||||
local GT1 = GroupUnit:GetState( self, "GT1" )
|
||||
local GT2 = timer.getTime()
|
||||
|
||||
local GV1 = GroupUnit:GetState( self, "GV1" )
|
||||
local GV2 = GuVec3
|
||||
|
||||
--[[
|
||||
GV2:AddX( math.random( -Formation.FlightRandomization / 2, Formation.FlightRandomization / 2 ) )
|
||||
GV2:AddY( math.random( -Formation.FlightRandomization / 2, Formation.FlightRandomization / 2 ) )
|
||||
GV2:AddZ( math.random( -Formation.FlightRandomization / 2, Formation.FlightRandomization / 2 ) )
|
||||
]]
|
||||
|
||||
GV2.x=GV2.x+math.random( -self.FlightRandomization / 2, self.FlightRandomization / 2 )
|
||||
GV2.y=GV2.y+math.random( -self.FlightRandomization / 2, self.FlightRandomization / 2 )
|
||||
GV2.z=GV2.z+math.random( -self.FlightRandomization / 2, self.FlightRandomization / 2 )
|
||||
|
||||
|
||||
GroupUnit:SetState( self, "GT1", GT2 )
|
||||
GroupUnit:SetState( self, "GV1", GV2 )
|
||||
|
||||
|
||||
local GD = ( ( GV2.x - GV1.x )^2 + ( GV2.y - GV1.y )^2 + ( GV2.z - GV1.z )^2 ) ^ 0.5
|
||||
local GT = GT2 - GT1
|
||||
|
||||
|
||||
-- Calculate the distance
|
||||
local GDv = { x = GV2.x - CV1.x, y = GV2.y - CV1.y, z = GV2.z - CV1.z }
|
||||
local Alpha_T = math.atan2( GDv.x, GDv.z ) - math.atan2( CDv.x, CDv.z )
|
||||
local Alpha_R = ( Alpha_T < 0 ) and Alpha_T + 2 * math.pi or Alpha_T
|
||||
local Position = math.cos( Alpha_R )
|
||||
local GD = ( ( GDv.x )^2 + ( GDv.z )^2 ) ^ 0.5
|
||||
local Distance = GD * Position + - CS * 0.5
|
||||
|
||||
-- Calculate the group direction vector
|
||||
local GV = { x = GV2.x - CV2.x, y = GV2.y - CV2.y, z = GV2.z - CV2.z }
|
||||
|
||||
-- Calculate GH2, GH2 with the same height as CV2.
|
||||
local GH2 = { x = GV2.x, y = CV2.y + FollowFormation.y, z = GV2.z }
|
||||
|
||||
-- Calculate the angle of GV to the orthonormal plane
|
||||
local alpha = math.atan2( GV.x, GV.z )
|
||||
|
||||
local GVx = FollowFormation.z * math.cos( Ca ) + FollowFormation.x * math.sin( Ca )
|
||||
local GVz = FollowFormation.x * math.cos( Ca ) - FollowFormation.z * math.sin( Ca )
|
||||
|
||||
|
||||
-- Now we calculate the intersecting vector between the circle around CV2 with radius FollowDistance and GH2.
|
||||
-- From the GeoGebra model: CVI = (x(CV2) + FollowDistance cos(alpha), y(GH2) + FollowDistance sin(alpha), z(CV2))
|
||||
local Inclination = ( Distance + FollowFormation.x ) / 10
|
||||
if Inclination < -30 then
|
||||
Inclination = - 30
|
||||
end
|
||||
|
||||
local CVI = {
|
||||
x = CV2.x + CS * 10 * math.sin(Ca),
|
||||
y = GH2.y + Inclination, -- + FollowFormation.y,
|
||||
y = GH2.y,
|
||||
z = CV2.z + CS * 10 * math.cos(Ca),
|
||||
}
|
||||
|
||||
-- Calculate the direction vector DV of the escort group. We use CVI as the base and CV2 as the direction.
|
||||
local DV = { x = CV2.x - CVI.x, y = CV2.y - CVI.y, z = CV2.z - CVI.z }
|
||||
|
||||
-- We now calculate the unary direction vector DVu, so that we can multiply DVu with the speed, which is expressed in meters / s.
|
||||
-- We need to calculate this vector to predict the point the escort group needs to fly to according its speed.
|
||||
-- The distance of the destination point should be far enough not to have the aircraft starting to swipe left to right...
|
||||
local DVu = { x = DV.x / FollowDistance, y = DV.y, z = DV.z / FollowDistance }
|
||||
|
||||
-- Now we can calculate the group destination vector GDV.
|
||||
local GDV = { x = CVI.x, y = CVI.y, z = CVI.z }
|
||||
|
||||
local ADDx = FollowFormation.x * math.cos(alpha) - FollowFormation.z * math.sin(alpha)
|
||||
local ADDz = FollowFormation.z * math.cos(alpha) + FollowFormation.x * math.sin(alpha)
|
||||
|
||||
local GDV_Formation = {
|
||||
x = GDV.x - GVx,
|
||||
y = GDV.y,
|
||||
z = GDV.z - GVz
|
||||
}
|
||||
|
||||
-- Debug smoke.
|
||||
if self.SmokeDirectionVector == true then
|
||||
trigger.action.smoke( GDV, trigger.smokeColor.Green )
|
||||
trigger.action.smoke( GDV_Formation, trigger.smokeColor.White )
|
||||
end
|
||||
|
||||
|
||||
|
||||
local Time = 120
|
||||
|
||||
local Speed = - ( Distance + FollowFormation.x ) / Time
|
||||
|
||||
if Distance > -10000 then
|
||||
Speed = - ( Distance + FollowFormation.x ) / 60
|
||||
end
|
||||
|
||||
if Distance > -2500 then
|
||||
Speed = - ( Distance + FollowFormation.x ) / 20
|
||||
end
|
||||
|
||||
local GS = Speed + CS
|
||||
|
||||
--self:F( { Distance = Distance, Speed = Speed, CS = CS, GS = GS } )
|
||||
|
||||
-- Now route the escort to the desired point with the desired speed.
|
||||
FollowGroup:RouteToVec3( GDV_Formation, GS ) -- DCS models speed in Mps (Miles per second)
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -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.
|
||||
--
|
||||
-- ===
|
||||
@@ -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 )
|
||||
|
||||
@@ -725,8 +725,8 @@ function AI_PATROL_ZONE:onafterRoute( Controllable, From, Event, To )
|
||||
return
|
||||
end
|
||||
|
||||
|
||||
if self.Controllable:IsAlive() then
|
||||
local life = self.Controllable:GetLife() or 0
|
||||
if self.Controllable:IsAlive() and life > 1 then
|
||||
-- Determine if the AIControllable is within the PatrolZone.
|
||||
-- If not, make a waypoint within the to that the AIControllable will fly at maximum speed to that point.
|
||||
|
||||
@@ -743,8 +743,9 @@ function AI_PATROL_ZONE:onafterRoute( Controllable, From, Event, To )
|
||||
if self.Controllable:InAir() == false then
|
||||
self:T( "Not in the air, finding route path within PatrolZone" )
|
||||
local CurrentVec2 = self.Controllable:GetVec2()
|
||||
--TODO: Create GetAltitude function for GROUP, and delete GetUnit(1).
|
||||
local CurrentAltitude = self.Controllable:GetUnit(1):GetAltitude()
|
||||
if not CurrentVec2 then return end
|
||||
--Done: Create GetAltitude function for GROUP, and delete GetUnit(1).
|
||||
local CurrentAltitude = self.Controllable:GetAltitude()
|
||||
local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y )
|
||||
local ToPatrolZoneSpeed = self.PatrolMaxSpeed
|
||||
local CurrentRoutePoint = CurrentPointVec3:WaypointAir(
|
||||
@@ -758,8 +759,9 @@ function AI_PATROL_ZONE:onafterRoute( Controllable, From, Event, To )
|
||||
else
|
||||
self:T( "In the air, finding route path within PatrolZone" )
|
||||
local CurrentVec2 = self.Controllable:GetVec2()
|
||||
--TODO: Create GetAltitude function for GROUP, and delete GetUnit(1).
|
||||
local CurrentAltitude = self.Controllable:GetUnit(1):GetAltitude()
|
||||
if not CurrentVec2 then return end
|
||||
--DONE: Create GetAltitude function for GROUP, and delete GetUnit(1).
|
||||
local CurrentAltitude = self.Controllable:GetAltitude()
|
||||
local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y )
|
||||
local ToPatrolZoneSpeed = self.PatrolMaxSpeed
|
||||
local CurrentRoutePoint = CurrentPointVec3:WaypointAir(
|
||||
@@ -870,9 +872,10 @@ function AI_PATROL_ZONE:onafterRTB()
|
||||
|
||||
--- Calculate the current route point.
|
||||
local CurrentVec2 = self.Controllable:GetVec2()
|
||||
|
||||
--TODO: Create GetAltitude function for GROUP, and delete GetUnit(1).
|
||||
local CurrentAltitude = self.Controllable:GetUnit(1):GetAltitude()
|
||||
if not CurrentVec2 then return end
|
||||
--DONE: Create GetAltitude function for GROUP, and delete GetUnit(1).
|
||||
--local CurrentAltitude = self.Controllable:GetUnit(1):GetAltitude()
|
||||
local CurrentAltitude = self.Controllable:GetAltitude()
|
||||
local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y )
|
||||
local ToPatrolZoneSpeed = self.PatrolMaxSpeed
|
||||
local CurrentRoutePoint = CurrentPointVec3:WaypointAir(
|
||||
|
||||
@@ -190,7 +190,7 @@ do -- ACT_ROUTE
|
||||
self:F( { ZoneName = ZoneName } )
|
||||
local Zone = Zone -- Core.Zone#ZONE
|
||||
local ZoneCoord = Zone:GetCoordinate()
|
||||
local ZoneDistance = ZoneCoord:Get2DDistance( self.Coordinate )
|
||||
local ZoneDistance = ZoneCoord:Get2DDistance( Coordinate )
|
||||
self:F( { ShortestDistance, ShortestReferenceName } )
|
||||
if ShortestDistance == 0 or ZoneDistance < ShortestDistance then
|
||||
ShortestDistance = ZoneDistance
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
--- **Core** -- Management of CARGO logistics, that can be transported from and to transportation carriers.
|
||||
--- **Cargo** - Management of CARGO logistics, that can be transported from and to transportation carriers.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
@@ -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:
|
||||
--
|
||||
@@ -206,27 +206,29 @@
|
||||
-- * **NR=** Provide the maximum range in meters when the cargo units will be boarded within the carrier during boarding.
|
||||
-- Note that this option is optional, so can be omitted. The default value of the RR is 10 meters.
|
||||
--
|
||||
-- ## 5.2) The \#CARGO tag to create CARGO_CRATE objects:
|
||||
-- ## 5.2) The \#CARGO tag to create CARGO_CRATE or CARGO_SLINGLOAD objects:
|
||||
--
|
||||
-- You can also use the \#CARGO tag on **static** objects, including **static cargo** objects of the mission editor.
|
||||
--
|
||||
-- For example, the following #CARGO naming in the **static name** of the object, will create a CARGO_CRATE object when the mission starts.
|
||||
--
|
||||
-- `Static #CARGO(T=Workmaterials,RR=500,NR=25)`
|
||||
-- `Static #CARGO(T=Workmaterials,C=CRATE,RR=500,NR=25)`
|
||||
--
|
||||
-- This will create a CARGO_CRATE object:
|
||||
--
|
||||
-- * with the group name `Static #CARGO`
|
||||
-- * is of type `Workmaterials`
|
||||
-- * is of category `CRATE` (as opposed to `SLING`)
|
||||
-- * 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:
|
||||
--
|
||||
-- `StaticName #CARGO(T=CargoTypeName,RR=Range,NR=Range)`
|
||||
-- `StaticName #CARGO(T=CargoTypeName,C=Category,RR=Range,NR=Range)`
|
||||
--
|
||||
-- * **T=** Provide a text that contains the type name of the cargo object. This type name can be used to filter cargo within a SET_CARGO object.
|
||||
-- * **C=** Provide either `CRATE` or `SLING` to have this static created as a CARGO_CRATE or CARGO_SLINGLOAD respectivly.
|
||||
-- * **RR=** Provide the minimal range in meters when the report to the carrier, and board to the carrier.
|
||||
-- Note that this option is optional, so can be omitted. The default value of the RR is 250 meters.
|
||||
-- * **NR=** Provide the maximum range in meters when the cargo units will be boarded within the carrier during boarding.
|
||||
@@ -1066,18 +1068,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 +1096,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
|
||||
|
||||
|
||||
943
Moose Development/Moose/Core/Astar.lua
Normal file
943
Moose Development/Moose/Core/Astar.lua
Normal file
@@ -0,0 +1,943 @@
|
||||
--- **Core** - A* Pathfinding.
|
||||
--
|
||||
-- **Main Features:**
|
||||
--
|
||||
-- * Find path from A to B.
|
||||
-- * Pre-defined as well as custom valid neighbour functions.
|
||||
-- * Pre-defined as well as custom cost functions.
|
||||
-- * Easy rectangular grid setup.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **funkyfranky**
|
||||
--
|
||||
-- ===
|
||||
-- @module Core.Astar
|
||||
-- @image CORE_Astar.png
|
||||
|
||||
|
||||
--- ASTAR class.
|
||||
-- @type ASTAR
|
||||
-- @field #string ClassName Name of the class.
|
||||
-- @field #boolean Debug Debug mode. Messages to all about status.
|
||||
-- @field #string lid Class id string for output to DCS log file.
|
||||
-- @field #table nodes Table of nodes.
|
||||
-- @field #number counter Node counter.
|
||||
-- @field #number Nnodes Number of nodes.
|
||||
-- @field #number nvalid Number of nvalid calls.
|
||||
-- @field #number nvalidcache Number of cached valid evals.
|
||||
-- @field #number ncost Number of cost evaluations.
|
||||
-- @field #number ncostcache Number of cached cost evals.
|
||||
-- @field #ASTAR.Node startNode Start node.
|
||||
-- @field #ASTAR.Node endNode End node.
|
||||
-- @field Core.Point#COORDINATE startCoord Start coordinate.
|
||||
-- @field Core.Point#COORDINATE endCoord End coordinate.
|
||||
-- @field #function ValidNeighbourFunc Function to check if a node is valid.
|
||||
-- @field #table ValidNeighbourArg Optional arguments passed to the valid neighbour function.
|
||||
-- @field #function CostFunc Function to calculate the heuristic "cost" to go from one node to another.
|
||||
-- @field #table CostArg Optional arguments passed to the cost function.
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
--- *When nothing goes right... Go left!*
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- # The ASTAR Concept
|
||||
--
|
||||
-- Pathfinding algorithm.
|
||||
--
|
||||
--
|
||||
-- # Start and Goal
|
||||
--
|
||||
-- The first thing we need to define is obviously the place where we want to start and where we want to go eventually.
|
||||
--
|
||||
-- ## Start
|
||||
--
|
||||
-- The start
|
||||
--
|
||||
-- ## Goal
|
||||
--
|
||||
--
|
||||
-- # Nodes
|
||||
--
|
||||
-- ## Rectangular Grid
|
||||
--
|
||||
-- A rectangular grid can be created using the @{#ASTAR.CreateGrid}(*ValidSurfaceTypes, BoxHY, SpaceX, deltaX, deltaY, MarkGrid*), where
|
||||
--
|
||||
-- * *ValidSurfaceTypes* is a table of valid surface types. By default all surface types are valid.
|
||||
-- * *BoxXY* is the width of the grid perpendicular the the line between start and end node. Default is 40,000 meters (40 km).
|
||||
-- * *SpaceX* is the additional space behind the start and end nodes. Default is 20,000 meters (20 km).
|
||||
-- * *deltaX* is the grid spacing between nodes in the direction of start and end node. Default is 2,000 meters (2 km).
|
||||
-- * *deltaY* is the grid spacing perpendicular to the direction of start and end node. Default is the same as *deltaX*.
|
||||
-- * *MarkGrid* If set to *true*, this places marker on the F10 map on each grid node. Note that this can stall DCS if too many nodes are created.
|
||||
--
|
||||
-- ## Valid Surfaces
|
||||
--
|
||||
-- Certain unit types can only travel on certain surfaces types, for example
|
||||
--
|
||||
-- * Naval units can only travel on water (that also excludes shallow water in DCS currently),
|
||||
-- * Ground units can only traval on land.
|
||||
--
|
||||
-- By restricting the surface type in the grid construction, we also reduce the number of nodes, which makes the algorithm more efficient.
|
||||
--
|
||||
-- ## Box Width (BoxHY)
|
||||
--
|
||||
-- The box width needs to be large enough to capture all paths you want to consider.
|
||||
--
|
||||
-- ## Space in X
|
||||
--
|
||||
-- The space in X value is important if the algorithm needs to to backwards from the start node or needs to extend even further than the end node.
|
||||
--
|
||||
-- ## Grid Spacing
|
||||
--
|
||||
-- The grid spacing is an important factor as it determines the number of nodes and hence the performance of the algorithm. It should be as large as possible.
|
||||
-- However, if the value is too large, the algorithm might fail to get a valid path.
|
||||
--
|
||||
-- A good estimate of the grid spacing is to set it to be smaller (~ half the size) of the smallest gap you need to path.
|
||||
--
|
||||
-- # Valid Neighbours
|
||||
--
|
||||
-- The A* algorithm needs to know if a transition from one node to another is allowed or not. By default, hopping from one node to another is always possible.
|
||||
--
|
||||
-- ## Line of Sight
|
||||
--
|
||||
-- For naval
|
||||
--
|
||||
--
|
||||
-- # Heuristic Cost
|
||||
--
|
||||
-- In order to determine the optimal path, the pathfinding algorithm needs to know, how costly it is to go from one node to another.
|
||||
-- Often, this can simply be determined by the distance between two nodes. Therefore, the default cost function is set to be the 2D distance between two nodes.
|
||||
--
|
||||
--
|
||||
-- # Calculate the Path
|
||||
--
|
||||
-- Finally, we have to calculate the path. This is done by the @{ASTAR.GetPath}(*ExcludeStart, ExcludeEnd*) function. This function returns a table of nodes, which
|
||||
-- describe the optimal path from the start node to the end node.
|
||||
--
|
||||
-- By default, the start and end node are include in the table that is returned.
|
||||
--
|
||||
-- Note that a valid path must not always exist. So you should check if the function returns *nil*.
|
||||
--
|
||||
-- Common reasons that a path cannot be found are:
|
||||
--
|
||||
-- * The grid is too small ==> increase grid size, e.g. *BoxHY* and/or *SpaceX* if you use a rectangular grid.
|
||||
-- * The grid spacing is too large ==> decrease *deltaX* and/or *deltaY*
|
||||
-- * There simply is no valid path ==> you are screwed :(
|
||||
--
|
||||
--
|
||||
-- # Examples
|
||||
--
|
||||
-- ## Strait of Hormuz
|
||||
--
|
||||
-- Carrier Group finds its way through the Stait of Hormuz.
|
||||
--
|
||||
-- ##
|
||||
--
|
||||
--
|
||||
--
|
||||
-- @field #ASTAR
|
||||
ASTAR = {
|
||||
ClassName = "ASTAR",
|
||||
Debug = nil,
|
||||
lid = nil,
|
||||
nodes = {},
|
||||
counter = 1,
|
||||
Nnodes = 0,
|
||||
ncost = 0,
|
||||
ncostcache = 0,
|
||||
nvalid = 0,
|
||||
nvalidcache = 0,
|
||||
}
|
||||
|
||||
--- Node data.
|
||||
-- @type ASTAR.Node
|
||||
-- @field #number id Node id.
|
||||
-- @field Core.Point#COORDINATE coordinate Coordinate of the node.
|
||||
-- @field #number surfacetype Surface type.
|
||||
-- @field #table valid Cached valid/invalid nodes.
|
||||
-- @field #table cost Cached cost.
|
||||
|
||||
--- ASTAR infinity.
|
||||
-- @field #number INF
|
||||
ASTAR.INF=1/0
|
||||
|
||||
--- ASTAR class version.
|
||||
-- @field #string version
|
||||
ASTAR.version="0.4.0"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- TODO list
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
-- TODO: Add more valid neighbour functions.
|
||||
-- TODO: Write docs.
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Constructor
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Create a new ASTAR object.
|
||||
-- @param #ASTAR self
|
||||
-- @return #ASTAR self
|
||||
function ASTAR:New()
|
||||
|
||||
-- Inherit everything from INTEL class.
|
||||
local self=BASE:Inherit(self, BASE:New()) --#ASTAR
|
||||
|
||||
self.lid="ASTAR | "
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- User functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Set coordinate from where to start.
|
||||
-- @param #ASTAR self
|
||||
-- @param Core.Point#COORDINATE Coordinate Start coordinate.
|
||||
-- @return #ASTAR self
|
||||
function ASTAR:SetStartCoordinate(Coordinate)
|
||||
|
||||
self.startCoord=Coordinate
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set coordinate where you want to go.
|
||||
-- @param #ASTAR self
|
||||
-- @param Core.Point#COORDINATE Coordinate end coordinate.
|
||||
-- @return #ASTAR self
|
||||
function ASTAR:SetEndCoordinate(Coordinate)
|
||||
|
||||
self.endCoord=Coordinate
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Create a node from a given coordinate.
|
||||
-- @param #ASTAR self
|
||||
-- @param Core.Point#COORDINATE Coordinate The coordinate where to create the node.
|
||||
-- @return #ASTAR.Node The node.
|
||||
function ASTAR:GetNodeFromCoordinate(Coordinate)
|
||||
|
||||
local node={} --#ASTAR.Node
|
||||
|
||||
node.coordinate=Coordinate
|
||||
node.surfacetype=Coordinate:GetSurfaceType()
|
||||
node.id=self.counter
|
||||
|
||||
node.valid={}
|
||||
node.cost={}
|
||||
|
||||
self.counter=self.counter+1
|
||||
|
||||
return node
|
||||
end
|
||||
|
||||
|
||||
--- Add a node to the table of grid nodes.
|
||||
-- @param #ASTAR self
|
||||
-- @param #ASTAR.Node Node The node to be added.
|
||||
-- @return #ASTAR self
|
||||
function ASTAR:AddNode(Node)
|
||||
|
||||
self.nodes[Node.id]=Node
|
||||
self.Nnodes=self.Nnodes+1
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Add a node to the table of grid nodes specifying its coordinate.
|
||||
-- @param #ASTAR self
|
||||
-- @param Core.Point#COORDINATE Coordinate The coordinate where the node is created.
|
||||
-- @return #ASTAR.Node The node.
|
||||
function ASTAR:AddNodeFromCoordinate(Coordinate)
|
||||
|
||||
local node=self:GetNodeFromCoordinate(Coordinate)
|
||||
|
||||
self:AddNode(node)
|
||||
|
||||
return node
|
||||
end
|
||||
|
||||
--- Check if the coordinate of a node has is at a valid surface type.
|
||||
-- @param #ASTAR self
|
||||
-- @param #ASTAR.Node Node The node to be added.
|
||||
-- @param #table SurfaceTypes Surface types, for example `{land.SurfaceType.WATER}`. By default all surface types are valid.
|
||||
-- @return #boolean If true, surface type of node is valid.
|
||||
function ASTAR:CheckValidSurfaceType(Node, SurfaceTypes)
|
||||
|
||||
if SurfaceTypes then
|
||||
|
||||
if type(SurfaceTypes)~="table" then
|
||||
SurfaceTypes={SurfaceTypes}
|
||||
end
|
||||
|
||||
for _,surface in pairs(SurfaceTypes) do
|
||||
if surface==Node.surfacetype then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
|
||||
else
|
||||
return true
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Add a function to determine if a neighbour of a node is valid.
|
||||
-- @param #ASTAR self
|
||||
-- @param #function NeighbourFunction Function that needs to return *true* for a neighbour to be valid.
|
||||
-- @param ... Condition function arguments if any.
|
||||
-- @return #ASTAR self
|
||||
function ASTAR:SetValidNeighbourFunction(NeighbourFunction, ...)
|
||||
|
||||
self.ValidNeighbourFunc=NeighbourFunction
|
||||
|
||||
self.ValidNeighbourArg={}
|
||||
if arg then
|
||||
self.ValidNeighbourArg=arg
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Set valid neighbours to require line of sight between two nodes.
|
||||
-- @param #ASTAR self
|
||||
-- @param #number CorridorWidth Width of LoS corridor in meters.
|
||||
-- @return #ASTAR self
|
||||
function ASTAR:SetValidNeighbourLoS(CorridorWidth)
|
||||
|
||||
self:SetValidNeighbourFunction(ASTAR.LoS, CorridorWidth)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set valid neighbours to be in a certain distance.
|
||||
-- @param #ASTAR self
|
||||
-- @param #number MaxDistance Max distance between nodes in meters. Default is 2000 m.
|
||||
-- @return #ASTAR self
|
||||
function ASTAR:SetValidNeighbourDistance(MaxDistance)
|
||||
|
||||
self:SetValidNeighbourFunction(ASTAR.DistMax, MaxDistance)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set valid neighbours to be in a certain distance.
|
||||
-- @param #ASTAR self
|
||||
-- @param #number MaxDistance Max distance between nodes in meters. Default is 2000 m.
|
||||
-- @return #ASTAR self
|
||||
function ASTAR:SetValidNeighbourRoad(MaxDistance)
|
||||
|
||||
self:SetValidNeighbourFunction(ASTAR.Road, MaxDistance)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set the function which calculates the "cost" to go from one to another node.
|
||||
-- The first to arguments of this function are always the two nodes under consideration. But you can add optional arguments.
|
||||
-- Very often the distance between nodes is a good measure for the cost.
|
||||
-- @param #ASTAR self
|
||||
-- @param #function CostFunction Function that returns the "cost".
|
||||
-- @param ... Condition function arguments if any.
|
||||
-- @return #ASTAR self
|
||||
function ASTAR:SetCostFunction(CostFunction, ...)
|
||||
|
||||
self.CostFunc=CostFunction
|
||||
|
||||
self.CostArg={}
|
||||
if arg then
|
||||
self.CostArg=arg
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set heuristic cost to go from one node to another to be their 2D distance.
|
||||
-- @param #ASTAR self
|
||||
-- @return #ASTAR self
|
||||
function ASTAR:SetCostDist2D()
|
||||
|
||||
self:SetCostFunction(ASTAR.Dist2D)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set heuristic cost to go from one node to another to be their 3D distance.
|
||||
-- @param #ASTAR self
|
||||
-- @return #ASTAR self
|
||||
function ASTAR:SetCostDist3D()
|
||||
|
||||
self:SetCostFunction(ASTAR.Dist3D)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set heuristic cost to go from one node to another to be their 3D distance.
|
||||
-- @param #ASTAR self
|
||||
-- @return #ASTAR self
|
||||
function ASTAR:SetCostRoad()
|
||||
|
||||
self:SetCostFunction(ASTAR)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Grid functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Create a rectangular grid of nodes between star and end coordinate.
|
||||
-- The coordinate system is oriented along the line between start and end point.
|
||||
-- @param #ASTAR self
|
||||
-- @param #table ValidSurfaceTypes Valid surface types. By default is all surfaces are allowed.
|
||||
-- @param #number BoxHY Box "height" in meters along the y-coordinate. Default 40000 meters (40 km).
|
||||
-- @param #number SpaceX Additional space in meters before start and after end coordinate. Default 10000 meters (10 km).
|
||||
-- @param #number deltaX Increment in the direction of start to end coordinate in meters. Default 2000 meters.
|
||||
-- @param #number deltaY Increment perpendicular to the direction of start to end coordinate in meters. Default is same as deltaX.
|
||||
-- @param #boolean MarkGrid If true, create F10 map markers at grid nodes.
|
||||
-- @return #ASTAR self
|
||||
function ASTAR:CreateGrid(ValidSurfaceTypes, BoxHY, SpaceX, deltaX, deltaY, MarkGrid)
|
||||
|
||||
-- Note that internally
|
||||
-- x coordinate is z: x-->z Line from start to end
|
||||
-- y coordinate is x: y-->x Perpendicular
|
||||
|
||||
-- Grid length and width.
|
||||
local Dz=SpaceX or 10000
|
||||
local Dx=BoxHY and BoxHY/2 or 20000
|
||||
|
||||
-- Increments.
|
||||
local dz=deltaX or 2000
|
||||
local dx=deltaY or dz
|
||||
|
||||
-- Heading from start to end coordinate.
|
||||
local angle=self.startCoord:HeadingTo(self.endCoord)
|
||||
|
||||
--Distance between start and end.
|
||||
local dist=self.startCoord:Get2DDistance(self.endCoord)+2*Dz
|
||||
|
||||
-- Origin of map. Needed to translate back to wanted position.
|
||||
local co=COORDINATE:New(0, 0, 0)
|
||||
local do1=co:Get2DDistance(self.startCoord)
|
||||
local ho1=co:HeadingTo(self.startCoord)
|
||||
|
||||
-- Start of grid.
|
||||
local xmin=-Dx
|
||||
local zmin=-Dz
|
||||
|
||||
-- Number of grid points.
|
||||
local nz=dist/dz+1
|
||||
local nx=2*Dx/dx+1
|
||||
|
||||
-- Debug info.
|
||||
local text=string.format("Building grid with nx=%d ny=%d => total=%d nodes", nx, nz, nx*nz)
|
||||
self:T(self.lid..text)
|
||||
|
||||
-- Loop over x and z coordinate to create a 2D grid.
|
||||
for i=1,nx do
|
||||
|
||||
-- x coordinate perpendicular to z.
|
||||
local x=xmin+dx*(i-1)
|
||||
|
||||
for j=1,nz do
|
||||
|
||||
-- z coordinate connecting start and end.
|
||||
local z=zmin+dz*(j-1)
|
||||
|
||||
-- Rotate 2D.
|
||||
local vec3=UTILS.Rotate2D({x=x, y=0, z=z}, angle)
|
||||
|
||||
-- Coordinate of the node.
|
||||
local c=COORDINATE:New(vec3.z, vec3.y, vec3.x):Translate(do1, ho1, true)
|
||||
|
||||
-- Create a node at this coordinate.
|
||||
local node=self:GetNodeFromCoordinate(c)
|
||||
|
||||
-- Check if node has valid surface type.
|
||||
if self:CheckValidSurfaceType(node, ValidSurfaceTypes) then
|
||||
|
||||
if MarkGrid then
|
||||
c:MarkToAll(string.format("i=%d, j=%d surface=%d", i, j, node.surfacetype))
|
||||
end
|
||||
|
||||
-- Add node to grid.
|
||||
self:AddNode(node)
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
-- Debug info.
|
||||
local text=string.format("Done building grid!")
|
||||
self:T2(self.lid..text)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Valid neighbour functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Function to check if two nodes have line of sight (LoS).
|
||||
-- @param #ASTAR.Node nodeA First node.
|
||||
-- @param #ASTAR.Node nodeB Other node.
|
||||
-- @param #number corridor (Optional) Width of corridor in meters.
|
||||
-- @return #boolean If true, two nodes have LoS.
|
||||
function ASTAR.LoS(nodeA, nodeB, corridor)
|
||||
|
||||
local offset=1
|
||||
|
||||
local dx=corridor and corridor/2 or nil
|
||||
local dy=dx
|
||||
|
||||
local cA=nodeA.coordinate:GetVec3()
|
||||
local cB=nodeB.coordinate:GetVec3()
|
||||
cA.y=offset
|
||||
cB.y=offset
|
||||
|
||||
local los=land.isVisible(cA, cB)
|
||||
|
||||
if los and corridor then
|
||||
|
||||
-- Heading from A to B.
|
||||
local heading=nodeA.coordinate:HeadingTo(nodeB.coordinate)
|
||||
|
||||
local Ap=UTILS.VecTranslate(cA, dx, heading+90)
|
||||
local Bp=UTILS.VecTranslate(cB, dx, heading+90)
|
||||
|
||||
los=land.isVisible(Ap, Bp)
|
||||
|
||||
if los then
|
||||
|
||||
local Am=UTILS.VecTranslate(cA, dx, heading-90)
|
||||
local Bm=UTILS.VecTranslate(cB, dx, heading-90)
|
||||
|
||||
los=land.isVisible(Am, Bm)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
return los
|
||||
end
|
||||
|
||||
--- Function to check if two nodes have a road connection.
|
||||
-- @param #ASTAR.Node nodeA First node.
|
||||
-- @param #ASTAR.Node nodeB Other node.
|
||||
-- @return #boolean If true, two nodes are connected via a road.
|
||||
function ASTAR.Road(nodeA, nodeB)
|
||||
|
||||
local path=land.findPathOnRoads("roads", nodeA.coordinate.x, nodeA.coordinate.z, nodeB.coordinate.x, nodeB.coordinate.z)
|
||||
|
||||
if path then
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Function to check if distance between two nodes is less than a threshold distance.
|
||||
-- @param #ASTAR.Node nodeA First node.
|
||||
-- @param #ASTAR.Node nodeB Other node.
|
||||
-- @param #number distmax Max distance in meters. Default is 2000 m.
|
||||
-- @return #boolean If true, distance between the two nodes is below threshold.
|
||||
function ASTAR.DistMax(nodeA, nodeB, distmax)
|
||||
|
||||
distmax=distmax or 2000
|
||||
|
||||
local dist=nodeA.coordinate:Get2DDistance(nodeB.coordinate)
|
||||
|
||||
return dist<=distmax
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Heuristic cost functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Heuristic cost is given by the 2D distance between the nodes.
|
||||
-- @param #ASTAR.Node nodeA First node.
|
||||
-- @param #ASTAR.Node nodeB Other node.
|
||||
-- @return #number Distance between the two nodes.
|
||||
function ASTAR.Dist2D(nodeA, nodeB)
|
||||
local dist=nodeA.coordinate:Get2DDistance(nodeB)
|
||||
return dist
|
||||
end
|
||||
|
||||
--- Heuristic cost is given by the 3D distance between the nodes.
|
||||
-- @param #ASTAR.Node nodeA First node.
|
||||
-- @param #ASTAR.Node nodeB Other node.
|
||||
-- @return #number Distance between the two nodes.
|
||||
function ASTAR.Dist3D(nodeA, nodeB)
|
||||
local dist=nodeA.coordinate:Get3DDistance(nodeB.coordinate)
|
||||
return dist
|
||||
end
|
||||
|
||||
--- Heuristic cost is given by the distance between the nodes on road.
|
||||
-- @param #ASTAR.Node nodeA First node.
|
||||
-- @param #ASTAR.Node nodeB Other node.
|
||||
-- @return #number Distance between the two nodes.
|
||||
function ASTAR.DistRoad(nodeA, nodeB)
|
||||
|
||||
-- Get the path.
|
||||
local path=land.findPathOnRoads("roads", nodeA.coordinate.x, nodeA.coordinate.z, nodeB.coordinate.x, nodeB.coordinate.z)
|
||||
|
||||
if path then
|
||||
|
||||
local dist=0
|
||||
|
||||
for i=2,#path do
|
||||
local b=path[i] --DCS#Vec2
|
||||
local a=path[i-1] --DCS#Vec2
|
||||
|
||||
dist=dist+UTILS.VecDist2D(a,b)
|
||||
|
||||
end
|
||||
|
||||
return dist
|
||||
end
|
||||
|
||||
|
||||
return math.huge
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Misc functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Find the closest node from a given coordinate.
|
||||
-- @param #ASTAR self
|
||||
-- @param Core.Point#COORDINATE Coordinate.
|
||||
-- @return #ASTAR.Node Cloest node to the coordinate.
|
||||
-- @return #number Distance to closest node in meters.
|
||||
function ASTAR:FindClosestNode(Coordinate)
|
||||
|
||||
local distMin=math.huge
|
||||
local closeNode=nil
|
||||
|
||||
for _,_node in pairs(self.nodes) do
|
||||
local node=_node --#ASTAR.Node
|
||||
|
||||
local dist=node.coordinate:Get2DDistance(Coordinate)
|
||||
|
||||
if dist<distMin then
|
||||
distMin=dist
|
||||
closeNode=node
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
return closeNode, distMin
|
||||
end
|
||||
|
||||
--- Find the start node.
|
||||
-- @param #ASTAR self
|
||||
-- @param #ASTAR.Node Node The node to be added to the nodes table.
|
||||
-- @return #ASTAR self
|
||||
function ASTAR:FindStartNode()
|
||||
|
||||
local node, dist=self:FindClosestNode(self.startCoord)
|
||||
|
||||
self.startNode=node
|
||||
|
||||
if dist>1000 then
|
||||
self:T(self.lid.."Adding start node to node grid!")
|
||||
self:AddNode(node)
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Add a node.
|
||||
-- @param #ASTAR self
|
||||
-- @param #ASTAR.Node Node The node to be added to the nodes table.
|
||||
-- @return #ASTAR self
|
||||
function ASTAR:FindEndNode()
|
||||
|
||||
local node, dist=self:FindClosestNode(self.endCoord)
|
||||
|
||||
self.endNode=node
|
||||
|
||||
if dist>1000 then
|
||||
self:T(self.lid.."Adding end node to node grid!")
|
||||
self:AddNode(node)
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Main A* pathfinding function
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- A* pathfinding function. This seaches the path along nodes between start and end nodes/coordinates.
|
||||
-- @param #ASTAR self
|
||||
-- @param #boolean ExcludeStartNode If *true*, do not include start node in found path. Default is to include it.
|
||||
-- @param #boolean ExcludeEndNode If *true*, do not include end node in found path. Default is to include it.
|
||||
-- @return #table Table of nodes from start to finish.
|
||||
function ASTAR:GetPath(ExcludeStartNode, ExcludeEndNode)
|
||||
|
||||
self:FindStartNode()
|
||||
self:FindEndNode()
|
||||
|
||||
local nodes=self.nodes
|
||||
local start=self.startNode
|
||||
local goal=self.endNode
|
||||
|
||||
-- Sets.
|
||||
local openset = {}
|
||||
local closedset = {}
|
||||
local came_from = {}
|
||||
local g_score = {}
|
||||
local f_score = {}
|
||||
|
||||
openset[start.id]=true
|
||||
local Nopen=1
|
||||
|
||||
-- Initial scores.
|
||||
g_score[start.id]=0
|
||||
f_score[start.id]=g_score[start.id]+self:_HeuristicCost(start, goal)
|
||||
|
||||
-- Set start time.
|
||||
local T0=timer.getAbsTime()
|
||||
|
||||
-- Debug message.
|
||||
local text=string.format("Starting A* pathfinding with %d Nodes", self.Nnodes)
|
||||
self:T(self.lid..text)
|
||||
|
||||
local Tstart=UTILS.GetOSTime()
|
||||
|
||||
-- Loop while we still have an open set.
|
||||
while Nopen > 0 do
|
||||
|
||||
-- Get current node.
|
||||
local current=self:_LowestFscore(openset, f_score)
|
||||
|
||||
-- Check if we are at the end node.
|
||||
if current.id==goal.id then
|
||||
|
||||
local path=self:_UnwindPath({}, came_from, goal)
|
||||
|
||||
if not ExcludeEndNode then
|
||||
table.insert(path, goal)
|
||||
end
|
||||
|
||||
if ExcludeStartNode then
|
||||
table.remove(path, 1)
|
||||
end
|
||||
|
||||
local Tstop=UTILS.GetOSTime()
|
||||
|
||||
local dT=nil
|
||||
if Tstart and Tstop then
|
||||
dT=Tstop-Tstart
|
||||
end
|
||||
|
||||
-- Debug message.
|
||||
local text=string.format("Found path with %d nodes (%d total)", #path, self.Nnodes)
|
||||
if dT then
|
||||
text=text..string.format(", OS Time %.6f sec", dT)
|
||||
end
|
||||
text=text..string.format(", Nvalid=%d [%d cached]", self.nvalid, self.nvalidcache)
|
||||
text=text..string.format(", Ncost=%d [%d cached]", self.ncost, self.ncostcache)
|
||||
self:T(self.lid..text)
|
||||
|
||||
return path
|
||||
end
|
||||
|
||||
-- Move Node from open to closed set.
|
||||
openset[current.id]=nil
|
||||
Nopen=Nopen-1
|
||||
closedset[current.id]=true
|
||||
|
||||
-- Get neighbour nodes.
|
||||
local neighbors=self:_NeighbourNodes(current, nodes)
|
||||
|
||||
-- Loop over neighbours.
|
||||
for _,neighbor in pairs(neighbors) do
|
||||
|
||||
if self:_NotIn(closedset, neighbor.id) then
|
||||
|
||||
local tentative_g_score=g_score[current.id]+self:_DistNodes(current, neighbor)
|
||||
|
||||
if self:_NotIn(openset, neighbor.id) or tentative_g_score < g_score[neighbor.id] then
|
||||
|
||||
came_from[neighbor]=current
|
||||
|
||||
g_score[neighbor.id]=tentative_g_score
|
||||
f_score[neighbor.id]=g_score[neighbor.id]+self:_HeuristicCost(neighbor, goal)
|
||||
|
||||
if self:_NotIn(openset, neighbor.id) then
|
||||
-- Add to open set.
|
||||
openset[neighbor.id]=true
|
||||
Nopen=Nopen+1
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Debug message.
|
||||
local text=string.format("WARNING: Could NOT find valid path!")
|
||||
self:E(self.lid..text)
|
||||
MESSAGE:New(text, 60, "ASTAR"):ToAllIf(self.Debug)
|
||||
|
||||
return nil -- no valid path
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- A* pathfinding helper functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Heuristic "cost" function to go from node A to node B. Default is the distance between the nodes.
|
||||
-- @param #ASTAR self
|
||||
-- @param #ASTAR.Node nodeA Node A.
|
||||
-- @param #ASTAR.Node nodeB Node B.
|
||||
-- @return #number "Cost" to go from node A to node B.
|
||||
function ASTAR:_HeuristicCost(nodeA, nodeB)
|
||||
|
||||
-- Counter.
|
||||
self.ncost=self.ncost+1
|
||||
|
||||
-- Get chached cost if available.
|
||||
local cost=nodeA.cost[nodeB.id]
|
||||
if cost~=nil then
|
||||
self.ncostcache=self.ncostcache+1
|
||||
return cost
|
||||
end
|
||||
|
||||
local cost=nil
|
||||
if self.CostFunc then
|
||||
cost=self.CostFunc(nodeA, nodeB, unpack(self.CostArg))
|
||||
else
|
||||
cost=self:_DistNodes(nodeA, nodeB)
|
||||
end
|
||||
|
||||
nodeA.cost[nodeB.id]=cost
|
||||
nodeB.cost[nodeA.id]=cost -- Symmetric problem.
|
||||
|
||||
return cost
|
||||
end
|
||||
|
||||
--- Check if going from a node to a neighbour is possible.
|
||||
-- @param #ASTAR self
|
||||
-- @param #ASTAR.Node node A node.
|
||||
-- @param #ASTAR.Node neighbor Neighbour node.
|
||||
-- @return #boolean If true, transition between nodes is possible.
|
||||
function ASTAR:_IsValidNeighbour(node, neighbor)
|
||||
|
||||
-- Counter.
|
||||
self.nvalid=self.nvalid+1
|
||||
|
||||
local valid=node.valid[neighbor.id]
|
||||
if valid~=nil then
|
||||
--env.info(string.format("Node %d has valid=%s neighbour %d", node.id, tostring(valid), neighbor.id))
|
||||
self.nvalidcache=self.nvalidcache+1
|
||||
return valid
|
||||
end
|
||||
|
||||
local valid=nil
|
||||
if self.ValidNeighbourFunc then
|
||||
valid=self.ValidNeighbourFunc(node, neighbor, unpack(self.ValidNeighbourArg))
|
||||
else
|
||||
valid=true
|
||||
end
|
||||
|
||||
node.valid[neighbor.id]=valid
|
||||
neighbor.valid[node.id]=valid -- Symmetric problem.
|
||||
|
||||
return valid
|
||||
end
|
||||
|
||||
--- Calculate 2D distance between two nodes.
|
||||
-- @param #ASTAR self
|
||||
-- @param #ASTAR.Node nodeA Node A.
|
||||
-- @param #ASTAR.Node nodeB Node B.
|
||||
-- @return #number Distance between nodes in meters.
|
||||
function ASTAR:_DistNodes(nodeA, nodeB)
|
||||
return nodeA.coordinate:Get2DDistance(nodeB.coordinate)
|
||||
end
|
||||
|
||||
--- Function that calculates the lowest F score.
|
||||
-- @param #ASTAR self
|
||||
-- @param #table set The set of nodes IDs.
|
||||
-- @param #number f_score F score.
|
||||
-- @return #ASTAR.Node Best node.
|
||||
function ASTAR:_LowestFscore(set, f_score)
|
||||
|
||||
local lowest, bestNode = ASTAR.INF, nil
|
||||
|
||||
for nid,node in pairs(set) do
|
||||
|
||||
local score=f_score[nid]
|
||||
|
||||
if score<lowest then
|
||||
lowest, bestNode = score, nid
|
||||
end
|
||||
end
|
||||
|
||||
return self.nodes[bestNode]
|
||||
end
|
||||
|
||||
--- Function to get valid neighbours of a node.
|
||||
-- @param #ASTAR self
|
||||
-- @param #ASTAR.Node theNode The node.
|
||||
-- @param #table nodes Possible neighbours.
|
||||
-- @param #table Valid neighbour nodes.
|
||||
function ASTAR:_NeighbourNodes(theNode, nodes)
|
||||
|
||||
local neighbors = {}
|
||||
|
||||
for _,node in pairs(nodes) do
|
||||
|
||||
if theNode.id~=node.id then
|
||||
|
||||
local isvalid=self:_IsValidNeighbour(theNode, node)
|
||||
|
||||
if isvalid then
|
||||
table.insert(neighbors, node)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
return neighbors
|
||||
end
|
||||
|
||||
--- Function to check if a node is not in a set.
|
||||
-- @param #ASTAR self
|
||||
-- @param #table set Set of nodes.
|
||||
-- @param #ASTAR.Node theNode The node to check.
|
||||
-- @return #boolean If true, the node is not in the set.
|
||||
function ASTAR:_NotIn(set, theNode)
|
||||
return set[theNode]==nil
|
||||
end
|
||||
|
||||
--- Unwind path function.
|
||||
-- @param #ASTAR self
|
||||
-- @param #table flat_path Flat path.
|
||||
-- @param #table map Map.
|
||||
-- @param #ASTAR.Node current_node The current node.
|
||||
-- @return #table Unwinded path.
|
||||
function ASTAR:_UnwindPath( flat_path, map, current_node )
|
||||
|
||||
if map [current_node] then
|
||||
table.insert (flat_path, 1, map[current_node])
|
||||
return self:_UnwindPath(flat_path, map, map[current_node])
|
||||
else
|
||||
return flat_path
|
||||
end
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
File diff suppressed because it is too large
Load Diff
478
Moose Development/Moose/Core/Beacon.lua
Normal file
478
Moose Development/Moose/Core/Beacon.lua
Normal file
@@ -0,0 +1,478 @@
|
||||
--- **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 usefull 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:CommandDeactivateLink4(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
|
||||
|
||||
--- Stops 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
|
||||
295
Moose Development/Moose/Core/Condition.lua
Normal file
295
Moose Development/Moose/Core/Condition.lua
Normal file
@@ -0,0 +1,295 @@
|
||||
--- **Core** - Define any or all conditions to be evaluated.
|
||||
--
|
||||
-- **Main Features:**
|
||||
--
|
||||
-- * Add arbitrary numbers of conditon functions
|
||||
-- * Evaluate *any* or *all* conditions
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ## Example Missions:
|
||||
--
|
||||
-- Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/OPS%20-%20Operation).
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **funkyfranky**
|
||||
--
|
||||
-- ===
|
||||
-- @module Core.Condition
|
||||
-- @image Core_Conditon.png
|
||||
|
||||
--- CONDITON class.
|
||||
-- @type CONDITION
|
||||
-- @field #string ClassName Name of the class.
|
||||
-- @field #string lid Class id string for output to DCS log file.
|
||||
-- @field #boolean isAny General functions are evaluated as any condition.
|
||||
-- @field #boolean negateResult Negeate result of evaluation.
|
||||
-- @field #table functionsGen General condition functions.
|
||||
-- @field #table functionsAny Any condition functions.
|
||||
-- @field #table functionsAll All condition functions.
|
||||
--
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
--- *Better three hours too soon than a minute too late.* - William Shakespeare
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- # The CONDITION Concept
|
||||
--
|
||||
--
|
||||
--
|
||||
-- @field #CONDITION
|
||||
CONDITION = {
|
||||
ClassName = "CONDITION",
|
||||
lid = nil,
|
||||
functionsGen = {},
|
||||
functionsAny = {},
|
||||
functionsAll = {},
|
||||
}
|
||||
|
||||
--- Condition function.
|
||||
-- @type CONDITION.Function
|
||||
-- @field #function func Callback function to check for a condition. Should return a `#boolean`.
|
||||
-- @field #table arg (Optional) Arguments passed to the condition callback function if any.
|
||||
|
||||
--- CONDITION class version.
|
||||
-- @field #string version
|
||||
CONDITION.version="0.1.0"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- TODO list
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
-- TODO: Make FSM.
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Constructor
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Create a new CONDITION object.
|
||||
-- @param #CONDITION self
|
||||
-- @param #string Name (Optional) Name used in the logs.
|
||||
-- @return #CONDITION self
|
||||
function CONDITION:New(Name)
|
||||
|
||||
-- Inherit BASE.
|
||||
local self=BASE:Inherit(self, BASE:New()) --#CONDITION
|
||||
|
||||
self.name=Name or "Condition X"
|
||||
|
||||
self.lid=string.format("%s | ", self.name)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set that general condition functions return `true` if `any` function returns `true`. Default is that *all* functions must return `true`.
|
||||
-- @param #CONDITION self
|
||||
-- @param #boolean Any If `true`, *any* condition can be true. Else *all* conditions must result `true`.
|
||||
-- @return #CONDITION self
|
||||
function CONDITION:SetAny(Any)
|
||||
self.isAny=Any
|
||||
return self
|
||||
end
|
||||
|
||||
--- Negate result.
|
||||
-- @param #CONDITION self
|
||||
-- @param #boolean Negate If `true`, result is negated else not.
|
||||
-- @return #CONDITION self
|
||||
function CONDITION:SetNegateResult(Negate)
|
||||
self.negateResult=Negate
|
||||
return self
|
||||
end
|
||||
|
||||
--- Add a function that is evaluated. It must return a `#boolean` value, *i.e.* either `true` or `false` (or `nil`).
|
||||
-- @param #CONDITION self
|
||||
-- @param #function Function The function to call.
|
||||
-- @param ... (Optional) Parameters passed to the function (if any).
|
||||
--
|
||||
-- @usage
|
||||
-- local function isAequalB(a, b)
|
||||
-- return a==b
|
||||
-- end
|
||||
--
|
||||
-- myCondition:AddFunction(isAequalB, a, b)
|
||||
--
|
||||
-- @return #CONDITION self
|
||||
function CONDITION:AddFunction(Function, ...)
|
||||
|
||||
-- Condition function.
|
||||
local condition=self:_CreateCondition(Function, ...)
|
||||
|
||||
-- Add to table.
|
||||
table.insert(self.functionsGen, condition)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Add a function that is evaluated. It must return a `#boolean` value, *i.e.* either `true` or `false` (or `nil`).
|
||||
-- @param #CONDITION self
|
||||
-- @param #function Function The function to call.
|
||||
-- @param ... (Optional) Parameters passed to the function (if any).
|
||||
-- @return #CONDITION self
|
||||
function CONDITION:AddFunctionAny(Function, ...)
|
||||
|
||||
-- Condition function.
|
||||
local condition=self:_CreateCondition(Function, ...)
|
||||
|
||||
-- Add to table.
|
||||
table.insert(self.functionsAny, condition)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Add a function that is evaluated. It must return a `#boolean` value, *i.e.* either `true` or `false` (or `nil`).
|
||||
-- @param #CONDITION self
|
||||
-- @param #function Function The function to call.
|
||||
-- @param ... (Optional) Parameters passed to the function (if any).
|
||||
-- @return #CONDITION self
|
||||
function CONDITION:AddFunctionAll(Function, ...)
|
||||
|
||||
-- Condition function.
|
||||
local condition=self:_CreateCondition(Function, ...)
|
||||
|
||||
-- Add to table.
|
||||
table.insert(self.functionsAll, condition)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Evaluate conditon functions.
|
||||
-- @param #CONDITION self
|
||||
-- @param #boolean AnyTrue If `true`, evaluation return `true` if *any* condition function returns `true`. By default, *all* condition functions must return true.
|
||||
-- @return #boolean Result of condition functions.
|
||||
function CONDITION:Evaluate(AnyTrue)
|
||||
|
||||
-- Check if at least one function was given.
|
||||
if #self.functionsAll + #self.functionsAny + #self.functionsAll == 0 then
|
||||
if self.negateResult then
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
-- Any condition for gen.
|
||||
local evalAny=self.isAny
|
||||
if AnyTrue~=nil then
|
||||
evalAny=AnyTrue
|
||||
end
|
||||
|
||||
local isGen=nil
|
||||
if evalAny then
|
||||
isGen=self:_EvalConditionsAny(self.functionsGen)
|
||||
else
|
||||
isGen=self:_EvalConditionsAll(self.functionsGen)
|
||||
end
|
||||
|
||||
-- Is any?
|
||||
local isAny=self:_EvalConditionsAny(self.functionsAny)
|
||||
|
||||
-- Is all?
|
||||
local isAll=self:_EvalConditionsAll(self.functionsAll)
|
||||
|
||||
-- Result.
|
||||
local result=isGen and isAny and isAll
|
||||
|
||||
-- Negate result.
|
||||
if self.negateResult then
|
||||
result=not result
|
||||
end
|
||||
|
||||
-- Debug message.
|
||||
self:T(self.lid..string.format("Evaluate: isGen=%s, isAny=%s, isAll=%s (negate=%s) ==> result=%s", tostring(isGen), tostring(isAny), tostring(isAll), tostring(self.negateResult), tostring(result)))
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
--- Check if all given condition are true.
|
||||
-- @param #CONDITION self
|
||||
-- @param #table functions Functions to evaluate.
|
||||
-- @return #boolean If true, all conditions were true (or functions was empty/nil). Returns false if at least one condition returned false.
|
||||
function CONDITION:_EvalConditionsAll(functions)
|
||||
|
||||
-- At least one condition?
|
||||
local gotone=false
|
||||
|
||||
|
||||
-- Any stop condition must be true.
|
||||
for _,_condition in pairs(functions or {}) do
|
||||
local condition=_condition --#CONDITION.Function
|
||||
|
||||
-- At least one condition was defined.
|
||||
gotone=true
|
||||
|
||||
-- Call function.
|
||||
local istrue=condition.func(unpack(condition.arg))
|
||||
|
||||
-- Any false will return false.
|
||||
if not istrue then
|
||||
return false
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-- All conditions were true.
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
--- Check if any of the given conditions is true.
|
||||
-- @param #CONDITION self
|
||||
-- @param #table functions Functions to evaluate.
|
||||
-- @return #boolean If true, at least one condition is true (or functions was emtpy/nil).
|
||||
function CONDITION:_EvalConditionsAny(functions)
|
||||
|
||||
-- At least one condition?
|
||||
local gotone=false
|
||||
|
||||
-- Any stop condition must be true.
|
||||
for _,_condition in pairs(functions or {}) do
|
||||
local condition=_condition --#CONDITION.Function
|
||||
|
||||
-- At least one condition was defined.
|
||||
gotone=true
|
||||
|
||||
-- Call function.
|
||||
local istrue=condition.func(unpack(condition.arg))
|
||||
|
||||
-- Any true will return true.
|
||||
if istrue then
|
||||
return true
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-- No condition was true.
|
||||
if gotone then
|
||||
return false
|
||||
else
|
||||
-- No functions passed.
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
--- Create conditon function object.
|
||||
-- @param #CONDITION self
|
||||
-- @param #function Function The function to call.
|
||||
-- @param ... (Optional) Parameters passed to the function (if any).
|
||||
-- @return #CONDITION.Function Condition function.
|
||||
function CONDITION:_CreateCondition(Function, ...)
|
||||
|
||||
local condition={} --#CONDITION.Function
|
||||
|
||||
condition.func=Function
|
||||
condition.arg={}
|
||||
if arg then
|
||||
condition.arg=arg
|
||||
end
|
||||
|
||||
return condition
|
||||
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,89 +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( {} )
|
||||
|
||||
@@ -104,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
|
||||
@@ -116,47 +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: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
|
||||
@@ -165,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
|
||||
@@ -173,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.
|
||||
@@ -183,4 +174,4 @@ do -- Goal
|
||||
return self:Is( "Achieved" )
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
267
Moose Development/Moose/Core/MarkerOps_Base.lua
Normal file
267
Moose Development/Moose/Core/MarkerOps_Base.lua
Normal file
@@ -0,0 +1,267 @@
|
||||
--- **Core** - MarkerOps_Base.
|
||||
--
|
||||
-- **Main Features:**
|
||||
--
|
||||
-- * Create an easy way to tap into markers added to the F10 map by users.
|
||||
-- * Recognize own tag and list of keywords.
|
||||
-- * Matched keywords are handed down to functions.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **Applevangelist**
|
||||
--
|
||||
-- Date: 5 May 2021
|
||||
-- Last Update: Sep 2022
|
||||
--
|
||||
-- ===
|
||||
---
|
||||
-- @module Core.MarkerOps_Base
|
||||
-- @image MOOSE_Core.JPG
|
||||
|
||||
--------------------------------------------------------------------------
|
||||
-- MARKEROPS_BASE Class Definition.
|
||||
--------------------------------------------------------------------------
|
||||
|
||||
--- MARKEROPS_BASE class.
|
||||
-- @type MARKEROPS_BASE
|
||||
-- @field #string ClassName Name of the class.
|
||||
-- @field #string Tag Tag to identify commands.
|
||||
-- @field #table Keywords Table of keywords to recognize.
|
||||
-- @field #string version Version of #MARKEROPS_BASE.
|
||||
-- @field #boolean Casesensitive Enforce case when identifying the Tag, i.e. tag ~= Tag
|
||||
-- @extends Core.Fsm#FSM
|
||||
|
||||
--- *Fiat lux.* -- Latin proverb.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- # The MARKEROPS_BASE Concept
|
||||
--
|
||||
-- This class enable scripting text-based actions from markers.
|
||||
--
|
||||
-- @field #MARKEROPS_BASE
|
||||
MARKEROPS_BASE = {
|
||||
ClassName = "MARKEROPS",
|
||||
Tag = "mytag",
|
||||
Keywords = {},
|
||||
version = "0.1.0",
|
||||
debug = false,
|
||||
Casesensitive = true,
|
||||
}
|
||||
|
||||
--- Function to instantiate a new #MARKEROPS_BASE object.
|
||||
-- @param #MARKEROPS_BASE self
|
||||
-- @param #string Tagname Name to identify us from the event text.
|
||||
-- @param #table Keywords Table of keywords recognized from the event text.
|
||||
-- @param #boolean Casesensitive (Optional) Switch case sensitive identification of Tagname. Defaults to true.
|
||||
-- @return #MARKEROPS_BASE self
|
||||
function MARKEROPS_BASE:New(Tagname,Keywords,Casesensitive)
|
||||
-- Inherit FSM
|
||||
local self=BASE:Inherit(self, FSM:New()) -- #MARKEROPS_BASE
|
||||
|
||||
-- Set some string id for output to DCS.log file.
|
||||
self.lid=string.format("MARKEROPS_BASE %s | ", tostring(self.version))
|
||||
|
||||
self.Tag = Tagname or "mytag"-- #string
|
||||
self.Keywords = Keywords or {} -- #table - might want to use lua regex here, too
|
||||
self.debug = false
|
||||
self.Casesensitive = true
|
||||
|
||||
if Casesensitive and Casesensitive == false then
|
||||
self.Casesensitive = false
|
||||
end
|
||||
|
||||
-----------------------
|
||||
--- FSM Transitions ---
|
||||
-----------------------
|
||||
|
||||
-- Start State.
|
||||
self:SetStartState("Stopped")
|
||||
|
||||
-- Add FSM transitions.
|
||||
-- From State --> Event --> To State
|
||||
self:AddTransition("Stopped", "Start", "Running") -- Start the FSM.
|
||||
self:AddTransition("*", "MarkAdded", "*") -- Start the FSM.
|
||||
self:AddTransition("*", "MarkChanged", "*") -- Start the FSM.
|
||||
self:AddTransition("*", "MarkDeleted", "*") -- Start the FSM.
|
||||
self:AddTransition("Running", "Stop", "Stopped") -- Stop the FSM.
|
||||
|
||||
self:HandleEvent(EVENTS.MarkAdded, self.OnEventMark)
|
||||
self:HandleEvent(EVENTS.MarkChange, self.OnEventMark)
|
||||
self:HandleEvent(EVENTS.MarkRemoved, self.OnEventMark)
|
||||
|
||||
-- start
|
||||
self:I(self.lid..string.format("started for %s",self.Tag))
|
||||
self:__Start(1)
|
||||
return self
|
||||
|
||||
-------------------
|
||||
-- PSEUDO Functions
|
||||
-------------------
|
||||
|
||||
--- On after "MarkAdded" event. Triggered when a Marker is added to the F10 map.
|
||||
-- @function [parent=#MARKEROPS_BASE] OnAfterMarkAdded
|
||||
-- @param #MARKEROPS_BASE self
|
||||
-- @param #string From The From state
|
||||
-- @param #string Event The Event called
|
||||
-- @param #string To The To state
|
||||
-- @param #string Text The text on the marker
|
||||
-- @param #table Keywords Table of matching keywords found in the Event text
|
||||
-- @param Core.Point#COORDINATE Coord Coordinate of the marker.
|
||||
|
||||
--- On after "MarkChanged" event. Triggered when a Marker is changed on the F10 map.
|
||||
-- @function [parent=#MARKEROPS_BASE] OnAfterMarkChanged
|
||||
-- @param #MARKEROPS_BASE self
|
||||
-- @param #string From The From state
|
||||
-- @param #string Event The Event called
|
||||
-- @param #string To The To state
|
||||
-- @param #string Text The text on the marker
|
||||
-- @param #table Keywords Table of matching keywords found in the Event text
|
||||
-- @param Core.Point#COORDINATE Coord Coordinate of the marker.
|
||||
|
||||
--- On after "MarkDeleted" event. Triggered when a Marker is deleted from the F10 map.
|
||||
-- @function [parent=#MARKEROPS_BASE] OnAfterMarkDeleted
|
||||
-- @param #MARKEROPS_BASE self
|
||||
-- @param #string From The From state
|
||||
-- @param #string Event The Event called
|
||||
-- @param #string To The To state
|
||||
|
||||
--- "Stop" trigger. Used to stop the function an unhandle events
|
||||
-- @function [parent=#MARKEROPS_BASE] Stop
|
||||
|
||||
end
|
||||
|
||||
--- (internal) Handle events.
|
||||
-- @param #MARKEROPS_BASE self
|
||||
-- @param Core.Event#EVENTDATA Event
|
||||
function MARKEROPS_BASE:OnEventMark(Event)
|
||||
self:T({Event})
|
||||
if Event == nil or Event.idx == nil then
|
||||
self:E("Skipping onEvent. Event or Event.idx unknown.")
|
||||
return true
|
||||
end
|
||||
--position
|
||||
local vec3={y=Event.pos.y, x=Event.pos.x, z=Event.pos.z}
|
||||
local coord=COORDINATE:NewFromVec3(vec3)
|
||||
if self.debug then
|
||||
local coordtext = coord:ToStringLLDDM()
|
||||
local text = tostring(Event.text)
|
||||
local m = MESSAGE:New(string.format("Mark added at %s with text: %s",coordtext,text),10,"Info",false):ToAll()
|
||||
end
|
||||
-- decision
|
||||
if Event.id==world.event.S_EVENT_MARK_ADDED then
|
||||
self:T({event="S_EVENT_MARK_ADDED", carrier=self.groupname, vec3=Event.pos})
|
||||
-- Handle event
|
||||
local Eventtext = tostring(Event.text)
|
||||
if Eventtext~=nil then
|
||||
if self:_MatchTag(Eventtext) then
|
||||
local matchtable = self:_MatchKeywords(Eventtext)
|
||||
self:MarkAdded(Eventtext,matchtable,coord)
|
||||
end
|
||||
end
|
||||
elseif Event.id==world.event.S_EVENT_MARK_CHANGE then
|
||||
self:T({event="S_EVENT_MARK_CHANGE", carrier=self.groupname, vec3=Event.pos})
|
||||
-- Handle event.
|
||||
local Eventtext = tostring(Event.text)
|
||||
if Eventtext~=nil then
|
||||
if self:_MatchTag(Eventtext) then
|
||||
local matchtable = self:_MatchKeywords(Eventtext)
|
||||
self:MarkChanged(Eventtext,matchtable,coord)
|
||||
end
|
||||
end
|
||||
elseif Event.id==world.event.S_EVENT_MARK_REMOVED then
|
||||
self:T({event="S_EVENT_MARK_REMOVED", carrier=self.groupname, vec3=Event.pos})
|
||||
-- Hande event.
|
||||
local Eventtext = tostring(Event.text)
|
||||
if Eventtext~=nil then
|
||||
if self:_MatchTag(Eventtext) then
|
||||
self:MarkDeleted()
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- (internal) Match tag.
|
||||
-- @param #MARKEROPS_BASE self
|
||||
-- @param #string Eventtext Text added to the marker.
|
||||
-- @return #boolean
|
||||
function MARKEROPS_BASE:_MatchTag(Eventtext)
|
||||
local matches = false
|
||||
if not self.Casesensitive then
|
||||
local type = string.lower(self.Tag) -- #string
|
||||
if string.find(string.lower(Eventtext),type) then
|
||||
matches = true --event text contains tag
|
||||
end
|
||||
else
|
||||
local type = self.Tag -- #string
|
||||
if string.find(Eventtext,type) then
|
||||
matches = true --event text contains tag
|
||||
end
|
||||
end
|
||||
return matches
|
||||
end
|
||||
|
||||
--- (internal) Match keywords table.
|
||||
-- @param #MARKEROPS_BASE self
|
||||
-- @param #string Eventtext Text added to the marker.
|
||||
-- @return #table
|
||||
function MARKEROPS_BASE:_MatchKeywords(Eventtext)
|
||||
local matchtable = {}
|
||||
local keytable = self.Keywords
|
||||
for _index,_word in pairs (keytable) do
|
||||
if string.find(string.lower(Eventtext),string.lower(_word))then
|
||||
table.insert(matchtable,_word)
|
||||
end
|
||||
end
|
||||
return matchtable
|
||||
end
|
||||
|
||||
--- On before "MarkAdded" event. Triggered when a Marker is added to the F10 map.
|
||||
-- @param #MARKEROPS_BASE self
|
||||
-- @param #string From The From state
|
||||
-- @param #string Event The Event called
|
||||
-- @param #string To The To state
|
||||
-- @param #string Text The text on the marker
|
||||
-- @param #table Keywords Table of matching keywords found in the Event text
|
||||
-- @param Core.Point#COORDINATE Coord Coordinate of the marker.
|
||||
function MARKEROPS_BASE:onbeforeMarkAdded(From,Event,To,Text,Keywords,Coord)
|
||||
self:T({self.lid,From,Event,To,Text,Keywords,Coord:ToStringLLDDM()})
|
||||
end
|
||||
|
||||
--- On before "MarkChanged" event. Triggered when a Marker is changed on the F10 map.
|
||||
-- @param #MARKEROPS_BASE self
|
||||
-- @param #string From The From state
|
||||
-- @param #string Event The Event called
|
||||
-- @param #string To The To state
|
||||
-- @param #string Text The text on the marker
|
||||
-- @param #table Keywords Table of matching keywords found in the Event text
|
||||
-- @param Core.Point#COORDINATE Coord Coordinate of the marker.
|
||||
function MARKEROPS_BASE:onbeforeMarkChanged(From,Event,To,Text,Keywords,Coord)
|
||||
self:T({self.lid,From,Event,To,Text,Keywords,Coord:ToStringLLDDM()})
|
||||
end
|
||||
|
||||
--- On before "MarkDeleted" event. Triggered when a Marker is removed from the F10 map.
|
||||
-- @param #MARKEROPS_BASE self
|
||||
-- @param #string From The From state
|
||||
-- @param #string Event The Event called
|
||||
-- @param #string To The To state
|
||||
function MARKEROPS_BASE:onbeforeMarkDeleted(From,Event,To)
|
||||
self:T({self.lid,From,Event,To})
|
||||
end
|
||||
|
||||
--- On enter "Stopped" event. Unsubscribe events.
|
||||
-- @param #MARKEROPS_BASE self
|
||||
-- @param #string From The From state
|
||||
-- @param #string Event The Event called
|
||||
-- @param #string To The To state
|
||||
function MARKEROPS_BASE:onenterStopped(From,Event,To)
|
||||
self:T({self.lid,From,Event,To})
|
||||
-- unsubscribe from events
|
||||
self:UnHandleEvent(EVENTS.MarkAdded)
|
||||
self:UnHandleEvent(EVENTS.MarkChange)
|
||||
self:UnHandleEvent(EVENTS.MarkRemoved)
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------
|
||||
-- MARKEROPS_BASE Class Definition End.
|
||||
--------------------------------------------------------------------------
|
||||
@@ -14,7 +14,7 @@
|
||||
-- * Only create or delete menus when required, and keep existing menus persistent.
|
||||
-- * Update menu structures.
|
||||
-- * Refresh menu structures intelligently, based on a time stamp of updates.
|
||||
-- - Delete obscolete menus.
|
||||
-- - Delete obsolete menus.
|
||||
-- - Create new one where required.
|
||||
-- - Don't touch the existing ones.
|
||||
-- * Provide a variable amount of parameters to menus.
|
||||
@@ -23,7 +23,7 @@
|
||||
-- * Provide a great tool to manage menus in your code.
|
||||
--
|
||||
-- DCS Menus can be managed using the MENU classes.
|
||||
-- The advantage of using MENU classes is that it hides the complexity of dealing with menu management in more advanced scanerios where you need to
|
||||
-- The advantage of using MENU classes is that it hides the complexity of dealing with menu management in more advanced scenarios where you need to
|
||||
-- set menus and later remove them, and later set them again. You'll find while using use normal DCS scripting functions, that setting and removing
|
||||
-- menus is not a easy feat if you have complex menu hierarchies defined.
|
||||
-- Using the MOOSE menu classes, the removal and refreshing of menus are nicely being handled within these classes, and becomes much more easy.
|
||||
@@ -53,7 +53,6 @@
|
||||
-- @module Core.Menu
|
||||
-- @image Core_Menu.JPG
|
||||
|
||||
|
||||
MENU_INDEX = {}
|
||||
MENU_INDEX.MenuMission = {}
|
||||
MENU_INDEX.MenuMission.Menus = {}
|
||||
@@ -64,10 +63,7 @@ MENU_INDEX.Coalition[coalition.side.RED] = {}
|
||||
MENU_INDEX.Coalition[coalition.side.RED].Menus = {}
|
||||
MENU_INDEX.Group = {}
|
||||
|
||||
|
||||
|
||||
function MENU_INDEX:ParentPath( ParentMenu, MenuText )
|
||||
|
||||
local Path = ParentMenu and "@" .. table.concat( ParentMenu.MenuPath or {}, "@" ) or ""
|
||||
if ParentMenu then
|
||||
if ParentMenu:IsInstanceOf( "MENU_GROUP" ) or ParentMenu:IsInstanceOf( "MENU_GROUP_COMMAND" ) then
|
||||
@@ -95,20 +91,16 @@ function MENU_INDEX:ParentPath( ParentMenu, MenuText )
|
||||
|
||||
Path = Path .. "@" .. MenuText
|
||||
return Path
|
||||
|
||||
end
|
||||
|
||||
|
||||
function MENU_INDEX:PrepareMission()
|
||||
self.MenuMission.Menus = self.MenuMission.Menus or {}
|
||||
end
|
||||
|
||||
|
||||
function MENU_INDEX:PrepareCoalition( CoalitionSide )
|
||||
self.Coalition[CoalitionSide] = self.Coalition[CoalitionSide] or {}
|
||||
self.Coalition[CoalitionSide].Menus = self.Coalition[CoalitionSide].Menus or {}
|
||||
end
|
||||
|
||||
---
|
||||
-- @param Wrapper.Group#GROUP Group
|
||||
function MENU_INDEX:PrepareGroup( Group )
|
||||
@@ -119,42 +111,26 @@ function MENU_INDEX:PrepareGroup( Group )
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
function MENU_INDEX:HasMissionMenu( Path )
|
||||
|
||||
return self.MenuMission.Menus[Path]
|
||||
end
|
||||
|
||||
function MENU_INDEX:SetMissionMenu( Path, Menu )
|
||||
|
||||
self.MenuMission.Menus[Path] = Menu
|
||||
end
|
||||
|
||||
function MENU_INDEX:ClearMissionMenu( Path )
|
||||
|
||||
self.MenuMission.Menus[Path] = nil
|
||||
end
|
||||
|
||||
|
||||
|
||||
function MENU_INDEX:HasCoalitionMenu( Coalition, Path )
|
||||
|
||||
return self.Coalition[Coalition].Menus[Path]
|
||||
end
|
||||
|
||||
function MENU_INDEX:SetCoalitionMenu( Coalition, Path, Menu )
|
||||
|
||||
self.Coalition[Coalition].Menus[Path] = Menu
|
||||
end
|
||||
|
||||
function MENU_INDEX:ClearCoalitionMenu( Coalition, Path )
|
||||
|
||||
self.Coalition[Coalition].Menus[Path] = nil
|
||||
end
|
||||
|
||||
|
||||
|
||||
function MENU_INDEX:HasGroupMenu( Group, Path )
|
||||
if Group and Group:IsAlive() then
|
||||
local MenuGroupName = Group:GetName()
|
||||
@@ -162,53 +138,36 @@ function MENU_INDEX:HasGroupMenu( Group, Path )
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
function MENU_INDEX:SetGroupMenu( Group, Path, Menu )
|
||||
|
||||
local MenuGroupName = Group:GetName()
|
||||
Group:F({MenuGroupName=MenuGroupName,Path=Path})
|
||||
self.Group[MenuGroupName].Menus[Path] = Menu
|
||||
end
|
||||
|
||||
function MENU_INDEX:ClearGroupMenu( Group, Path )
|
||||
|
||||
local MenuGroupName = Group:GetName()
|
||||
self.Group[MenuGroupName].Menus[Path] = nil
|
||||
end
|
||||
|
||||
function MENU_INDEX:Refresh( Group )
|
||||
|
||||
for MenuID, Menu in pairs( self.MenuMission.Menus ) do
|
||||
Menu:Refresh()
|
||||
end
|
||||
|
||||
for MenuID, Menu in pairs( self.Coalition[coalition.side.BLUE].Menus ) do
|
||||
Menu:Refresh()
|
||||
end
|
||||
|
||||
for MenuID, Menu in pairs( self.Coalition[coalition.side.RED].Menus ) do
|
||||
Menu:Refresh()
|
||||
end
|
||||
|
||||
local GroupName = Group:GetName()
|
||||
for MenuID, Menu in pairs( self.Group[GroupName].Menus ) do
|
||||
Menu:Refresh()
|
||||
end
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
do -- MENU_BASE
|
||||
|
||||
--- @type MENU_BASE
|
||||
-- @extends Base#BASE
|
||||
|
||||
-- @extends Core.Base#BASE
|
||||
--- Defines the main MENU class where other MENU classes are derived from.
|
||||
-- This is an abstract class, so don't use it.
|
||||
-- @field #MENU_BASE
|
||||
@@ -216,10 +175,10 @@ do -- MENU_BASE
|
||||
ClassName = "MENU_BASE",
|
||||
MenuPath = nil,
|
||||
MenuText = "",
|
||||
MenuParentPath = nil
|
||||
MenuParentPath = nil,
|
||||
}
|
||||
|
||||
--- Consructor
|
||||
--- Constructor
|
||||
-- @param #MENU_BASE
|
||||
-- @return #MENU_BASE
|
||||
function MENU_BASE:New( MenuText, ParentMenu )
|
||||
@@ -228,27 +187,25 @@ do -- MENU_BASE
|
||||
if ParentMenu ~= nil then
|
||||
MenuParentPath = ParentMenu.MenuPath
|
||||
end
|
||||
|
||||
local self = BASE:Inherit( self, BASE:New() )
|
||||
local self = BASE:Inherit( self, BASE:New() )
|
||||
|
||||
self.MenuPath = nil
|
||||
self.MenuText = MenuText
|
||||
self.ParentMenu = ParentMenu
|
||||
self.MenuParentPath = MenuParentPath
|
||||
self.Path = ( self.ParentMenu and "@" .. table.concat( self.MenuParentPath or {}, "@" ) or "" ) .. "@" .. self.MenuText
|
||||
self.MenuPath = nil
|
||||
self.MenuText = MenuText
|
||||
self.ParentMenu = ParentMenu
|
||||
self.MenuParentPath = MenuParentPath
|
||||
self.Path = ( self.ParentMenu and "@" .. table.concat( self.MenuParentPath or {}, "@" ) or "" ) .. "@" .. self.MenuText
|
||||
self.Menus = {}
|
||||
self.MenuCount = 0
|
||||
self.MenuStamp = timer.getTime()
|
||||
self.MenuRemoveParent = false
|
||||
|
||||
|
||||
if self.ParentMenu then
|
||||
self.ParentMenu.Menus = self.ParentMenu.Menus or {}
|
||||
self.ParentMenu.Menus[MenuText] = self
|
||||
end
|
||||
|
||||
return self
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function MENU_BASE:SetParentMenu( MenuText, Menu )
|
||||
if self.ParentMenu then
|
||||
self.ParentMenu.Menus = self.ParentMenu.Menus or {}
|
||||
@@ -256,7 +213,6 @@ do -- MENU_BASE
|
||||
self.ParentMenu.MenuCount = self.ParentMenu.MenuCount + 1
|
||||
end
|
||||
end
|
||||
|
||||
function MENU_BASE:ClearParentMenu( MenuText )
|
||||
if self.ParentMenu and self.ParentMenu.Menus[MenuText] then
|
||||
self.ParentMenu.Menus[MenuText] = nil
|
||||
@@ -266,7 +222,6 @@ do -- MENU_BASE
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Sets a @{Menu} to remove automatically the parent menu when the menu removed is the last child menu of that parent @{Menu}.
|
||||
-- @param #MENU_BASE self
|
||||
-- @param #boolean RemoveParent If true, the parent menu is automatically removed when this menu is the last child menu of that parent @{Menu}.
|
||||
@@ -276,7 +231,6 @@ do -- MENU_BASE
|
||||
self.MenuRemoveParent = RemoveParent
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Gets a @{Menu} from a parent @{Menu}
|
||||
-- @param #MENU_BASE self
|
||||
@@ -285,7 +239,7 @@ do -- MENU_BASE
|
||||
function MENU_BASE:GetMenu( MenuText )
|
||||
return self.Menus[MenuText]
|
||||
end
|
||||
|
||||
|
||||
--- Sets a menu stamp for later prevention of menu removal.
|
||||
-- @param #MENU_BASE self
|
||||
-- @param MenuStamp
|
||||
@@ -323,9 +277,7 @@ do -- MENU_BASE
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
do -- MENU_COMMAND_BASE
|
||||
|
||||
--- @type MENU_COMMAND_BASE
|
||||
-- @field #function MenuCallHandler
|
||||
-- @extends Core.Menu#MENU_BASE
|
||||
@@ -346,8 +298,7 @@ do -- MENU_COMMAND_BASE
|
||||
-- @return #MENU_COMMAND_BASE
|
||||
function MENU_COMMAND_BASE:New( MenuText, ParentMenu, CommandMenuFunction, CommandMenuArguments )
|
||||
|
||||
local self = BASE:Inherit( self, MENU_BASE:New( MenuText, ParentMenu ) ) -- #MENU_COMMAND_BASE
|
||||
|
||||
local self = BASE:Inherit( self, MENU_BASE:New( MenuText, ParentMenu ) ) -- #MENU_COMMAND_BASE
|
||||
-- When a menu function goes into error, DCS displays an obscure menu message.
|
||||
-- This error handler catches the menu error and displays the full call stack.
|
||||
local ErrorHandler = function( errmsg )
|
||||
@@ -367,7 +318,7 @@ do -- MENU_COMMAND_BASE
|
||||
local Status, Result = xpcall( MenuFunction, ErrorHandler )
|
||||
end
|
||||
|
||||
return self
|
||||
return self
|
||||
end
|
||||
|
||||
--- This sets the new command function of a menu,
|
||||
@@ -380,7 +331,6 @@ do -- MENU_COMMAND_BASE
|
||||
self.CommandMenuFunction = CommandMenuFunction
|
||||
return self
|
||||
end
|
||||
|
||||
--- This sets the new command arguments of a menu,
|
||||
-- so that if a menu is regenerated, or if command arguments change,
|
||||
-- that the arguments set for the menu are loosely coupled with the menu itself!!!
|
||||
@@ -391,35 +341,30 @@ do -- MENU_COMMAND_BASE
|
||||
self.CommandMenuArguments = CommandMenuArguments
|
||||
return self
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
do -- MENU_MISSION
|
||||
|
||||
--- @type MENU_MISSION
|
||||
-- @extends Core.Menu#MENU_BASE
|
||||
|
||||
--- Manages the main menus for a complete mission.
|
||||
--
|
||||
-- You can add menus with the @{#MENU_MISSION.New} method, which constructs a MENU_MISSION object and returns you the object reference.
|
||||
-- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_MISSION.Remove}.
|
||||
-- @field #MENU_MISSION
|
||||
MENU_MISSION = {
|
||||
ClassName = "MENU_MISSION"
|
||||
ClassName = "MENU_MISSION",
|
||||
}
|
||||
|
||||
--- MENU_MISSION constructor. Creates a new MENU_MISSION object and creates the menu for a complete mission file.
|
||||
-- @param #MENU_MISSION self
|
||||
-- @param #string MenuText The text for the menu.
|
||||
-- @param #table ParentMenu The parent menu. This parameter can be ignored if you want the menu to be located at the perent menu of DCS world (under F10 other).
|
||||
-- @param #table ParentMenu The parent menu. This parameter can be ignored if you want the menu to be located at the parent menu of DCS world (under F10 other).
|
||||
-- @return #MENU_MISSION
|
||||
function MENU_MISSION:New( MenuText, ParentMenu )
|
||||
|
||||
MENU_INDEX:PrepareMission()
|
||||
local Path = MENU_INDEX:ParentPath( ParentMenu, MenuText )
|
||||
local MissionMenu = MENU_INDEX:HasMissionMenu( Path )
|
||||
|
||||
if MissionMenu then
|
||||
return MissionMenu
|
||||
else
|
||||
@@ -432,17 +377,16 @@ do -- MENU_MISSION
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- Refreshes a radio item for a mission
|
||||
-- @param #MENU_MISSION self
|
||||
-- @return #MENU_MISSION
|
||||
function MENU_MISSION:Refresh()
|
||||
|
||||
do
|
||||
missionCommands.removeItem( self.MenuPath )
|
||||
self.MenuPath = missionCommands.addSubMenu( self.MenuText, self.MenuParentPath )
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Removes the sub menus recursively of this MENU_MISSION. Note that the main menu is kept!
|
||||
@@ -466,7 +410,6 @@ do -- MENU_MISSION
|
||||
MENU_INDEX:PrepareMission()
|
||||
local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText )
|
||||
local MissionMenu = MENU_INDEX:HasMissionMenu( Path )
|
||||
|
||||
if MissionMenu == self then
|
||||
self:RemoveSubMenus()
|
||||
if not MenuStamp or self.MenuStamp ~= MenuStamp then
|
||||
@@ -487,10 +430,7 @@ do -- MENU_MISSION
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
|
||||
end
|
||||
|
||||
do -- MENU_MISSION_COMMAND
|
||||
|
||||
--- @type MENU_MISSION_COMMAND
|
||||
@@ -503,7 +443,7 @@ do -- MENU_MISSION_COMMAND
|
||||
--
|
||||
-- @field #MENU_MISSION_COMMAND
|
||||
MENU_MISSION_COMMAND = {
|
||||
ClassName = "MENU_MISSION_COMMAND"
|
||||
ClassName = "MENU_MISSION_COMMAND",
|
||||
}
|
||||
|
||||
--- MENU_MISSION constructor. Creates a new radio command item for a complete mission file, which can invoke a function with parameters.
|
||||
@@ -518,7 +458,6 @@ do -- MENU_MISSION_COMMAND
|
||||
MENU_INDEX:PrepareMission()
|
||||
local Path = MENU_INDEX:ParentPath( ParentMenu, MenuText )
|
||||
local MissionMenu = MENU_INDEX:HasMissionMenu( Path )
|
||||
|
||||
if MissionMenu then
|
||||
MissionMenu:SetCommandMenuFunction( CommandMenuFunction )
|
||||
MissionMenu:SetCommandMenuArguments( arg )
|
||||
@@ -532,17 +471,15 @@ do -- MENU_MISSION_COMMAND
|
||||
return self
|
||||
end
|
||||
end
|
||||
|
||||
--- Refreshes a radio item for a mission
|
||||
-- @param #MENU_MISSION_COMMAND self
|
||||
-- @return #MENU_MISSION_COMMAND
|
||||
function MENU_MISSION_COMMAND:Refresh()
|
||||
|
||||
do
|
||||
missionCommands.removeItem( self.MenuPath )
|
||||
missionCommands.addCommand( self.MenuText, self.MenuParentPath, self.MenuCallHandler )
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Removes a radio command item for a coalition
|
||||
@@ -553,7 +490,6 @@ do -- MENU_MISSION_COMMAND
|
||||
MENU_INDEX:PrepareMission()
|
||||
local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText )
|
||||
local MissionMenu = MENU_INDEX:HasMissionMenu( Path )
|
||||
|
||||
if MissionMenu == self then
|
||||
if not MenuStamp or self.MenuStamp ~= MenuStamp then
|
||||
if ( not MenuTag ) or ( MenuTag and self.MenuTag and MenuTag == self.MenuTag ) then
|
||||
@@ -572,13 +508,8 @@ do -- MENU_MISSION_COMMAND
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
|
||||
do -- MENU_COALITION
|
||||
|
||||
--- @type MENU_COALITION
|
||||
-- @extends Core.Menu#MENU_BASE
|
||||
|
||||
@@ -634,18 +565,15 @@ do -- MENU_COALITION
|
||||
-- @param #MENU_COALITION self
|
||||
-- @param DCS#coalition.side Coalition The coalition owning the menu.
|
||||
-- @param #string MenuText The text for the menu.
|
||||
-- @param #table ParentMenu The parent menu. This parameter can be ignored if you want the menu to be located at the perent menu of DCS world (under F10 other).
|
||||
-- @param #table ParentMenu The parent menu. This parameter can be ignored if you want the menu to be located at the parent menu of DCS world (under F10 other).
|
||||
-- @return #MENU_COALITION self
|
||||
function MENU_COALITION:New( Coalition, MenuText, ParentMenu )
|
||||
|
||||
MENU_INDEX:PrepareCoalition( Coalition )
|
||||
local Path = MENU_INDEX:ParentPath( ParentMenu, MenuText )
|
||||
local CoalitionMenu = MENU_INDEX:HasCoalitionMenu( Coalition, Path )
|
||||
|
||||
if CoalitionMenu then
|
||||
return CoalitionMenu
|
||||
else
|
||||
|
||||
local self = BASE:Inherit( self, MENU_BASE:New( MenuText, ParentMenu ) )
|
||||
MENU_INDEX:SetCoalitionMenu( Coalition, Path, self )
|
||||
|
||||
@@ -656,17 +584,15 @@ do -- MENU_COALITION
|
||||
return self
|
||||
end
|
||||
end
|
||||
|
||||
--- Refreshes a radio item for a coalition
|
||||
-- @param #MENU_COALITION self
|
||||
-- @return #MENU_COALITION
|
||||
function MENU_COALITION:Refresh()
|
||||
|
||||
do
|
||||
missionCommands.removeItemForCoalition( self.Coalition, self.MenuPath )
|
||||
missionCommands.addSubMenuForCoalition( self.Coalition, self.MenuText, self.MenuParentPath )
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Removes the sub menus recursively of this MENU_COALITION. Note that the main menu is kept!
|
||||
@@ -689,7 +615,6 @@ do -- MENU_COALITION
|
||||
MENU_INDEX:PrepareCoalition( self.Coalition )
|
||||
local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText )
|
||||
local CoalitionMenu = MENU_INDEX:HasCoalitionMenu( self.Coalition, Path )
|
||||
|
||||
if CoalitionMenu == self then
|
||||
self:RemoveSubMenus()
|
||||
if not MenuStamp or self.MenuStamp ~= MenuStamp then
|
||||
@@ -709,11 +634,7 @@ do -- MENU_COALITION
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
|
||||
do -- MENU_COALITION_COMMAND
|
||||
|
||||
--- @type MENU_COALITION_COMMAND
|
||||
@@ -742,7 +663,6 @@ do -- MENU_COALITION_COMMAND
|
||||
MENU_INDEX:PrepareCoalition( Coalition )
|
||||
local Path = MENU_INDEX:ParentPath( ParentMenu, MenuText )
|
||||
local CoalitionMenu = MENU_INDEX:HasCoalitionMenu( Coalition, Path )
|
||||
|
||||
if CoalitionMenu then
|
||||
CoalitionMenu:SetCommandMenuFunction( CommandMenuFunction )
|
||||
CoalitionMenu:SetCommandMenuArguments( arg )
|
||||
@@ -757,20 +677,17 @@ do -- MENU_COALITION_COMMAND
|
||||
self:SetParentMenu( self.MenuText, self )
|
||||
return self
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- Refreshes a radio item for a coalition
|
||||
-- @param #MENU_COALITION_COMMAND self
|
||||
-- @return #MENU_COALITION_COMMAND
|
||||
function MENU_COALITION_COMMAND:Refresh()
|
||||
|
||||
do
|
||||
missionCommands.removeItemForCoalition( self.Coalition, self.MenuPath )
|
||||
missionCommands.addCommandForCoalition( self.Coalition, self.MenuText, self.MenuParentPath, self.MenuCallHandler )
|
||||
end
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Removes a radio command item for a coalition
|
||||
@@ -781,7 +698,6 @@ do -- MENU_COALITION_COMMAND
|
||||
MENU_INDEX:PrepareCoalition( self.Coalition )
|
||||
local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText )
|
||||
local CoalitionMenu = MENU_INDEX:HasCoalitionMenu( self.Coalition, Path )
|
||||
|
||||
if CoalitionMenu == self then
|
||||
if not MenuStamp or self.MenuStamp ~= MenuStamp then
|
||||
if ( not MenuTag ) or ( MenuTag and self.MenuTag and MenuTag == self.MenuTag ) then
|
||||
@@ -800,20 +716,16 @@ do -- MENU_COALITION_COMMAND
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- MENU_GROUP
|
||||
|
||||
do
|
||||
-- This local variable is used to cache the menus registered under groups.
|
||||
-- Menus don't dissapear when groups for players are destroyed and restarted.
|
||||
-- Menus don't disappear when groups for players are destroyed and restarted.
|
||||
-- So every menu for a client created must be tracked so that program logic accidentally does not create.
|
||||
-- the same menus twice during initialization logic.
|
||||
-- These menu classes are handling this logic with this variable.
|
||||
local _MENUGROUPS = {}
|
||||
|
||||
--- @type MENU_GROUP
|
||||
-- @extends Core.Menu#MENU_BASE
|
||||
|
||||
@@ -889,16 +801,13 @@ do
|
||||
MENU_INDEX:PrepareGroup( Group )
|
||||
local Path = MENU_INDEX:ParentPath( ParentMenu, MenuText )
|
||||
local GroupMenu = MENU_INDEX:HasGroupMenu( Group, Path )
|
||||
|
||||
if GroupMenu then
|
||||
return GroupMenu
|
||||
else
|
||||
self = BASE:Inherit( self, MENU_BASE:New( MenuText, ParentMenu ) )
|
||||
MENU_INDEX:SetGroupMenu( Group, Path, self )
|
||||
|
||||
self.Group = Group
|
||||
self.GroupID = Group:GetID()
|
||||
|
||||
self.MenuPath = missionCommands.addSubMenuForGroup( self.GroupID, MenuText, self.MenuParentPath )
|
||||
|
||||
self:SetParentMenu( self.MenuText, self )
|
||||
@@ -906,12 +815,11 @@ do
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- Refreshes a new radio item for a group and submenus
|
||||
-- @param #MENU_GROUP self
|
||||
-- @return #MENU_GROUP
|
||||
function MENU_GROUP:Refresh()
|
||||
|
||||
do
|
||||
missionCommands.removeItemForGroup( self.GroupID, self.MenuPath )
|
||||
missionCommands.addSubMenuForGroup( self.GroupID, self.MenuText, self.MenuParentPath )
|
||||
@@ -920,7 +828,31 @@ do
|
||||
Menu:Refresh()
|
||||
end
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Refreshes a new radio item for a group and submenus, ordering by (numerical) MenuTag
|
||||
-- @param #MENU_GROUP self
|
||||
-- @return #MENU_GROUP
|
||||
function MENU_GROUP:RefreshAndOrderByTag()
|
||||
|
||||
do
|
||||
missionCommands.removeItemForGroup( self.GroupID, self.MenuPath )
|
||||
missionCommands.addSubMenuForGroup( self.GroupID, self.MenuText, self.MenuParentPath )
|
||||
|
||||
local MenuTable = {}
|
||||
for MenuText, Menu in pairs( self.Menus or {} ) do
|
||||
local tag = Menu.MenuTag or math.random(1,10000)
|
||||
MenuTable[#MenuTable+1] = {Tag=tag, Enty=Menu}
|
||||
end
|
||||
table.sort(MenuTable, function (k1, k2) return k1.tag < k2.tag end )
|
||||
for _, Menu in pairs( MenuTable ) do
|
||||
Menu.Entry:Refresh()
|
||||
end
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Removes the sub menus recursively of this MENU_GROUP.
|
||||
@@ -929,7 +861,6 @@ do
|
||||
-- @param MenuTag A Tag or Key to filter the menus to be refreshed with the Tag set.
|
||||
-- @return #MENU_GROUP self
|
||||
function MENU_GROUP:RemoveSubMenus( MenuStamp, MenuTag )
|
||||
|
||||
for MenuText, Menu in pairs( self.Menus or {} ) do
|
||||
Menu:Remove( MenuStamp, MenuTag )
|
||||
end
|
||||
@@ -938,18 +869,15 @@ do
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- Removes the main menu and sub menus recursively of this MENU_GROUP.
|
||||
-- @param #MENU_GROUP self
|
||||
-- @param MenuStamp
|
||||
-- @param MenuTag A Tag or Key to filter the menus to be refreshed with the Tag set.
|
||||
-- @return #nil
|
||||
function MENU_GROUP:Remove( MenuStamp, MenuTag )
|
||||
|
||||
MENU_INDEX:PrepareGroup( self.Group )
|
||||
local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText )
|
||||
local GroupMenu = MENU_INDEX:HasGroupMenu( self.Group, Path )
|
||||
|
||||
if GroupMenu == self then
|
||||
self:RemoveSubMenus( MenuStamp, MenuTag )
|
||||
if not MenuStamp or self.MenuStamp ~= MenuStamp then
|
||||
@@ -993,18 +921,15 @@ do
|
||||
-- @param CommandMenuArgument An argument for the function.
|
||||
-- @return #MENU_GROUP_COMMAND
|
||||
function MENU_GROUP_COMMAND:New( Group, MenuText, ParentMenu, CommandMenuFunction, ... )
|
||||
|
||||
MENU_INDEX:PrepareGroup( Group )
|
||||
local Path = MENU_INDEX:ParentPath( ParentMenu, MenuText )
|
||||
local GroupMenu = MENU_INDEX:HasGroupMenu( Group, Path )
|
||||
|
||||
if GroupMenu then
|
||||
GroupMenu:SetCommandMenuFunction( CommandMenuFunction )
|
||||
GroupMenu:SetCommandMenuArguments( arg )
|
||||
return GroupMenu
|
||||
else
|
||||
self = BASE:Inherit( self, MENU_COMMAND_BASE:New( MenuText, ParentMenu, CommandMenuFunction, arg ) )
|
||||
|
||||
MENU_INDEX:SetGroupMenu( Group, Path, self )
|
||||
|
||||
self.Group = Group
|
||||
@@ -1015,19 +940,17 @@ do
|
||||
self:SetParentMenu( self.MenuText, self )
|
||||
return self
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Refreshes a radio item for a group
|
||||
-- @param #MENU_GROUP_COMMAND self
|
||||
-- @return #MENU_GROUP_COMMAND
|
||||
function MENU_GROUP_COMMAND:Refresh()
|
||||
|
||||
do
|
||||
missionCommands.removeItemForGroup( self.GroupID, self.MenuPath )
|
||||
missionCommands.addCommandForGroup( self.GroupID, self.MenuText, self.MenuParentPath, self.MenuCallHandler )
|
||||
end
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Removes a menu structure for a group.
|
||||
@@ -1036,11 +959,9 @@ do
|
||||
-- @param MenuTag A Tag or Key to filter the menus to be refreshed with the Tag set.
|
||||
-- @return #nil
|
||||
function MENU_GROUP_COMMAND:Remove( MenuStamp, MenuTag )
|
||||
|
||||
MENU_INDEX:PrepareGroup( self.Group )
|
||||
local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText )
|
||||
local GroupMenu = MENU_INDEX:HasGroupMenu( self.Group, Path )
|
||||
|
||||
if GroupMenu == self then
|
||||
if not MenuStamp or self.MenuStamp ~= MenuStamp then
|
||||
if ( not MenuTag ) or ( MenuTag and self.MenuTag and MenuTag == self.MenuTag ) then
|
||||
@@ -1059,13 +980,9 @@ do
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- MENU_GROUP_DELAYED
|
||||
|
||||
do
|
||||
|
||||
--- @type MENU_GROUP_DELAYED
|
||||
-- @extends Core.Menu#MENU_BASE
|
||||
|
||||
@@ -1093,16 +1010,13 @@ do
|
||||
MENU_INDEX:PrepareGroup( Group )
|
||||
local Path = MENU_INDEX:ParentPath( ParentMenu, MenuText )
|
||||
local GroupMenu = MENU_INDEX:HasGroupMenu( Group, Path )
|
||||
|
||||
if GroupMenu then
|
||||
return GroupMenu
|
||||
else
|
||||
self = BASE:Inherit( self, MENU_BASE:New( MenuText, ParentMenu ) )
|
||||
MENU_INDEX:SetGroupMenu( Group, Path, self )
|
||||
|
||||
self.Group = Group
|
||||
self.GroupID = Group:GetID()
|
||||
|
||||
if self.MenuParentPath then
|
||||
self.MenuPath = UTILS.DeepCopy( self.MenuParentPath )
|
||||
else
|
||||
@@ -1116,12 +1030,10 @@ do
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- Refreshes a new radio item for a group and submenus
|
||||
-- @param #MENU_GROUP_DELAYED self
|
||||
-- @return #MENU_GROUP_DELAYED
|
||||
function MENU_GROUP_DELAYED:Set()
|
||||
|
||||
do
|
||||
if not self.MenuSet then
|
||||
missionCommands.addSubMenuForGroup( self.GroupID, self.MenuText, self.MenuParentPath )
|
||||
@@ -1132,15 +1044,12 @@ do
|
||||
Menu:Set()
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- Refreshes a new radio item for a group and submenus
|
||||
-- @param #MENU_GROUP_DELAYED self
|
||||
-- @return #MENU_GROUP_DELAYED
|
||||
function MENU_GROUP_DELAYED:Refresh()
|
||||
|
||||
do
|
||||
missionCommands.removeItemForGroup( self.GroupID, self.MenuPath )
|
||||
missionCommands.addSubMenuForGroup( self.GroupID, self.MenuText, self.MenuParentPath )
|
||||
@@ -1149,7 +1058,8 @@ do
|
||||
Menu:Refresh()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Removes the sub menus recursively of this MENU_GROUP_DELAYED.
|
||||
@@ -1158,7 +1068,6 @@ do
|
||||
-- @param MenuTag A Tag or Key to filter the menus to be refreshed with the Tag set.
|
||||
-- @return #MENU_GROUP_DELAYED self
|
||||
function MENU_GROUP_DELAYED:RemoveSubMenus( MenuStamp, MenuTag )
|
||||
|
||||
for MenuText, Menu in pairs( self.Menus or {} ) do
|
||||
Menu:Remove( MenuStamp, MenuTag )
|
||||
end
|
||||
@@ -1167,18 +1076,15 @@ do
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- Removes the main menu and sub menus recursively of this MENU_GROUP.
|
||||
-- @param #MENU_GROUP_DELAYED self
|
||||
-- @param MenuStamp
|
||||
-- @param MenuTag A Tag or Key to filter the menus to be refreshed with the Tag set.
|
||||
-- @return #nil
|
||||
function MENU_GROUP_DELAYED:Remove( MenuStamp, MenuTag )
|
||||
|
||||
MENU_INDEX:PrepareGroup( self.Group )
|
||||
local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText )
|
||||
local GroupMenu = MENU_INDEX:HasGroupMenu( self.Group, Path )
|
||||
|
||||
if GroupMenu == self then
|
||||
self:RemoveSubMenus( MenuStamp, MenuTag )
|
||||
if not MenuStamp or self.MenuStamp ~= MenuStamp then
|
||||
@@ -1223,18 +1129,15 @@ do
|
||||
-- @param CommandMenuArgument An argument for the function.
|
||||
-- @return #MENU_GROUP_COMMAND_DELAYED
|
||||
function MENU_GROUP_COMMAND_DELAYED:New( Group, MenuText, ParentMenu, CommandMenuFunction, ... )
|
||||
|
||||
MENU_INDEX:PrepareGroup( Group )
|
||||
local Path = MENU_INDEX:ParentPath( ParentMenu, MenuText )
|
||||
local GroupMenu = MENU_INDEX:HasGroupMenu( Group, Path )
|
||||
|
||||
if GroupMenu then
|
||||
GroupMenu:SetCommandMenuFunction( CommandMenuFunction )
|
||||
GroupMenu:SetCommandMenuArguments( arg )
|
||||
return GroupMenu
|
||||
else
|
||||
self = BASE:Inherit( self, MENU_COMMAND_BASE:New( MenuText, ParentMenu, CommandMenuFunction, arg ) )
|
||||
|
||||
MENU_INDEX:SetGroupMenu( Group, Path, self )
|
||||
|
||||
self.Group = Group
|
||||
@@ -1250,33 +1153,29 @@ do
|
||||
self:SetParentMenu( self.MenuText, self )
|
||||
return self
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Refreshes a radio item for a group
|
||||
-- @param #MENU_GROUP_COMMAND_DELAYED self
|
||||
-- @return #MENU_GROUP_COMMAND_DELAYED
|
||||
function MENU_GROUP_COMMAND_DELAYED:Set()
|
||||
|
||||
do
|
||||
if not self.MenuSet then
|
||||
self.MenuPath = missionCommands.addCommandForGroup( self.GroupID, self.MenuText, self.MenuParentPath, self.MenuCallHandler )
|
||||
self.MenuSet = true
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Refreshes a radio item for a group
|
||||
-- @param #MENU_GROUP_COMMAND_DELAYED self
|
||||
-- @return #MENU_GROUP_COMMAND_DELAYED
|
||||
function MENU_GROUP_COMMAND_DELAYED:Refresh()
|
||||
|
||||
do
|
||||
missionCommands.removeItemForGroup( self.GroupID, self.MenuPath )
|
||||
missionCommands.addCommandForGroup( self.GroupID, self.MenuText, self.MenuParentPath, self.MenuCallHandler )
|
||||
end
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Removes a menu structure for a group.
|
||||
@@ -1285,11 +1184,9 @@ do
|
||||
-- @param MenuTag A Tag or Key to filter the menus to be refreshed with the Tag set.
|
||||
-- @return #nil
|
||||
function MENU_GROUP_COMMAND_DELAYED:Remove( MenuStamp, MenuTag )
|
||||
|
||||
MENU_INDEX:PrepareGroup( self.Group )
|
||||
local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText )
|
||||
local GroupMenu = MENU_INDEX:HasGroupMenu( self.Group, Path )
|
||||
|
||||
if GroupMenu == self then
|
||||
if not MenuStamp or self.MenuStamp ~= MenuStamp then
|
||||
if ( not MenuTag ) or ( MenuTag and self.MenuTag and MenuTag == self.MenuTag ) then
|
||||
@@ -1308,6 +1205,4 @@ do
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
--- **Core** - Informs the players using messages during a simulation.
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- ## Features:
|
||||
--
|
||||
--
|
||||
-- * A more advanced messaging system using the DCS message system.
|
||||
-- * Time messages.
|
||||
-- * Send messages based on a message type, which has a pre-defined duration that can be tweaked in SETTINGS.
|
||||
-- * Send message to all players.
|
||||
-- * Send messages to a coalition.
|
||||
-- * Send messages to a specific group.
|
||||
-- * Send messages to a specific unit or client.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- @module Core.Message
|
||||
-- @image Core_Message.JPG
|
||||
|
||||
@@ -23,42 +24,43 @@
|
||||
--- Message System to display Messages to Clients, Coalitions or All.
|
||||
-- Messages are shown on the display panel for an amount of seconds, and will then disappear.
|
||||
-- Messages can contain a category which is indicating the category of the message.
|
||||
--
|
||||
--
|
||||
-- ## MESSAGE construction
|
||||
--
|
||||
--
|
||||
-- Messages are created with @{#MESSAGE.New}. Note that when the MESSAGE object is created, no message is sent yet.
|
||||
-- To send messages, you need to use the To functions.
|
||||
--
|
||||
--
|
||||
-- ## Send messages to an audience
|
||||
--
|
||||
--
|
||||
-- Messages are sent:
|
||||
--
|
||||
-- * To a @{Client} using @{#MESSAGE.ToClient}().
|
||||
-- * To a @{Wrapper.Group} using @{#MESSAGE.ToGroup}()
|
||||
-- * To a @{Wrapper.Unit} using @{#MESSAGE.ToUnit}()
|
||||
-- * To a coalition using @{#MESSAGE.ToCoalition}().
|
||||
-- * To the red coalition using @{#MESSAGE.ToRed}().
|
||||
-- * To the blue coalition using @{#MESSAGE.ToBlue}().
|
||||
-- * To all Players using @{#MESSAGE.ToAll}().
|
||||
--
|
||||
--
|
||||
-- ## Send conditionally to an audience
|
||||
--
|
||||
--
|
||||
-- Messages can be sent conditionally to an audience (when a condition is true):
|
||||
--
|
||||
--
|
||||
-- * To all players using @{#MESSAGE.ToAllIf}().
|
||||
-- * To a coalition using @{#MESSAGE.ToCoalitionIf}().
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
-- ### Contributions:
|
||||
--
|
||||
-- ### Contributions:
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- @field #MESSAGE
|
||||
MESSAGE = {
|
||||
ClassName = "MESSAGE",
|
||||
MessageCategory = 0,
|
||||
MessageID = 0,
|
||||
ClassName = "MESSAGE",
|
||||
MessageCategory = 0,
|
||||
MessageID = 0,
|
||||
}
|
||||
|
||||
--- Message Types
|
||||
@@ -68,10 +70,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 +81,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 +135,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)
|
||||
--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 +222,129 @@ 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
|
||||
--- Sends a MESSAGE to a Unit.
|
||||
-- @param #MESSAGE self
|
||||
-- @param Wrapper.Unit#UNIT Unit to which the message is displayed.
|
||||
-- @return #MESSAGE Message object.
|
||||
function MESSAGE:ToUnit( Unit, Settings )
|
||||
self:F( Unit.IdentifiableName )
|
||||
|
||||
if Unit then
|
||||
|
||||
if self.MessageType then
|
||||
local Settings = Settings or ( Unit and _DATABASE:GetPlayerSettings( Unit:GetPlayerName() ) ) or _SETTINGS -- Core.Settings#SETTINGS
|
||||
self.MessageDuration = Settings:GetMessageTime( self.MessageType )
|
||||
self.MessageCategory = "" -- self.MessageType .. ": "
|
||||
end
|
||||
|
||||
if self.MessageDuration ~= 0 then
|
||||
self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration )
|
||||
trigger.action.outTextForUnit( Unit:GetID(), self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration, self.ClearScreen )
|
||||
end
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sends a MESSAGE to the Red Coalition.
|
||||
--- Sends a MESSAGE to the Blue 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 +352,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 +373,7 @@ function MESSAGE:ToCoalitionIf( CoalitionSide, Condition )
|
||||
if Condition and Condition == true then
|
||||
self:ToCoalition( CoalitionSide )
|
||||
end
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -323,14 +382,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 +401,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 +417,5 @@ function MESSAGE:ToAllIf( Condition )
|
||||
self:ToAll()
|
||||
end
|
||||
|
||||
return self
|
||||
return self
|
||||
end
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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,28 +44,26 @@ 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
|
||||
|
||||
@@ -76,17 +73,17 @@ end
|
||||
-- @param #string Separator (optional) The start of each report line can begin with an optional separator character. This can be a "-", or "#", or "*". You're free to choose what you find the best.
|
||||
-- @return #REPORT
|
||||
function REPORT:AddIndent( Text, Separator )
|
||||
self.Report[#self.Report+1] = ( ( Separator and Separator .. string.rep( " ", self.Indent - 1 ) ) or string.rep(" ", self.Indent ) ) .. Text:gsub("\n","\n"..string.rep( " ", self.Indent ) )
|
||||
self.Report[#self.Report + 1] = ((Separator and Separator .. string.rep( " ", self.Indent - 1 )) or string.rep( " ", self.Indent )) .. Text:gsub( "\n", "\n" .. string.rep( " ", self.Indent ) )
|
||||
return self
|
||||
end
|
||||
|
||||
--- Produces the text of the report, taking into account an optional 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
|
||||
|
||||
@@ -95,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,36 +1,36 @@
|
||||
--- **Core** -- SCHEDULEDISPATCHER dispatches the different schedules.
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- Takes care of the creation and dispatching of scheduled functions for SCHEDULER objects.
|
||||
--
|
||||
--
|
||||
-- This class is tricky and needs some thorough explanation.
|
||||
-- SCHEDULE classes are used to schedule functions for objects, or as persistent objects.
|
||||
-- The SCHEDULEDISPATCHER class ensures that:
|
||||
--
|
||||
--
|
||||
-- - Scheduled functions are planned according the SCHEDULER object parameters.
|
||||
-- - Scheduled functions are repeated when requested, according the SCHEDULER object parameters.
|
||||
-- - Scheduled functions are automatically removed when the schedule is finished, according the SCHEDULER object parameters.
|
||||
--
|
||||
--
|
||||
-- The SCHEDULEDISPATCHER class will manage SCHEDULER object in memory during garbage collection:
|
||||
--
|
||||
--
|
||||
-- - When a SCHEDULER object is not attached to another object (that is, it's first :Schedule() parameter is nil), then the SCHEDULER object is _persistent_ within memory.
|
||||
-- - When a SCHEDULER object *is* attached to another object, then the SCHEDULER object is _not persistent_ within memory after a garbage collection!
|
||||
--
|
||||
-- The 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
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
-- @type SCHEDULEDISPATCHER
|
||||
-- @field #string ClassName Name of the class.
|
||||
-- @field #number CallID Call ID counter.
|
||||
-- @field #table PersistentSchedulers Persistant schedulers.
|
||||
-- @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
|
||||
@@ -46,11 +46,11 @@
|
||||
--- The SCHEDULEDISPATCHER structure
|
||||
-- @type SCHEDULEDISPATCHER
|
||||
SCHEDULEDISPATCHER = {
|
||||
ClassName = "SCHEDULEDISPATCHER",
|
||||
CallID = 0,
|
||||
PersistentSchedulers = {},
|
||||
ObjectSchedulers = {},
|
||||
Schedule = nil,
|
||||
ClassName = "SCHEDULEDISPATCHER",
|
||||
CallID = 0,
|
||||
PersistentSchedulers = {},
|
||||
ObjectSchedulers = {},
|
||||
Schedule = nil,
|
||||
}
|
||||
|
||||
--- Player data table holding all important parameters of each player.
|
||||
@@ -58,7 +58,7 @@ SCHEDULEDISPATCHER = {
|
||||
-- @field #function Function The schedule function to be called.
|
||||
-- @field #table Arguments Schedule function arguments.
|
||||
-- @field #number Start Start time in seconds.
|
||||
-- @field #number Repeat Repeat time intervall 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.
|
||||
@@ -77,7 +77,7 @@ end
|
||||
|
||||
--- Add a Schedule to the ScheduleDispatcher.
|
||||
-- The development of this method was really tidy.
|
||||
-- It is constructed as such that a garbage collection is executed on the weak tables, when the Scheduler is 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 Scheduler object.
|
||||
@@ -85,7 +85,7 @@ end
|
||||
-- @param #table ScheduleArguments Table of arguments passed to the ScheduleFunction.
|
||||
-- @param #number Start Start time in seconds.
|
||||
-- @param #number Repeat Repeat interval in seconds.
|
||||
-- @param #number Randomize Radomization factor [0,1].
|
||||
-- @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.
|
||||
@@ -95,39 +95,40 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr
|
||||
|
||||
-- Increase counter.
|
||||
self.CallID = self.CallID + 1
|
||||
|
||||
|
||||
-- Create ID.
|
||||
local CallID = self.CallID .. "#" .. ( Scheduler.MasterObject and Scheduler.MasterObject.GetClassNameAndID and Scheduler.MasterObject:GetClassNameAndID() or "" ) or ""
|
||||
|
||||
self:T2(string.format("Adding schedule #%d CallID=%s", self.CallID, CallID))
|
||||
local CallID = self.CallID .. "#" .. (Scheduler.MasterObject and Scheduler.MasterObject.GetClassNameAndID and Scheduler.MasterObject:GetClassNameAndID() or "") or ""
|
||||
|
||||
self:T2( string.format( "Adding schedule #%d CallID=%s", self.CallID, CallID ) )
|
||||
|
||||
-- Initialize PersistentSchedulers
|
||||
self.PersistentSchedulers = self.PersistentSchedulers or {}
|
||||
|
||||
-- Initialize the ObjectSchedulers array, which is a weakly coupled table.
|
||||
-- If the object used as the key is nil, then the garbage collector will remove the item from the Functions array.
|
||||
self.ObjectSchedulers = self.ObjectSchedulers or setmetatable( {}, { __mode = "v" } )
|
||||
|
||||
self.ObjectSchedulers = self.ObjectSchedulers or setmetatable( {}, { __mode = "v" } )
|
||||
|
||||
if Scheduler.MasterObject then
|
||||
--env.info("FF Object Scheduler")
|
||||
self.ObjectSchedulers[CallID] = Scheduler
|
||||
self:F3( { CallID = CallID, ObjectScheduler = tostring(self.ObjectSchedulers[CallID]), MasterObject = tostring(Scheduler.MasterObject) } )
|
||||
self:F3( { CallID = CallID, ObjectScheduler = tostring( self.ObjectSchedulers[CallID] ), MasterObject = tostring( Scheduler.MasterObject ) } )
|
||||
else
|
||||
--env.info("FF Persistent Scheduler")
|
||||
self.PersistentSchedulers[CallID] = Scheduler
|
||||
self:F3( { CallID = CallID, PersistentScheduler = self.PersistentSchedulers[CallID] } )
|
||||
end
|
||||
|
||||
|
||||
self.Schedule = self.Schedule or setmetatable( {}, { __mode = "k" } )
|
||||
self.Schedule[Scheduler] = self.Schedule[Scheduler] or {}
|
||||
self.Schedule[Scheduler][CallID] = {} --#SCHEDULEDISPATCHER.ScheduleData
|
||||
self.Schedule[Scheduler][CallID] = {} -- #SCHEDULEDISPATCHER.ScheduleData
|
||||
self.Schedule[Scheduler][CallID].Function = ScheduleFunction
|
||||
self.Schedule[Scheduler][CallID].Arguments = ScheduleArguments
|
||||
self.Schedule[Scheduler][CallID].StartTime = timer.getTime() + ( Start or 0 )
|
||||
self.Schedule[Scheduler][CallID].Start = Start + 0.1
|
||||
self.Schedule[Scheduler][CallID].Start = Start + 0.001
|
||||
self.Schedule[Scheduler][CallID].Repeat = Repeat or 0
|
||||
self.Schedule[Scheduler][CallID].Randomize = Randomize or 0
|
||||
self.Schedule[Scheduler][CallID].Stop = Stop
|
||||
|
||||
|
||||
|
||||
-- This section handles the tracing of the scheduled calls.
|
||||
-- Because these calls will be executed with a delay, we inspect the place where these scheduled calls are initiated.
|
||||
-- The Info structure contains the output of the debug.getinfo() calls, which inspects the call stack for the function name, line number and source name.
|
||||
@@ -149,10 +150,10 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr
|
||||
-- Therefore, in the call stack, at the TraceLevel these functions are mentioned as "tail calls", and the Info.name field will be nil as a result.
|
||||
-- To obtain the correct function name for FSM object calls, the function is mentioned in the call stack at a higher stack level.
|
||||
-- So when function name stored in Info.name is nil, then I inspect the function name within the call stack one level higher.
|
||||
-- So this little piece of code does its magic wonderfully, preformance overhead is neglectible, as scheduled calls don't happen that often.
|
||||
-- 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" )
|
||||
@@ -166,7 +167,7 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr
|
||||
|
||||
--- Function passed to the DCS timer.scheduleFunction()
|
||||
self.Schedule[Scheduler][CallID].CallHandler = function( Params )
|
||||
|
||||
|
||||
local CallID = Params.CallID
|
||||
local Info = Params.Info or {}
|
||||
local Source = Info.source or "?"
|
||||
@@ -180,27 +181,27 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr
|
||||
end
|
||||
return errmsg
|
||||
end
|
||||
|
||||
-- Get object or persistant scheduler object.
|
||||
local Scheduler = self.ObjectSchedulers[CallID] --Core.Scheduler#SCHEDULER
|
||||
|
||||
-- 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)
|
||||
|
||||
-- Schedule object.
|
||||
local Schedule = self.Schedule[Scheduler][CallID] --#SCHEDULEDISPATCHER.ScheduleData
|
||||
|
||||
--self:T3( { Schedule = Schedule } )
|
||||
local MasterObject = tostring( Scheduler.MasterObject )
|
||||
|
||||
local SchedulerObject = Scheduler.MasterObject --Scheduler.SchedulerObject Now is this the Maste or Scheduler object?
|
||||
-- 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
|
||||
@@ -208,17 +209,17 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr
|
||||
local Randomize = Schedule.Randomize or 0
|
||||
local Stop = Schedule.Stop or 0
|
||||
local ScheduleID = Schedule.ScheduleID
|
||||
|
||||
|
||||
local Prefix = ( Repeat == 0 ) and "--->" or "+++>"
|
||||
|
||||
|
||||
local Prefix = (Repeat == 0) and "--->" or "+++>"
|
||||
|
||||
local Status, Result
|
||||
--self:E( { SchedulerObject = SchedulerObject } )
|
||||
-- self:E( { SchedulerObject = SchedulerObject } )
|
||||
if SchedulerObject then
|
||||
local function Timer()
|
||||
if ShowTrace then
|
||||
SchedulerObject:T( Prefix .. Name .. ":" .. Line .. " (" .. Source .. ")" )
|
||||
end
|
||||
-- The master object is passed as first parameter. A few :Schedule() calls in MOOSE expect this currently. But in principle it should be removed.
|
||||
return ScheduleFunction( SchedulerObject, unpack( ScheduleArguments ) )
|
||||
end
|
||||
Status, Result = xpcall( Timer, ErrorHandler )
|
||||
@@ -227,40 +228,39 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr
|
||||
if ShowTrace then
|
||||
self:T( Prefix .. Name .. ":" .. Line .. " (" .. Source .. ")" )
|
||||
end
|
||||
return ScheduleFunction( unpack( ScheduleArguments ) )
|
||||
return ScheduleFunction( unpack( ScheduleArguments ) )
|
||||
end
|
||||
Status, Result = xpcall( Timer, ErrorHandler )
|
||||
end
|
||||
|
||||
|
||||
local CurrentTime = timer.getTime()
|
||||
local StartTime = Schedule.StartTime
|
||||
|
||||
-- Debug info.
|
||||
self:F3( { CallID=CallID, ScheduleID=ScheduleID, Master = MasterObject, CurrentTime = CurrentTime, StartTime = StartTime, Start = Start, Repeat = Repeat, Randomize = Randomize, Stop = Stop } )
|
||||
|
||||
|
||||
if Status and (( Result == nil ) or ( Result and Result ~= false ) ) then
|
||||
|
||||
if Repeat ~= 0 and ( ( Stop == 0 ) or ( Stop ~= 0 and CurrentTime <= StartTime + Stop ) ) then
|
||||
local ScheduleTime = CurrentTime + Repeat + math.random(- ( Randomize * Repeat / 2 ), ( Randomize * Repeat / 2 )) + 0.0001 -- Accuracy
|
||||
--self:T3( { Repeat = CallID, CurrentTime, ScheduleTime, ScheduleArguments } )
|
||||
self:F3( { CallID = CallID, ScheduleID = ScheduleID, Master = MasterObject, CurrentTime = CurrentTime, StartTime = StartTime, Start = Start, Repeat = Repeat, Randomize = Randomize, Stop = Stop } )
|
||||
|
||||
if Status and ((Result == nil) or (Result and Result ~= false)) then
|
||||
|
||||
if Repeat ~= 0 and ((Stop == 0) or (Stop ~= 0 and CurrentTime <= StartTime + Stop)) then
|
||||
local ScheduleTime = CurrentTime + Repeat + math.random( -(Randomize * Repeat / 2), (Randomize * Repeat / 2) ) + 0.0001 -- Accuracy
|
||||
-- self:T3( { Repeat = CallID, CurrentTime, ScheduleTime, ScheduleArguments } )
|
||||
return ScheduleTime -- returns the next time the function needs to be called.
|
||||
else
|
||||
self:Stop( Scheduler, CallID )
|
||||
end
|
||||
|
||||
|
||||
else
|
||||
self:Stop( Scheduler, CallID )
|
||||
end
|
||||
else
|
||||
self:I( "<<<>" .. Name .. ":" .. Line .. " (" .. Source .. ")" )
|
||||
end
|
||||
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
self:Start( Scheduler, CallID, Info )
|
||||
|
||||
|
||||
return CallID
|
||||
end
|
||||
|
||||
@@ -284,67 +284,67 @@ end
|
||||
-- @param #string Info (Optional) Debug info.
|
||||
function SCHEDULEDISPATCHER:Start( Scheduler, CallID, Info )
|
||||
self:F2( { Start = CallID, Scheduler = Scheduler } )
|
||||
|
||||
|
||||
if CallID then
|
||||
|
||||
local Schedule = self.Schedule[Scheduler][CallID] --#SCHEDULEDISPATCHER.ScheduleData
|
||||
|
||||
|
||||
local Schedule = self.Schedule[Scheduler][CallID] -- #SCHEDULEDISPATCHER.ScheduleData
|
||||
|
||||
-- Only start when there is no ScheduleID defined!
|
||||
-- This prevents to "Start" the scheduler twice with the same CallID...
|
||||
if not Schedule.ScheduleID then
|
||||
|
||||
|
||||
-- Current time in seconds.
|
||||
local Tnow=timer.getTime()
|
||||
|
||||
Schedule.StartTime = Tnow -- Set the StartTime field to indicate when the scheduler started.
|
||||
|
||||
local Tnow = timer.getTime()
|
||||
|
||||
Schedule.StartTime = Tnow -- Set the StartTime field to indicate when the scheduler started.
|
||||
|
||||
-- Start DCS schedule function https://wiki.hoggitworld.com/view/DCS_func_scheduleFunction
|
||||
Schedule.ScheduleID = timer.scheduleFunction(Schedule.CallHandler, { CallID = CallID, Info = Info }, Tnow + Schedule.Start)
|
||||
|
||||
self:T(string.format("Starting scheduledispatcher Call ID=%s ==> Schedule ID=%s", tostring(CallID), tostring(Schedule.ScheduleID)))
|
||||
Schedule.ScheduleID = timer.scheduleFunction( Schedule.CallHandler, { CallID = CallID, Info = Info }, Tnow + Schedule.Start )
|
||||
|
||||
self:T( string.format( "Starting SCHEDULEDISPATCHER Call ID=%s ==> Schedule ID=%s", tostring( CallID ), tostring( Schedule.ScheduleID ) ) )
|
||||
end
|
||||
|
||||
|
||||
else
|
||||
|
||||
|
||||
-- Recursive.
|
||||
for CallID, Schedule in pairs( self.Schedule[Scheduler] or {} ) do
|
||||
self:Start( Scheduler, CallID, Info ) -- Recursive
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
--- Stop dispatcher.
|
||||
-- @param #SCHEDULEDISPATCHER self
|
||||
-- @param Core.Scheduler#SCHEDULER Scheduler Scheduler object.
|
||||
-- @param #table CallID Call ID.
|
||||
-- @param #string CallID (Optional) Scheduler Call ID. If nil, all pending schedules are stopped recursively.
|
||||
function SCHEDULEDISPATCHER:Stop( Scheduler, CallID )
|
||||
self:F2( { Stop = CallID, Scheduler = Scheduler } )
|
||||
|
||||
if CallID then
|
||||
|
||||
local Schedule = self.Schedule[Scheduler][CallID] --#SCHEDULEDISPATCHER.ScheduleData
|
||||
|
||||
|
||||
local Schedule = self.Schedule[Scheduler][CallID] -- #SCHEDULEDISPATCHER.ScheduleData
|
||||
|
||||
-- Only stop when there is a ScheduleID defined for the CallID. So, when the scheduler was stopped before, do nothing.
|
||||
if Schedule.ScheduleID then
|
||||
|
||||
self:T(string.format("scheduledispatcher stopping scheduler CallID=%s, ScheduleID=%s", tostring(CallID), tostring(Schedule.ScheduleID)))
|
||||
|
||||
|
||||
self:T( string.format( "SCHEDULEDISPATCHER stopping scheduler CallID=%s, ScheduleID=%s", tostring( CallID ), tostring( Schedule.ScheduleID ) ) )
|
||||
|
||||
-- Remove schedule function https://wiki.hoggitworld.com/view/DCS_func_removeFunction
|
||||
timer.removeFunction(Schedule.ScheduleID)
|
||||
|
||||
timer.removeFunction( Schedule.ScheduleID )
|
||||
|
||||
Schedule.ScheduleID = nil
|
||||
|
||||
|
||||
else
|
||||
self:T(string.format("Error no ScheduleID for CallID=%s", tostring(CallID)))
|
||||
self:T( string.format( "Error no ScheduleID for CallID=%s", tostring( CallID ) ) )
|
||||
end
|
||||
|
||||
|
||||
else
|
||||
|
||||
|
||||
for CallID, Schedule in pairs( self.Schedule[Scheduler] or {} ) do
|
||||
self:Stop( Scheduler, CallID ) -- Recursive
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
@@ -359,7 +359,7 @@ function SCHEDULEDISPATCHER:Clear( Scheduler )
|
||||
end
|
||||
end
|
||||
|
||||
--- Shopw tracing info.
|
||||
--- Show tracing info.
|
||||
-- @param #SCHEDULEDISPATCHER self
|
||||
-- @param Core.Scheduler#SCHEDULER Scheduler Scheduler object.
|
||||
function SCHEDULEDISPATCHER:ShowTrace( Scheduler )
|
||||
|
||||
@@ -3,39 +3,39 @@
|
||||
-- ===
|
||||
--
|
||||
-- ## 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
|
||||
@@ -48,62 +48,61 @@
|
||||
-- @field #boolean ShowTrace Trace info if true.
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
|
||||
--- Creates and handles schedules over time, which allow to execute code at specific time intervals with randomization.
|
||||
--
|
||||
--
|
||||
-- A SCHEDULER can manage **multiple** (repeating) schedules. Each planned or executing schedule has a unique **ScheduleID**.
|
||||
-- The ScheduleID is returned when the method @{#SCHEDULER.Schedule}() is called.
|
||||
-- It is recommended to store the ScheduleID in a variable, as it is used in the methods @{SCHEDULER.Start}() and @{SCHEDULER.Stop}(),
|
||||
-- which can start and stop specific repeating schedules respectively within a SCHEDULER object.
|
||||
--
|
||||
-- ## SCHEDULER constructor
|
||||
--
|
||||
--
|
||||
-- The SCHEDULER class is quite easy to use, but note that the New constructor has variable parameters:
|
||||
--
|
||||
--
|
||||
-- The @{#SCHEDULER.New}() method returns 2 variables:
|
||||
--
|
||||
--
|
||||
-- 1. The SCHEDULER object reference.
|
||||
-- 2. The first schedule planned in the SCHEDULER object.
|
||||
--
|
||||
--
|
||||
-- To clarify the different appliances, lets have a look at the following examples:
|
||||
--
|
||||
--
|
||||
-- ### Construct a SCHEDULER object without a persistent schedule.
|
||||
--
|
||||
--
|
||||
-- * @{#SCHEDULER.New}( nil ): Setup a new SCHEDULER object, which is persistently executed after garbage collection.
|
||||
--
|
||||
--
|
||||
-- MasterObject = SCHEDULER:New()
|
||||
-- SchedulerID = MasterObject:Schedule( nil, ScheduleFunction, {} )
|
||||
--
|
||||
--
|
||||
-- The above example creates a new MasterObject, but does not schedule anything.
|
||||
-- A separate schedule is created by using the MasterObject using the method :Schedule..., which returns a ScheduleID
|
||||
--
|
||||
--
|
||||
-- ### Construct a SCHEDULER object without a volatile schedule, but volatile to the Object existence...
|
||||
--
|
||||
-- * @{#SCHEDULER.New}( Object ): Setup a new SCHEDULER object, which is linked to the Object. When the Object is 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" )
|
||||
-- MasterObject = SCHEDULER:New( ZoneObject )
|
||||
-- SchedulerID = MasterObject:Schedule( ZoneObject, ScheduleFunction, {} )
|
||||
-- ...
|
||||
-- ZoneObject = nil
|
||||
-- garbagecollect()
|
||||
--
|
||||
--
|
||||
-- The above example creates a new MasterObject, but does not schedule anything, and is bound to the existence of ZoneObject, which is a ZONE.
|
||||
-- A separate schedule is created by using the MasterObject using the method :Schedule()..., which returns a ScheduleID
|
||||
-- Later in the logic, the ZoneObject is put to nil, and garbage is collected.
|
||||
-- As a result, the MasterObject will cancel any planned schedule.
|
||||
--
|
||||
--
|
||||
-- ### Construct a SCHEDULER object with a persistent schedule.
|
||||
--
|
||||
--
|
||||
-- * @{#SCHEDULER.New}( nil, Function, FunctionArguments, Start, ... ): Setup a new persistent SCHEDULER object, and start a new schedule for the Function with the defined FunctionArguments according the Start and sequent parameters.
|
||||
--
|
||||
--
|
||||
-- MasterObject, SchedulerID = SCHEDULER:New( nil, ScheduleFunction, {} )
|
||||
--
|
||||
--
|
||||
-- The above example creates a new MasterObject, and does schedule the first schedule as part of the call.
|
||||
-- Note that 2 variables are returned here: MasterObject, ScheduleID...
|
||||
--
|
||||
--
|
||||
-- ### Construct a SCHEDULER object without a schedule, but volatile to the Object existence...
|
||||
--
|
||||
--
|
||||
-- * @{#SCHEDULER.New}( Object, Function, FunctionArguments, Start, ... ): Setup a new SCHEDULER object, linked to Object, and start a new schedule for the Function with the defined FunctionArguments according the Start and sequent parameters.
|
||||
--
|
||||
-- ZoneObject = ZONE:New( "ZoneName" )
|
||||
@@ -112,13 +111,13 @@
|
||||
-- ...
|
||||
-- ZoneObject = nil
|
||||
-- garbagecollect()
|
||||
--
|
||||
--
|
||||
-- The above example creates a new MasterObject, and schedules a method call (ScheduleFunction),
|
||||
-- and is bound to the existence of ZoneObject, which is a ZONE object (ZoneObject).
|
||||
-- Both a MasterObject and a SchedulerID variable are returned.
|
||||
-- Later in the logic, the ZoneObject is put to nil, and garbage is collected.
|
||||
-- As a result, the MasterObject will cancel the planned schedule.
|
||||
--
|
||||
--
|
||||
-- ## SCHEDULER timer stopping and (re-)starting.
|
||||
--
|
||||
-- The SCHEDULER can be stopped and restarted with the following methods:
|
||||
@@ -133,70 +132,70 @@
|
||||
-- MasterObject:Stop( SchedulerID )
|
||||
-- ...
|
||||
-- MasterObject:Start( SchedulerID )
|
||||
--
|
||||
--
|
||||
-- The above example creates a new MasterObject, and does schedule the first schedule as part of the call.
|
||||
-- Note that 2 variables are returned here: MasterObject, ScheduleID...
|
||||
-- Later in the logic, the repeating schedule with SchedulerID is stopped.
|
||||
-- A bit later, the repeating schedule with SchedulerId is (re)-started.
|
||||
--
|
||||
-- Note that 2 variables are returned here: MasterObject, ScheduleID...
|
||||
-- Later in the logic, the repeating schedule with SchedulerID is stopped.
|
||||
-- A bit later, the repeating schedule with SchedulerId is (re)-started.
|
||||
--
|
||||
-- ## Create a new schedule
|
||||
--
|
||||
-- With the method @{#SCHEDULER.Schedule}() a new time event can be scheduled.
|
||||
--
|
||||
-- With the method @{#SCHEDULER.Schedule}() a new time event can be scheduled.
|
||||
-- This method is used by the :New() constructor when a new schedule is planned.
|
||||
--
|
||||
--
|
||||
-- Consider the following code fragment of the SCHEDULER object creation.
|
||||
--
|
||||
--
|
||||
-- ZoneObject = ZONE:New( "ZoneName" )
|
||||
-- MasterObject = SCHEDULER:New( ZoneObject )
|
||||
--
|
||||
-- Several parameters can be specified that influence the behaviour of a Schedule.
|
||||
--
|
||||
--
|
||||
-- Several parameters can be specified that influence the behavior of a Schedule.
|
||||
--
|
||||
-- ### A single schedule, immediately executed
|
||||
--
|
||||
--
|
||||
-- SchedulerID = MasterObject:Schedule( ZoneObject, ScheduleFunction, {} )
|
||||
--
|
||||
-- The above example schedules a new ScheduleFunction call to be executed asynchronously, within milleseconds ...
|
||||
--
|
||||
--
|
||||
-- The above example schedules a new ScheduleFunction call to be executed asynchronously, within milliseconds ...
|
||||
--
|
||||
-- ### A single schedule, planned over time
|
||||
--
|
||||
--
|
||||
-- SchedulerID = MasterObject:Schedule( ZoneObject, ScheduleFunction, {}, 10 )
|
||||
--
|
||||
--
|
||||
-- The above example schedules a new ScheduleFunction call to be executed asynchronously, within 10 seconds ...
|
||||
--
|
||||
--
|
||||
-- ### A schedule with a repeating time interval, planned over time
|
||||
--
|
||||
--
|
||||
-- SchedulerID = MasterObject:Schedule( ZoneObject, ScheduleFunction, {}, 10, 60 )
|
||||
--
|
||||
-- The above example schedules a new ScheduleFunction call to be executed asynchronously, within 10 seconds,
|
||||
--
|
||||
-- The above example schedules a new ScheduleFunction call to be executed asynchronously, within 10 seconds,
|
||||
-- and repeating 60 every seconds ...
|
||||
--
|
||||
--
|
||||
-- ### A schedule with a repeating time interval, planned over time, with time interval randomization
|
||||
--
|
||||
--
|
||||
-- SchedulerID = MasterObject:Schedule( ZoneObject, ScheduleFunction, {}, 10, 60, 0.5 )
|
||||
--
|
||||
-- The above example schedules a new ScheduleFunction call to be executed asynchronously, within 10 seconds,
|
||||
--
|
||||
-- The above example schedules a new ScheduleFunction call to be executed asynchronously, within 10 seconds,
|
||||
-- and repeating 60 seconds, with a 50% time interval randomization ...
|
||||
-- So the repeating time interval will be randomized using the **0.5**,
|
||||
-- and will calculate between **60 - ( 60 * 0.5 )** and **60 + ( 60 * 0.5 )** for each repeat,
|
||||
-- So the repeating time interval will be randomized using the **0.5**,
|
||||
-- and will calculate between **60 - ( 60 * 0.5 )** and **60 + ( 60 * 0.5 )** for each repeat,
|
||||
-- which is in this example between **30** and **90** seconds.
|
||||
--
|
||||
--
|
||||
-- ### A schedule with a repeating time interval, planned over time, with time interval randomization, and stop after a time interval
|
||||
--
|
||||
--
|
||||
-- SchedulerID = MasterObject:Schedule( ZoneObject, ScheduleFunction, {}, 10, 60, 0.5, 300 )
|
||||
--
|
||||
-- The above example schedules a new ScheduleFunction call to be executed asynchronously, within 10 seconds,
|
||||
--
|
||||
-- The above example schedules a new ScheduleFunction call to be executed asynchronously, within 10 seconds,
|
||||
-- The schedule will repeat every 60 seconds.
|
||||
-- So the repeating time interval will be randomized using the **0.5**,
|
||||
-- and will calculate between **60 - ( 60 * 0.5 )** and **60 + ( 60 * 0.5 )** for each repeat,
|
||||
-- So the repeating time interval will be randomized using the **0.5**,
|
||||
-- and will calculate between **60 - ( 60 * 0.5 )** and **60 + ( 60 * 0.5 )** for each repeat,
|
||||
-- which is in this example between **30** and **90** seconds.
|
||||
-- The schedule will stop after **300** seconds.
|
||||
--
|
||||
--
|
||||
-- @field #SCHEDULER
|
||||
SCHEDULER = {
|
||||
ClassName = "SCHEDULER",
|
||||
Schedules = {},
|
||||
MasterObject = nil,
|
||||
ShowTrace = nil,
|
||||
ClassName = "SCHEDULER",
|
||||
Schedules = {},
|
||||
MasterObject = nil,
|
||||
ShowTrace = nil,
|
||||
}
|
||||
|
||||
--- SCHEDULER constructor.
|
||||
@@ -211,15 +210,15 @@ SCHEDULER = {
|
||||
-- @return #SCHEDULER self.
|
||||
-- @return #table The ScheduleID of the planned schedule.
|
||||
function SCHEDULER:New( MasterObject, SchedulerFunction, SchedulerArguments, Start, Repeat, RandomizeFactor, Stop )
|
||||
|
||||
|
||||
local self = BASE:Inherit( self, BASE:New() ) -- #SCHEDULER
|
||||
self:F2( { Start, Repeat, RandomizeFactor, Stop } )
|
||||
|
||||
local ScheduleID = nil
|
||||
|
||||
|
||||
self.MasterObject = MasterObject
|
||||
self.ShowTrace = false
|
||||
|
||||
|
||||
if SchedulerFunction then
|
||||
ScheduleID = self:Schedule( MasterObject, SchedulerFunction, SchedulerArguments, Start, Repeat, RandomizeFactor, Stop, 3 )
|
||||
end
|
||||
@@ -235,48 +234,47 @@ end
|
||||
-- @param #number Start Specifies the amount of seconds that will be waited before the scheduling is started, and the event function is called.
|
||||
-- @param #number Repeat Specifies the time interval in seconds when the scheduler will call the event function.
|
||||
-- @param #number RandomizeFactor Specifies a randomization factor between 0 and 1 to randomize the Repeat.
|
||||
-- @param #number Stop Time interval in seconds after which the scheduler will be stoppe.
|
||||
-- @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.
|
||||
-- @return #string The Schedule ID of the planned schedule.
|
||||
function SCHEDULER:Schedule( MasterObject, SchedulerFunction, SchedulerArguments, Start, Repeat, RandomizeFactor, Stop, TraceLevel, Fsm )
|
||||
self:F2( { Start, Repeat, RandomizeFactor, Stop } )
|
||||
self:T3( { SchedulerArguments } )
|
||||
|
||||
-- Debug info.
|
||||
local ObjectName = "-"
|
||||
if MasterObject and MasterObject.ClassName and MasterObject.ClassID then
|
||||
if MasterObject and MasterObject.ClassName and MasterObject.ClassID then
|
||||
ObjectName = MasterObject.ClassName .. MasterObject.ClassID
|
||||
end
|
||||
self:F3( { "Schedule :", ObjectName, tostring( MasterObject ), Start, Repeat, RandomizeFactor, Stop } )
|
||||
|
||||
self:F3( { "Schedule :", ObjectName, tostring( MasterObject ), Start, Repeat, RandomizeFactor, Stop } )
|
||||
|
||||
-- Set master object.
|
||||
self.MasterObject = MasterObject
|
||||
|
||||
|
||||
-- Add schedule.
|
||||
local ScheduleID = _SCHEDULEDISPATCHER:AddSchedule(
|
||||
self,
|
||||
SchedulerFunction,
|
||||
SchedulerArguments,
|
||||
Start,
|
||||
Repeat,
|
||||
RandomizeFactor,
|
||||
Stop,
|
||||
TraceLevel or 3,
|
||||
Fsm
|
||||
)
|
||||
|
||||
self.Schedules[#self.Schedules+1] = ScheduleID
|
||||
local ScheduleID = _SCHEDULEDISPATCHER:AddSchedule( self,
|
||||
SchedulerFunction,
|
||||
SchedulerArguments,
|
||||
Start,
|
||||
Repeat,
|
||||
RandomizeFactor,
|
||||
Stop,
|
||||
TraceLevel or 3,
|
||||
Fsm
|
||||
)
|
||||
|
||||
self.Schedules[#self.Schedules + 1] = ScheduleID
|
||||
|
||||
return ScheduleID
|
||||
end
|
||||
|
||||
--- (Re-)Starts the schedules or a specific schedule if a valid ScheduleID is provided.
|
||||
-- @param #SCHEDULER self
|
||||
-- @param #string ScheduleID (Optional) The ScheduleID of the planned (repeating) schedule.
|
||||
-- @param #string ScheduleID (Optional) The Schedule ID of the planned (repeating) schedule.
|
||||
function SCHEDULER:Start( ScheduleID )
|
||||
self:F3( { ScheduleID } )
|
||||
self:T(string.format("Starting scheduler ID=%s", tostring(ScheduleID)))
|
||||
self:T( string.format( "Starting scheduler ID=%s", tostring( ScheduleID ) ) )
|
||||
_SCHEDULEDISPATCHER:Start( self, ScheduleID )
|
||||
end
|
||||
|
||||
@@ -285,7 +283,7 @@ end
|
||||
-- @param #string ScheduleID (Optional) The ScheduleID of the planned (repeating) schedule.
|
||||
function SCHEDULER:Stop( ScheduleID )
|
||||
self:F3( { ScheduleID } )
|
||||
self:T(string.format("Stopping scheduler ID=%s", tostring(ScheduleID)))
|
||||
self:T( string.format( "Stopping scheduler ID=%s", tostring( ScheduleID ) ) )
|
||||
_SCHEDULEDISPATCHER:Stop( self, ScheduleID )
|
||||
end
|
||||
|
||||
@@ -294,15 +292,15 @@ end
|
||||
-- @param #string ScheduleID (optional) The ScheduleID of the planned (repeating) schedule.
|
||||
function SCHEDULER:Remove( ScheduleID )
|
||||
self:F3( { ScheduleID } )
|
||||
self:T(string.format("Removing scheduler ID=%s", tostring(ScheduleID)))
|
||||
self:T( string.format( "Removing scheduler ID=%s", tostring( ScheduleID ) ) )
|
||||
_SCHEDULEDISPATCHER:RemoveSchedule( self, ScheduleID )
|
||||
end
|
||||
|
||||
--- Clears all pending schedules.
|
||||
-- @param #SCHEDULER self
|
||||
function SCHEDULER:Clear()
|
||||
self:F3( )
|
||||
self:T(string.format("Clearing scheduler"))
|
||||
self:F3()
|
||||
self:T( string.format( "Clearing scheduler" ) )
|
||||
_SCHEDULEDISPATCHER:Clear( self )
|
||||
end
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -29,15 +29,14 @@
|
||||
-- @module Core.Settings
|
||||
-- @image Core_Settings.JPG
|
||||
|
||||
|
||||
--- @type SETTINGS
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
--- Takes care of various settings that influence the behaviour of certain functionalities and classes within the MOOSE framework.
|
||||
--- Takes care of various settings that influence the behavior of certain functionalities and classes within the MOOSE framework.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- The SETTINGS class takes care of various settings that influence the behaviour of certain functionalities and classes within the MOOSE framework.
|
||||
-- The SETTINGS class takes care of various settings that influence the behavior of certain functionalities and classes within the MOOSE framework.
|
||||
-- SETTINGS can work on 2 levels:
|
||||
--
|
||||
-- - **Default settings**: A running mission has **Default settings**.
|
||||
@@ -59,7 +58,7 @@
|
||||
--
|
||||
-- A menu is created automatically per Command Center that allows to modify the **Default** settings.
|
||||
-- So, when joining a CC unit, a menu will be available that allows to change the settings parameters **FOR ALL THE PLAYERS**!
|
||||
-- Note that the **Default settings** will only be used when a player has not choosen its own settings.
|
||||
-- Note that the **Default settings** will only be used when a player has not chosen its own settings.
|
||||
--
|
||||
-- ## 2.2) Player settings menu
|
||||
--
|
||||
@@ -69,7 +68,7 @@
|
||||
--
|
||||
-- ## 2.3) Show or Hide the Player Setting menus
|
||||
--
|
||||
-- Of course, it may be requried not to show any setting menus. In this case, a method is available on the **\_SETTINGS object**.
|
||||
-- Of course, it may be required not to show any setting menus. In this case, a method is available on the **\_SETTINGS object**.
|
||||
-- Use @{#SETTINGS.SetPlayerMenuOff}() to hide the player menus, and use @{#SETTINGS.SetPlayerMenuOn}() show the player menus.
|
||||
-- Note that when this method is used, any player already in a slot will not have its menus visibility changed.
|
||||
-- The option will only have effect when a player enters a new slot or changes a slot.
|
||||
@@ -94,8 +93,8 @@
|
||||
--
|
||||
-- - A2G BR: [Bearing Range](https://en.wikipedia.org/wiki/Bearing_(navigation)).
|
||||
-- - A2G MGRS: The [Military Grid Reference System](https://en.wikipedia.org/wiki/Military_Grid_Reference_System). The accuracy can also be adapted.
|
||||
-- - A2G LL DMS: Lattitude Longitude [Degrees Minutes Seconds](https://en.wikipedia.org/wiki/Geographic_coordinate_conversion). The accuracy can also be adapted.
|
||||
-- - A2G LL DDM: Lattitude Longitude [Decimal Degrees Minutes](https://en.wikipedia.org/wiki/Decimal_degrees). The accuracy can also be adapted.
|
||||
-- - A2G LL DMS: Latitude Longitude [Degrees Minutes Seconds](https://en.wikipedia.org/wiki/Geographic_coordinate_conversion). The accuracy can also be adapted.
|
||||
-- - A2G LL DDM: Latitude Longitude [Decimal Degrees Minutes](https://en.wikipedia.org/wiki/Decimal_degrees). The accuracy can also be adapted.
|
||||
--
|
||||
-- ### 3.1.2) A2G coordinates setting **menu**
|
||||
--
|
||||
@@ -183,7 +182,7 @@
|
||||
-- The settings can be changed by using the **Default settings menu** on the Command Center or the **Player settings menu** on the Player Slot.
|
||||
--
|
||||
-- Each Message Type has specific timings that will be applied when the message is displayed.
|
||||
-- The Settings Menu will provide for each Message Type a selection of proposed durations from which can be choosen.
|
||||
-- The Settings Menu will provide for each Message Type a selection of proposed durations from which can be chosen.
|
||||
-- So the player can choose its own amount of seconds how long a message should be displayed of a certain type.
|
||||
-- Note that **Update** messages can be chosen not to be displayed at all!
|
||||
--
|
||||
@@ -196,7 +195,7 @@
|
||||
--
|
||||
-- ## 3.5) **Era** of the battle
|
||||
--
|
||||
-- The threat level metric is scaled according the era of the battle. A target that is AAA, will pose a much greather threat in WWII than on modern warfare.
|
||||
-- The threat level metric is scaled according the era of the battle. A target that is AAA, will pose a much greater threat in WWII than on modern warfare.
|
||||
-- Therefore, there are 4 era that are defined within the settings:
|
||||
--
|
||||
-- - **WWII** era: Use for warfare with equipment during the world war II time.
|
||||
@@ -213,8 +212,8 @@
|
||||
SETTINGS = {
|
||||
ClassName = "SETTINGS",
|
||||
ShowPlayerMenu = true,
|
||||
MenuShort = false,
|
||||
MenuStatic = false,
|
||||
MenuShort = false,
|
||||
MenuStatic = false,
|
||||
}
|
||||
|
||||
SETTINGS.__Enum = {}
|
||||
@@ -231,11 +230,11 @@ SETTINGS.__Enum.Era = {
|
||||
Modern = 4,
|
||||
}
|
||||
|
||||
|
||||
do -- SETTINGS
|
||||
|
||||
--- SETTINGS constructor.
|
||||
-- @param #SETTINGS self
|
||||
-- @param #string PlayerName (Optional) Set settings for this player.
|
||||
-- @return #SETTINGS
|
||||
function SETTINGS:Set( PlayerName )
|
||||
|
||||
@@ -252,6 +251,7 @@ do -- SETTINGS
|
||||
self:SetMessageTime( MESSAGE.Type.Overview, 60 )
|
||||
self:SetMessageTime( MESSAGE.Type.Update, 15 )
|
||||
self:SetEraModern()
|
||||
self:SetLocale("en")
|
||||
return self
|
||||
else
|
||||
local Settings = _DATABASE:GetPlayerSettings( PlayerName )
|
||||
@@ -267,14 +267,14 @@ do -- SETTINGS
|
||||
-- Short text are better suited for, e.g., VR.
|
||||
-- @param #SETTINGS self
|
||||
-- @param #boolean onoff If *true* use short menu texts. If *false* long ones (default).
|
||||
function SETTINGS:SetMenutextShort(onoff)
|
||||
function SETTINGS:SetMenutextShort( onoff )
|
||||
_SETTINGS.MenuShort = onoff
|
||||
end
|
||||
|
||||
--- Set menu to be static.
|
||||
-- @param #SETTINGS self
|
||||
-- @param #boolean onoff If *true* menu is static. If *false* menu will be updated after changes (default).
|
||||
function SETTINGS:SetMenuStatic(onoff)
|
||||
function SETTINGS:SetMenuStatic( onoff )
|
||||
_SETTINGS.MenuStatic = onoff
|
||||
end
|
||||
|
||||
@@ -283,12 +283,26 @@ do -- SETTINGS
|
||||
function SETTINGS:SetMetric()
|
||||
self.Metric = true
|
||||
end
|
||||
|
||||
|
||||
--- Sets the SETTINGS default text locale.
|
||||
-- @param #SETTINGS self
|
||||
-- @param #string Locale
|
||||
function SETTINGS:SetLocale(Locale)
|
||||
self.Locale = Locale or "en"
|
||||
end
|
||||
|
||||
--- Gets the SETTINGS text locale.
|
||||
-- @param #SETTINGS self
|
||||
-- @return #string
|
||||
function SETTINGS:GetLocale()
|
||||
return self.Locale or _SETTINGS:GetLocale()
|
||||
end
|
||||
|
||||
--- Gets if the SETTINGS is metric.
|
||||
-- @param #SETTINGS self
|
||||
-- @return #boolean true if metric.
|
||||
function SETTINGS:IsMetric()
|
||||
return ( self.Metric ~= nil and self.Metric == true ) or ( self.Metric == nil and _SETTINGS:IsMetric() )
|
||||
return (self.Metric ~= nil and self.Metric == true) or (self.Metric == nil and _SETTINGS:IsMetric())
|
||||
end
|
||||
|
||||
--- Sets the SETTINGS imperial.
|
||||
@@ -301,7 +315,7 @@ do -- SETTINGS
|
||||
-- @param #SETTINGS self
|
||||
-- @return #boolean true if imperial.
|
||||
function SETTINGS:IsImperial()
|
||||
return ( self.Metric ~= nil and self.Metric == false ) or ( self.Metric == nil and _SETTINGS:IsMetric() )
|
||||
return (self.Metric ~= nil and self.Metric == false) or (self.Metric == nil and _SETTINGS:IsImperial())
|
||||
end
|
||||
|
||||
--- Sets the SETTINGS LL accuracy.
|
||||
@@ -343,13 +357,12 @@ do -- SETTINGS
|
||||
self.MessageTypeTimings[MessageType] = MessageTime
|
||||
end
|
||||
|
||||
|
||||
--- Gets the SETTINGS Message Display Timing of a MessageType
|
||||
-- @param #SETTINGS self
|
||||
-- @param Core.Message#MESSAGE MessageType The type of the message.
|
||||
-- @return #number
|
||||
function SETTINGS:GetMessageTime( MessageType )
|
||||
return ( self.MessageTypeTimings and self.MessageTypeTimings[MessageType] ) or _SETTINGS:GetMessageTime( MessageType )
|
||||
return (self.MessageTypeTimings and self.MessageTypeTimings[MessageType]) or _SETTINGS:GetMessageTime( MessageType )
|
||||
end
|
||||
|
||||
--- Sets A2G LL DMS
|
||||
@@ -370,14 +383,14 @@ do -- SETTINGS
|
||||
-- @param #SETTINGS self
|
||||
-- @return #boolean true if LL DMS
|
||||
function SETTINGS:IsA2G_LL_DMS()
|
||||
return ( self.A2GSystem and self.A2GSystem == "LL DMS" ) or ( not self.A2GSystem and _SETTINGS:IsA2G_LL_DMS() )
|
||||
return (self.A2GSystem and self.A2GSystem == "LL DMS") or (not self.A2GSystem and _SETTINGS:IsA2G_LL_DMS())
|
||||
end
|
||||
|
||||
--- Is LL DDM
|
||||
-- @param #SETTINGS self
|
||||
-- @return #boolean true if LL DDM
|
||||
function SETTINGS:IsA2G_LL_DDM()
|
||||
return ( self.A2GSystem and self.A2GSystem == "LL DDM" ) or ( not self.A2GSystem and _SETTINGS:IsA2G_LL_DDM() )
|
||||
return (self.A2GSystem and self.A2GSystem == "LL DDM") or (not self.A2GSystem and _SETTINGS:IsA2G_LL_DDM())
|
||||
end
|
||||
|
||||
--- Sets A2G MGRS
|
||||
@@ -391,7 +404,7 @@ do -- SETTINGS
|
||||
-- @param #SETTINGS self
|
||||
-- @return #boolean true if MGRS
|
||||
function SETTINGS:IsA2G_MGRS()
|
||||
return ( self.A2GSystem and self.A2GSystem == "MGRS" ) or ( not self.A2GSystem and _SETTINGS:IsA2G_MGRS() )
|
||||
return (self.A2GSystem and self.A2GSystem == "MGRS") or (not self.A2GSystem and _SETTINGS:IsA2G_MGRS())
|
||||
end
|
||||
|
||||
--- Sets A2G BRA
|
||||
@@ -405,7 +418,7 @@ do -- SETTINGS
|
||||
-- @param #SETTINGS self
|
||||
-- @return #boolean true if BRA
|
||||
function SETTINGS:IsA2G_BR()
|
||||
return ( self.A2GSystem and self.A2GSystem == "BR" ) or ( not self.A2GSystem and _SETTINGS:IsA2G_BR() )
|
||||
return (self.A2GSystem and self.A2GSystem == "BR") or (not self.A2GSystem and _SETTINGS:IsA2G_BR())
|
||||
end
|
||||
|
||||
--- Sets A2A BRA
|
||||
@@ -419,7 +432,7 @@ do -- SETTINGS
|
||||
-- @param #SETTINGS self
|
||||
-- @return #boolean true if BRA
|
||||
function SETTINGS:IsA2A_BRAA()
|
||||
return ( self.A2ASystem and self.A2ASystem == "BRAA" ) or ( not self.A2ASystem and _SETTINGS:IsA2A_BRAA() )
|
||||
return (self.A2ASystem and self.A2ASystem == "BRAA") or (not self.A2ASystem and _SETTINGS:IsA2A_BRAA())
|
||||
end
|
||||
|
||||
--- Sets A2A BULLS
|
||||
@@ -433,7 +446,7 @@ do -- SETTINGS
|
||||
-- @param #SETTINGS self
|
||||
-- @return #boolean true if BULLS
|
||||
function SETTINGS:IsA2A_BULLS()
|
||||
return ( self.A2ASystem and self.A2ASystem == "BULLS" ) or ( not self.A2ASystem and _SETTINGS:IsA2A_BULLS() )
|
||||
return (self.A2ASystem and self.A2ASystem == "BULLS") or (not self.A2ASystem and _SETTINGS:IsA2A_BULLS())
|
||||
end
|
||||
|
||||
--- Sets A2A LL DMS
|
||||
@@ -454,14 +467,14 @@ do -- SETTINGS
|
||||
-- @param #SETTINGS self
|
||||
-- @return #boolean true if LL DMS
|
||||
function SETTINGS:IsA2A_LL_DMS()
|
||||
return ( self.A2ASystem and self.A2ASystem == "LL DMS" ) or ( not self.A2ASystem and _SETTINGS:IsA2A_LL_DMS() )
|
||||
return (self.A2ASystem and self.A2ASystem == "LL DMS") or (not self.A2ASystem and _SETTINGS:IsA2A_LL_DMS())
|
||||
end
|
||||
|
||||
--- Is LL DDM
|
||||
-- @param #SETTINGS self
|
||||
-- @return #boolean true if LL DDM
|
||||
function SETTINGS:IsA2A_LL_DDM()
|
||||
return ( self.A2ASystem and self.A2ASystem == "LL DDM" ) or ( not self.A2ASystem and _SETTINGS:IsA2A_LL_DDM() )
|
||||
return (self.A2ASystem and self.A2ASystem == "LL DDM") or (not self.A2ASystem and _SETTINGS:IsA2A_LL_DDM())
|
||||
end
|
||||
|
||||
--- Sets A2A MGRS
|
||||
@@ -475,7 +488,7 @@ do -- SETTINGS
|
||||
-- @param #SETTINGS self
|
||||
-- @return #boolean true if MGRS
|
||||
function SETTINGS:IsA2A_MGRS()
|
||||
return ( self.A2ASystem and self.A2ASystem == "MGRS" ) or ( not self.A2ASystem and _SETTINGS:IsA2A_MGRS() )
|
||||
return (self.A2ASystem and self.A2ASystem == "MGRS") or (not self.A2ASystem and _SETTINGS:IsA2A_MGRS())
|
||||
end
|
||||
|
||||
--- @param #SETTINGS self
|
||||
@@ -494,37 +507,37 @@ do -- SETTINGS
|
||||
-- A2G Coordinate System
|
||||
-------
|
||||
|
||||
local text="A2G Coordinate System"
|
||||
local text = "A2G Coordinate System"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="A2G Coordinates"
|
||||
text = "A2G Coordinates"
|
||||
end
|
||||
local A2GCoordinateMenu = MENU_GROUP:New( MenuGroup, text, SettingsMenu ):SetTime( MenuTime )
|
||||
|
||||
-- Set LL DMS
|
||||
if not self:IsA2G_LL_DMS() then
|
||||
local text="Lat/Lon Degree Min Sec (LL DMS)"
|
||||
local text = "Lat/Lon Degree Min Sec (LL DMS)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="LL DMS"
|
||||
text = "LL DMS"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( MenuGroup, text, A2GCoordinateMenu, self.A2GMenuSystem, self, MenuGroup, RootMenu, "LL DMS" ):SetTime( MenuTime )
|
||||
end
|
||||
|
||||
-- Set LL DDM
|
||||
if not self:IsA2G_LL_DDM() then
|
||||
local text="Lat/Lon Degree Dec Min (LL DDM)"
|
||||
local text = "Lat/Lon Degree Dec Min (LL DDM)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="LL DDM"
|
||||
text = "LL DDM"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( MenuGroup, "Lat/Lon Degree Dec Min (LL DDM)", A2GCoordinateMenu, self.A2GMenuSystem, self, MenuGroup, RootMenu, "LL DDM" ):SetTime( MenuTime )
|
||||
end
|
||||
|
||||
-- Set LL DMS accuracy.
|
||||
if self:IsA2G_LL_DDM() then
|
||||
local text1="LL DDM Accuracy 1"
|
||||
local text2="LL DDM Accuracy 2"
|
||||
local text3="LL DDM Accuracy 3"
|
||||
local text1 = "LL DDM Accuracy 1"
|
||||
local text2 = "LL DDM Accuracy 2"
|
||||
local text3 = "LL DDM Accuracy 3"
|
||||
if _SETTINGS.MenuShort then
|
||||
text1="LL DDM"
|
||||
text1 = "LL DDM"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( MenuGroup, "LL DDM Accuracy 1", A2GCoordinateMenu, self.MenuLL_DDM_Accuracy, self, MenuGroup, RootMenu, 1 ):SetTime( MenuTime )
|
||||
MENU_GROUP_COMMAND:New( MenuGroup, "LL DDM Accuracy 2", A2GCoordinateMenu, self.MenuLL_DDM_Accuracy, self, MenuGroup, RootMenu, 2 ):SetTime( MenuTime )
|
||||
@@ -533,18 +546,18 @@ do -- SETTINGS
|
||||
|
||||
-- Set BR.
|
||||
if not self:IsA2G_BR() then
|
||||
local text="Bearing, Range (BR)"
|
||||
local text = "Bearing, Range (BR)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="BR"
|
||||
text = "BR"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( MenuGroup, text , A2GCoordinateMenu, self.A2GMenuSystem, self, MenuGroup, RootMenu, "BR" ):SetTime( MenuTime )
|
||||
MENU_GROUP_COMMAND:New( MenuGroup, text, A2GCoordinateMenu, self.A2GMenuSystem, self, MenuGroup, RootMenu, "BR" ):SetTime( MenuTime )
|
||||
end
|
||||
|
||||
-- Set MGRS.
|
||||
if not self:IsA2G_MGRS() then
|
||||
local text="Military Grid (MGRS)"
|
||||
local text = "Military Grid (MGRS)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="MGRS"
|
||||
text = "MGRS"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( MenuGroup, text, A2GCoordinateMenu, self.A2GMenuSystem, self, MenuGroup, RootMenu, "MGRS" ):SetTime( MenuTime )
|
||||
end
|
||||
@@ -562,24 +575,24 @@ do -- SETTINGS
|
||||
-- A2A Coordinate System
|
||||
-------
|
||||
|
||||
local text="A2A Coordinate System"
|
||||
local text = "A2A Coordinate System"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="A2A Coordinates"
|
||||
text = "A2A Coordinates"
|
||||
end
|
||||
local A2ACoordinateMenu = MENU_GROUP:New( MenuGroup, text, SettingsMenu ):SetTime( MenuTime )
|
||||
|
||||
if not self:IsA2A_LL_DMS() then
|
||||
local text="Lat/Lon Degree Min Sec (LL DMS)"
|
||||
local text = "Lat/Lon Degree Min Sec (LL DMS)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="LL DMS"
|
||||
text = "LL DMS"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( MenuGroup, text, A2ACoordinateMenu, self.A2AMenuSystem, self, MenuGroup, RootMenu, "LL DMS" ):SetTime( MenuTime )
|
||||
end
|
||||
|
||||
if not self:IsA2A_LL_DDM() then
|
||||
local text="Lat/Lon Degree Dec Min (LL DDM)"
|
||||
local text = "Lat/Lon Degree Dec Min (LL DDM)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="LL DDM"
|
||||
text = "LL DDM"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( MenuGroup, text, A2ACoordinateMenu, self.A2AMenuSystem, self, MenuGroup, RootMenu, "LL DDM" ):SetTime( MenuTime )
|
||||
end
|
||||
@@ -592,25 +605,25 @@ do -- SETTINGS
|
||||
end
|
||||
|
||||
if not self:IsA2A_BULLS() then
|
||||
local text="Bullseye (BULLS)"
|
||||
local text = "Bullseye (BULLS)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="Bulls"
|
||||
text = "Bulls"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( MenuGroup, text, A2ACoordinateMenu, self.A2AMenuSystem, self, MenuGroup, RootMenu, "BULLS" ):SetTime( MenuTime )
|
||||
end
|
||||
|
||||
if not self:IsA2A_BRAA() then
|
||||
local text="Bearing Range Altitude Aspect (BRAA)"
|
||||
local text = "Bearing Range Altitude Aspect (BRAA)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="BRAA"
|
||||
text = "BRAA"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( MenuGroup, text, A2ACoordinateMenu, self.A2AMenuSystem, self, MenuGroup, RootMenu, "BRAA" ):SetTime( MenuTime )
|
||||
end
|
||||
|
||||
if not self:IsA2A_MGRS() then
|
||||
local text="Military Grid (MGRS)"
|
||||
local text = "Military Grid (MGRS)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="MGRS"
|
||||
text = "MGRS"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( MenuGroup, text, A2ACoordinateMenu, self.A2AMenuSystem, self, MenuGroup, RootMenu, "MGRS" ):SetTime( MenuTime )
|
||||
end
|
||||
@@ -623,31 +636,31 @@ do -- SETTINGS
|
||||
MENU_GROUP_COMMAND:New( MenuGroup, "MGRS Accuracy 5", A2ACoordinateMenu, self.MenuMGRS_Accuracy, self, MenuGroup, RootMenu, 5 ):SetTime( MenuTime )
|
||||
end
|
||||
|
||||
local text="Measures and Weights System"
|
||||
local text = "Measures and Weights System"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="Unit System"
|
||||
text = "Unit System"
|
||||
end
|
||||
local MetricsMenu = MENU_GROUP:New( MenuGroup, text, SettingsMenu ):SetTime( MenuTime )
|
||||
|
||||
if self:IsMetric() then
|
||||
local text="Imperial (Miles,Feet)"
|
||||
local text = "Imperial (Miles,Feet)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="Imperial"
|
||||
text = "Imperial"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( MenuGroup, text, MetricsMenu, self.MenuMWSystem, self, MenuGroup, RootMenu, false ):SetTime( MenuTime )
|
||||
end
|
||||
|
||||
if self:IsImperial() then
|
||||
local text="Metric (Kilometers,Meters)"
|
||||
local text = "Metric (Kilometers,Meters)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="Metric"
|
||||
text = "Metric"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( MenuGroup, text, MetricsMenu, self.MenuMWSystem, self, MenuGroup, RootMenu, true ):SetTime( MenuTime )
|
||||
end
|
||||
|
||||
local text="Messages and Reports"
|
||||
local text = "Messages and Reports"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="Messages & Reports"
|
||||
text = "Messages & Reports"
|
||||
end
|
||||
local MessagesMenu = MENU_GROUP:New( MenuGroup, text, SettingsMenu ):SetTime( MenuTime )
|
||||
|
||||
@@ -688,7 +701,6 @@ do -- SETTINGS
|
||||
MENU_GROUP_COMMAND:New( MenuGroup, "2 minutes", DetailedReportsMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.DetailedReportsMenu, 120 ):SetTime( MenuTime )
|
||||
MENU_GROUP_COMMAND:New( MenuGroup, "3 minutes", DetailedReportsMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.DetailedReportsMenu, 180 ):SetTime( MenuTime )
|
||||
|
||||
|
||||
SettingsMenu:Remove( MenuTime )
|
||||
|
||||
return self
|
||||
@@ -732,11 +744,11 @@ do -- SETTINGS
|
||||
|
||||
self.PlayerMenu = PlayerMenu
|
||||
|
||||
self:I(string.format("Setting menu for player %s", tostring(PlayerName)))
|
||||
self:I( string.format( "Setting menu for player %s", tostring( PlayerName ) ) )
|
||||
|
||||
local submenu = MENU_GROUP:New( PlayerGroup, "LL Accuracy", PlayerMenu )
|
||||
MENU_GROUP_COMMAND:New( PlayerGroup, "LL 0 Decimals", submenu, self.MenuGroupLL_DDM_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 0 )
|
||||
MENU_GROUP_COMMAND:New( PlayerGroup, "LL 1 Decimal", submenu, self.MenuGroupLL_DDM_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 1 )
|
||||
MENU_GROUP_COMMAND:New( PlayerGroup, "LL 1 Decimal", submenu, self.MenuGroupLL_DDM_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 1 )
|
||||
MENU_GROUP_COMMAND:New( PlayerGroup, "LL 2 Decimals", submenu, self.MenuGroupLL_DDM_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 2 )
|
||||
MENU_GROUP_COMMAND:New( PlayerGroup, "LL 3 Decimals", submenu, self.MenuGroupLL_DDM_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 3 )
|
||||
MENU_GROUP_COMMAND:New( PlayerGroup, "LL 4 Decimals", submenu, self.MenuGroupLL_DDM_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 4 )
|
||||
@@ -753,40 +765,40 @@ do -- SETTINGS
|
||||
-- A2G Coordinate System
|
||||
------
|
||||
|
||||
local text="A2G Coordinate System"
|
||||
local text = "A2G Coordinate System"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="A2G Coordinates"
|
||||
text = "A2G Coordinates"
|
||||
end
|
||||
local A2GCoordinateMenu = MENU_GROUP:New( PlayerGroup, text, PlayerMenu )
|
||||
|
||||
if not self:IsA2G_LL_DMS() or _SETTINGS.MenuStatic then
|
||||
local text="Lat/Lon Degree Min Sec (LL DMS)"
|
||||
local text = "Lat/Lon Degree Min Sec (LL DMS)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="A2G LL DMS"
|
||||
text = "A2G LL DMS"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( PlayerGroup, text, A2GCoordinateMenu, self.MenuGroupA2GSystem, self, PlayerUnit, PlayerGroup, PlayerName, "LL DMS" )
|
||||
end
|
||||
|
||||
if not self:IsA2G_LL_DDM() or _SETTINGS.MenuStatic then
|
||||
local text="Lat/Lon Degree Dec Min (LL DDM)"
|
||||
local text = "Lat/Lon Degree Dec Min (LL DDM)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="A2G LL DDM"
|
||||
text = "A2G LL DDM"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( PlayerGroup, text, A2GCoordinateMenu, self.MenuGroupA2GSystem, self, PlayerUnit, PlayerGroup, PlayerName, "LL DDM" )
|
||||
end
|
||||
|
||||
if not self:IsA2G_BR() or _SETTINGS.MenuStatic then
|
||||
local text="Bearing, Range (BR)"
|
||||
local text = "Bearing, Range (BR)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="A2G BR"
|
||||
text = "A2G BR"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( PlayerGroup, text, A2GCoordinateMenu, self.MenuGroupA2GSystem, self, PlayerUnit, PlayerGroup, PlayerName, "BR" )
|
||||
end
|
||||
|
||||
if not self:IsA2G_MGRS() or _SETTINGS.MenuStatic then
|
||||
local text="Military Grid (MGRS)"
|
||||
local text = "Military Grid (MGRS)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="A2G MGRS"
|
||||
text = "A2G MGRS"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( PlayerGroup, text, A2GCoordinateMenu, self.MenuGroupA2GSystem, self, PlayerUnit, PlayerGroup, PlayerName, "MGRS" )
|
||||
end
|
||||
@@ -795,49 +807,48 @@ do -- SETTINGS
|
||||
-- A2A Coordinates Menu
|
||||
------
|
||||
|
||||
local text="A2A Coordinate System"
|
||||
local text = "A2A Coordinate System"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="A2A Coordinates"
|
||||
text = "A2A Coordinates"
|
||||
end
|
||||
local A2ACoordinateMenu = MENU_GROUP:New( PlayerGroup, text, PlayerMenu )
|
||||
|
||||
|
||||
if not self:IsA2A_LL_DMS() or _SETTINGS.MenuStatic then
|
||||
local text="Lat/Lon Degree Min Sec (LL DMS)"
|
||||
local text = "Lat/Lon Degree Min Sec (LL DMS)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="A2A LL DMS"
|
||||
text = "A2A LL DMS"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( PlayerGroup, text, A2ACoordinateMenu, self.MenuGroupA2GSystem, self, PlayerUnit, PlayerGroup, PlayerName, "LL DMS" )
|
||||
end
|
||||
|
||||
if not self:IsA2A_LL_DDM() or _SETTINGS.MenuStatic then
|
||||
local text="Lat/Lon Degree Dec Min (LL DDM)"
|
||||
local text = "Lat/Lon Degree Dec Min (LL DDM)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="A2A LL DDM"
|
||||
text = "A2A LL DDM"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( PlayerGroup, text, A2ACoordinateMenu, self.MenuGroupA2ASystem, self, PlayerUnit, PlayerGroup, PlayerName, "LL DDM" )
|
||||
end
|
||||
|
||||
if not self:IsA2A_BULLS() or _SETTINGS.MenuStatic then
|
||||
local text="Bullseye (BULLS)"
|
||||
local text = "Bullseye (BULLS)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="A2A BULLS"
|
||||
text = "A2A BULLS"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( PlayerGroup, text, A2ACoordinateMenu, self.MenuGroupA2ASystem, self, PlayerUnit, PlayerGroup, PlayerName, "BULLS" )
|
||||
end
|
||||
|
||||
if not self:IsA2A_BRAA() or _SETTINGS.MenuStatic then
|
||||
local text="Bearing Range Altitude Aspect (BRAA)"
|
||||
local text = "Bearing Range Altitude Aspect (BRAA)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="A2A BRAA"
|
||||
text = "A2A BRAA"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( PlayerGroup, text, A2ACoordinateMenu, self.MenuGroupA2ASystem, self, PlayerUnit, PlayerGroup, PlayerName, "BRAA" )
|
||||
end
|
||||
|
||||
if not self:IsA2A_MGRS() or _SETTINGS.MenuStatic then
|
||||
local text="Military Grid (MGRS)"
|
||||
local text = "Military Grid (MGRS)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="A2A MGRS"
|
||||
text = "A2A MGRS"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( PlayerGroup, text, A2ACoordinateMenu, self.MenuGroupA2ASystem, self, PlayerUnit, PlayerGroup, PlayerName, "MGRS" )
|
||||
end
|
||||
@@ -846,24 +857,24 @@ do -- SETTINGS
|
||||
-- Unit system
|
||||
---
|
||||
|
||||
local text="Measures and Weights System"
|
||||
local text = "Measures and Weights System"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="Unit System"
|
||||
text = "Unit System"
|
||||
end
|
||||
local MetricsMenu = MENU_GROUP:New( PlayerGroup, text, PlayerMenu )
|
||||
|
||||
if self:IsMetric() or _SETTINGS.MenuStatic then
|
||||
local text="Imperial (Miles,Feet)"
|
||||
local text = "Imperial (Miles,Feet)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="Imperial"
|
||||
text = "Imperial"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( PlayerGroup, text, MetricsMenu, self.MenuGroupMWSystem, self, PlayerUnit, PlayerGroup, PlayerName, false )
|
||||
end
|
||||
|
||||
if self:IsImperial() or _SETTINGS.MenuStatic then
|
||||
local text="Metric (Kilometers,Meters)"
|
||||
local text = "Metric (Kilometers,Meters)"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="Metric"
|
||||
text = "Metric"
|
||||
end
|
||||
MENU_GROUP_COMMAND:New( PlayerGroup, text, MetricsMenu, self.MenuGroupMWSystem, self, PlayerUnit, PlayerGroup, PlayerName, true )
|
||||
end
|
||||
@@ -872,9 +883,9 @@ do -- SETTINGS
|
||||
-- Messages and Reports
|
||||
---
|
||||
|
||||
local text="Messages and Reports"
|
||||
local text = "Messages and Reports"
|
||||
if _SETTINGS.MenuShort then
|
||||
text="Messages & Reports"
|
||||
text = "Messages & Reports"
|
||||
end
|
||||
local MessagesMenu = MENU_GROUP:New( PlayerGroup, text, PlayerMenu )
|
||||
|
||||
@@ -934,39 +945,38 @@ do -- SETTINGS
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- @param #SETTINGS self
|
||||
function SETTINGS:A2GMenuSystem( MenuGroup, RootMenu, A2GSystem )
|
||||
self.A2GSystem = A2GSystem
|
||||
MESSAGE:New( string.format("Settings: Default A2G coordinate system set to %s for all players!", A2GSystem ), 5 ):ToAll()
|
||||
MESSAGE:New( string.format( "Settings: Default A2G coordinate system set to %s for all players!", A2GSystem ), 5 ):ToAll()
|
||||
self:SetSystemMenu( MenuGroup, RootMenu )
|
||||
end
|
||||
|
||||
--- @param #SETTINGS self
|
||||
function SETTINGS:A2AMenuSystem( MenuGroup, RootMenu, A2ASystem )
|
||||
self.A2ASystem = A2ASystem
|
||||
MESSAGE:New( string.format("Settings: Default A2A coordinate system set to %s for all players!", A2ASystem ), 5 ):ToAll()
|
||||
MESSAGE:New( string.format( "Settings: Default A2A coordinate system set to %s for all players!", A2ASystem ), 5 ):ToAll()
|
||||
self:SetSystemMenu( MenuGroup, RootMenu )
|
||||
end
|
||||
|
||||
--- @param #SETTINGS self
|
||||
function SETTINGS:MenuLL_DDM_Accuracy( MenuGroup, RootMenu, LL_Accuracy )
|
||||
self.LL_Accuracy = LL_Accuracy
|
||||
MESSAGE:New( string.format("Settings: Default LL accuracy set to %s for all players!", LL_Accuracy ), 5 ):ToAll()
|
||||
MESSAGE:New( string.format( "Settings: Default LL accuracy set to %s for all players!", LL_Accuracy ), 5 ):ToAll()
|
||||
self:SetSystemMenu( MenuGroup, RootMenu )
|
||||
end
|
||||
|
||||
--- @param #SETTINGS self
|
||||
function SETTINGS:MenuMGRS_Accuracy( MenuGroup, RootMenu, MGRS_Accuracy )
|
||||
self.MGRS_Accuracy = MGRS_Accuracy
|
||||
MESSAGE:New( string.format("Settings: Default MGRS accuracy set to %s for all players!", MGRS_Accuracy ), 5 ):ToAll()
|
||||
MESSAGE:New( string.format( "Settings: Default MGRS accuracy set to %s for all players!", MGRS_Accuracy ), 5 ):ToAll()
|
||||
self:SetSystemMenu( MenuGroup, RootMenu )
|
||||
end
|
||||
|
||||
--- @param #SETTINGS self
|
||||
function SETTINGS:MenuMWSystem( MenuGroup, RootMenu, MW )
|
||||
self.Metric = MW
|
||||
MESSAGE:New( string.format("Settings: Default measurement format set to %s for all players!", MW and "Metric" or "Imperial" ), 5 ):ToAll()
|
||||
MESSAGE:New( string.format( "Settings: Default measurement format set to %s for all players!", MW and "Metric" or "Imperial" ), 5 ):ToAll()
|
||||
self:SetSystemMenu( MenuGroup, RootMenu )
|
||||
end
|
||||
|
||||
@@ -979,12 +989,12 @@ do -- SETTINGS
|
||||
do
|
||||
--- @param #SETTINGS self
|
||||
function SETTINGS:MenuGroupA2GSystem( PlayerUnit, PlayerGroup, PlayerName, A2GSystem )
|
||||
BASE:E( {self, PlayerUnit:GetName(), A2GSystem} )
|
||||
BASE:E( { self, PlayerUnit:GetName(), A2GSystem } )
|
||||
self.A2GSystem = A2GSystem
|
||||
MESSAGE:New( string.format( "Settings: A2G format set to %s for player %s.", A2GSystem, PlayerName ), 5 ):ToGroup( PlayerGroup )
|
||||
if _SETTINGS.MenuStatic==false then
|
||||
self:RemovePlayerMenu(PlayerUnit)
|
||||
self:SetPlayerMenu(PlayerUnit)
|
||||
if _SETTINGS.MenuStatic == false then
|
||||
self:RemovePlayerMenu( PlayerUnit )
|
||||
self:SetPlayerMenu( PlayerUnit )
|
||||
end
|
||||
end
|
||||
|
||||
@@ -992,9 +1002,9 @@ do -- SETTINGS
|
||||
function SETTINGS:MenuGroupA2ASystem( PlayerUnit, PlayerGroup, PlayerName, A2ASystem )
|
||||
self.A2ASystem = A2ASystem
|
||||
MESSAGE:New( string.format( "Settings: A2A format set to %s for player %s.", A2ASystem, PlayerName ), 5 ):ToGroup( PlayerGroup )
|
||||
if _SETTINGS.MenuStatic==false then
|
||||
self:RemovePlayerMenu(PlayerUnit)
|
||||
self:SetPlayerMenu(PlayerUnit)
|
||||
if _SETTINGS.MenuStatic == false then
|
||||
self:RemovePlayerMenu( PlayerUnit )
|
||||
self:SetPlayerMenu( PlayerUnit )
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1002,9 +1012,9 @@ do -- SETTINGS
|
||||
function SETTINGS:MenuGroupLL_DDM_AccuracySystem( PlayerUnit, PlayerGroup, PlayerName, LL_Accuracy )
|
||||
self.LL_Accuracy = LL_Accuracy
|
||||
MESSAGE:New( string.format( "Settings: LL format accuracy set to %d decimal places for player %s.", LL_Accuracy, PlayerName ), 5 ):ToGroup( PlayerGroup )
|
||||
if _SETTINGS.MenuStatic==false then
|
||||
self:RemovePlayerMenu(PlayerUnit)
|
||||
self:SetPlayerMenu(PlayerUnit)
|
||||
if _SETTINGS.MenuStatic == false then
|
||||
self:RemovePlayerMenu( PlayerUnit )
|
||||
self:SetPlayerMenu( PlayerUnit )
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1012,9 +1022,9 @@ do -- SETTINGS
|
||||
function SETTINGS:MenuGroupMGRS_AccuracySystem( PlayerUnit, PlayerGroup, PlayerName, MGRS_Accuracy )
|
||||
self.MGRS_Accuracy = MGRS_Accuracy
|
||||
MESSAGE:New( string.format( "Settings: MGRS format accuracy set to %d for player %s.", MGRS_Accuracy, PlayerName ), 5 ):ToGroup( PlayerGroup )
|
||||
if _SETTINGS.MenuStatic==false then
|
||||
self:RemovePlayerMenu(PlayerUnit)
|
||||
self:SetPlayerMenu(PlayerUnit)
|
||||
if _SETTINGS.MenuStatic == false then
|
||||
self:RemovePlayerMenu( PlayerUnit )
|
||||
self:SetPlayerMenu( PlayerUnit )
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1022,9 +1032,9 @@ do -- SETTINGS
|
||||
function SETTINGS:MenuGroupMWSystem( PlayerUnit, PlayerGroup, PlayerName, MW )
|
||||
self.Metric = MW
|
||||
MESSAGE:New( string.format( "Settings: Measurement format set to %s for player %s.", MW and "Metric" or "Imperial", PlayerName ), 5 ):ToGroup( PlayerGroup )
|
||||
if _SETTINGS.MenuStatic==false then
|
||||
self:RemovePlayerMenu(PlayerUnit)
|
||||
self:SetPlayerMenu(PlayerUnit)
|
||||
if _SETTINGS.MenuStatic == false then
|
||||
self:RemovePlayerMenu( PlayerUnit )
|
||||
self:SetPlayerMenu( PlayerUnit )
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1054,7 +1064,6 @@ do -- SETTINGS
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- Configures the era of the mission to be Cold war.
|
||||
-- @param #SETTINGS self
|
||||
-- @return #SETTINGS self
|
||||
@@ -1064,7 +1073,6 @@ do -- SETTINGS
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- Configures the era of the mission to be Modern war.
|
||||
-- @param #SETTINGS self
|
||||
-- @return #SETTINGS self
|
||||
@@ -1074,7 +1082,4 @@ do -- SETTINGS
|
||||
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
end
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,311 +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
|
||||
|
||||
|
||||
--- Creates a new @{Static} from a COORDINATE.
|
||||
-- @param #SPAWNSTATIC self
|
||||
-- @param Core.Point#COORDINATE Coordinate The 3D coordinate where to spawn the static.
|
||||
-- @param #number Heading (Optional) Heading The heading of the static, which is a number in degrees from 0 to 360. Default is 0 degrees.
|
||||
-- @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 #SPAWNSTATIC
|
||||
function SPAWNSTATIC:SpawnFromCoordinate(Coordinate, Heading, NewName) --R2.4
|
||||
self:F( { PointVec2, Heading, NewName } )
|
||||
|
||||
local StaticTemplate, CoalitionID, CategoryID, CountryID = _DATABASE:GetStaticGroupTemplate( self.SpawnTemplatePrefix )
|
||||
|
||||
if StaticTemplate then
|
||||
|
||||
Heading=Heading or 0
|
||||
|
||||
local StaticUnitTemplate = StaticTemplate.units[1]
|
||||
|
||||
StaticUnitTemplate.x = Coordinate.x
|
||||
StaticUnitTemplate.y = Coordinate.z
|
||||
StaticUnitTemplate.alt = Coordinate.y
|
||||
|
||||
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})
|
||||
-- @return Wrapper.Static#STATIC The spawned STATIC object.
|
||||
function SPAWNSTATIC:SpawnFromCoordinate(Coordinate, Heading, NewName)
|
||||
|
||||
local Static = coalition.addStaticObject( self.CountryID or CountryID, StaticTemplate.units[1] )
|
||||
|
||||
self.SpawnIndex = self.SpawnIndex + 1
|
||||
|
||||
return _DATABASE:FindStatic(Static:getName())
|
||||
-- Set up coordinate.
|
||||
self.InitStaticCoordinate=Coordinate
|
||||
|
||||
if Heading then
|
||||
self.InitStaticHeading=Heading
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
--- Respawns the original @{Static}.
|
||||
-- @param #SPAWNSTATIC self
|
||||
-- @param #number delay Delay before respawn in seconds.
|
||||
-- @return #SPAWNSTATIC
|
||||
function SPAWNSTATIC:ReSpawn(delay)
|
||||
|
||||
if delay and delay>0 then
|
||||
self:ScheduleOnce(delay, SPAWNSTATIC.ReSpawn, self)
|
||||
else
|
||||
|
||||
local StaticTemplate, CoalitionID, CategoryID, CountryID = _DATABASE:GetStaticGroupTemplate( self.SpawnTemplatePrefix )
|
||||
|
||||
if StaticTemplate then
|
||||
|
||||
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())
|
||||
end
|
||||
|
||||
return nil
|
||||
if NewName then
|
||||
self.InitStaticName=NewName
|
||||
end
|
||||
|
||||
return self
|
||||
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())
|
||||
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 NewName (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
|
||||
|
||||
@@ -341,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
|
||||
|
||||
|
||||
202
Moose Development/Moose/Core/TextAndSound.lua
Normal file
202
Moose Development/Moose/Core/TextAndSound.lua
Normal file
@@ -0,0 +1,202 @@
|
||||
--- **Core** - TEXTANDSOUND (MOOSE gettext) system
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ## Main Features:
|
||||
--
|
||||
-- * A GetText for Moose
|
||||
-- * Build a set of localized text entries, alongside their sounds and subtitles
|
||||
-- * Aimed at class developers to offer localizable language support
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ## Example Missions:
|
||||
--
|
||||
-- Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/).
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **applevangelist**
|
||||
-- ## Date: April 2022
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module Core.TextAndSound
|
||||
-- @image MOOSE.JPG
|
||||
|
||||
--- Text and Sound class.
|
||||
-- @type TEXTANDSOUND
|
||||
-- @field #string ClassName Name of this class.
|
||||
-- @field #string version Versioning.
|
||||
-- @field #string lid LID for log entries.
|
||||
-- @field #string locale Default locale of this object.
|
||||
-- @field #table entries Table of entries.
|
||||
-- @field #string textclass Name of the class the texts belong to.
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
---
|
||||
--
|
||||
-- @field #TEXTANDSOUND
|
||||
TEXTANDSOUND = {
|
||||
ClassName = "TEXTANDSOUND",
|
||||
version = "0.0.1",
|
||||
lid = "",
|
||||
locale = "en",
|
||||
entries = {},
|
||||
textclass = "",
|
||||
}
|
||||
|
||||
--- Text and Sound entry.
|
||||
-- @type TEXTANDSOUND.Entry
|
||||
-- @field #string Classname Name of the class this entry is for.
|
||||
-- @field #string Locale Locale of this entry, defaults to "en".
|
||||
-- @field #table Data The list of entries.
|
||||
|
||||
--- Text and Sound data
|
||||
-- @type TEXTANDSOUND.Data
|
||||
-- @field #string ID ID of this entry for retrieval.
|
||||
-- @field #string Text Text of this entry.
|
||||
-- @field #string Soundfile (optional) Soundfile File name of the corresponding sound file.
|
||||
-- @field #number Soundlength (optional) Length of the sound file in seconds.
|
||||
-- @field #string Subtitle (optional) Subtitle for the sound file.
|
||||
|
||||
--- Instantiate a new object
|
||||
-- @param #TEXTANDSOUND self
|
||||
-- @param #string ClassName Name of the class this instance is providing texts for.
|
||||
-- @param #string Defaultlocale (Optional) Default locale of this instance, defaults to "en".
|
||||
-- @return #TEXTANDSOUND self
|
||||
function TEXTANDSOUND:New(ClassName,Defaultlocale)
|
||||
-- Inherit everything from BASE class.
|
||||
local self=BASE:Inherit(self, BASE:New())
|
||||
-- Set some string id for output to DCS.log file.
|
||||
self.lid=string.format("%s (%s) | ", self.ClassName, self.version)
|
||||
self.locale = Defaultlocale or (_SETTINGS:GetLocale() or "en")
|
||||
self.textclass = ClassName or "none"
|
||||
self.entries = {}
|
||||
local initentry = {} -- #TEXTANDSOUND.Entry
|
||||
initentry.Classname = ClassName
|
||||
initentry.Data = {}
|
||||
initentry.Locale = self.locale
|
||||
self.entries[self.locale] = initentry
|
||||
self:I(self.lid .. "Instantiated.")
|
||||
self:T({self.entries[self.locale]})
|
||||
return self
|
||||
end
|
||||
|
||||
--- Add an entry
|
||||
-- @param #TEXTANDSOUND self
|
||||
-- @param #string Locale Locale to set for this entry, e.g. "de".
|
||||
-- @param #string ID Unique(!) ID of this entry under this locale (i.e. use the same ID to get localized text for the entry in another language).
|
||||
-- @param #string Text Text for this entry.
|
||||
-- @param #string Soundfile (Optional) Sound file name for this entry.
|
||||
-- @param #number Soundlength (Optional) Length of the sound file in seconds.
|
||||
-- @param #string Subtitle (Optional) Subtitle to be used alongside the sound file.
|
||||
-- @return #TEXTANDSOUND self
|
||||
function TEXTANDSOUND:AddEntry(Locale,ID,Text,Soundfile,Soundlength,Subtitle)
|
||||
self:T(self.lid .. "AddEntry")
|
||||
local locale = Locale or self.locale
|
||||
local dataentry = {} -- #TEXTANDSOUND.Data
|
||||
dataentry.ID = ID or "1"
|
||||
dataentry.Text = Text or "none"
|
||||
dataentry.Soundfile = Soundfile
|
||||
dataentry.Soundlength = Soundlength or 0
|
||||
dataentry.Subtitle = Subtitle
|
||||
if not self.entries[locale] then
|
||||
local initentry = {} -- #TEXTANDSOUND.Entry
|
||||
initentry.Classname = self.textclass -- class name entry
|
||||
initentry.Data = {} -- data array
|
||||
initentry.Locale = locale -- default locale
|
||||
self.entries[locale] = initentry
|
||||
end
|
||||
self.entries[locale].Data[ID] = dataentry
|
||||
self:T({self.entries[locale].Data})
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get an entry
|
||||
-- @param #TEXTANDSOUND self
|
||||
-- @param #string ID The unique ID of the data to be retrieved.
|
||||
-- @param #string Locale (Optional) The locale of the text to be retrieved - defauls to default locale set with `New()`.
|
||||
-- @return #string Text Text or nil if not found and no fallback.
|
||||
-- @return #string Soundfile Filename or nil if not found and no fallback.
|
||||
-- @return #string Soundlength Length of the sound or 0 if not found and no fallback.
|
||||
-- @return #string Subtitle Text for subtitle or nil if not found and no fallback.
|
||||
function TEXTANDSOUND:GetEntry(ID,Locale)
|
||||
self:T(self.lid .. "GetEntry")
|
||||
local locale = Locale or self.locale
|
||||
if not self.entries[locale] then
|
||||
-- fall back to default "en"
|
||||
locale = self.locale
|
||||
end
|
||||
local Text,Soundfile,Soundlength,Subtitle = nil, nil, 0, nil
|
||||
if self.entries[locale] then
|
||||
if self.entries[locale].Data then
|
||||
local data = self.entries[locale].Data[ID] -- #TEXTANDSOUND.Data
|
||||
if data then
|
||||
Text = data.Text
|
||||
Soundfile = data.Soundfile
|
||||
Soundlength = data.Soundlength
|
||||
Subtitle = data.Subtitle
|
||||
elseif self.entries[self.locale].Data[ID] then
|
||||
-- no matching entry, try default
|
||||
local data = self.entries[self.locale].Data[ID]
|
||||
Text = data.Text
|
||||
Soundfile = data.Soundfile
|
||||
Soundlength = data.Soundlength
|
||||
Subtitle = data.Subtitle
|
||||
end
|
||||
end
|
||||
else
|
||||
return nil, nil, 0, nil
|
||||
end
|
||||
return Text,Soundfile,Soundlength,Subtitle
|
||||
end
|
||||
|
||||
--- Get the default locale of this object
|
||||
-- @param #TEXTANDSOUND self
|
||||
-- @return #string locale
|
||||
function TEXTANDSOUND:GetDefaultLocale()
|
||||
self:T(self.lid .. "GetDefaultLocale")
|
||||
return self.locale
|
||||
end
|
||||
|
||||
--- Set default locale of this object
|
||||
-- @param #TEXTANDSOUND self
|
||||
-- @param #string locale
|
||||
-- @return #TEXTANDSOUND self
|
||||
function TEXTANDSOUND:SetDefaultLocale(locale)
|
||||
self:T(self.lid .. "SetDefaultLocale")
|
||||
self.locale = locale or "en"
|
||||
return self
|
||||
end
|
||||
|
||||
--- Check if a locale exists
|
||||
-- @param #TEXTANDSOUND self
|
||||
-- @return #boolean outcome
|
||||
function TEXTANDSOUND:HasLocale(Locale)
|
||||
self:T(self.lid .. "HasLocale")
|
||||
return self.entries[Locale] and true or false
|
||||
end
|
||||
|
||||
--- Flush all entries to the log
|
||||
-- @param #TEXTANDSOUND self
|
||||
-- @return #TEXTANDSOUND self
|
||||
function TEXTANDSOUND:FlushToLog()
|
||||
self:I(self.lid .. "Flushing entries:")
|
||||
local text = string.format("Textclass: %s | Default Locale: %s",self.textclass, self.locale)
|
||||
for _,_entry in pairs(self.entries) do
|
||||
local entry = _entry -- #TEXTANDSOUND.Entry
|
||||
local text = string.format("Textclassname: %s | Locale: %s",entry.Classname, entry.Locale)
|
||||
self:I(text)
|
||||
for _ID,_data in pairs(entry.Data) do
|
||||
local data = _data -- #TEXTANDSOUND.Data
|
||||
local text = string.format("ID: %s\nText: %s\nSoundfile: %s With length: %d\nSubtitle: %s",tostring(_ID), data.Text or "none",data.Soundfile or "none",data.Soundlength or 0,data.Subtitle or "none")
|
||||
self:I(text)
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
----------------------------------------------------------------
|
||||
-- End TextAndSound
|
||||
----------------------------------------------------------------
|
||||
291
Moose Development/Moose/Core/Timer.lua
Normal file
291
Moose Development/Moose/Core/Timer.lua
Normal file
@@ -0,0 +1,291 @@
|
||||
--- **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 class version.
|
||||
-- @field #string version
|
||||
TIMER.version="0.1.2"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- TODO list
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
-- TODO: Randomization.
|
||||
-- TODO: Pause/unpause.
|
||||
-- DONE: 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)
|
||||
|
||||
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+math.max(Tstart, 0.001) 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
|
||||
|
||||
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
|
||||
|
||||
--- Set time interval. Can also be set when the timer is already running and is applied after the next function call.
|
||||
-- @param #TIMER self
|
||||
-- @param #number dT Time interval in seconds.
|
||||
-- @return #TIMER self
|
||||
function TIMER:SetTimeInterval(dT)
|
||||
self.dT=dT
|
||||
return self
|
||||
end
|
||||
|
||||
--- Check if the timer has been started and was not stopped.
|
||||
-- @param #TIMER self
|
||||
-- @return #boolean If `true`, the timer is running.
|
||||
function TIMER:IsRunning()
|
||||
return self.isrunning
|
||||
end
|
||||
|
||||
--- Call timer function.
|
||||
-- @param #TIMER self
|
||||
-- @param #number time DCS model time in seconds.
|
||||
-- @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
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
File diff suppressed because it is too large
Load Diff
@@ -95,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
|
||||
|
||||
@@ -290,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
|
||||
|
||||
@@ -360,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
|
||||
@@ -368,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
|
||||
@@ -401,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
|
||||
@@ -455,6 +471,22 @@ do -- Types
|
||||
--@field #boolean lateActivated
|
||||
--@field #boolean uncontrolled
|
||||
|
||||
--- DCS template data structure.
|
||||
-- @type Template
|
||||
-- @field #boolean uncontrolled Aircraft is uncontrolled.
|
||||
-- @field #boolean lateActivation Group is late activated.
|
||||
-- @field #number x 2D Position on x-axis in meters.
|
||||
-- @field #number y 2D Position on y-axis in meters.
|
||||
-- @field #table units Unit list.
|
||||
--
|
||||
|
||||
--- Unit data structure.
|
||||
--@type Template.Unit
|
||||
--@field #string name Name of the unit.
|
||||
--@field #number x
|
||||
--@field #number y
|
||||
--@field #number alt
|
||||
|
||||
end --
|
||||
|
||||
|
||||
@@ -471,8 +503,9 @@ do -- Object
|
||||
-- @field UNIT
|
||||
-- @field WEAPON
|
||||
-- @field STATIC
|
||||
-- @field SCENERY
|
||||
-- @field BASE
|
||||
-- @field SCENERY
|
||||
-- @field CARGO
|
||||
|
||||
--- @type Object.Desc
|
||||
-- @extends #Desc
|
||||
@@ -482,6 +515,10 @@ do -- Object
|
||||
--- @function [parent=#Object] isExist
|
||||
-- @param #Object self
|
||||
-- @return #boolean
|
||||
|
||||
--- @function [parent=#Object] isActive
|
||||
-- @param #Object self
|
||||
-- @return #boolean
|
||||
|
||||
--- @function [parent=#Object] destroy
|
||||
-- @param #Object self
|
||||
@@ -514,7 +551,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
|
||||
@@ -524,7 +561,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
|
||||
@@ -731,7 +768,66 @@ do -- Airbase
|
||||
|
||||
end -- Airbase
|
||||
|
||||
do -- Spot
|
||||
|
||||
--- [DCS Class Spot](https://wiki.hoggitworld.com/view/DCS_Class_Spot)
|
||||
-- Represents a spot from laser or IR-pointer.
|
||||
-- @type Spot
|
||||
-- @field #Spot.Category Category enum that stores spot categories.
|
||||
|
||||
--- Enum that stores spot categories.
|
||||
-- @type Spot.Category
|
||||
-- @field #string INFRA_RED
|
||||
-- @field #string LASER
|
||||
|
||||
|
||||
--- Creates a laser ray emanating from the given object to a point in 3d space.
|
||||
-- @function [parent=#Spot] createLaser
|
||||
-- @param DCS#Object Source The source object of the laser.
|
||||
-- @param DCS#Vec3 LocalRef An optional 3D offset for the source.
|
||||
-- @param DCS#Vec3 Vec3 Target coordinate where the ray is pointing at.
|
||||
-- @param #number LaserCode Any 4 digit number between 1111 and 1788.
|
||||
-- @return #Spot
|
||||
|
||||
--- Creates an infrared ray emanating from the given object to a point in 3d space. Can be seen with night vision goggles.
|
||||
-- @function [parent=#Spot] createInfraRed
|
||||
-- @param DCS#Object Source Source position of the IR ray.
|
||||
-- @param DCS#Vec3 LocalRef An optional 3D offset for the source.
|
||||
-- @param DCS#Vec3 Vec3 Target coordinate where the ray is pointing at.
|
||||
-- @return #Spot
|
||||
|
||||
--- Returns a vec3 table of the x, y, and z coordinates for the position of the given object in 3D space. Coordinates are dependent on the position of the maps origin.
|
||||
-- @function [parent=#Spot] getPoint
|
||||
-- @param #Spot self
|
||||
-- @return DCS#Vec3 Point in 3D, where the beam is pointing at.
|
||||
|
||||
--- Sets the destination point from which the source of the spot is drawn toward.
|
||||
-- @function [parent=#Spot] setPoint
|
||||
-- @param #Spot self
|
||||
-- @param DCS#Vec3 Vec3 Point in 3D, where the beam is pointing at.
|
||||
|
||||
--- Returns the number that is used to define the laser code for which laser designation can track.
|
||||
-- @function [parent=#Spot] getCode
|
||||
-- @param #Spot self
|
||||
-- @return #number Code The laser code used.
|
||||
|
||||
--- Sets the number that is used to define the laser code for which laser designation can track.
|
||||
-- @function [parent=#Spot] setCode
|
||||
-- @param #Spot self
|
||||
-- @param #number Code The laser code. Default value is 1688.
|
||||
|
||||
--- Destroys the spot.
|
||||
-- @function [parent=#Spot] destroy
|
||||
-- @param #Spot self
|
||||
|
||||
--- Gets the category of the spot (laser or IR).
|
||||
-- @function [parent=#Spot] getCategory
|
||||
-- @param #Spot self
|
||||
-- @return #string Category.
|
||||
|
||||
Spot = {} --#Spot
|
||||
|
||||
end -- Spot
|
||||
|
||||
do -- Controller
|
||||
--- Controller is an object that performs A.I.-routines. Other words controller is an instance of A.I.. Controller stores current main task, active enroute tasks and behavior options. Controller performs commands. Please, read DCS A-10C GUI Manual EN.pdf chapter "Task Planning for Unit Groups", page 91 to understand A.I. system of DCS:A-10C.
|
||||
@@ -1055,6 +1151,11 @@ do -- Unit
|
||||
-- @function [parent=#Unit] getAmmo
|
||||
-- @param #Unit self
|
||||
-- @return #Unit.Ammo
|
||||
|
||||
--- Returns the number of infantry that can be embark onto the aircraft. Only returns a value if run on airplanes or helicopters. Returns nil if run on ground or ship units.
|
||||
-- @function [parent=#Unit] getDescentCapacity
|
||||
-- @param #Unit self
|
||||
-- @return #number Number of soldiers that embark.
|
||||
|
||||
--- Returns the unit sensors.
|
||||
-- @function [parent=#Unit] getSensors
|
||||
@@ -1088,6 +1189,10 @@ do -- Unit
|
||||
-- @param #Unit self
|
||||
-- @return #Unit.Desc
|
||||
|
||||
--- GROUND - Switch on/off radar emissions
|
||||
-- @function [parent=#Unit] enableEmission
|
||||
-- @param #Unit self
|
||||
-- @param #boolean switch
|
||||
|
||||
Unit = {} --#Unit
|
||||
|
||||
@@ -1158,7 +1263,7 @@ do -- Group
|
||||
-- @param #Group self
|
||||
-- @return #number
|
||||
|
||||
--- Returns initial size of the group. If some of the units will be destroyed, initial size of the group will not be changed. Initial size limits the unitNumber parameter for Group.getUnit() function.
|
||||
--- Returns initial size of the group. If some of the units will be destroyed, initial size of the group will not be changed; Initial size limits the unitNumber parameter for Group.getUnit() function.
|
||||
-- @function [parent=#Group] getInitialSize
|
||||
-- @param #Group self
|
||||
-- @return #number
|
||||
@@ -1173,6 +1278,11 @@ do -- Group
|
||||
-- @param #Group self
|
||||
-- @return #Controller
|
||||
|
||||
--- GROUND - Switch on/off radar emissions
|
||||
-- @function [parent=#Group] enableEmission
|
||||
-- @param #Group self
|
||||
-- @param #boolean switch
|
||||
|
||||
Group = {} --#Group
|
||||
|
||||
end -- Group
|
||||
@@ -1264,7 +1374,9 @@ do -- AI
|
||||
--- @type AI.Option.Ground
|
||||
-- @field #AI.Option.Ground.id id
|
||||
-- @field #AI.Option.Ground.val val
|
||||
|
||||
-- @field #AI.Option.Ground.mid mid
|
||||
-- @field #AI.Option.Ground.mval mval
|
||||
--
|
||||
--- @type AI.Option.Naval
|
||||
-- @field #AI.Option.Naval.id id
|
||||
-- @field #AI.Option.Naval.val val
|
||||
@@ -1287,6 +1399,11 @@ do -- AI
|
||||
-- @field PROHIBIT_AG
|
||||
-- @field MISSILE_ATTACK
|
||||
-- @field PROHIBIT_WP_PASS_REPORT
|
||||
-- @field OPTION_RADIO_USAGE_CONTACT
|
||||
-- @field OPTION_RADIO_USAGE_ENGAGE
|
||||
-- @field OPTION_RADIO_USAGE_KILL
|
||||
-- @field JETT_TANKS_IF_EMPTY
|
||||
-- @field FORCED_ATTACK
|
||||
|
||||
--- @type AI.Option.Air.id.FORMATION
|
||||
-- @field LINE_ABREAST
|
||||
@@ -1356,19 +1473,35 @@ do -- AI
|
||||
--- @type AI.Option.Ground.id
|
||||
-- @field NO_OPTION
|
||||
-- @field ROE @{#AI.Option.Ground.val.ROE}
|
||||
-- @field FORMATION
|
||||
-- @field DISPERSE_ON_ATTACK true or false
|
||||
-- @field ALARM_STATE @{#AI.Option.Ground.val.ALARM_STATE}
|
||||
-- @field ENGAGE_AIR_WEAPONS
|
||||
-- @field AC_ENGAGEMENT_RANGE_RESTRICTION
|
||||
|
||||
--- @type AI.Option.Ground.mid -- Moose added
|
||||
-- @field RESTRICT_AAA_MIN 27
|
||||
-- @field RESTRICT_AAA_MAX 29
|
||||
-- @field RESTRICT_TARGETS @{#AI.Option.Ground.mval.ENGAGE_TARGETS} 28
|
||||
|
||||
--- @type AI.Option.Ground.val
|
||||
-- @field #AI.Option.Ground.val.ROE ROE
|
||||
-- @field #AI.Option.Ground.val.ALARM_STATE ALARM_STATE
|
||||
-- @field #AI.Option.Ground.val.ENGAGE_TARGETS RESTRICT_TARGETS
|
||||
|
||||
--- @type AI.Option.Ground.val.ROE
|
||||
-- @field OPEN_FIRE
|
||||
-- @field RETURN_FIRE
|
||||
-- @field WEAPON_HOLD
|
||||
|
||||
--- @type AI.Option.Ground.mval -- Moose added
|
||||
-- @field #AI.Option.Ground.mval.ENGAGE_TARGETS ENGAGE_TARGETS
|
||||
|
||||
--- @type AI.Option.Ground.mval.ENGAGE_TARGETS -- Moose added
|
||||
-- @field ANY_TARGET -- 0
|
||||
-- @field AIR_UNITS_ONLY -- 1
|
||||
-- @field GROUND_UNITS_ONLY -- 2
|
||||
|
||||
--- @type AI.Option.Ground.val.ALARM_STATE
|
||||
-- @field AUTO
|
||||
-- @field GREEN
|
||||
@@ -1388,4 +1521,4 @@ do -- AI
|
||||
|
||||
AI = {} --#AI
|
||||
|
||||
end -- AI
|
||||
end -- AI
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -73,7 +73,7 @@
|
||||
-- @field Core.Point#COORDINATE RearmingPlaceCoord Coordinates of the rearming place. If the place is more than 100 m away from the ARTY group, the group will go there.
|
||||
-- @field #boolean RearmingArtyOnRoad If true, ARTY group will move to rearming place using mainly roads. Default false.
|
||||
-- @field Core.Point#COORDINATE InitialCoord Initial coordinates of the ARTY group.
|
||||
-- @field #boolean report Arty group sends messages about their current state or target to its coaliton.
|
||||
-- @field #boolean report Arty group sends messages about their current state or target to its coalition.
|
||||
-- @field #table ammoshells Table holding names of the shell types which are included when counting the ammo. Default is {"weapons.shells"} which include most shells.
|
||||
-- @field #table ammorockets Table holding names of the rocket types which are included when counting the ammo. Default is {"weapons.nurs"} which includes most unguided rockets.
|
||||
-- @field #table ammomissiles Table holding names of the missile types which are included when counting the ammo. Default is {"weapons.missiles"} which includes some guided missiles.
|
||||
@@ -693,7 +693,7 @@ ARTY.db={
|
||||
|
||||
--- Arty script version.
|
||||
-- @field #string version
|
||||
ARTY.version="1.1.7"
|
||||
ARTY.version="1.2.0"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
@@ -1146,12 +1146,11 @@ end
|
||||
-- @param alias (Optional) Alias name the group will be calling itself when sending messages. Default is the group name.
|
||||
-- @return #ARTY ARTY object or nil if group does not exist or is not a ground or naval group.
|
||||
function ARTY:NewFromCargoGroup(cargogroup, alias)
|
||||
BASE:F2({cargogroup=cargogroup, alias=alias})
|
||||
|
||||
if cargogroup then
|
||||
BASE:T(self.lid..string.format("ARTY script version %s. Added CARGO group %s.", ARTY.version, cargogroup:GetName()))
|
||||
BASE:T(string.format("ARTY script version %s. Added CARGO group %s.", ARTY.version, cargogroup:GetName()))
|
||||
else
|
||||
BASE:E(self.lid.."ERROR: Requested ARTY CARGO GROUP does not exist! (Has to be a MOOSE CARGO(!) group.)")
|
||||
BASE:E("ERROR: Requested ARTY CARGO GROUP does not exist! (Has to be a MOOSE CARGO(!) group.)")
|
||||
return nil
|
||||
end
|
||||
|
||||
@@ -2766,38 +2765,32 @@ function ARTY:onafterStatus(Controllable, From, Event, To)
|
||||
self:_EventFromTo("onafterStatus", Event, From, To)
|
||||
|
||||
-- Get ammo.
|
||||
local ntot, nshells, nrockets, nmissiles=self:GetAmmo()
|
||||
local nammo, nshells, nrockets, nmissiles=self:GetAmmo()
|
||||
|
||||
-- We have a cargo group ==> check if group was loaded into a carrier.
|
||||
if self.iscargo and self.cargogroup then
|
||||
if self.cargogroup:IsLoaded() and not self:is("InTransit") then
|
||||
-- Group is now InTransit state. Current target is canceled.
|
||||
self:T(self.lid..string.format("Group %s has been loaded into a carrier and is now transported.", self.alias))
|
||||
self:Loaded()
|
||||
elseif self.cargogroup:IsUnLoaded() then
|
||||
-- Group has been unloaded and is combat ready again.
|
||||
self:T(self.lid..string.format("Group %s has been unloaded from the carrier.", self.alias))
|
||||
self:UnLoaded()
|
||||
end
|
||||
end
|
||||
|
||||
-- FSM state.
|
||||
local fsmstate=self:GetState()
|
||||
self:I(self.lid..string.format("Status %s, Ammo total=%d: shells=%d [smoke=%d, illu=%d, nukes=%d*%.3f kT], rockets=%d, missiles=%d", fsmstate, ntot, nshells, self.Nsmoke, self.Nillu, self.Nukes, self.nukewarhead/1000000, nrockets, nmissiles))
|
||||
self:T(self.lid..string.format("Status %s, Ammo total=%d: shells=%d [smoke=%d, illu=%d, nukes=%d*%.3f kT], rockets=%d, missiles=%d", fsmstate, nammo, nshells, self.Nsmoke, self.Nillu, self.Nukes, self.nukewarhead/1000000, nrockets, nmissiles))
|
||||
|
||||
if self.Controllable and self.Controllable:IsAlive() then
|
||||
|
||||
-- We have a cargo group ==> check if group was loaded into a carrier.
|
||||
if self.cargogroup then
|
||||
if self.cargogroup:IsLoaded() and not self:is("InTransit") then
|
||||
-- Group is now InTransit state. Current target is canceled.
|
||||
self:T(self.lid..string.format("Group %s has been loaded into a carrier and is now transported.", self.alias))
|
||||
self:Loaded()
|
||||
elseif self.cargogroup:IsUnLoaded() then
|
||||
-- Group has been unloaded and is combat ready again.
|
||||
self:T(self.lid..string.format("Group %s has been unloaded from the carrier.", self.alias))
|
||||
self:UnLoaded()
|
||||
end
|
||||
end
|
||||
|
||||
-- Debug current status info.
|
||||
if self.Debug then
|
||||
self:_StatusReport()
|
||||
end
|
||||
|
||||
-- Group is being transported as cargo ==> skip everything and check again in 5 seconds.
|
||||
if self:is("InTransit") then
|
||||
self:__Status(-5)
|
||||
return
|
||||
end
|
||||
|
||||
-- Group on the move.
|
||||
if self:is("Moving") then
|
||||
self:T2(self.lid..string.format("%s: Moving", Controllable:GetName()))
|
||||
@@ -2884,7 +2877,7 @@ function ARTY:onafterStatus(Controllable, From, Event, To)
|
||||
end
|
||||
|
||||
-- Get ammo.
|
||||
local nammo, nshells, nrockets, nmissiles=self:GetAmmo()
|
||||
--local nammo, nshells, nrockets, nmissiles=self:GetAmmo()
|
||||
|
||||
-- Check if we have a target in the queue for which weapons are still available.
|
||||
local gotsome=false
|
||||
@@ -2914,9 +2907,23 @@ function ARTY:onafterStatus(Controllable, From, Event, To)
|
||||
-- Call status again in ~10 sec.
|
||||
self:__Status(self.StatusInterval)
|
||||
|
||||
elseif self.iscargo then
|
||||
|
||||
-- We have a cargo group ==> check if group was loaded into a carrier.
|
||||
if self.cargogroup and self.cargogroup:IsAlive() then
|
||||
|
||||
-- Group is being transported as cargo ==> skip everything and check again in 5 seconds.
|
||||
if self:is("InTransit") then
|
||||
self:__Status(-5)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
else
|
||||
self:E(self.lid..string.format("Arty group %s is not alive!", self.groupname))
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
@@ -4814,7 +4821,8 @@ function ARTY:_CheckShootingStarted()
|
||||
end
|
||||
|
||||
-- Check if we waited long enough and no shot was fired.
|
||||
if dt > self.WaitForShotTime and self.Nshots==0 then
|
||||
--if dt > self.WaitForShotTime and self.Nshots==0 then
|
||||
if dt > self.WaitForShotTime and (self.Nshots==0 or self.currentTarget.nshells >= self.Nshots) then --https://github.com/FlightControl-Master/MOOSE/issues/1356
|
||||
|
||||
-- Debug info.
|
||||
self:T(self.lid..string.format("%s, no shot event after %d seconds. Removing current target %s from list.", self.groupname, self.WaitForShotTime, name))
|
||||
|
||||
@@ -213,7 +213,7 @@ do -- DESIGNATE
|
||||
-- In order to prevent an overflow of designations due to many Detected Targets, there is a
|
||||
-- Maximum Designations scope that is set in the DesignationObject.
|
||||
--
|
||||
-- The method @{#DESIGNATE.SetMaximumDesignations}() will put a limit on the amount of designations put in scope of the DesignationObject.
|
||||
-- The method @{#DESIGNATE.SetMaximumDesignations}() will put a limit on the amount of designations (target groups) put in scope of the DesignationObject.
|
||||
-- Using the menu system, the player can "forget" a designation, so that gradually a new designation can be put in scope when detected.
|
||||
--
|
||||
-- # 4. Laser codes
|
||||
@@ -562,7 +562,8 @@ do -- DESIGNATE
|
||||
end
|
||||
|
||||
|
||||
--- Set the maximum amount of designations.
|
||||
--- Set the maximum amount of designations (target groups). This will put a limit on the amount of designations in scope.
|
||||
-- Using the menu system, the player can "forget" a designation, so that gradually a new designation can be put in scope when detected.
|
||||
-- @param #DESIGNATE self
|
||||
-- @param #number MaximumDesignations
|
||||
-- @return #DESIGNATE
|
||||
@@ -602,7 +603,7 @@ do -- DESIGNATE
|
||||
end
|
||||
|
||||
|
||||
--- Set the maximum amount of markings FACs will do, per designated target group.
|
||||
--- Set the maximum amount of markings FACs will do, per designated target group. This will limit the number of parallelly marked units of a target group.
|
||||
-- @param #DESIGNATE self
|
||||
-- @param #number MaximumMarkings Maximum markings FACs will do, per designated target group.
|
||||
-- @return #DESIGNATE
|
||||
@@ -911,8 +912,8 @@ do -- DESIGNATE
|
||||
for DesignateIndex, Designating in pairs( self.Designating ) do
|
||||
local DetectedItem = DetectedItems[DesignateIndex]
|
||||
if DetectedItem then
|
||||
local Report = self.Detection:DetectedItemReportSummary( DetectedItem, AttackGroup ):Text( ", " )
|
||||
DetectedReport:Add( string.rep( "-", 140 ) )
|
||||
local Report = self.Detection:DetectedItemReportSummary( DetectedItem, AttackGroup, nil, true ):Text( ", " )
|
||||
DetectedReport:Add( string.rep( "-", 40 ) )
|
||||
DetectedReport:Add( " - " .. Report )
|
||||
if string.find( Designating, "L" ) then
|
||||
DetectedReport:Add( " - " .. "Lasing Targets" )
|
||||
@@ -1001,7 +1002,13 @@ do -- DESIGNATE
|
||||
local ID = self.Detection:GetDetectedItemID( DetectedItem )
|
||||
local MenuText = ID --.. ", " .. Coord:ToStringA2G( AttackGroup )
|
||||
|
||||
MenuText = string.format( "(%3s) %s", Designating, MenuText )
|
||||
-- Use injected MenuName from TaskA2GDispatcher if using same Detection Object
|
||||
if DetectedItem.DesignateMenuName then
|
||||
MenuText = string.format( "(%3s) %s", Designating, DetectedItem.DesignateMenuName )
|
||||
else
|
||||
MenuText = string.format( "(%3s) %s", Designating, MenuText )
|
||||
end
|
||||
|
||||
local DetectedMenu = MENU_GROUP_DELAYED:New( AttackGroup, MenuText, MenuDesignate ):SetTime( MenuTime ):SetTag( self.DesignateName )
|
||||
|
||||
-- Build the Lasing menu.
|
||||
@@ -1186,8 +1193,8 @@ do -- DESIGNATE
|
||||
|
||||
local MarkingCount = 0
|
||||
local MarkedTypes = {}
|
||||
local ReportTypes = REPORT:New()
|
||||
local ReportLaserCodes = REPORT:New()
|
||||
--local ReportTypes = REPORT:New()
|
||||
--local ReportLaserCodes = REPORT:New()
|
||||
|
||||
TargetSetUnit:Flush( self )
|
||||
|
||||
@@ -1237,8 +1244,8 @@ do -- DESIGNATE
|
||||
if not Recce then
|
||||
|
||||
self:F( "Lasing..." )
|
||||
self.RecceSet:Flush( self)
|
||||
|
||||
--self.RecceSet:Flush( self)
|
||||
|
||||
for RecceGroupID, RecceGroup in pairs( self.RecceSet:GetSet() ) do
|
||||
for UnitID, UnitData in pairs( RecceGroup:GetUnits() or {} ) do
|
||||
|
||||
@@ -1276,13 +1283,13 @@ do -- DESIGNATE
|
||||
-- OK. We have assigned for the Recce a TargetUnit. We can exit the function.
|
||||
MarkingCount = MarkingCount + 1
|
||||
local TargetUnitType = TargetUnit:GetTypeName()
|
||||
--RecceUnit:MessageToSetGroup( "Marking " .. TargetUnit:GetTypeName() .. " with laser " .. RecceUnit:GetSpot().LaserCode .. " for " .. Duration .. "s.",
|
||||
-- 5, self.AttackSet, DesignateName )
|
||||
RecceUnit:MessageToSetGroup( "Marking " .. TargetUnit:GetTypeName() .. " with laser " .. RecceUnit:GetSpot().LaserCode .. " for " .. Duration .. "s.",
|
||||
10, self.AttackSet, DesignateName )
|
||||
if not MarkedTypes[TargetUnitType] then
|
||||
MarkedTypes[TargetUnitType] = true
|
||||
ReportTypes:Add(TargetUnitType)
|
||||
--ReportTypes:Add(TargetUnitType)
|
||||
end
|
||||
ReportLaserCodes:Add(RecceUnit.LaserCode)
|
||||
--ReportLaserCodes:Add(RecceUnit.LaserCode)
|
||||
return
|
||||
end
|
||||
else
|
||||
@@ -1297,16 +1304,16 @@ do -- DESIGNATE
|
||||
|
||||
if Recce then
|
||||
Recce:LaseOff()
|
||||
Recce:MessageToSetGroup( "Target " .. TargetUnit:GetTypeName() "out of LOS. Cancelling lase!", 5, self.AttackSet, self.DesignateName )
|
||||
Recce:MessageToSetGroup( "Target " .. TargetUnit:GetTypeName() "out of LOS. Cancelling lase!", 10, self.AttackSet, self.DesignateName )
|
||||
end
|
||||
else
|
||||
--MarkingCount = MarkingCount + 1
|
||||
local TargetUnitType = TargetUnit:GetTypeName()
|
||||
if not MarkedTypes[TargetUnitType] then
|
||||
MarkedTypes[TargetUnitType] = true
|
||||
ReportTypes:Add(TargetUnitType)
|
||||
--ReportTypes:Add(TargetUnitType)
|
||||
end
|
||||
ReportLaserCodes:Add(RecceUnit.LaserCode)
|
||||
--ReportLaserCodes:Add(RecceUnit.LaserCode)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1316,19 +1323,19 @@ do -- DESIGNATE
|
||||
local TargetUnitType = TargetUnit:GetTypeName()
|
||||
if not MarkedTypes[TargetUnitType] then
|
||||
MarkedTypes[TargetUnitType] = true
|
||||
ReportTypes:Add(TargetUnitType)
|
||||
--ReportTypes:Add(TargetUnitType)
|
||||
end
|
||||
ReportLaserCodes:Add(Recce.LaserCode)
|
||||
--Recce:MessageToSetGroup( self.DesignateName .. ": Marking " .. TargetUnit:GetTypeName() .. " with laser " .. Recce.LaserCode .. ".", 5, self.AttackSet )
|
||||
--ReportLaserCodes:Add(Recce.LaserCode)
|
||||
Recce:MessageToSetGroup( self.DesignateName .. ": Marking " .. TargetUnit:GetTypeName() .. " with laser " .. Recce.LaserCode .. ".", 10, self.AttackSet )
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
)
|
||||
|
||||
local MarkedTypesText = ReportTypes:Text(', ')
|
||||
local MarkedLaserCodesText = ReportLaserCodes:Text(', ')
|
||||
self.CC:GetPositionable():MessageToSetGroup( "Marking " .. MarkingCount .. " x " .. MarkedTypesText .. ", code " .. MarkedLaserCodesText .. ".", 5, self.AttackSet, self.DesignateName )
|
||||
--local MarkedTypesText = ReportTypes:Text(', ')
|
||||
--local MarkedLaserCodesText = ReportLaserCodes:Text(', ')
|
||||
--self.CC:GetPositionable():MessageToSetGroup( "Marking " .. MarkingCount .. " x " .. MarkedTypesText .. ", code " .. MarkedLaserCodesText .. ".", 5, self.AttackSet, self.DesignateName )
|
||||
|
||||
self:__Lasing( -self.LaseDuration, Index, Duration, LaserCodeRequested )
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -23,7 +23,6 @@
|
||||
-- @module Functional.FOX
|
||||
-- @image Functional_FOX.png
|
||||
|
||||
|
||||
--- FOX class.
|
||||
-- @type FOX
|
||||
-- @field #string ClassName Name of the class.
|
||||
@@ -47,8 +46,7 @@
|
||||
-- @field #number dt10 Time step [sec] for missile position updates if distance to target > 10 km and < 50 km. Default 1 sec.
|
||||
-- @field #number dt05 Time step [sec] for missile position updates if distance to target > 5 km and < 10 km. Default 0.5 sec.
|
||||
-- @field #number dt01 Time step [sec] for missile position updates if distance to target > 1 km and < 5 km. Default 0.1 sec.
|
||||
-- @field #number dt00 Time step [sec] for missile position updates if distance to target < 1 km. Default 0.01 sec.
|
||||
-- @field #boolean
|
||||
-- @field #number dt00 Time step [sec] for missile position updates if distance to target < 1 km. Default 0.01 sec.
|
||||
-- @extends Core.Fsm#FSM
|
||||
|
||||
--- Fox 3!
|
||||
@@ -794,7 +792,7 @@ function FOX:onafterMissileLaunch(From, Event, To, missile)
|
||||
local text=string.format("Missile launch detected! Distance %.1f NM, bearing %03d°.", UTILS.MetersToNM(distance), bearing)
|
||||
|
||||
-- Say notching headings.
|
||||
BASE:ScheduleOnce(5, FOX._SayNotchingHeadings, self, player, missile.weapon)
|
||||
self:ScheduleOnce(5, FOX._SayNotchingHeadings, self, player, missile.weapon)
|
||||
|
||||
--TODO: ALERT or INFO depending on whether this is a direct target.
|
||||
--TODO: lauchalertall option.
|
||||
@@ -1116,6 +1114,13 @@ end
|
||||
-- Event Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- FOX event handler for event birth.
|
||||
-- @param #FOX self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function FOX:OnEventPlayerEnterAircraft(EventData)
|
||||
|
||||
end
|
||||
|
||||
--- FOX event handler for event birth.
|
||||
-- @param #FOX self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
@@ -1157,7 +1162,7 @@ function FOX:OnEventBirth(EventData)
|
||||
|
||||
-- Add F10 radio menu for player.
|
||||
if not self.menudisabled then
|
||||
SCHEDULER:New(nil, self._AddF10Commands, {self,_unitName}, 0.1)
|
||||
self:ScheduleOnce(0.1, FOX._AddF10Commands, self, _unitname)
|
||||
end
|
||||
|
||||
-- Player data.
|
||||
@@ -1707,8 +1712,8 @@ end
|
||||
--- Returns the unit of a player and the player name. If the unit does not belong to a player, nil is returned.
|
||||
-- @param #FOX self
|
||||
-- @param DCS#Weapon weapon The weapon.
|
||||
-- @return #number Notching heading right, i.e. missile heading +90<EFBFBD>
|
||||
-- @return #number Notching heading left, i.e. missile heading -90<EFBFBD>.
|
||||
-- @return #number Notching heading right, i.e. missile heading +90°.
|
||||
-- @return #number Notching heading left, i.e. missile heading -90°.
|
||||
function FOX:_GetNotchingHeadings(weapon)
|
||||
|
||||
if weapon then
|
||||
@@ -1813,4 +1818,4 @@ end
|
||||
|
||||
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
1903
Moose Development/Moose/Functional/Mantis.lua
Normal file
1903
Moose Development/Moose/Functional/Mantis.lua
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,28 +1,28 @@
|
||||
--- **Functional** -- Train missile defence and deflection.
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ## Features:
|
||||
--
|
||||
--
|
||||
-- * Track the missiles fired at you and other players, providing bearing and range information of the missiles towards the airplanes.
|
||||
-- * Provide alerts of missile launches, including detailed information of the units launching, including bearing, range <20>
|
||||
-- * Provide alerts of missile launches, including detailed information of the units launching, including bearing, range
|
||||
-- * Provide alerts when a missile would have killed your aircraft.
|
||||
-- * Provide alerts when the missile self destructs.
|
||||
-- * Enable / Disable and Configure the Missile Trainer using the various menu options.
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- ## Missions:
|
||||
--
|
||||
--
|
||||
-- [MIT - Missile Trainer](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/MIT%20-%20Missile%20Trainer)
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- Uses the MOOSE messaging system to be alerted of any missiles fired, and when a missile would hit your aircraft,
|
||||
-- the class will destroy the missile within a certain range, to avoid damage to your aircraft.
|
||||
--
|
||||
--
|
||||
-- When running a mission where the missile trainer is used, the following radio menu structure ( 'Radio Menu' -> 'Other (F10)' -> 'MissileTrainer' ) options are available for the players:
|
||||
--
|
||||
--
|
||||
-- * **Messages**: Menu to configure all messages.
|
||||
-- * **Messages On**: Show all messages.
|
||||
-- * **Messages Off**: Disable all messages.
|
||||
@@ -45,23 +45,23 @@
|
||||
-- * **Range Off**: Disable range information when a missile is fired to a target.
|
||||
-- * **Bearing On**: Shows bearing information when a missile is fired to a target.
|
||||
-- * **Bearing Off**: Disable bearing information when a missile is fired to a target.
|
||||
-- * **Distance**: Menu to configure the distance when a missile needs to be destroyed when near to a player, during tracking. This will improve/influence hit calculation accuracy, but has the risk of damaging the aircraft when the missile reaches the aircraft before the distance is measured.
|
||||
-- * **Distance**: Menu to configure the distance when a missile needs to be destroyed when near to a player, during tracking. This will improve/influence hit calculation accuracy, but has the risk of damaging the aircraft when the missile reaches the aircraft before the distance is measured.
|
||||
-- * **50 meter**: Destroys the missile when the distance to the aircraft is below or equal to 50 meter.
|
||||
-- * **100 meter**: Destroys the missile when the distance to the aircraft is below or equal to 100 meter.
|
||||
-- * **150 meter**: Destroys the missile when the distance to the aircraft is below or equal to 150 meter.
|
||||
-- * **200 meter**: Destroys the missile when the distance to the aircraft is below or equal to 200 meter.
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- ### Authors: **FlightControl**
|
||||
--
|
||||
--
|
||||
-- ### Contributions:
|
||||
--
|
||||
-- * **Stuka (Danny)**: Who you can search on the Eagle Dynamics Forums. Working together with Danny has resulted in the MISSILETRAINER class.
|
||||
-- Danny has shared his ideas and together we made a design.
|
||||
--
|
||||
-- * **Stuka (Danny)**: Who you can search on the Eagle Dynamics Forums. Working together with Danny has resulted in the MISSILETRAINER class.
|
||||
-- Danny has shared his ideas and together we made a design.
|
||||
-- Together with the **476 virtual team**, we tested the MISSILETRAINER class, and got much positive feedback!
|
||||
-- * **132nd Squadron**: Testing and optimizing the logic.
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module Functional.MissileTrainer
|
||||
@@ -76,7 +76,7 @@
|
||||
---
|
||||
--
|
||||
-- # Constructor:
|
||||
--
|
||||
--
|
||||
-- Create a new MISSILETRAINER object with the @{#MISSILETRAINER.New} method:
|
||||
--
|
||||
-- * @{#MISSILETRAINER.New}: Creates a new MISSILETRAINER object taking the maximum distance to your aircraft to evaluate when a missile needs to be destroyed.
|
||||
@@ -84,7 +84,7 @@
|
||||
-- MISSILETRAINER will collect each unit declared in the mission with a skill level "Client" and "Player", and will monitor the missiles shot at those.
|
||||
--
|
||||
-- # Initialization:
|
||||
--
|
||||
--
|
||||
-- A MISSILETRAINER object will behave differently based on the usage of initialization methods:
|
||||
--
|
||||
-- * @{#MISSILETRAINER.InitMessagesOnOff}: Sets by default the display of any message to be ON or OFF.
|
||||
@@ -97,8 +97,8 @@
|
||||
-- * @{#MISSILETRAINER.InitRangeOnOff}: Sets by default the display of range information of missiles ON of OFF.
|
||||
-- * @{#MISSILETRAINER.InitBearingOnOff}: Sets by default the display of bearing information of missiles ON of OFF.
|
||||
-- * @{#MISSILETRAINER.InitMenusOnOff}: Allows to configure the options through the radio menu.
|
||||
--
|
||||
-- @field #MISSILETRAINER
|
||||
--
|
||||
-- @field #MISSILETRAINER
|
||||
MISSILETRAINER = {
|
||||
ClassName = "MISSILETRAINER",
|
||||
TrackingMissiles = {},
|
||||
@@ -167,7 +167,7 @@ end
|
||||
-- When a missile is fired a SCHEDULER is set off that follows the missile. When near a certain a client player, the missile will be destroyed.
|
||||
-- @param #MISSILETRAINER self
|
||||
-- @param #number Distance The distance in meters when a tracked missile needs to be destroyed when close to a player.
|
||||
-- @param #string Briefing (Optional) Will show a text to the players when starting their mission. Can be used for briefing purposes.
|
||||
-- @param #string Briefing (Optional) Will show a text to the players when starting their mission. Can be used for briefing purposes.
|
||||
-- @return #MISSILETRAINER
|
||||
function MISSILETRAINER:New( Distance, Briefing )
|
||||
local self = BASE:Inherit( self, BASE:New() )
|
||||
@@ -194,8 +194,8 @@ function MISSILETRAINER:New( Distance, Briefing )
|
||||
-- self:F( "ForEach:" .. Client.UnitName )
|
||||
-- Client:Alive( self._Alive, self )
|
||||
-- end
|
||||
--
|
||||
self.DBClients:ForEachClient(
|
||||
--
|
||||
self.DBClients:ForEachClient(
|
||||
function( Client )
|
||||
self:F( "ForEach:" .. Client.UnitName )
|
||||
Client:Alive( self._Alive, self )
|
||||
@@ -207,9 +207,9 @@ function MISSILETRAINER:New( Distance, Briefing )
|
||||
-- self.DB:ForEachClient(
|
||||
-- --- @param Wrapper.Client#CLIENT Client
|
||||
-- function( Client )
|
||||
--
|
||||
--
|
||||
-- ... actions ...
|
||||
--
|
||||
--
|
||||
-- end
|
||||
-- )
|
||||
|
||||
@@ -225,7 +225,7 @@ function MISSILETRAINER:New( Distance, Briefing )
|
||||
|
||||
self.DetailsRangeOnOff = true
|
||||
self.DetailsBearingOnOff = true
|
||||
|
||||
|
||||
self.MenusOnOff = true
|
||||
|
||||
self.TrackingMissiles = {}
|
||||
@@ -293,7 +293,7 @@ end
|
||||
--- Increases, decreases the missile tracking message display frequency with the provided time interval in seconds.
|
||||
-- The default frequency is a 3 second interval, so the Tracking Frequency parameter specifies the increase or decrease from the default 3 seconds or the last frequency update.
|
||||
-- @param #MISSILETRAINER self
|
||||
-- @param #number TrackingFrequency Provide a negative or positive value in seconds to incraese or decrease the display frequency.
|
||||
-- @param #number TrackingFrequency Provide a negative or positive value in seconds to incraese or decrease the display frequency.
|
||||
-- @return #MISSILETRAINER self
|
||||
function MISSILETRAINER:InitTrackingFrequency( TrackingFrequency )
|
||||
self:F( TrackingFrequency )
|
||||
@@ -478,30 +478,30 @@ function MISSILETRAINER:OnEventShot( EVentData )
|
||||
if TrainerTargetDCSUnit then
|
||||
local TrainerTargetDCSUnitName = Unit.getName( TrainerTargetDCSUnit )
|
||||
local TrainerTargetSkill = _DATABASE.Templates.Units[TrainerTargetDCSUnitName].Template.skill
|
||||
|
||||
|
||||
self:T(TrainerTargetDCSUnitName )
|
||||
|
||||
|
||||
local Client = self.DBClients:FindClient( TrainerTargetDCSUnitName )
|
||||
if Client then
|
||||
|
||||
|
||||
local TrainerSourceUnit = UNIT:Find( TrainerSourceDCSUnit )
|
||||
local TrainerTargetUnit = UNIT:Find( TrainerTargetDCSUnit )
|
||||
|
||||
|
||||
if self.MessagesOnOff == true and self.AlertsLaunchesOnOff == true then
|
||||
|
||||
|
||||
local Message = MESSAGE:New(
|
||||
string.format( "%s launched a %s",
|
||||
TrainerSourceUnit:GetTypeName(),
|
||||
TrainerWeaponName
|
||||
) .. self:_AddRange( Client, TrainerWeapon ) .. self:_AddBearing( Client, TrainerWeapon ), 5, "Launch Alert" )
|
||||
|
||||
|
||||
if self.AlertsToAll then
|
||||
Message:ToAll()
|
||||
else
|
||||
Message:ToClient( Client )
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local ClientID = Client:GetID()
|
||||
self:T( ClientID )
|
||||
local MissileData = {}
|
||||
@@ -579,52 +579,52 @@ function MISSILETRAINER:_TrackMissiles()
|
||||
end
|
||||
|
||||
-- ALERTS PART
|
||||
|
||||
|
||||
-- Loop for all Player Clients to check the alerts and deletion of missiles.
|
||||
for ClientDataID, ClientData in pairs( self.TrackingMissiles ) do
|
||||
|
||||
local Client = ClientData.Client
|
||||
|
||||
|
||||
if Client and Client:IsAlive() then
|
||||
|
||||
for MissileDataID, MissileData in pairs( ClientData.MissileData ) do
|
||||
self:T3( MissileDataID )
|
||||
|
||||
|
||||
local TrainerSourceUnit = MissileData.TrainerSourceUnit
|
||||
local TrainerWeapon = MissileData.TrainerWeapon
|
||||
local TrainerTargetUnit = MissileData.TrainerTargetUnit
|
||||
local TrainerWeaponTypeName = MissileData.TrainerWeaponTypeName
|
||||
local TrainerWeaponLaunched = MissileData.TrainerWeaponLaunched
|
||||
|
||||
|
||||
if Client and Client:IsAlive() and TrainerSourceUnit and TrainerSourceUnit:IsAlive() and TrainerWeapon and TrainerWeapon:isExist() and TrainerTargetUnit and TrainerTargetUnit:IsAlive() then
|
||||
local PositionMissile = TrainerWeapon:getPosition().p
|
||||
local TargetVec3 = Client:GetVec3()
|
||||
|
||||
|
||||
local Distance = ( ( PositionMissile.x - TargetVec3.x )^2 +
|
||||
( PositionMissile.y - TargetVec3.y )^2 +
|
||||
( PositionMissile.z - TargetVec3.z )^2
|
||||
) ^ 0.5 / 1000
|
||||
|
||||
|
||||
if Distance <= self.Distance then
|
||||
-- Hit alert
|
||||
TrainerWeapon:destroy()
|
||||
if self.MessagesOnOff == true and self.AlertsHitsOnOff == true then
|
||||
|
||||
|
||||
self:T( "killed" )
|
||||
|
||||
|
||||
local Message = MESSAGE:New(
|
||||
string.format( "%s launched by %s killed %s",
|
||||
TrainerWeapon:getTypeName(),
|
||||
TrainerSourceUnit:GetTypeName(),
|
||||
TrainerTargetUnit:GetPlayerName()
|
||||
), 15, "Hit Alert" )
|
||||
|
||||
|
||||
if self.AlertsToAll == true then
|
||||
Message:ToAll()
|
||||
else
|
||||
Message:ToClient( Client )
|
||||
end
|
||||
|
||||
|
||||
MissileData = nil
|
||||
table.remove( ClientData.MissileData, MissileDataID )
|
||||
self:T(ClientData.MissileData)
|
||||
@@ -639,7 +639,7 @@ function MISSILETRAINER:_TrackMissiles()
|
||||
TrainerWeaponTypeName,
|
||||
TrainerSourceUnit:GetTypeName()
|
||||
), 5, "Tracking" )
|
||||
|
||||
|
||||
if self.AlertsToAll == true then
|
||||
Message:ToAll()
|
||||
else
|
||||
@@ -660,41 +660,41 @@ function MISSILETRAINER:_TrackMissiles()
|
||||
if ShowMessages == true and self.MessagesOnOff == true and self.TrackingOnOff == true then -- Only do this when tracking information needs to be displayed.
|
||||
|
||||
-- TRACKING PART
|
||||
|
||||
|
||||
-- For the current client, the missile range and bearing details are displayed To the Player Client.
|
||||
-- For the other clients, the missile range and bearing details are displayed To the other Player Clients.
|
||||
-- To achieve this, a cross loop is done for each Player Client <-> Other Player Client missile information.
|
||||
|
||||
-- To achieve this, a cross loop is done for each Player Client <-> Other Player Client missile information.
|
||||
|
||||
-- Main Player Client loop
|
||||
for ClientDataID, ClientData in pairs( self.TrackingMissiles ) do
|
||||
|
||||
|
||||
local Client = ClientData.Client
|
||||
--self:T2( { Client:GetName() } )
|
||||
|
||||
|
||||
|
||||
|
||||
ClientData.MessageToClient = ""
|
||||
ClientData.MessageToAll = ""
|
||||
|
||||
|
||||
-- Other Players Client loop
|
||||
for TrackingDataID, TrackingData in pairs( self.TrackingMissiles ) do
|
||||
|
||||
|
||||
for MissileDataID, MissileData in pairs( TrackingData.MissileData ) do
|
||||
--self:T3( MissileDataID )
|
||||
|
||||
|
||||
local TrainerSourceUnit = MissileData.TrainerSourceUnit
|
||||
local TrainerWeapon = MissileData.TrainerWeapon
|
||||
local TrainerTargetUnit = MissileData.TrainerTargetUnit
|
||||
local TrainerWeaponTypeName = MissileData.TrainerWeaponTypeName
|
||||
local TrainerWeaponLaunched = MissileData.TrainerWeaponLaunched
|
||||
|
||||
|
||||
if Client and Client:IsAlive() and TrainerSourceUnit and TrainerSourceUnit:IsAlive() and TrainerWeapon and TrainerWeapon:isExist() and TrainerTargetUnit and TrainerTargetUnit:IsAlive() then
|
||||
|
||||
|
||||
if ShowMessages == true then
|
||||
local TrackingTo
|
||||
TrackingTo = string.format( " -> %s",
|
||||
TrainerWeaponTypeName
|
||||
)
|
||||
|
||||
|
||||
if ClientDataID == TrackingDataID then
|
||||
if ClientData.MessageToClient == "" then
|
||||
ClientData.MessageToClient = "Missiles to You:\n"
|
||||
@@ -712,7 +712,7 @@ function MISSILETRAINER:_TrackMissiles()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- Once the Player Client and the Other Player Client tracking messages are prepared, show them.
|
||||
if ClientData.MessageToClient ~= "" or ClientData.MessageToAll ~= "" then
|
||||
local Message = MESSAGE:New( ClientData.MessageToClient .. ClientData.MessageToAll, 1, "Tracking" ):ToClient( Client )
|
||||
|
||||
@@ -742,7 +742,7 @@ function PSEUDOATC:ReportWeather(GID, UID, position, location)
|
||||
local T=position:GetTemperature()
|
||||
|
||||
-- Correct unit system.
|
||||
local _T=string.format('%d°F', UTILS.CelciusToFarenheit(T))
|
||||
local _T=string.format('%d°F', UTILS.CelsiusToFahrenheit(T))
|
||||
if settings:IsMetric() then
|
||||
_T=string.format('%d°C', T)
|
||||
end
|
||||
@@ -949,11 +949,14 @@ function PSEUDOATC:LocalAirports(GID, UID)
|
||||
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.group[GID].player[UID].airports, {distance=d, name=name})
|
||||
-- Add to table.
|
||||
table.insert(self.group[GID].player[UID].airports, {distance=d, name=name})
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@@ -1041,6 +1044,3 @@ function PSEUDOATC:_myname(unitname)
|
||||
|
||||
return string.format("%s (%s)", csign, pname)
|
||||
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,201 +1,520 @@
|
||||
--- **Functional** -- Make SAM sites execute evasive and defensive behaviour when being fired upon.
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- ## Features:
|
||||
--
|
||||
--
|
||||
-- * When SAM sites are being fired upon, the SAMs will take evasive action will reposition themselves when possible.
|
||||
-- * When SAM sites are being fired upon, the SAMs will take defensive action by shutting down their radars.
|
||||
--
|
||||
-- * SEAD calculates the time it takes for a HARM to reach the target - and will attempt to minimize the shut-down time.
|
||||
-- * Detection and evasion of shots has a random component based on the skill level of the SAM groups.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- ## Missions:
|
||||
--
|
||||
--
|
||||
-- [SEV - SEAD Evasion](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/SEV%20-%20SEAD%20Evasion)
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Authors: **FlightControl**
|
||||
--
|
||||
--
|
||||
-- ### Authors: **FlightControl**, **applevangelist**
|
||||
--
|
||||
-- Last Update: Feb 2022
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- @module Functional.Sead
|
||||
-- @image SEAD.JPG
|
||||
|
||||
--- @type SEAD
|
||||
---
|
||||
-- @type SEAD
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
--- Make SAM sites execute evasive and defensive behaviour when being fired upon.
|
||||
--
|
||||
--
|
||||
-- This class is very easy to use. Just setup a SEAD object by using @{#SEAD.New}() and SAMs will evade and take defensive action when being fired upon.
|
||||
-- Once a HARM attack is detected, SEAD will shut down the radars of the attacked SAM site and take evasive action by moving the SAM
|
||||
-- vehicles around (*if* they are drivable, that is). There's a component of randomness in detection and evasion, which is based on the
|
||||
-- skill set of the SAM set (the higher the skill, the more likely). When a missile is fired from far away, the SAM will stay active for a
|
||||
-- period of time to stay defensive, before it takes evasive actions.
|
||||
--
|
||||
-- # Constructor:
|
||||
--
|
||||
--
|
||||
-- Use the @{#SEAD.New}() constructor to create a new SEAD object.
|
||||
--
|
||||
--
|
||||
-- SEAD_RU_SAM_Defenses = SEAD:New( { 'RU SA-6 Kub', 'RU SA-6 Defenses', 'RU MI-26 Troops', 'RU Attack Gori' } )
|
||||
--
|
||||
--
|
||||
-- @field #SEAD
|
||||
SEAD = {
|
||||
ClassName = "SEAD",
|
||||
TargetSkill = {
|
||||
Average = { Evade = 50, DelayOff = { 10, 25 }, DelayOn = { 10, 30 } } ,
|
||||
Good = { Evade = 30, DelayOff = { 8, 20 }, DelayOn = { 20, 40 } } ,
|
||||
High = { Evade = 15, DelayOff = { 5, 17 }, DelayOn = { 30, 50 } } ,
|
||||
Excellent = { Evade = 10, DelayOff = { 3, 10 }, DelayOn = { 30, 60 } }
|
||||
},
|
||||
SEADGroupPrefixes = {}
|
||||
ClassName = "SEAD",
|
||||
TargetSkill = {
|
||||
Average = { Evade = 30, DelayOn = { 40, 60 } } ,
|
||||
Good = { Evade = 20, DelayOn = { 30, 50 } } ,
|
||||
High = { Evade = 15, DelayOn = { 20, 40 } } ,
|
||||
Excellent = { Evade = 10, DelayOn = { 10, 30 } }
|
||||
},
|
||||
SEADGroupPrefixes = {},
|
||||
SuppressedGroups = {},
|
||||
EngagementRange = 75, -- default 75% engagement range Feature Request #1355
|
||||
Padding = 10,
|
||||
CallBack = nil,
|
||||
UseCallBack = false,
|
||||
debug = false,
|
||||
}
|
||||
|
||||
--- Missile enumerators
|
||||
-- @field Harms
|
||||
SEAD.Harms = {
|
||||
["AGM_88"] = "AGM_88",
|
||||
["AGM_45"] = "AGM_45",
|
||||
["AGM_122"] = "AGM_122",
|
||||
["AGM_84"] = "AGM_84",
|
||||
["AGM_45"] = "AGM_45",
|
||||
["ALARM"] = "ALARM",
|
||||
["LD-10"] = "LD-10",
|
||||
["X_58"] = "X_58",
|
||||
["X_28"] = "X_28",
|
||||
["X_25"] = "X_25",
|
||||
["X_31"] = "X_31",
|
||||
["Kh25"] = "Kh25",
|
||||
["BGM_109"] = "BGM_109",
|
||||
["AGM_154"] = "AGM_154",
|
||||
["HY-2"] = "HY-2",
|
||||
}
|
||||
|
||||
--- Missile enumerators - from DCS ME and Wikipedia
|
||||
-- @field HarmData
|
||||
SEAD.HarmData = {
|
||||
-- km and mach
|
||||
["AGM_88"] = { 150, 3},
|
||||
["AGM_45"] = { 12, 2},
|
||||
["AGM_122"] = { 16.5, 2.3},
|
||||
["AGM_84"] = { 280, 0.8},
|
||||
["ALARM"] = { 45, 2},
|
||||
["LD-10"] = { 60, 4},
|
||||
["X_58"] = { 70, 4},
|
||||
["X_28"] = { 80, 2.5},
|
||||
["X_25"] = { 25, 0.76},
|
||||
["X_31"] = {150, 3},
|
||||
["Kh25"] = {25, 0.8},
|
||||
["BGM_109"] = {460, 0.705}, --in-game ~465kn
|
||||
["AGM_154"] = {130, 0.61},
|
||||
["HY-2"] = {90,1},
|
||||
}
|
||||
|
||||
--- Creates the main object which is handling defensive actions for SA sites or moving SA vehicles.
|
||||
-- When an anti radiation missile is fired (KH-58, KH-31P, KH-31A, KH-25MPU, HARM missiles), the SA will shut down their radars and will take evasive actions...
|
||||
-- Chances are big that the missile will miss.
|
||||
-- @param table{string,...}|string SEADGroupPrefixes which is a table of Prefixes of the SA Groups in the DCSRTE on which evasive actions need to be taken.
|
||||
-- @return SEAD
|
||||
-- @param #SEAD self
|
||||
-- @param #table SEADGroupPrefixes Table of #string entries or single #string, which is a table of Prefixes of the SA Groups in the DCS mission editor on which evasive actions need to be taken.
|
||||
-- @param #number Padding (Optional) Extra number of seconds to add to radar switch-back-on time
|
||||
-- @return #SEAD self
|
||||
-- @usage
|
||||
-- -- CCCP SEAD Defenses
|
||||
-- -- Defends the Russian SA installations from SEAD attacks.
|
||||
-- SEAD_RU_SAM_Defenses = SEAD:New( { 'RU SA-6 Kub', 'RU SA-6 Defenses', 'RU MI-26 Troops', 'RU Attack Gori' } )
|
||||
function SEAD:New( SEADGroupPrefixes )
|
||||
function SEAD:New( SEADGroupPrefixes, Padding )
|
||||
|
||||
local self = BASE:Inherit( self, BASE:New() )
|
||||
self:F( SEADGroupPrefixes )
|
||||
|
||||
if type( SEADGroupPrefixes ) == 'table' then
|
||||
for SEADGroupPrefixID, SEADGroupPrefix in pairs( SEADGroupPrefixes ) do
|
||||
self.SEADGroupPrefixes[SEADGroupPrefix] = SEADGroupPrefix
|
||||
end
|
||||
else
|
||||
self.SEADGroupNames[SEADGroupPrefixes] = SEADGroupPrefixes
|
||||
end
|
||||
|
||||
self:HandleEvent( EVENTS.Shot )
|
||||
|
||||
return self
|
||||
local self = BASE:Inherit( self, FSM:New() )
|
||||
self:T( SEADGroupPrefixes )
|
||||
|
||||
if type( SEADGroupPrefixes ) == 'table' then
|
||||
for SEADGroupPrefixID, SEADGroupPrefix in pairs( SEADGroupPrefixes ) do
|
||||
self.SEADGroupPrefixes[SEADGroupPrefix] = SEADGroupPrefix
|
||||
end
|
||||
else
|
||||
self.SEADGroupPrefixes[SEADGroupPrefixes] = SEADGroupPrefixes
|
||||
end
|
||||
|
||||
local padding = Padding or 10
|
||||
if padding < 10 then padding = 10 end
|
||||
self.Padding = padding
|
||||
self.UseEmissionsOnOff = true
|
||||
|
||||
self.debug = false
|
||||
|
||||
self.CallBack = nil
|
||||
self.UseCallBack = false
|
||||
|
||||
self:HandleEvent( EVENTS.Shot, self.HandleEventShot )
|
||||
|
||||
-- Start State.
|
||||
self:SetStartState("Running")
|
||||
self:AddTransition("*", "ManageEvasion", "*")
|
||||
self:AddTransition("*", "CalculateHitZone", "*")
|
||||
|
||||
self:I("*** SEAD - Started Version 0.4.3")
|
||||
return self
|
||||
end
|
||||
|
||||
--- Detects if an SA site was shot with an anti radiation missile. In this case, take evasive actions based on the skill level set within the ME.
|
||||
-- @see SEAD
|
||||
-- @param #SEAD
|
||||
--- Update the active SEAD Set (while running)
|
||||
-- @param #SEAD self
|
||||
-- @param #table SEADGroupPrefixes The prefixes to add, note: can also be a single #string
|
||||
-- @return #SEAD self
|
||||
function SEAD:UpdateSet( SEADGroupPrefixes )
|
||||
|
||||
self:T( SEADGroupPrefixes )
|
||||
|
||||
if type( SEADGroupPrefixes ) == 'table' then
|
||||
for SEADGroupPrefixID, SEADGroupPrefix in pairs( SEADGroupPrefixes ) do
|
||||
self.SEADGroupPrefixes[SEADGroupPrefix] = SEADGroupPrefix
|
||||
end
|
||||
else
|
||||
self.SEADGroupPrefixes[SEADGroupPrefixes] = SEADGroupPrefixes
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Sets the engagement range of the SAMs. Defaults to 75% to make it more deadly. Feature Request #1355
|
||||
-- @param #SEAD self
|
||||
-- @param #number range Set the engagement range in percent, e.g. 55 (default 75)
|
||||
-- @return #SEAD self
|
||||
function SEAD:SetEngagementRange(range)
|
||||
self:T( { range } )
|
||||
range = range or 75
|
||||
if range < 0 or range > 100 then
|
||||
range = 75
|
||||
end
|
||||
self.EngagementRange = range
|
||||
self:T(string.format("*** SEAD - Engagement range set to %s",range))
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set the padding in seconds, which extends the radar off time calculated by SEAD
|
||||
-- @param #SEAD self
|
||||
-- @param #number Padding Extra number of seconds to add for the switch-on (default 10 seconds)
|
||||
-- @return #SEAD self
|
||||
function SEAD:SetPadding(Padding)
|
||||
self:T( { Padding } )
|
||||
local padding = Padding or 10
|
||||
if padding < 10 then padding = 10 end
|
||||
self.Padding = padding
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set SEAD to use emissions on/off in addition to alarm state.
|
||||
-- @param #SEAD self
|
||||
-- @param #boolean Switch True for on, false for off.
|
||||
-- @return #SEAD self
|
||||
function SEAD:SwitchEmissions(Switch)
|
||||
self:T({Switch})
|
||||
self.UseEmissionsOnOff = Switch
|
||||
return self
|
||||
end
|
||||
|
||||
--- Add an object to call back when going evasive.
|
||||
-- @param #SEAD self
|
||||
-- @param #table Object The object to call. Needs to have object functions as follows:
|
||||
-- `:SeadSuppressionPlanned(Group, Name, SuppressionStartTime, SuppressionEndTime)`
|
||||
-- `:SeadSuppressionStart(Group, Name)`,
|
||||
-- `:SeadSuppressionEnd(Group, Name)`,
|
||||
-- @return #SEAD self
|
||||
function SEAD:AddCallBack(Object)
|
||||
self:T({Class=Object.ClassName})
|
||||
self.CallBack = Object
|
||||
self.UseCallBack = true
|
||||
return self
|
||||
end
|
||||
|
||||
--- (Internal) Check if a known HARM was fired
|
||||
-- @param #SEAD self
|
||||
-- @param #string WeaponName
|
||||
-- @return #boolean Returns true for a match
|
||||
-- @return #string name Name of hit in table
|
||||
function SEAD:_CheckHarms(WeaponName)
|
||||
self:T( { WeaponName } )
|
||||
local hit = false
|
||||
local name = ""
|
||||
for _,_name in pairs (SEAD.Harms) do
|
||||
if string.find(WeaponName,_name,1,true) then
|
||||
hit = true
|
||||
name = _name
|
||||
break
|
||||
end
|
||||
end
|
||||
return hit, name
|
||||
end
|
||||
|
||||
--- (Internal) Return distance in meters between two coordinates or -1 on error.
|
||||
-- @param #SEAD self
|
||||
-- @param Core.Point#COORDINATE _point1 Coordinate one
|
||||
-- @param Core.Point#COORDINATE _point2 Coordinate two
|
||||
-- @return #number Distance in meters
|
||||
function SEAD:_GetDistance(_point1, _point2)
|
||||
self:T("_GetDistance")
|
||||
if _point1 and _point2 then
|
||||
local distance1 = _point1:Get2DDistance(_point2)
|
||||
local distance2 = _point1:DistanceFromPointVec2(_point2)
|
||||
--self:T({dist1=distance1, dist2=distance2})
|
||||
if distance1 and type(distance1) == "number" then
|
||||
return distance1
|
||||
elseif distance2 and type(distance2) == "number" then
|
||||
return distance2
|
||||
else
|
||||
self:E("*****Cannot calculate distance!")
|
||||
self:E({_point1,_point2})
|
||||
return -1
|
||||
end
|
||||
else
|
||||
self:E("******Cannot calculate distance!")
|
||||
self:E({_point1,_point2})
|
||||
return -1
|
||||
end
|
||||
end
|
||||
|
||||
--- (Internal) Calculate hit zone of an AGM-88
|
||||
-- @param #SEAD self
|
||||
-- @param #table SEADWeapon DCS.Weapon object
|
||||
-- @param Core.Point#COORDINATE pos0 Position of the plane when it fired
|
||||
-- @param #number height Height when the missile was fired
|
||||
-- @param Wrapper.Group#GROUP SEADGroup Attacker group
|
||||
-- @param #string SEADWeaponName Weapon Name
|
||||
-- @return #SEAD self
|
||||
function SEAD:onafterCalculateHitZone(From,Event,To,SEADWeapon,pos0,height,SEADGroup,SEADWeaponName)
|
||||
self:T("**** Calculating hit zone for " .. (SEADWeaponName or "None"))
|
||||
if SEADWeapon and SEADWeapon:isExist() then
|
||||
--local pos = SEADWeapon:getPoint()
|
||||
|
||||
-- postion and height
|
||||
local position = SEADWeapon:getPosition()
|
||||
local mheight = height
|
||||
-- heading
|
||||
local wph = math.atan2(position.x.z, position.x.x)
|
||||
if wph < 0 then
|
||||
wph=wph+2*math.pi
|
||||
end
|
||||
wph=math.deg(wph)
|
||||
|
||||
-- velocity
|
||||
local wpndata = SEAD.HarmData["AGM_88"]
|
||||
if string.find(SEADWeaponName,"154",1) then
|
||||
wpndata = SEAD.HarmData["AGM_154"]
|
||||
end
|
||||
local mveloc = math.floor(wpndata[2] * 340.29)
|
||||
local c1 = (2*mheight*9.81)/(mveloc^2)
|
||||
local c2 = (mveloc^2) / 9.81
|
||||
local Ropt = c2 * math.sqrt(c1+1)
|
||||
if height <= 5000 then
|
||||
Ropt = Ropt * 0.72
|
||||
elseif height <= 7500 then
|
||||
Ropt = Ropt * 0.82
|
||||
elseif height <= 10000 then
|
||||
Ropt = Ropt * 0.87
|
||||
elseif height <= 12500 then
|
||||
Ropt = Ropt * 0.98
|
||||
end
|
||||
|
||||
-- look at a couple of zones across the trajectory
|
||||
for n=1,3 do
|
||||
local dist = Ropt - ((n-1)*20000)
|
||||
local predpos= pos0:Translate(dist,wph)
|
||||
if predpos then
|
||||
|
||||
local targetzone = ZONE_RADIUS:New("Target Zone",predpos:GetVec2(),20000)
|
||||
|
||||
if self.debug then
|
||||
predpos:MarkToAll(string.format("height=%dm | heading=%d | velocity=%ddeg | Ropt=%dm",mheight,wph,mveloc,Ropt),false)
|
||||
targetzone:DrawZone(coalition.side.BLUE,{0,0,1},0.2,nil,nil,3,true)
|
||||
end
|
||||
|
||||
local seadset = SET_GROUP:New():FilterPrefixes(self.SEADGroupPrefixes):FilterZones({targetzone}):FilterOnce()
|
||||
local tgtcoord = targetzone:GetRandomPointVec2()
|
||||
--if tgtcoord and tgtcoord.ClassName == "COORDINATE" then
|
||||
--local tgtgrp = seadset:FindNearestGroupFromPointVec2(tgtcoord)
|
||||
local tgtgrp = seadset:GetRandom()
|
||||
local _targetgroup = nil
|
||||
local _targetgroupname = "none"
|
||||
local _targetskill = "Random"
|
||||
if tgtgrp and tgtgrp:IsAlive() then
|
||||
_targetgroup = tgtgrp
|
||||
_targetgroupname = tgtgrp:GetName() -- group name
|
||||
_targetskill = tgtgrp:GetUnit(1):GetSkill()
|
||||
self:T("*** Found Target = ".. _targetgroupname)
|
||||
self:ManageEvasion(_targetskill,_targetgroup,pos0,"AGM_88",SEADGroup, 20)
|
||||
end
|
||||
--end
|
||||
end
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- (Internal) Handle Evasion
|
||||
-- @param #SEAD self
|
||||
-- @param #string _targetskill
|
||||
-- @param Wrapper.Group#GROUP _targetgroup
|
||||
-- @param Core.Point#COORDINATE SEADPlanePos
|
||||
-- @param #string SEADWeaponName
|
||||
-- @param Wrapper.Group#GROUP SEADGroup Attacker Group
|
||||
-- @param #number timeoffset Offset for tti calc
|
||||
-- @return #SEAD self
|
||||
function SEAD:onafterManageEvasion(From,Event,To,_targetskill,_targetgroup,SEADPlanePos,SEADWeaponName,SEADGroup,timeoffset)
|
||||
local timeoffset = timeoffset or 0
|
||||
if _targetskill == "Random" then -- when skill is random, choose a skill
|
||||
local Skills = { "Average", "Good", "High", "Excellent" }
|
||||
_targetskill = Skills[ math.random(1,4) ]
|
||||
end
|
||||
--self:T( _targetskill )
|
||||
if self.TargetSkill[_targetskill] then
|
||||
local _evade = math.random (1,100) -- random number for chance of evading action
|
||||
if (_evade > self.TargetSkill[_targetskill].Evade) then
|
||||
self:T("*** SEAD - Evading")
|
||||
-- calculate distance of attacker
|
||||
local _targetpos = _targetgroup:GetCoordinate()
|
||||
local _distance = self:_GetDistance(SEADPlanePos, _targetpos)
|
||||
-- weapon speed
|
||||
local hit, data = self:_CheckHarms(SEADWeaponName)
|
||||
local wpnspeed = 666 -- ;)
|
||||
local reach = 10
|
||||
if hit then
|
||||
local wpndata = SEAD.HarmData[data]
|
||||
reach = wpndata[1] * 1,1
|
||||
local mach = wpndata[2]
|
||||
wpnspeed = math.floor(mach * 340.29)
|
||||
end
|
||||
-- time to impact
|
||||
local _tti = math.floor(_distance / wpnspeed) - timeoffset -- estimated impact time
|
||||
if _distance > 0 then
|
||||
_distance = math.floor(_distance / 1000) -- km
|
||||
else
|
||||
_distance = 0
|
||||
end
|
||||
|
||||
self:T( string.format("*** SEAD - target skill %s, distance %dkm, reach %dkm, tti %dsec", _targetskill, _distance,reach,_tti ))
|
||||
|
||||
if reach >= _distance then
|
||||
self:T("*** SEAD - Shot in Reach")
|
||||
|
||||
local function SuppressionStart(args)
|
||||
self:T(string.format("*** SEAD - %s Radar Off & Relocating",args[2]))
|
||||
local grp = args[1] -- Wrapper.Group#GROUP
|
||||
local name = args[2] -- #string Group Name
|
||||
local attacker = args[3] -- Wrapper.Group#GROUP
|
||||
if self.UseEmissionsOnOff then
|
||||
grp:EnableEmission(false)
|
||||
end
|
||||
grp:OptionAlarmStateGreen() -- needed else we cannot move around
|
||||
grp:RelocateGroundRandomInRadius(20,300,false,false,"Diamond")
|
||||
if self.UseCallBack then
|
||||
local object = self.CallBack
|
||||
object:SeadSuppressionStart(grp,name,attacker)
|
||||
end
|
||||
end
|
||||
|
||||
local function SuppressionStop(args)
|
||||
self:T(string.format("*** SEAD - %s Radar On",args[2]))
|
||||
local grp = args[1] -- Wrapper.Group#GROUP
|
||||
local name = args[2] -- #string Group Nam
|
||||
if self.UseEmissionsOnOff then
|
||||
grp:EnableEmission(true)
|
||||
end
|
||||
grp:OptionAlarmStateRed()
|
||||
grp:OptionEngageRange(self.EngagementRange)
|
||||
self.SuppressedGroups[name] = false
|
||||
if self.UseCallBack then
|
||||
local object = self.CallBack
|
||||
object:SeadSuppressionEnd(grp,name)
|
||||
end
|
||||
end
|
||||
|
||||
-- randomize switch-on time
|
||||
local delay = math.random(self.TargetSkill[_targetskill].DelayOn[1], self.TargetSkill[_targetskill].DelayOn[2])
|
||||
if delay > _tti then delay = delay / 2 end -- speed up
|
||||
if _tti > 600 then delay = _tti - 90 end -- shot from afar, 600 is default shorad ontime
|
||||
|
||||
local SuppressionStartTime = timer.getTime() + delay
|
||||
local SuppressionEndTime = timer.getTime() + _tti + self.Padding
|
||||
local _targetgroupname = _targetgroup:GetName()
|
||||
if not self.SuppressedGroups[_targetgroupname] then
|
||||
self:T(string.format("*** SEAD - %s | Parameters TTI %ds | Switch-Off in %ds",_targetgroupname,_tti,delay))
|
||||
timer.scheduleFunction(SuppressionStart,{_targetgroup,_targetgroupname, SEADGroup},SuppressionStartTime)
|
||||
timer.scheduleFunction(SuppressionStop,{_targetgroup,_targetgroupname},SuppressionEndTime)
|
||||
self.SuppressedGroups[_targetgroupname] = true
|
||||
if self.UseCallBack then
|
||||
local object = self.CallBack
|
||||
object:SeadSuppressionPlanned(_targetgroup,_targetgroupname,SuppressionStartTime,SuppressionEndTime, SEADGroup)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- (Internal) Detects if an SAM site was shot with an anti radiation missile. In this case, take evasive actions based on the skill level set within the ME.
|
||||
-- @param #SEAD self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function SEAD:OnEventShot( EventData )
|
||||
self:F( { EventData } )
|
||||
-- @return #SEAD self
|
||||
function SEAD:HandleEventShot( EventData )
|
||||
self:T( { EventData.id } )
|
||||
local SEADPlane = EventData.IniUnit -- Wrapper.Unit#UNIT
|
||||
local SEADGroup = EventData.IniGroup -- Wrapper.Group#GROUP
|
||||
local SEADPlanePos = SEADPlane:GetCoordinate() -- Core.Point#COORDINATE
|
||||
local SEADUnit = EventData.IniDCSUnit
|
||||
local SEADUnitName = EventData.IniDCSUnitName
|
||||
local SEADWeapon = EventData.Weapon -- Identify the weapon fired
|
||||
local SEADWeaponName = EventData.WeaponName -- return weapon type
|
||||
|
||||
local SEADUnit = EventData.IniDCSUnit
|
||||
local SEADUnitName = EventData.IniDCSUnitName
|
||||
local SEADWeapon = EventData.Weapon -- Identify the weapon fired
|
||||
local SEADWeaponName = EventData.WeaponName -- return weapon type
|
||||
-- Start of the 2nd loop
|
||||
self:T( "Missile Launched = " .. SEADWeaponName )
|
||||
|
||||
--if SEADWeaponName == "KH-58" or SEADWeaponName == "KH-25MPU" or SEADWeaponName == "AGM-88" or SEADWeaponName == "KH-31A" or SEADWeaponName == "KH-31P" then -- Check if the missile is a SEAD
|
||||
if SEADWeaponName == "weapons.missiles.X_58" --Kh-58U anti-radiation missiles fired
|
||||
or
|
||||
SEADWeaponName == "weapons.missiles.Kh25MP_PRGS1VP" --Kh-25MP anti-radiation missiles fired
|
||||
or
|
||||
SEADWeaponName == "weapons.missiles.X_25MP" --Kh-25MPU anti-radiation missiles fired
|
||||
or
|
||||
SEADWeaponName == "weapons.missiles.X_28" --Kh-28 anti-radiation missiles fired
|
||||
or
|
||||
SEADWeaponName == "weapons.missiles.X_31P" --Kh-31P anti-radiation missiles fired
|
||||
or
|
||||
SEADWeaponName == "weapons.missiles.AGM_45A" --AGM-45A anti-radiation missiles fired
|
||||
or
|
||||
SEADWeaponName == "weapons.missiles.AGM_45" --AGM-45B anti-radiation missiles fired
|
||||
or
|
||||
SEADWeaponName == "weapons.missiles.AGM_88" --AGM-88C anti-radiation missiles fired
|
||||
or
|
||||
SEADWeaponName == "weapons.missiles.AGM_122" --AGM-122 Sidearm anti-radiation missiles fired
|
||||
or
|
||||
SEADWeaponName == "weapons.missiles.ALARM" --ALARM anti-radiation missiles fired
|
||||
then
|
||||
|
||||
local _evade = math.random (1,100) -- random number for chance of evading action
|
||||
local _targetMim = EventData.Weapon:getTarget() -- Identify target
|
||||
local _targetMimname = Unit.getName(_targetMim)
|
||||
local _targetMimgroup = Unit.getGroup(Weapon.getTarget(SEADWeapon))
|
||||
local _targetMimgroupName = _targetMimgroup:getName()
|
||||
local _targetMimcont= _targetMimgroup:getController()
|
||||
local _targetskill = _DATABASE.Templates.Units[_targetMimname].Template.skill
|
||||
self:T( self.SEADGroupPrefixes )
|
||||
self:T( _targetMimgroupName )
|
||||
local SEADGroupFound = false
|
||||
for SEADGroupPrefixID, SEADGroupPrefix in pairs( self.SEADGroupPrefixes ) do
|
||||
if string.find( _targetMimgroupName, SEADGroupPrefix, 1, true ) then
|
||||
SEADGroupFound = true
|
||||
self:T( 'Group Found' )
|
||||
break
|
||||
end
|
||||
end
|
||||
if SEADGroupFound == true then
|
||||
if _targetskill == "Random" then -- when skill is random, choose a skill
|
||||
local Skills = { "Average", "Good", "High", "Excellent" }
|
||||
_targetskill = Skills[ math.random(1,4) ]
|
||||
end
|
||||
self:T( _targetskill )
|
||||
if self.TargetSkill[_targetskill] then
|
||||
if (_evade > self.TargetSkill[_targetskill].Evade) then
|
||||
|
||||
self:T( string.format("Evading, target skill " ..string.format(_targetskill)) )
|
||||
|
||||
local _targetMim = Weapon.getTarget(SEADWeapon)
|
||||
local _targetMimname = Unit.getName(_targetMim)
|
||||
local _targetMimgroup = Unit.getGroup(Weapon.getTarget(SEADWeapon))
|
||||
local _targetMimcont= _targetMimgroup:getController()
|
||||
|
||||
routines.groupRandomDistSelf(_targetMimgroup,300,'Diamond',250,20) -- move randomly
|
||||
|
||||
local SuppressedGroups1 = {} -- unit suppressed radar off for a random time
|
||||
|
||||
local function SuppressionEnd1(id)
|
||||
id.ctrl:setOption(AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.GREEN)
|
||||
SuppressedGroups1[id.groupName] = nil
|
||||
end
|
||||
|
||||
local id = {
|
||||
groupName = _targetMimgroup,
|
||||
ctrl = _targetMimcont
|
||||
}
|
||||
|
||||
local delay1 = math.random(self.TargetSkill[_targetskill].DelayOff[1], self.TargetSkill[_targetskill].DelayOff[2])
|
||||
|
||||
if SuppressedGroups1[id.groupName] == nil then
|
||||
|
||||
SuppressedGroups1[id.groupName] = {
|
||||
SuppressionEndTime1 = timer.getTime() + delay1,
|
||||
SuppressionEndN1 = SuppressionEndCounter1 --Store instance of SuppressionEnd() scheduled function
|
||||
}
|
||||
|
||||
Controller.setOption(_targetMimcont, AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.GREEN)
|
||||
timer.scheduleFunction(SuppressionEnd1, id, SuppressedGroups1[id.groupName].SuppressionEndTime1) --Schedule the SuppressionEnd() function
|
||||
--trigger.action.outText( string.format("Radar Off " ..string.format(delay1)), 20)
|
||||
end
|
||||
|
||||
local SuppressedGroups = {}
|
||||
|
||||
local function SuppressionEnd(id)
|
||||
id.ctrl:setOption(AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.RED)
|
||||
SuppressedGroups[id.groupName] = nil
|
||||
end
|
||||
|
||||
local id = {
|
||||
groupName = _targetMimgroup,
|
||||
ctrl = _targetMimcont
|
||||
}
|
||||
|
||||
local delay = math.random(self.TargetSkill[_targetskill].DelayOn[1], self.TargetSkill[_targetskill].DelayOn[2])
|
||||
|
||||
if SuppressedGroups[id.groupName] == nil then
|
||||
SuppressedGroups[id.groupName] = {
|
||||
SuppressionEndTime = timer.getTime() + delay,
|
||||
SuppressionEndN = SuppressionEndCounter --Store instance of SuppressionEnd() scheduled function
|
||||
}
|
||||
|
||||
timer.scheduleFunction(SuppressionEnd, id, SuppressedGroups[id.groupName].SuppressionEndTime) --Schedule the SuppressionEnd() function
|
||||
--trigger.action.outText( string.format("Radar On " ..string.format(delay)), 20)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
self:T( "*** SEAD - Missile Launched = " .. SEADWeaponName)
|
||||
--self:T({ SEADWeapon })
|
||||
|
||||
if self:_CheckHarms(SEADWeaponName) then
|
||||
self:T( '*** SEAD - Weapon Match' )
|
||||
local _targetskill = "Random"
|
||||
local _targetgroupname = "none"
|
||||
local _target = EventData.Weapon:getTarget() -- Identify target
|
||||
if not _target or self.debug then -- AGM-88 or 154 w/o target data
|
||||
self:E("***** SEAD - No target data for " .. (SEADWeaponName or "None"))
|
||||
if string.find(SEADWeaponName,"AGM_88",1,true) or string.find(SEADWeaponName,"AGM_154",1,true) then
|
||||
self:I("**** Tracking AGM-88/154 with no target data.")
|
||||
local pos0 = SEADPlane:GetCoordinate()
|
||||
local fheight = SEADPlane:GetHeight()
|
||||
self:__CalculateHitZone(20,SEADWeapon,pos0,fheight,SEADGroup,SEADWeaponName)
|
||||
end
|
||||
return self
|
||||
end
|
||||
local targetcat = _target:getCategory() -- Identify category
|
||||
local _targetUnit = nil -- Wrapper.Unit#UNIT
|
||||
local _targetgroup = nil -- Wrapper.Group#GROUP
|
||||
self:T(string.format("*** Targetcat = %d",targetcat))
|
||||
if targetcat == Object.Category.UNIT then -- UNIT
|
||||
self:T("*** Target Category UNIT")
|
||||
_targetUnit = UNIT:Find(_target) -- Wrapper.Unit#UNIT
|
||||
if _targetUnit and _targetUnit:IsAlive() then
|
||||
_targetgroup = _targetUnit:GetGroup()
|
||||
_targetgroupname = _targetgroup:GetName() -- group name
|
||||
local _targetUnitName = _targetUnit:GetName()
|
||||
_targetUnit:GetSkill()
|
||||
_targetskill = _targetUnit:GetSkill()
|
||||
end
|
||||
elseif targetcat == Object.Category.STATIC then
|
||||
self:T("*** Target Category STATIC")
|
||||
local seadset = SET_GROUP:New():FilterPrefixes(self.SEADGroupPrefixes):FilterOnce()
|
||||
local targetpoint = _target:getPoint() or {x=0,y=0,z=0}
|
||||
local tgtcoord = COORDINATE:NewFromVec3(targetpoint)
|
||||
local tgtgrp = seadset:FindNearestGroupFromPointVec2(tgtcoord)
|
||||
if tgtgrp and tgtgrp:IsAlive() then
|
||||
_targetgroup = tgtgrp
|
||||
_targetgroupname = tgtgrp:GetName() -- group name
|
||||
_targetskill = tgtgrp:GetUnit(1):GetSkill()
|
||||
self:T("*** Found Target = ".. _targetgroupname)
|
||||
end
|
||||
end
|
||||
-- see if we are shot at
|
||||
local SEADGroupFound = false
|
||||
for SEADGroupPrefixID, SEADGroupPrefix in pairs( self.SEADGroupPrefixes ) do
|
||||
self:T("Target = ".. _targetgroupname .. " | Prefix = " .. SEADGroupPrefix )
|
||||
if string.find( _targetgroupname, SEADGroupPrefix,1,true ) then
|
||||
SEADGroupFound = true
|
||||
self:T( '*** SEAD - Group Match Found' )
|
||||
break
|
||||
end
|
||||
end
|
||||
if SEADGroupFound == true then -- yes we are being attacked
|
||||
self:ManageEvasion(_targetskill,_targetgroup,SEADPlanePos,SEADWeaponName,SEADGroup)
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
667
Moose Development/Moose/Functional/Shorad.lua
Normal file
667
Moose Development/Moose/Functional/Shorad.lua
Normal file
@@ -0,0 +1,667 @@
|
||||
--- **Functional** -- Short Range Air Defense System
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- **SHORAD** - Short Range Air Defense System
|
||||
-- Controls a network of short range air/missile defense groups.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ## Missions:
|
||||
--
|
||||
-- ### [SHORAD - Short Range Air Defense](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/SRD%20-%20SHORAD%20Defense)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author : **applevangelist **
|
||||
--
|
||||
-- @module Functional.Shorad
|
||||
-- @image Functional.Shorad.jpg
|
||||
--
|
||||
-- Date: Nov 2021
|
||||
|
||||
-------------------------------------------------------------------------
|
||||
--- **SHORAD** class, extends Core.Base#BASE
|
||||
-- @type SHORAD
|
||||
-- @field #string ClassName
|
||||
-- @field #string name Name of this Shorad
|
||||
-- @field #boolean debug Set the debug state
|
||||
-- @field #string Prefixes String to be used to build the @{#Core.Set#SET_GROUP}
|
||||
-- @field #number Radius Shorad defense radius in meters
|
||||
-- @field Core.Set#SET_GROUP Groupset The set of Shorad groups
|
||||
-- @field Core.Set#SET_GROUP Samset The set of SAM groups to defend
|
||||
-- @field #string Coalition The coalition of this Shorad
|
||||
-- @field #number ActiveTimer How long a Shorad stays active after wake-up in seconds
|
||||
-- @field #table ActiveGroups Table for the timer function
|
||||
-- @field #string lid The log ID for the dcs.log
|
||||
-- @field #boolean DefendHarms Default true, intercept incoming HARMS
|
||||
-- @field #boolean DefendMavs Default true, intercept incoming AG-Missiles
|
||||
-- @field #number DefenseLowProb Default 70, minimum detection limit
|
||||
-- @field #number DefenseHighProb Default 90, maximim detection limit
|
||||
-- @field #boolean UseEmOnOff Decide if we are using Emission on/off (default) or AlarmState red/green.
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
|
||||
--- *Good friends are worth defending.* Mr Tushman, Wonder (the Movie)
|
||||
--
|
||||
-- Simple Class for a more intelligent Short Range Air Defense System
|
||||
--
|
||||
-- #SHORAD
|
||||
-- Moose derived missile intercepting short range defense system.
|
||||
-- Protects a network of SAM sites. Uses events to switch on the defense groups closest to the enemy.
|
||||
-- Easily integrated with @{Functional.Mantis#MANTIS} to complete the defensive system setup.
|
||||
--
|
||||
-- ## Usage
|
||||
--
|
||||
-- Set up a #SET_GROUP for the SAM sites to be protected:
|
||||
--
|
||||
-- `local SamSet = SET_GROUP:New():FilterPrefixes("Red SAM"):FilterCoalitions("red"):FilterStart()`
|
||||
--
|
||||
-- By default, SHORAD will defense against both HARMs and AG-Missiles with short to medium range. The default defense probability is 70-90%.
|
||||
-- When a missile is detected, SHORAD will activate defense groups in the given radius around the target for 10 minutes. It will *not* react to friendly fire.
|
||||
--
|
||||
-- ### Start a new SHORAD system, parameters are:
|
||||
--
|
||||
-- * Name: Name of this SHORAD.
|
||||
-- * ShoradPrefix: Filter for the Shorad #SET_GROUP.
|
||||
-- * Samset: The #SET_GROUP of SAM sites to defend.
|
||||
-- * Radius: Defense radius in meters.
|
||||
-- * ActiveTimer: Determines how many seconds the systems stay on red alert after wake-up call.
|
||||
-- * Coalition: Coalition, i.e. "blue", "red", or "neutral".*
|
||||
--
|
||||
-- `myshorad = SHORAD:New("RedShorad", "Red SHORAD", SamSet, 25000, 600, "red")`
|
||||
--
|
||||
-- ## Customize options
|
||||
--
|
||||
-- * SHORAD:SwitchDebug(debug)
|
||||
-- * SHORAD:SwitchHARMDefense(onoff)
|
||||
-- * SHORAD:SwitchAGMDefense(onoff)
|
||||
-- * SHORAD:SetDefenseLimits(low,high)
|
||||
-- * SHORAD:SetActiveTimer(seconds)
|
||||
-- * SHORAD:SetDefenseRadius(meters)
|
||||
--
|
||||
-- @field #SHORAD
|
||||
SHORAD = {
|
||||
ClassName = "SHORAD",
|
||||
name = "MyShorad",
|
||||
debug = false,
|
||||
Prefixes = "",
|
||||
Radius = 20000,
|
||||
Groupset = nil,
|
||||
Samset = nil,
|
||||
Coalition = nil,
|
||||
ActiveTimer = 600, --stay on 10 mins
|
||||
ActiveGroups = {},
|
||||
lid = "",
|
||||
DefendHarms = true,
|
||||
DefendMavs = true,
|
||||
DefenseLowProb = 70,
|
||||
DefenseHighProb = 90,
|
||||
UseEmOnOff = false,
|
||||
}
|
||||
|
||||
-----------------------------------------------------------------------
|
||||
-- SHORAD System
|
||||
-----------------------------------------------------------------------
|
||||
|
||||
do
|
||||
-- TODO Complete list?
|
||||
--- Missile enumerators
|
||||
-- @field Harms
|
||||
SHORAD.Harms = {
|
||||
["AGM_88"] = "AGM_88",
|
||||
["AGM_45"] = "AGM_45",
|
||||
["AGM_122"] = "AGM_122",
|
||||
["AGM_84"] = "AGM_84",
|
||||
["AGM_45"] = "AGM_45",
|
||||
["ALARM"] = "ALARM",
|
||||
["LD-10"] = "LD-10",
|
||||
["X_58"] = "X_58",
|
||||
["X_28"] = "X_28",
|
||||
["X_25"] = "X_25",
|
||||
["X_31"] = "X_31",
|
||||
["Kh25"] = "Kh25",
|
||||
}
|
||||
|
||||
--- TODO complete list?
|
||||
-- @field Mavs
|
||||
SHORAD.Mavs = {
|
||||
["AGM"] = "AGM",
|
||||
["C-701"] = "C-701",
|
||||
["Kh25"] = "Kh25",
|
||||
["Kh29"] = "Kh29",
|
||||
["Kh31"] = "Kh31",
|
||||
["Kh66"] = "Kh66",
|
||||
--["BGM_109"] = "BGM_109",
|
||||
}
|
||||
|
||||
--- Instantiates a new SHORAD object
|
||||
-- @param #SHORAD self
|
||||
-- @param #string Name Name of this SHORAD
|
||||
-- @param #string ShoradPrefix Filter for the Shorad #SET_GROUP
|
||||
-- @param Core.Set#SET_GROUP Samset The #SET_GROUP of SAM sites to defend
|
||||
-- @param #number Radius Defense radius in meters, used to switch on SHORAD groups **within** this radius
|
||||
-- @param #number ActiveTimer Determines how many seconds the systems stay on red alert after wake-up call
|
||||
-- @param #string Coalition Coalition, i.e. "blue", "red", or "neutral"
|
||||
-- @param #boolean UseEmOnOff Use Emissions On/Off rather than Alarm State Red/Green (default: use Emissions switch)
|
||||
-- @retunr #SHORAD self
|
||||
function SHORAD:New(Name, ShoradPrefix, Samset, Radius, ActiveTimer, Coalition, UseEmOnOff)
|
||||
local self = BASE:Inherit( self, FSM:New() )
|
||||
self:T({Name, ShoradPrefix, Samset, Radius, ActiveTimer, Coalition})
|
||||
|
||||
local GroupSet = SET_GROUP:New():FilterPrefixes(ShoradPrefix):FilterCoalitions(Coalition):FilterCategoryGround():FilterStart()
|
||||
|
||||
self.name = Name or "MyShorad"
|
||||
self.Prefixes = ShoradPrefix or "SAM SHORAD"
|
||||
self.Radius = Radius or 20000
|
||||
self.Coalition = Coalition or "blue"
|
||||
self.Samset = Samset or GroupSet
|
||||
self.ActiveTimer = ActiveTimer or 600
|
||||
self.ActiveGroups = {}
|
||||
self.Groupset = GroupSet
|
||||
self.DefendHarms = true
|
||||
self.DefendMavs = true
|
||||
self.DefenseLowProb = 70 -- probability to detect a missile shot, low margin
|
||||
self.DefenseHighProb = 90 -- probability to detect a missile shot, high margin
|
||||
self.UseEmOnOff = UseEmOnOff or false -- Decide if we are using Emission on/off (default) or AlarmState red/green
|
||||
self:I("*** SHORAD - Started Version 0.3.1")
|
||||
-- Set the string id for output to DCS.log file.
|
||||
self.lid=string.format("SHORAD %s | ", self.name)
|
||||
self:_InitState()
|
||||
self:HandleEvent(EVENTS.Shot, self.HandleEventShot)
|
||||
|
||||
-- Start State.
|
||||
self:SetStartState("Running")
|
||||
self:AddTransition("*", "WakeUpShorad", "*")
|
||||
self:AddTransition("*", "CalculateHitZone", "*")
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Initially set all groups to alarm state GREEN
|
||||
-- @param #SHORAD self
|
||||
function SHORAD:_InitState()
|
||||
self:T(self.lid .. " _InitState")
|
||||
local table = {}
|
||||
local set = self.Groupset
|
||||
self:T({set = set})
|
||||
local aliveset = set:GetAliveSet() --#table
|
||||
for _,_group in pairs (aliveset) do
|
||||
if self.UseEmOnOff then
|
||||
--_group:SetAIOff()
|
||||
_group:EnableEmission(false)
|
||||
_group:OptionAlarmStateRed() --Wrapper.Group#GROUP
|
||||
else
|
||||
_group:OptionAlarmStateGreen() --Wrapper.Group#GROUP
|
||||
end
|
||||
_group:OptionDisperseOnAttack(30)
|
||||
end
|
||||
-- gather entropy
|
||||
for i=1,100 do
|
||||
math.random()
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Switch debug state on
|
||||
-- @param #SHORAD self
|
||||
-- @param #boolean debug Switch debug on (true) or off (false)
|
||||
function SHORAD:SwitchDebug(onoff)
|
||||
self:T( { onoff } )
|
||||
if onoff then
|
||||
self:SwitchDebugOn()
|
||||
else
|
||||
self.SwitchDebugOff()
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Switch debug state on
|
||||
-- @param #SHORAD self
|
||||
function SHORAD:SwitchDebugOn()
|
||||
self.debug = true
|
||||
--tracing
|
||||
BASE:TraceOn()
|
||||
BASE:TraceClass("SHORAD")
|
||||
return self
|
||||
end
|
||||
|
||||
--- Switch debug state off
|
||||
-- @param #SHORAD self
|
||||
function SHORAD:SwitchDebugOff()
|
||||
self.debug = false
|
||||
BASE:TraceOff()
|
||||
return self
|
||||
end
|
||||
|
||||
--- Switch defense for HARMs
|
||||
-- @param #SHORAD self
|
||||
-- @param #boolean onoff
|
||||
function SHORAD:SwitchHARMDefense(onoff)
|
||||
self:T( { onoff } )
|
||||
local onoff = onoff or true
|
||||
self.DefendHarms = onoff
|
||||
return self
|
||||
end
|
||||
|
||||
--- Switch defense for AGMs
|
||||
-- @param #SHORAD self
|
||||
-- @param #boolean onoff
|
||||
function SHORAD:SwitchAGMDefense(onoff)
|
||||
self:T( { onoff } )
|
||||
local onoff = onoff or true
|
||||
self.DefendMavs = onoff
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set defense probability limits
|
||||
-- @param #SHORAD self
|
||||
-- @param #number low Minimum detection limit, integer 1-100
|
||||
-- @param #number high Maximum detection limit integer 1-100
|
||||
function SHORAD:SetDefenseLimits(low,high)
|
||||
self:T( { low, high } )
|
||||
local low = low or 70
|
||||
local high = high or 90
|
||||
if (low < 0) or (low > 100) or (low > high) then
|
||||
low = 70
|
||||
end
|
||||
if (high < 0) or (high > 100) or (high < low ) then
|
||||
high = 90
|
||||
end
|
||||
self.DefenseLowProb = low
|
||||
self.DefenseHighProb = high
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set the number of seconds a SHORAD site will stay active
|
||||
-- @param #SHORAD self
|
||||
-- @param #number seconds Number of seconds systems stay active
|
||||
function SHORAD:SetActiveTimer(seconds)
|
||||
self:T(self.lid .. " SetActiveTimer")
|
||||
local timer = seconds or 600
|
||||
if timer < 0 then
|
||||
timer = 600
|
||||
end
|
||||
self.ActiveTimer = timer
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set the number of meters for the SHORAD defense zone
|
||||
-- @param #SHORAD self
|
||||
-- @param #number meters Radius of the defense search zone in meters. #SHORADs in this range around a targeted group will go active
|
||||
function SHORAD:SetDefenseRadius(meters)
|
||||
self:T(self.lid .. " SetDefenseRadius")
|
||||
local radius = meters or 20000
|
||||
if radius < 0 then
|
||||
radius = 20000
|
||||
end
|
||||
self.Radius = radius
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set using Emission on/off instead of changing alarm state
|
||||
-- @param #SHORAD self
|
||||
-- @param #boolean switch Decide if we are changing alarm state or AI state
|
||||
function SHORAD:SetUsingEmOnOff(switch)
|
||||
self:T(self.lid .. " SetUsingEmOnOff")
|
||||
self.UseEmOnOff = switch or false
|
||||
return self
|
||||
end
|
||||
|
||||
--- Check if a HARM was fired
|
||||
-- @param #SHORAD self
|
||||
-- @param #string WeaponName
|
||||
-- @return #boolean Returns true for a match
|
||||
function SHORAD:_CheckHarms(WeaponName)
|
||||
self:T(self.lid .. " _CheckHarms")
|
||||
self:T( { WeaponName } )
|
||||
local hit = false
|
||||
if self.DefendHarms then
|
||||
for _,_name in pairs (SHORAD.Harms) do
|
||||
if string.find(WeaponName,_name,1,true) then hit = true end
|
||||
end
|
||||
end
|
||||
return hit
|
||||
end
|
||||
|
||||
--- Check if an AGM was fired
|
||||
-- @param #SHORAD self
|
||||
-- @param #string WeaponName
|
||||
-- @return #boolean Returns true for a match
|
||||
function SHORAD:_CheckMavs(WeaponName)
|
||||
self:T(self.lid .. " _CheckMavs")
|
||||
self:T( { WeaponName } )
|
||||
local hit = false
|
||||
if self.DefendMavs then
|
||||
for _,_name in pairs (SHORAD.Mavs) do
|
||||
if string.find(WeaponName,_name,1,true) then hit = true end
|
||||
end
|
||||
end
|
||||
return hit
|
||||
end
|
||||
|
||||
--- Check the coalition of the attacker
|
||||
-- @param #SHORAD self
|
||||
-- @param #string Coalition name
|
||||
-- @return #boolean Returns false for a match
|
||||
function SHORAD:_CheckCoalition(Coalition)
|
||||
self:T(self.lid .. " _CheckCoalition")
|
||||
local owncoalition = self.Coalition
|
||||
local othercoalition = ""
|
||||
if Coalition == 0 then
|
||||
othercoalition = "neutral"
|
||||
elseif Coalition == 1 then
|
||||
othercoalition = "red"
|
||||
else
|
||||
othercoalition = "blue"
|
||||
end
|
||||
self:T({owncoalition = owncoalition, othercoalition = othercoalition})
|
||||
if owncoalition ~= othercoalition then
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
--- Check if the missile is aimed at a SHORAD
|
||||
-- @param #SHORAD self
|
||||
-- @param #string TargetGroupName Name of the target group
|
||||
-- @return #boolean Returns true for a match, else false
|
||||
function SHORAD:_CheckShotAtShorad(TargetGroupName)
|
||||
self:T(self.lid .. " _CheckShotAtShorad")
|
||||
local tgtgrp = TargetGroupName
|
||||
local shorad = self.Groupset
|
||||
local shoradset = shorad:GetAliveSet() --#table
|
||||
local returnname = false
|
||||
for _,_groups in pairs (shoradset) do
|
||||
local groupname = _groups:GetName()
|
||||
if string.find(groupname, tgtgrp, 1, true) then
|
||||
returnname = true
|
||||
--_groups:RelocateGroundRandomInRadius(7,100,false,false) -- be a bit evasive
|
||||
end
|
||||
end
|
||||
return returnname
|
||||
end
|
||||
|
||||
--- Check if the missile is aimed at a SAM site
|
||||
-- @param #SHORAD self
|
||||
-- @param #string TargetGroupName Name of the target group
|
||||
-- @return #boolean Returns true for a match, else false
|
||||
function SHORAD:_CheckShotAtSams(TargetGroupName)
|
||||
self:T(self.lid .. " _CheckShotAtSams")
|
||||
local tgtgrp = TargetGroupName
|
||||
local shorad = self.Samset
|
||||
--local shoradset = shorad:GetAliveSet() --#table
|
||||
local shoradset = shorad:GetSet() --#table
|
||||
local returnname = false
|
||||
for _,_groups in pairs (shoradset) do
|
||||
local groupname = _groups:GetName()
|
||||
if string.find(groupname, tgtgrp, 1, true) then
|
||||
returnname = true
|
||||
end
|
||||
end
|
||||
return returnname
|
||||
end
|
||||
|
||||
--- Calculate if the missile shot is detected
|
||||
-- @param #SHORAD self
|
||||
-- @return #boolean Returns true for a detection, else false
|
||||
function SHORAD:_ShotIsDetected()
|
||||
self:T(self.lid .. " _ShotIsDetected")
|
||||
if self.debug then return true end
|
||||
local IsDetected = false
|
||||
local DetectionProb = math.random(self.DefenseLowProb, self.DefenseHighProb) -- reference value
|
||||
local ActualDetection = math.random(1,100) -- value for this shot
|
||||
if ActualDetection <= DetectionProb then
|
||||
IsDetected = true
|
||||
end
|
||||
return IsDetected
|
||||
end
|
||||
|
||||
--- Wake up #SHORADs in a zone with diameter Radius for ActiveTimer seconds
|
||||
-- @param #SHORAD self
|
||||
-- @param #string TargetGroup Name of the target group used to build the #ZONE
|
||||
-- @param #number Radius Radius of the #ZONE
|
||||
-- @param #number ActiveTimer Number of seconds to stay active
|
||||
-- @param #number TargetCat (optional) Category, i.e. Object.Category.UNIT or Object.Category.STATIC
|
||||
-- @usage Use this function to integrate with other systems, example
|
||||
--
|
||||
-- local SamSet = SET_GROUP:New():FilterPrefixes("Blue SAM"):FilterCoalitions("blue"):FilterStart()
|
||||
-- myshorad = SHORAD:New("BlueShorad", "Blue SHORAD", SamSet, 22000, 600, "blue")
|
||||
-- myshorad:SwitchDebug(true)
|
||||
-- mymantis = MANTIS:New("BlueMantis","Blue SAM","Blue EWR",nil,"blue",false,"Blue Awacs")
|
||||
-- mymantis:AddShorad(myshorad,720)
|
||||
-- mymantis:Start()
|
||||
function SHORAD:onafterWakeUpShorad(From, Event, To, TargetGroup, Radius, ActiveTimer, TargetCat)
|
||||
self:T(self.lid .. " WakeUpShorad")
|
||||
self:T({TargetGroup, Radius, ActiveTimer, TargetCat})
|
||||
local targetcat = TargetCat or Object.Category.UNIT
|
||||
local targetgroup = TargetGroup
|
||||
local targetvec2 = nil
|
||||
if targetcat == Object.Category.UNIT then
|
||||
targetvec2 = GROUP:FindByName(targetgroup):GetVec2()
|
||||
elseif targetcat == Object.Category.STATIC then
|
||||
targetvec2 = STATIC:FindByName(targetgroup,false):GetVec2()
|
||||
else
|
||||
local samset = self.Samset
|
||||
local sam = samset:GetRandom()
|
||||
targetvec2 = sam:GetVec2()
|
||||
end
|
||||
local targetzone = ZONE_RADIUS:New("Shorad",targetvec2,Radius) -- create a defense zone to check
|
||||
local groupset = self.Groupset --Core.Set#SET_GROUP
|
||||
local shoradset = groupset:GetAliveSet() --#table
|
||||
-- local function to switch off shorad again
|
||||
local function SleepShorad(group)
|
||||
local groupname = group:GetName()
|
||||
self.ActiveGroups[groupname] = nil
|
||||
if self.UseEmOnOff then
|
||||
group:EnableEmission(false)
|
||||
--group:SetAIOff()
|
||||
else
|
||||
group:OptionAlarmStateGreen()
|
||||
end
|
||||
local text = string.format("Sleeping SHORAD %s", group:GetName())
|
||||
self:T(text)
|
||||
local m = MESSAGE:New(text,10,"SHORAD"):ToAllIf(self.debug)
|
||||
end
|
||||
-- go through set and find the one(s) to activate
|
||||
for _,_group in pairs (shoradset) do
|
||||
if _group:IsAnyInZone(targetzone) then
|
||||
local text = string.format("Waking up SHORAD %s", _group:GetName())
|
||||
self:T(text)
|
||||
local m = MESSAGE:New(text,10,"SHORAD"):ToAllIf(self.debug)
|
||||
if self.UseEmOnOff then
|
||||
--_group:SetAIOn()
|
||||
_group:EnableEmission(true)
|
||||
end
|
||||
_group:OptionAlarmStateRed()
|
||||
local groupname = _group:GetName()
|
||||
if self.ActiveGroups[groupname] == nil then -- no timer yet for this group
|
||||
self.ActiveGroups[groupname] = { Timing = ActiveTimer }
|
||||
local endtime = timer.getTime() + (ActiveTimer * math.random(75,100) / 100 ) -- randomize wakeup a bit
|
||||
timer.scheduleFunction(SleepShorad, _group, endtime)
|
||||
end
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- (Internal) Calculate hit zone of an AGM-88
|
||||
-- @param #SHORAD self
|
||||
-- @param #table SEADWeapon DCS.Weapon object
|
||||
-- @param Core.Point#COORDINATE pos0 Position of the plane when it fired
|
||||
-- @param #number height Height when the missile was fired
|
||||
-- @param Wrapper.Group#GROUP SEADGroup Attacker group
|
||||
-- @return #SHORAD self
|
||||
function SHORAD:onafterCalculateHitZone(From,Event,To,SEADWeapon,pos0,height,SEADGroup)
|
||||
self:T("**** Calculating hit zone")
|
||||
if SEADWeapon and SEADWeapon:isExist() then
|
||||
--local pos = SEADWeapon:getPoint()
|
||||
|
||||
-- postion and height
|
||||
local position = SEADWeapon:getPosition()
|
||||
local mheight = height
|
||||
-- heading
|
||||
local wph = math.atan2(position.x.z, position.x.x)
|
||||
if wph < 0 then
|
||||
wph=wph+2*math.pi
|
||||
end
|
||||
wph=math.deg(wph)
|
||||
|
||||
-- velocity
|
||||
local wpndata = SEAD.HarmData["AGM_88"]
|
||||
local mveloc = math.floor(wpndata[2] * 340.29)
|
||||
local c1 = (2*mheight*9.81)/(mveloc^2)
|
||||
local c2 = (mveloc^2) / 9.81
|
||||
local Ropt = c2 * math.sqrt(c1+1)
|
||||
if height <= 5000 then
|
||||
Ropt = Ropt * 0.72
|
||||
elseif height <= 7500 then
|
||||
Ropt = Ropt * 0.82
|
||||
elseif height <= 10000 then
|
||||
Ropt = Ropt * 0.87
|
||||
elseif height <= 12500 then
|
||||
Ropt = Ropt * 0.98
|
||||
end
|
||||
|
||||
-- look at a couple of zones across the trajectory
|
||||
for n=1,3 do
|
||||
local dist = Ropt - ((n-1)*20000)
|
||||
local predpos= pos0:Translate(dist,wph)
|
||||
if predpos then
|
||||
|
||||
local targetzone = ZONE_RADIUS:New("Target Zone",predpos:GetVec2(),20000)
|
||||
|
||||
if self.debug then
|
||||
predpos:MarkToAll(string.format("height=%dm | heading=%d | velocity=%ddeg | Ropt=%dm",mheight,wph,mveloc,Ropt),false)
|
||||
targetzone:DrawZone(coalition.side.BLUE,{0,0,1},0.2,nil,nil,3,true)
|
||||
end
|
||||
|
||||
local seadset = self.Groupset
|
||||
local tgtcoord = targetzone:GetRandomPointVec2()
|
||||
local tgtgrp = seadset:FindNearestGroupFromPointVec2(tgtcoord)
|
||||
local _targetgroup = nil
|
||||
local _targetgroupname = "none"
|
||||
local _targetskill = "Random"
|
||||
if tgtgrp and tgtgrp:IsAlive() then
|
||||
_targetgroup = tgtgrp
|
||||
_targetgroupname = tgtgrp:GetName() -- group name
|
||||
_targetskill = tgtgrp:GetUnit(1):GetSkill()
|
||||
self:T("*** Found Target = ".. _targetgroupname)
|
||||
self:WakeUpShorad(_targetgroupname, self.Radius, self.ActiveTimer, Object.Category.UNIT)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Main function - work on the EventData
|
||||
-- @param #SHORAD self
|
||||
-- @param Core.Event#EVENTDATA EventData The event details table data set
|
||||
function SHORAD:HandleEventShot( EventData )
|
||||
self:T( { EventData } )
|
||||
self:T(self.lid .. " HandleEventShot")
|
||||
--local ShootingUnit = EventData.IniDCSUnit
|
||||
--local ShootingUnitName = EventData.IniDCSUnitName
|
||||
local ShootingWeapon = EventData.Weapon -- Identify the weapon fired
|
||||
local ShootingWeaponName = EventData.WeaponName -- return weapon type
|
||||
-- get firing coalition
|
||||
local weaponcoalition = EventData.IniGroup:GetCoalition()
|
||||
-- get detection probability
|
||||
if self:_CheckCoalition(weaponcoalition) then --avoid overhead on friendly fire
|
||||
local IsDetected = self:_ShotIsDetected()
|
||||
-- convert to text
|
||||
local DetectedText = "false"
|
||||
if IsDetected then
|
||||
DetectedText = "true"
|
||||
end
|
||||
local text = string.format("%s Missile Launched = %s | Detected probability state is %s", self.lid, ShootingWeaponName, DetectedText)
|
||||
self:T( text )
|
||||
local m = MESSAGE:New(text,10,"Info"):ToAllIf(self.debug)
|
||||
--
|
||||
if (self:_CheckHarms(ShootingWeaponName) or self:_CheckMavs(ShootingWeaponName)) and IsDetected then
|
||||
-- get target data
|
||||
local targetdata = EventData.Weapon:getTarget() -- Identify target
|
||||
-- Is there target data?
|
||||
if not targetdata or self.debug then
|
||||
if string.find(ShootingWeaponName,"AGM_88",1,true) then
|
||||
self:I("**** Tracking AGM-88 with no target data.")
|
||||
local pos0 = EventData.IniUnit:GetCoordinate()
|
||||
local fheight = EventData.IniUnit:GetHeight()
|
||||
self:__CalculateHitZone(20,ShootingWeapon,pos0,fheight,EventData.IniGroup)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
local targetcat = targetdata:getCategory() -- Identify category
|
||||
self:T(string.format("Target Category (3=STATIC, 1=UNIT)= %s",tostring(targetcat)))
|
||||
self:T({targetdata})
|
||||
local targetunit = nil
|
||||
if targetcat == Object.Category.UNIT then -- UNIT
|
||||
targetunit = UNIT:Find(targetdata)
|
||||
elseif targetcat == Object.Category.STATIC then -- STATIC
|
||||
--self:T("Static Target Data")
|
||||
--self:T({targetdata:isExist()})
|
||||
--self:T({targetdata:getPoint()})
|
||||
local tgtcoord = COORDINATE:NewFromVec3(targetdata:getPoint())
|
||||
--tgtcoord:MarkToAll("Missile Target",true)
|
||||
|
||||
local tgtgrp1 = self.Samset:FindNearestGroupFromPointVec2(tgtcoord)
|
||||
local tgtcoord1 = tgtgrp1:GetCoordinate()
|
||||
--tgtcoord1:MarkToAll("Close target SAM",true)
|
||||
|
||||
local tgtgrp2 = self.Groupset:FindNearestGroupFromPointVec2(tgtcoord)
|
||||
local tgtcoord2 = tgtgrp2:GetCoordinate()
|
||||
--tgtcoord2:MarkToAll("Close target SHORAD",true)
|
||||
|
||||
local dist1 = tgtcoord:Get2DDistance(tgtcoord1)
|
||||
local dist2 = tgtcoord:Get2DDistance(tgtcoord2)
|
||||
|
||||
if dist1 < dist2 then
|
||||
targetunit = tgtgrp1
|
||||
targetcat = Object.Category.UNIT
|
||||
else
|
||||
targetunit = tgtgrp2
|
||||
targetcat = Object.Category.UNIT
|
||||
end
|
||||
end
|
||||
--local targetunitname = Unit.getName(targetdata) -- Unit name
|
||||
if targetunit and targetunit:IsAlive() then
|
||||
local targetunitname = targetunit:GetName()
|
||||
--local targetgroup = Unit.getGroup(Weapon.getTarget(ShootingWeapon)) --targeted group
|
||||
local targetgroup = nil
|
||||
local targetgroupname = "none"
|
||||
if targetcat == Object.Category.UNIT then
|
||||
if targetunit.ClassName == "UNIT" then
|
||||
targetgroup = targetunit:GetGroup()
|
||||
elseif targetunit.ClassName == "GROUP" then
|
||||
targetgroup = targetunit
|
||||
end
|
||||
targetgroupname = targetgroup:GetName() -- group name
|
||||
elseif targetcat == Object.Category.STATIC then
|
||||
targetgroup = targetunit
|
||||
targetgroupname = targetunitname
|
||||
end
|
||||
local text = string.format("%s Missile Target = %s", self.lid, tostring(targetgroupname))
|
||||
self:T( text )
|
||||
local m = MESSAGE:New(text,10,"Info"):ToAllIf(self.debug)
|
||||
-- check if we or a SAM site are the target
|
||||
--local TargetGroup = EventData.TgtGroup -- Wrapper.Group#GROUP
|
||||
local shotatus = self:_CheckShotAtShorad(targetgroupname) --#boolean
|
||||
local shotatsams = self:_CheckShotAtSams(targetgroupname) --#boolean
|
||||
-- if being shot at, find closest SHORADs to activate
|
||||
if shotatsams or shotatus then
|
||||
self:T({shotatsams=shotatsams,shotatus=shotatus})
|
||||
self:WakeUpShorad(targetgroupname, self.Radius, self.ActiveTimer, targetcat)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
--
|
||||
end
|
||||
-----------------------------------------------------------------------
|
||||
-- SHORAD end
|
||||
-----------------------------------------------------------------------
|
||||
File diff suppressed because it is too large
Load Diff
@@ -715,20 +715,21 @@ do -- ZONE_CAPTURE_COALITION
|
||||
|
||||
local UnitHit = EventData.TgtUnit
|
||||
|
||||
if UnitHit.ClassName ~= "SCENERY" then
|
||||
-- Check if unit is inside the capture zone and that it is of the defending coalition.
|
||||
if UnitHit and UnitHit:IsInZone(self) and UnitHit:GetCoalition()==self.Coalition then
|
||||
|
||||
-- Update last hit time.
|
||||
self.HitTimeLast=timer.getTime()
|
||||
|
||||
-- Only trigger attacked event if not already in state "Attacked".
|
||||
if self:GetState()~="Attacked" then
|
||||
self:F2("Hit ==> Attack")
|
||||
self:Attack()
|
||||
end
|
||||
|
||||
if UnitHit and UnitHit:IsInZone(self) and UnitHit:GetCoalition()==self.Coalition then
|
||||
|
||||
-- Update last hit time.
|
||||
self.HitTimeLast=timer.getTime()
|
||||
|
||||
-- Only trigger attacked event if not already in state "Attacked".
|
||||
if self:GetState()~="Attacked" then
|
||||
self:F2("Hit ==> Attack")
|
||||
self:Attack()
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
@@ -803,7 +804,7 @@ do -- ZONE_CAPTURE_COALITION
|
||||
return IsEmpty
|
||||
end
|
||||
|
||||
--- Check if zone is "Guarded", i.e. only one (the defending) coaliton is present inside the zone.
|
||||
--- 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()
|
||||
@@ -825,7 +826,7 @@ do -- ZONE_CAPTURE_COALITION
|
||||
return IsCaptured
|
||||
end
|
||||
|
||||
--- Check if zone is "Attacked", i.e. another coaliton entered the zone.
|
||||
--- 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()
|
||||
@@ -890,30 +891,31 @@ do -- ZONE_CAPTURE_COALITION
|
||||
end
|
||||
|
||||
-- Status text.
|
||||
local text=string.format("CAPTURE ZONE %s: Owner=%s (Previous=%s): #blue=%d, #red=%d, Status %s", self:GetZoneName(), self:GetCoalitionName(), UTILS.GetCoalitionName(self:GetPreviousCoalition()), nBlue, nRed, State)
|
||||
local NewState = self:GetState()
|
||||
if NewState~=State then
|
||||
text=text..string.format(" --> %s", NewState)
|
||||
if false then
|
||||
local text=string.format("CAPTURE ZONE %s: Owner=%s (Previous=%s): #blue=%d, #red=%d, Status %s", self:GetZoneName(), self:GetCoalitionName(), UTILS.GetCoalitionName(self:GetPreviousCoalition()), nBlue, nRed, State)
|
||||
local NewState = self:GetState()
|
||||
if NewState~=State then
|
||||
text=text..string.format(" --> %s", NewState)
|
||||
end
|
||||
self:I(text)
|
||||
end
|
||||
self:I(text)
|
||||
|
||||
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- 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)
|
||||
@@ -921,21 +923,21 @@ do -- ZONE_CAPTURE_COALITION
|
||||
if self.MarkBlue then
|
||||
Coord:RemoveMark(self.MarkBlue)
|
||||
end
|
||||
|
||||
-- Create new marks for each coaliton.
|
||||
|
||||
-- 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.MarkBlue = Coord:MarkToCoalitionBlue( "Coalition: Blue\nGuard Zone: " .. ZoneName .. "\nStatus: " .. State )
|
||||
self.MarkRed = Coord:MarkToCoalitionRed( "Coalition: Blue\nCapture Zone: " .. ZoneName .. "\nStatus: " .. State )
|
||||
elseif self.Coalition == coalition.side.RED then
|
||||
self.MarkRed = Coord:MarkToCoalitionRed( "Coalition: Red\nGuard Zone: " .. ZoneName .. "\nStatus: " .. State )
|
||||
self.MarkRed = Coord:MarkToCoalitionRed( "Coalition: Red\nGuard Zone: " .. ZoneName .. "\nStatus: " .. State )
|
||||
self.MarkBlue = Coord:MarkToCoalitionBlue( "Coalition: Red\nCapture Zone: " .. ZoneName .. "\nStatus: " .. State )
|
||||
else
|
||||
self.MarkRed = Coord:MarkToCoalitionRed( "Coalition: Neutral\nCapture Zone: " .. ZoneName .. "\nStatus: " .. State )
|
||||
self.MarkRed = Coord:MarkToCoalitionRed( "Coalition: Neutral\nCapture Zone: " .. ZoneName .. "\nStatus: " .. State )
|
||||
self.MarkBlue = Coord:MarkToCoalitionBlue( "Coalition: Neutral\nCapture Zone: " .. ZoneName .. "\nStatus: " .. State )
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
--- **Functional (WIP)** -- Base class that models processes to achieve goals involving a Zone for a Coalition.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ZONE_GOAL_COALITION models processes that have a Goal with a defined achievement involving a Zone for a Coalition.
|
||||
--
|
||||
-- ZONE_GOAL_COALITION models processes that have a Goal with a defined achievement involving a Zone for a Coalition.
|
||||
-- Derived classes implement the ways how the achievements can be realized.
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- @module Functional.ZoneGoalCoalition
|
||||
-- @image MOOSE.JPG
|
||||
|
||||
@@ -24,34 +24,33 @@ do -- ZoneGoal
|
||||
-- @field #table ObjectCategories Table of object categories that are able to hold a zone. Default is UNITS and STATICS.
|
||||
-- @extends Functional.ZoneGoal#ZONE_GOAL
|
||||
|
||||
|
||||
--- ZONE_GOAL_COALITION models processes that have a Goal with a defined achievement involving a Zone for a Coalition.
|
||||
--- ZONE_GOAL_COALITION models processes that have a Goal with a defined achievement involving a Zone for a Coalition.
|
||||
-- Derived classes implement the ways how the achievements can be realized.
|
||||
--
|
||||
--
|
||||
-- ## 1. ZONE_GOAL_COALITION constructor
|
||||
--
|
||||
--
|
||||
-- * @{#ZONE_GOAL_COALITION.New}(): Creates a new ZONE_GOAL_COALITION object.
|
||||
--
|
||||
--
|
||||
-- ## 2. ZONE_GOAL_COALITION is a finite state machine (FSM).
|
||||
--
|
||||
--
|
||||
-- ### 2.1 ZONE_GOAL_COALITION States
|
||||
--
|
||||
--
|
||||
-- ### 2.2 ZONE_GOAL_COALITION Events
|
||||
--
|
||||
--
|
||||
-- ### 2.3 ZONE_GOAL_COALITION State Machine
|
||||
--
|
||||
--
|
||||
-- @field #ZONE_GOAL_COALITION
|
||||
ZONE_GOAL_COALITION = {
|
||||
ClassName = "ZONE_GOAL_COALITION",
|
||||
Coalition = nil,
|
||||
PreviousCoaliton = nil,
|
||||
UnitCategories = nil,
|
||||
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.
|
||||
@@ -59,33 +58,32 @@ do -- ZoneGoal
|
||||
-- @param #table UnitCategories Table of unit categories. See [DCS Class Unit](https://wiki.hoggitworld.com/view/DCS_Class_Unit). Default {Unit.Category.GROUND_UNIT}.
|
||||
-- @return #ZONE_GOAL_COALITION
|
||||
function ZONE_GOAL_COALITION:New( Zone, Coalition, UnitCategories )
|
||||
|
||||
|
||||
if not Zone then
|
||||
BASE:E("ERROR: No Zone specified in ZONE_GOAL_COALITON!")
|
||||
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 } )
|
||||
|
||||
-- Set initial owner.
|
||||
self:SetCoalition( Coalition or coalition.side.NEUTRAL)
|
||||
|
||||
self:SetCoalition( Coalition or coalition.side.NEUTRAL )
|
||||
|
||||
-- Set default unit and object categories for the zone scan.
|
||||
self:SetUnitCategories(UnitCategories)
|
||||
self:SetUnitCategories( UnitCategories )
|
||||
self:SetObjectCategories()
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Set the owning coalition of the zone.
|
||||
-- @param #ZONE_GOAL_COALITION self
|
||||
-- @param DCSCoalition.DCSCoalition#coalition Coalition The coalition ID, e.g. *coalition.side.RED*.
|
||||
-- @return #ZONE_GOAL_COALITION
|
||||
function ZONE_GOAL_COALITION:SetCoalition( Coalition )
|
||||
self.PreviousCoalition=self.Coalition or Coalition
|
||||
self.PreviousCoalition = self.Coalition or Coalition
|
||||
self.Coalition = Coalition
|
||||
return self
|
||||
end
|
||||
@@ -95,31 +93,31 @@ do -- ZoneGoal
|
||||
-- @param #table UnitCategories Table of unit categories. See [DCS Class Unit](https://wiki.hoggitworld.com/view/DCS_Class_Unit). Default {Unit.Category.GROUND_UNIT}.
|
||||
-- @return #ZONE_GOAL_COALITION
|
||||
function ZONE_GOAL_COALITION:SetUnitCategories( UnitCategories )
|
||||
|
||||
if UnitCategories and type(UnitCategories)~="table" then
|
||||
UnitCategories={UnitCategories}
|
||||
|
||||
if UnitCategories and type( UnitCategories ) ~= "table" then
|
||||
UnitCategories = { UnitCategories }
|
||||
end
|
||||
|
||||
self.UnitCategories=UnitCategories or {Unit.Category.GROUND_UNIT}
|
||||
|
||||
|
||||
self.UnitCategories = UnitCategories or { Unit.Category.GROUND_UNIT }
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Set the owning coalition of the zone.
|
||||
-- @param #ZONE_GOAL_COALITION self
|
||||
-- @param #table ObjectCategories Table of unit categories. See [DCS Class Object](https://wiki.hoggitworld.com/view/DCS_Class_Object). Default {Object.Category.UNIT, Object.Category.STATIC}, i.e. all UNITS and STATICS.
|
||||
-- @return #ZONE_GOAL_COALITION
|
||||
function ZONE_GOAL_COALITION:SetObjectCategories( ObjectCategories )
|
||||
|
||||
if ObjectCategories and type(ObjectCategories)~="table" then
|
||||
ObjectCategories={ObjectCategories}
|
||||
|
||||
if ObjectCategories and type( ObjectCategories ) ~= "table" then
|
||||
ObjectCategories = { ObjectCategories }
|
||||
end
|
||||
|
||||
self.ObjectCategories=ObjectCategories or {Object.Category.UNIT, Object.Category.STATIC}
|
||||
|
||||
|
||||
self.ObjectCategories = ObjectCategories or { Object.Category.UNIT, Object.Category.STATIC }
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Get the owning coalition of the zone.
|
||||
-- @param #ZONE_GOAL_COALITION self
|
||||
-- @return DCSCoalition.DCSCoalition#coalition Coalition.
|
||||
@@ -127,39 +125,37 @@ do -- ZoneGoal
|
||||
return self.Coalition
|
||||
end
|
||||
|
||||
--- Get the previous coaliton, i.e. the one owning the zone before the current one.
|
||||
--- 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()
|
||||
return UTILS.GetCoalitionName(self.Coalition)
|
||||
return UTILS.GetCoalitionName( self.Coalition )
|
||||
end
|
||||
|
||||
|
||||
--- Check status Coalition ownership.
|
||||
-- @param #ZONE_GOAL_COALITION self
|
||||
-- @return #ZONE_GOAL_COALITION
|
||||
function ZONE_GOAL_COALITION:StatusZone()
|
||||
|
||||
|
||||
-- Get current state.
|
||||
local State = self:GetState()
|
||||
|
||||
|
||||
-- Debug text.
|
||||
local text=string.format("Zone state=%s, Owner=%s, Scanning...", State, self:GetCoalitionName())
|
||||
self:F(text)
|
||||
|
||||
local text = string.format( "Zone state=%s, Owner=%s, Scanning...", State, self:GetCoalitionName() )
|
||||
self:F( text )
|
||||
|
||||
-- Scan zone.
|
||||
self:Scan( self.ObjectCategories, self.UnitCategories )
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
-- The order of the declarations is important here. Don't touch it.
|
||||
|
||||
--- GLOBALS: The order of the declarations is important here. Don't touch it.
|
||||
|
||||
--- Declare the event dispatcher based on the EVENT class
|
||||
_EVENTDISPATCHER = EVENT:New() -- Core.Event#EVENT
|
||||
@@ -10,9 +9,38 @@ _SCHEDULEDISPATCHER = SCHEDULEDISPATCHER:New() -- Core.ScheduleDispatcher#SCHEDU
|
||||
--- Declare the main database object, which is used internally by the MOOSE classes.
|
||||
_DATABASE = DATABASE:New() -- Core.Database#DATABASE
|
||||
|
||||
--- Settings
|
||||
_SETTINGS = SETTINGS:Set()
|
||||
_SETTINGS:SetPlayerMenuOn()
|
||||
|
||||
--- Register cargos.
|
||||
_DATABASE:_RegisterCargos()
|
||||
_DATABASE:_RegisterZones()
|
||||
|
||||
--- Register zones.
|
||||
_DATABASE:_RegisterZones()
|
||||
_DATABASE:_RegisterAirbases()
|
||||
|
||||
--- Check if os etc is available.
|
||||
BASE:I("Checking de-sanitization of os, io and lfs:")
|
||||
local __na=false
|
||||
if os then
|
||||
BASE:I("- os available")
|
||||
else
|
||||
BASE:I("- os NOT available! Some functions may not work.")
|
||||
__na=true
|
||||
end
|
||||
if io then
|
||||
BASE:I("- io available")
|
||||
else
|
||||
BASE:I("- io NOT available! Some functions may not work.")
|
||||
__na=true
|
||||
end
|
||||
if lfs then
|
||||
BASE:I("- lfs available")
|
||||
else
|
||||
BASE:I("- lfs NOT available! Some functions may not work.")
|
||||
__na=true
|
||||
end
|
||||
if __na then
|
||||
BASE:I("Check <DCS install folder>/Scripts/MissionScripting.lua and comment out the lines with sanitizeModule(''). Use at your own risk!)")
|
||||
end
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
__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/Utilities/Socket.lua' )
|
||||
|
||||
__Moose.Include( 'Scripts/Moose/Core/Base.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/Astar.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/Beacon.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/Condition.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/UserFlag.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/UserSound.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/Report.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/Scheduler.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/ScheduleDispatcher.lua' )
|
||||
@@ -19,13 +26,13 @@ __Moose.Include( 'Scripts/Moose/Core/Point.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/Velocity.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/Message.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/Fsm.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/Radio.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/RadioQueue.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/RadioSpeech.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/Spawn.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/SpawnStatic.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/Timer.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/Goal.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/Spot.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/MarkerOps_Base.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Core/TextAndSound.lua' )
|
||||
|
||||
__Moose.Include( 'Scripts/Moose/Wrapper/Object.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Wrapper/Identifiable.lua' )
|
||||
@@ -37,6 +44,7 @@ __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' )
|
||||
@@ -64,11 +72,15 @@ __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' )
|
||||
@@ -95,16 +107,25 @@ __Moose.Include( 'Scripts/Moose/AI/AI_Cargo.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/AI/AI_Cargo_APC.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/AI/AI_Cargo_Helicopter.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/AI/AI_Cargo_Airplane.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/AI/AI_Cargo_Ship.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/AI/AI_Cargo_Dispatcher.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/AI/AI_Cargo_Dispatcher_APC.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/AI/AI_Cargo_Dispatcher_Helicopter.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/AI/AI_Cargo_Dispatcher_Airplane.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/AI/AI_Cargo_Dispatcher_Ship.lua' )
|
||||
|
||||
__Moose.Include( 'Scripts/Moose/Actions/Act_Assign.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Actions/Act_Route.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Actions/Act_Account.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Actions/Act_Assist.lua' )
|
||||
|
||||
__Moose.Include( 'Scripts/Moose/Sound/UserSound.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Sound/SoundOutput.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Sound/Radio.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Sound/RadioQueue.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Sound/RadioSpeech.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Sound/SRS.lua' )
|
||||
|
||||
__Moose.Include( 'Scripts/Moose/Tasking/CommandCenter.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Tasking/Mission.lua' )
|
||||
__Moose.Include( 'Scripts/Moose/Tasking/Task.lua' )
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
2340
Moose Development/Moose/Ops/CSAR.lua
Normal file
2340
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
@@ -31,7 +31,7 @@
|
||||
-- @field #string tankergroupname Name of the late activated tanker template group.
|
||||
-- @field Wrapper.Group#GROUP tanker Tanker group.
|
||||
-- @field Wrapper.Airbase#AIRBASE airbase The home airbase object of the tanker. Normally the aircraft carrier.
|
||||
-- @field Core.Radio#BEACON beacon Tanker TACAN beacon.
|
||||
-- @field Core.Beacon#BEACON beacon Tanker TACAN beacon.
|
||||
-- @field #number TACANchannel TACAN channel. Default 1.
|
||||
-- @field #string TACANmode TACAN mode, i.e. "X" or "Y". Default "Y". Use only "Y" for AA TACAN stations!
|
||||
-- @field #string TACANmorse TACAN morse code. Three letters identifying the TACAN station. Default "TKR".
|
||||
@@ -784,10 +784,11 @@ end
|
||||
-- @param #RECOVERYTANKER self
|
||||
-- @param #number channel TACAN channel. Default 1.
|
||||
-- @param #string morse TACAN morse code identifier. Three letters. Default "TKR".
|
||||
-- @param #string mode TACAN mode, which can be either "Y" (default) or "X".
|
||||
-- @return #RECOVERYTANKER self
|
||||
function RECOVERYTANKER:SetTACAN(channel, morse)
|
||||
function RECOVERYTANKER:SetTACAN(channel, morse, mode)
|
||||
self.TACANchannel=channel or 1
|
||||
self.TACANmode="Y"
|
||||
self.TACANmode=mode or "Y"
|
||||
self.TACANmorse=morse or "TKR"
|
||||
self.TACANon=true
|
||||
return self
|
||||
@@ -1345,7 +1346,6 @@ function RECOVERYTANKER:OnEventEngineShutdown(EventData)
|
||||
group:InitModex(self.modex)
|
||||
|
||||
-- Respawn tanker. Delaying respawn due to DCS bug https://github.com/FlightControl-Master/MOOSE/issues/1076
|
||||
--SCHEDULER:New(nil , group.RespawnAtCurrentAirbase, {group}, 1)
|
||||
self:ScheduleOnce(1, GROUP.RespawnAtCurrentAirbase, group)
|
||||
|
||||
-- Create tanker beacon and activate TACAN.
|
||||
@@ -1364,7 +1364,6 @@ function RECOVERYTANKER:OnEventEngineShutdown(EventData)
|
||||
end
|
||||
|
||||
-- Initial route.
|
||||
--SCHEDULER:New(nil, self._InitRoute, {self, -self.distStern+UTILS.NMToMeters(3)}, 2)
|
||||
self:ScheduleOnce(2, RECOVERYTANKER._InitRoute, self, -self.distStern+UTILS.NMToMeters(3))
|
||||
end
|
||||
|
||||
@@ -1627,7 +1626,6 @@ function RECOVERYTANKER:_ActivateTACAN(delay)
|
||||
if delay and delay>0 then
|
||||
|
||||
-- Schedule TACAN activation.
|
||||
--SCHEDULER:New(nil, self._ActivateTACAN, {self}, delay)
|
||||
self:ScheduleOnce(delay, RECOVERYTANKER._ActivateTACAN, self)
|
||||
|
||||
else
|
||||
|
||||
@@ -802,7 +802,7 @@ function RESCUEHELO:_OnEventCrashOrEject(EventData)
|
||||
-- Debug.
|
||||
local text=string.format("Unit %s crashed or ejected.", unitname)
|
||||
MESSAGE:New(text, 10, "DEBUG"):ToAllIf(self.Debug)
|
||||
self:I(self.lid..text)
|
||||
self:T(self.lid..text)
|
||||
|
||||
-- Get coordinate of unit.
|
||||
local coord=unit:GetCoordinate()
|
||||
@@ -876,8 +876,8 @@ function RESCUEHELO:onafterStart(From, Event, To)
|
||||
local Spawn=SPAWN:NewWithAlias(self.helogroupname, self.alias)
|
||||
|
||||
-- Set modex for spawn.
|
||||
Spawn:InitModex(self.modex)
|
||||
|
||||
Spawn:InitModex(self.modex)
|
||||
|
||||
-- Spawn in air or at airbase.
|
||||
if self.takeoff==SPAWN.Takeoff.Air then
|
||||
|
||||
|
||||
@@ -1,31 +1,28 @@
|
||||
--- **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.
|
||||
--- **Sound** - Radio transmissions.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ## Features:
|
||||
--
|
||||
-- * Provide radio functionality to broadcast radio transmissions.
|
||||
--
|
||||
-- The Radio contains 2 classes : RADIO and BEACON
|
||||
--
|
||||
-- What are radio communications in DCS?
|
||||
--
|
||||
--
|
||||
-- * Radio transmissions consist of **sound files** that are broadcasted on a specific **frequency** (e.g. 115MHz) and **modulation** (e.g. AM),
|
||||
-- * They can be **subtitled** for a specific **duration**, the **power** in Watts of the transmiter's antenna can be set, and the transmission can be **looped**.
|
||||
--
|
||||
--
|
||||
-- How to supply DCS my own Sound Files?
|
||||
--
|
||||
--
|
||||
-- * Your sound files need to be encoded in **.ogg** or .wav,
|
||||
-- * Your sound files should be **as tiny as possible**. It is suggested you encode in .ogg with low bitrate and sampling settings,
|
||||
-- * They need to be added in .\l10n\DEFAULT\ in you .miz file (wich can be decompressed like a .zip file),
|
||||
-- * For simplicity sake, you can **let DCS' Mission Editor add the file** itself, by creating a new Trigger with the action "Sound to Country", and choosing your sound file and a country you don't use in your mission.
|
||||
--
|
||||
--
|
||||
-- Due to weird DCS quirks, **radio communications behave differently** if sent by a @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP} or by any other @{Wrapper.Positionable#POSITIONABLE}
|
||||
--
|
||||
--
|
||||
-- * If the transmitter is a @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP}, DCS will set the power of the transmission automatically,
|
||||
-- * If the transmitter is any other @{Wrapper.Positionable#POSITIONABLE}, the transmisison can't be subtitled or looped.
|
||||
--
|
||||
--
|
||||
-- Note that obviously, the **frequency** and the **modulation** of the transmission are important only if the players are piloting an **Advanced System Modelling** enabled aircraft,
|
||||
-- like the A10C or the Mirage 2000C. They will **hear the transmission** if they are tuned on the **right frequency and modulation** (and if they are close enough - more on that below).
|
||||
-- If an FC3 aircraft is used, it will **hear every communication, whatever the frequency and the modulation** is set to. The same is true for TACAN beacons. If your aircraft isn't compatible,
|
||||
@@ -35,46 +32,46 @@
|
||||
--
|
||||
-- ### Authors: Hugues "Grey_Echo" Bousquet, funkyfranky
|
||||
--
|
||||
-- @module Core.Radio
|
||||
-- @module Sound.Radio
|
||||
-- @image Core_Radio.JPG
|
||||
|
||||
|
||||
--- Models the radio capability.
|
||||
--
|
||||
-- ## RADIO usage
|
||||
--
|
||||
--- *It's not true I had nothing on, I had the radio on.* -- Marilyn Monroe
|
||||
--
|
||||
-- # RADIO usage
|
||||
--
|
||||
-- There are 3 steps to a successful radio transmission.
|
||||
--
|
||||
--
|
||||
-- * First, you need to **"add a @{#RADIO} object** to your @{Wrapper.Positionable#POSITIONABLE}. This is done using the @{Wrapper.Positionable#POSITIONABLE.GetRadio}() function,
|
||||
-- * Then, you will **set the relevant parameters** to the transmission (see below),
|
||||
-- * When done, you can actually **broadcast the transmission** (i.e. play the sound) with the @{RADIO.Broadcast}() function.
|
||||
--
|
||||
--
|
||||
-- Methods to set relevant parameters for both a @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP} or any other @{Wrapper.Positionable#POSITIONABLE}
|
||||
--
|
||||
--
|
||||
-- * @{#RADIO.SetFileName}() : Sets the file name of your sound file (e.g. "Noise.ogg"),
|
||||
-- * @{#RADIO.SetFrequency}() : Sets the frequency of your transmission.
|
||||
-- * @{#RADIO.SetModulation}() : Sets the modulation of your transmission.
|
||||
-- * @{#RADIO.SetLoop}() : Choose if you want the transmission to be looped. If you need your transmission to be looped, you might need a @{#BEACON} instead...
|
||||
--
|
||||
--
|
||||
-- Additional Methods to set relevant parameters if the transmitter is a @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP}
|
||||
--
|
||||
--
|
||||
-- * @{#RADIO.SetSubtitle}() : Set both the subtitle and its duration,
|
||||
-- * @{#RADIO.NewUnitTransmission}() : Shortcut to set all the relevant parameters in one method call
|
||||
--
|
||||
--
|
||||
-- Additional Methods to set relevant parameters if the transmitter is any other @{Wrapper.Positionable#POSITIONABLE}
|
||||
--
|
||||
--
|
||||
-- * @{#RADIO.SetPower}() : Sets the power of the antenna in Watts
|
||||
-- * @{#RADIO.NewGenericTransmission}() : Shortcut to set all the relevant parameters in one method call
|
||||
--
|
||||
--
|
||||
-- What is this power thing?
|
||||
--
|
||||
--
|
||||
-- * If your transmission is sent by a @{Wrapper.Positionable#POSITIONABLE} other than a @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP}, you can set the power of the antenna,
|
||||
-- * Otherwise, DCS sets it automatically, depending on what's available on your Unit,
|
||||
-- * If the player gets **too far** from the transmitter, or if the antenna is **too weak**, the transmission will **fade** and **become noisyer**,
|
||||
-- * This an automated DCS calculation you have no say on,
|
||||
-- * For reference, a standard VOR station has a 100 W antenna, a standard AA TACAN has a 120 W antenna, and civilian ATC's antenna usually range between 300 and 500 W,
|
||||
-- * Note that if the transmission has a subtitle, it will be readable, regardless of the quality of the transmission.
|
||||
--
|
||||
-- * Note that if the transmission has a subtitle, it will be readable, regardless of the quality of the transmission.
|
||||
--
|
||||
-- @type RADIO
|
||||
-- @field Wrapper.Controllable#CONTROLLABLE Positionable The @{#CONTROLLABLE} that will transmit the radio calls.
|
||||
-- @field #string FileName Name of the sound file played.
|
||||
@@ -87,15 +84,15 @@
|
||||
-- @field #string alias Name of the radio transmitter.
|
||||
-- @extends Core.Base#BASE
|
||||
RADIO = {
|
||||
ClassName = "RADIO",
|
||||
FileName = "",
|
||||
Frequency = 0,
|
||||
Modulation = radio.modulation.AM,
|
||||
Subtitle = "",
|
||||
ClassName = "RADIO",
|
||||
FileName = "",
|
||||
Frequency = 0,
|
||||
Modulation = radio.modulation.AM,
|
||||
Subtitle = "",
|
||||
SubtitleDuration = 0,
|
||||
Power = 100,
|
||||
Loop = false,
|
||||
alias=nil,
|
||||
Power = 100,
|
||||
Loop = false,
|
||||
alias = nil,
|
||||
}
|
||||
|
||||
--- Create a new RADIO Object. This doesn't broadcast a transmission, though, use @{#RADIO.Broadcast} to actually broadcast.
|
||||
@@ -108,12 +105,12 @@ function RADIO:New(Positionable)
|
||||
-- Inherit base
|
||||
local self = BASE:Inherit( self, BASE:New() ) -- Core.Radio#RADIO
|
||||
self:F(Positionable)
|
||||
|
||||
|
||||
if Positionable:GetPointVec2() then -- It's stupid, but the only way I found to make sure positionable is valid
|
||||
self.Positionable = Positionable
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
self:E({error="The passed positionable is invalid, no RADIO created!", positionable=Positionable})
|
||||
return nil
|
||||
end
|
||||
@@ -140,19 +137,19 @@ end
|
||||
-- @return #RADIO self
|
||||
function RADIO:SetFileName(FileName)
|
||||
self:F2(FileName)
|
||||
|
||||
|
||||
if type(FileName) == "string" then
|
||||
|
||||
|
||||
if FileName:find(".ogg") or FileName:find(".wav") then
|
||||
if not FileName:find("l10n/DEFAULT/") then
|
||||
FileName = "l10n/DEFAULT/" .. FileName
|
||||
end
|
||||
|
||||
|
||||
self.FileName = FileName
|
||||
return self
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
self:E({"File name invalid. Maybe something wrong with the extension?", FileName})
|
||||
return self
|
||||
end
|
||||
@@ -160,39 +157,39 @@ end
|
||||
--- Set the frequency for the radio transmission.
|
||||
-- If the transmitting positionable is a unit or group, this also set the command "SetFrequency" with the defined frequency and modulation.
|
||||
-- @param #RADIO self
|
||||
-- @param #number Frequency Frequency in MHz. Ranges allowed for radio transmissions in DCS : 30-87.995 / 108-173.995 / 225-399.975MHz.
|
||||
-- @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
|
||||
|
||||
--if (Frequency >= 30 and Frequency <= 87.995) or (Frequency >= 108 and Frequency <= 173.995) or (Frequency >= 225 and Frequency <= 399.975) then
|
||||
|
||||
-- Convert frequency from MHz to Hz
|
||||
self.Frequency = Frequency * 1000000
|
||||
|
||||
|
||||
-- If the RADIO is attached to a UNIT or a GROUP, we need to send the DCS Command "SetFrequency" to change the UNIT or GROUP frequency
|
||||
if self.Positionable.ClassName == "UNIT" or self.Positionable.ClassName == "GROUP" then
|
||||
|
||||
|
||||
local commandSetFrequency={
|
||||
id = "SetFrequency",
|
||||
params = {
|
||||
frequency = self.Frequency,
|
||||
modulation = self.Modulation,
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
self:T2(commandSetFrequency)
|
||||
self.Positionable:SetCommand(commandSetFrequency)
|
||||
end
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
--end
|
||||
end
|
||||
|
||||
self:E({"Frequency is outside of DCS Frequency ranges (30-80, 108-152, 225-400). Frequency unchanged.", Frequency})
|
||||
|
||||
self:E({"Frequency is not a number. Frequency unchanged.", Frequency})
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -218,13 +215,13 @@ end
|
||||
-- @return #RADIO self
|
||||
function RADIO:SetPower(Power)
|
||||
self:F2(Power)
|
||||
|
||||
|
||||
if type(Power) == "number" then
|
||||
self.Power = math.floor(math.abs(Power)) --TODO Find what is the maximum power allowed by DCS and limit power to that
|
||||
else
|
||||
self:E({"Power is invalid. Power unchanged.", self.Power})
|
||||
end
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -252,7 +249,7 @@ end
|
||||
-- -- create the broadcaster and attaches it a RADIO
|
||||
-- local MyUnit = UNIT:FindByName("MyUnit")
|
||||
-- local MyUnitRadio = MyUnit:GetRadio()
|
||||
--
|
||||
--
|
||||
-- -- add a subtitle for the next transmission, which will be up for 10s
|
||||
-- MyUnitRadio:SetSubtitle("My Subtitle, 10)
|
||||
function RADIO:SetSubtitle(Subtitle, SubtitleDuration)
|
||||
@@ -267,14 +264,14 @@ function RADIO:SetSubtitle(Subtitle, SubtitleDuration)
|
||||
self.SubtitleDuration = SubtitleDuration
|
||||
else
|
||||
self.SubtitleDuration = 0
|
||||
self:E({"SubtitleDuration is invalid. SubtitleDuration reset.", self.SubtitleDuration})
|
||||
self:E({"SubtitleDuration is invalid. SubtitleDuration reset.", self.SubtitleDuration})
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Create a new transmission, that is to say, populate the RADIO with relevant data
|
||||
-- In this function the data is especially relevant if the broadcaster is anything but a UNIT or a GROUP,
|
||||
-- but it will work with a UNIT or a GROUP anyway.
|
||||
-- but it will work with a UNIT or a GROUP anyway.
|
||||
-- Only the #RADIO and the Filename are mandatory
|
||||
-- @param #RADIO self
|
||||
-- @param #string FileName Name of the sound file that will be transmitted.
|
||||
@@ -284,20 +281,20 @@ end
|
||||
-- @return #RADIO self
|
||||
function RADIO:NewGenericTransmission(FileName, Frequency, Modulation, Power, Loop)
|
||||
self:F({FileName, Frequency, Modulation, Power})
|
||||
|
||||
|
||||
self:SetFileName(FileName)
|
||||
if Frequency then self:SetFrequency(Frequency) end
|
||||
if Modulation then self:SetModulation(Modulation) end
|
||||
if Power then self:SetPower(Power) end
|
||||
if Loop then self:SetLoop(Loop) end
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Create a new transmission, that is to say, populate the RADIO with relevant data
|
||||
-- In this function the data is especially relevant if the broadcaster is a UNIT or a GROUP,
|
||||
-- but it will work for any @{Wrapper.Positionable#POSITIONABLE}.
|
||||
-- but it will work for any @{Wrapper.Positionable#POSITIONABLE}.
|
||||
-- Only the RADIO and the Filename are mandatory.
|
||||
-- @param #RADIO self
|
||||
-- @param #string FileName Name of sound file.
|
||||
@@ -319,20 +316,20 @@ function RADIO:NewUnitTransmission(FileName, Subtitle, SubtitleDuration, Frequen
|
||||
end
|
||||
|
||||
-- Set frequency.
|
||||
if Frequency then
|
||||
if Frequency then
|
||||
self:SetFrequency(Frequency)
|
||||
end
|
||||
|
||||
|
||||
-- Set subtitle.
|
||||
if Subtitle then
|
||||
self:SetSubtitle(Subtitle, SubtitleDuration or 0)
|
||||
end
|
||||
|
||||
|
||||
-- Set Looping.
|
||||
if Loop then
|
||||
if Loop then
|
||||
self:SetLoop(Loop)
|
||||
end
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -349,7 +346,7 @@ end
|
||||
-- @return #RADIO self
|
||||
function RADIO:Broadcast(viatrigger)
|
||||
self:F({viatrigger=viatrigger})
|
||||
|
||||
|
||||
-- If the POSITIONABLE is actually a UNIT or a GROUP, use the more complicated DCS command system.
|
||||
if (self.Positionable.ClassName=="UNIT" or self.Positionable.ClassName=="GROUP") and (not viatrigger) then
|
||||
self:T("Broadcasting from a UNIT or a GROUP")
|
||||
@@ -362,7 +359,7 @@ function RADIO:Broadcast(viatrigger)
|
||||
subtitle = self.Subtitle,
|
||||
loop = self.Loop,
|
||||
}}
|
||||
|
||||
|
||||
self:T3(commandTransmitMessage)
|
||||
self.Positionable:SetCommand(commandTransmitMessage)
|
||||
else
|
||||
@@ -371,7 +368,7 @@ function RADIO:Broadcast(viatrigger)
|
||||
self:T("Broadcasting from a POSITIONABLE")
|
||||
trigger.action.radioTransmission(self.FileName, self.Positionable:GetPositionVec3(), self.Modulation, self.Loop, self.Frequency, self.Power, tostring(self.ID))
|
||||
end
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -383,11 +380,11 @@ end
|
||||
-- @return #RADIO self
|
||||
function RADIO:StopBroadcast()
|
||||
self:F()
|
||||
-- If the POSITIONABLE is a UNIT or a GROUP, stop the transmission with the DCS "StopTransmission" command
|
||||
-- If the POSITIONABLE is a UNIT or a GROUP, stop the transmission with the DCS "StopTransmission" command
|
||||
if self.Positionable.ClassName == "UNIT" or self.Positionable.ClassName == "GROUP" then
|
||||
|
||||
|
||||
local commandStopTransmission={id="StopTransmission", params={}}
|
||||
|
||||
|
||||
self.Positionable:SetCommand(commandStopTransmission)
|
||||
else
|
||||
-- Else, we use the appropriate singleton funciton
|
||||
@@ -395,409 +392,3 @@ function RADIO:StopBroadcast()
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- After attaching a @{#BEACON} to your @{Wrapper.Positionable#POSITIONABLE}, you need to select the right function to activate the kind of beacon you want.
|
||||
-- There are two types of BEACONs available : the AA TACAN Beacon and the general purpose Radio Beacon.
|
||||
-- Note that in both case, you can set an optional parameter : the `BeaconDuration`. This can be very usefull to simulate the battery time if your BEACON is
|
||||
-- attach to a cargo crate, for exemple.
|
||||
--
|
||||
-- ## AA TACAN Beacon usage
|
||||
--
|
||||
-- This beacon only works with airborne @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP}. Use @{#BEACON:AATACAN}() to set the beacon parameters and start the beacon.
|
||||
-- Use @#BEACON:StopAATACAN}() to stop it.
|
||||
--
|
||||
-- ## General Purpose Radio Beacon usage
|
||||
--
|
||||
-- This beacon will work with any @{Wrapper.Positionable#POSITIONABLE}, but **it won't follow the @{Wrapper.Positionable#POSITIONABLE}** ! This means that you should only use it with
|
||||
-- @{Wrapper.Positionable#POSITIONABLE} that don't move, or move very slowly. Use @{#BEACON:RadioBeacon}() to set the beacon parameters and start the beacon.
|
||||
-- Use @{#BEACON:StopRadioBeacon}() to stop it.
|
||||
--
|
||||
-- @type BEACON
|
||||
-- @field #string ClassName Name of the class "BEACON".
|
||||
-- @field Wrapper.Controllable#CONTROLLABLE Positionable The @{#CONTROLLABLE} that will receive radio capabilities.
|
||||
-- @extends Core.Base#BASE
|
||||
BEACON = {
|
||||
ClassName = "BEACON",
|
||||
Positionable = nil,
|
||||
name=nil,
|
||||
}
|
||||
|
||||
--- Beacon types supported by DCS.
|
||||
-- @type BEACON.Type
|
||||
-- @field #number NULL
|
||||
-- @field #number VOR
|
||||
-- @field #number DME
|
||||
-- @field #number VOR_DME
|
||||
-- @field #number TACAN
|
||||
-- @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 NAUTICAL_HOMER
|
||||
-- @field #number ICLS
|
||||
BEACON.Type={
|
||||
NULL = 0,
|
||||
VOR = 1,
|
||||
DME = 2,
|
||||
VOR_DME = 3,
|
||||
TACAN = 4,
|
||||
VORTAC = 5,
|
||||
RSBN = 32,
|
||||
BROADCAST_STATION = 1024,
|
||||
HOMER = 8,
|
||||
AIRPORT_HOMER = 4104,
|
||||
AIRPORT_HOMER_WITH_MARKER = 4136,
|
||||
ILS_FAR_HOMER = 16408,
|
||||
ILS_NEAR_HOMER = 16456,
|
||||
ILS_LOCALIZER = 16640,
|
||||
ILS_GLIDESLOPE = 16896,
|
||||
NAUTICAL_HOMER = 32776,
|
||||
ICLS = 131584,
|
||||
}
|
||||
|
||||
--- Beacon systems supported by DCS. https://wiki.hoggitworld.com/view/DCS_command_activateBeacon
|
||||
-- @type BEACON.System
|
||||
-- @field #number PAR_10
|
||||
-- @field #number RSBN_5
|
||||
-- @field #number TACAN
|
||||
-- @field #number TACAN_TANKER
|
||||
-- @field #number ILS_LOCALIZER (This is the one to be used for AA TACAN Tanker!)
|
||||
-- @field #number ILS_GLIDESLOPE
|
||||
-- @field #number BROADCAST_STATION
|
||||
BEACON.System={
|
||||
PAR_10 = 1,
|
||||
RSBN_5 = 2,
|
||||
TACAN = 3,
|
||||
TACAN_TANKER = 4,
|
||||
ILS_LOCALIZER = 5,
|
||||
ILS_GLIDESLOPE = 6,
|
||||
BROADCAST_STATION = 7,
|
||||
}
|
||||
|
||||
--- Create a new BEACON Object. This doesn't activate the beacon, though, use @{#BEACON.ActivateTACAN} etc.
|
||||
-- If you want to create a BEACON, you probably should use @{Wrapper.Positionable#POSITIONABLE.GetBeacon}() instead.
|
||||
-- @param #BEACON self
|
||||
-- @param Wrapper.Positionable#POSITIONABLE Positionable The @{Positionable} that will receive radio capabilities.
|
||||
-- @return #BEACON Beacon object or #nil if the positionable is invalid.
|
||||
function BEACON:New(Positionable)
|
||||
|
||||
-- Inherit BASE.
|
||||
local self=BASE:Inherit(self, BASE:New()) --#BEACON
|
||||
|
||||
-- Debug.
|
||||
self:F(Positionable)
|
||||
|
||||
-- Set positionable.
|
||||
if Positionable:GetPointVec2() then -- It's stupid, but the only way I found to make sure positionable is valid
|
||||
self.Positionable = Positionable
|
||||
self.name=Positionable:GetName()
|
||||
self:I(string.format("New BEACON %s", tostring(self.name)))
|
||||
return self
|
||||
end
|
||||
|
||||
self:E({"The passed positionable is invalid, no BEACON created", Positionable})
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
--- Activates a TACAN BEACON.
|
||||
-- @param #BEACON self
|
||||
-- @param #number Channel TACAN channel, i.e. the "10" part in "10Y".
|
||||
-- @param #string Mode TACAN mode, i.e. the "Y" part in "10Y".
|
||||
-- @param #string Message The Message that is going to be coded in Morse and broadcasted by the beacon.
|
||||
-- @param #boolean Bearing If true, beacon provides bearing information. If false (or nil), only distance information is available.
|
||||
-- @param #number Duration How long will the beacon last in seconds. Omit for forever.
|
||||
-- @return #BEACON self
|
||||
-- @usage
|
||||
-- -- Let's create a TACAN Beacon for a tanker
|
||||
-- local myUnit = UNIT:FindByName("MyUnit")
|
||||
-- local myBeacon = myUnit:GetBeacon() -- Creates the beacon
|
||||
--
|
||||
-- myBeacon:ActivateTACAN(20, "Y", "TEXACO", true) -- Activate the beacon
|
||||
function BEACON:ActivateTACAN(Channel, Mode, Message, Bearing, Duration)
|
||||
self:T({channel=Channel, mode=Mode, callsign=Message, bearing=Bearing, duration=Duration})
|
||||
|
||||
-- Get frequency.
|
||||
local Frequency=UTILS.TACANToFrequency(Channel, Mode)
|
||||
|
||||
-- Check.
|
||||
if not Frequency then
|
||||
self:E({"The passed TACAN channel is invalid, the BEACON is not emitting"})
|
||||
return self
|
||||
end
|
||||
|
||||
-- Beacon type.
|
||||
local Type=BEACON.Type.TACAN
|
||||
|
||||
-- Beacon system.
|
||||
local System=BEACON.System.TACAN
|
||||
|
||||
-- Check if unit is an aircraft and set system accordingly.
|
||||
local AA=self.Positionable:IsAir()
|
||||
if AA then
|
||||
System=5 --NOTE: 5 is how you cat the correct tanker behaviour! --BEACON.System.TACAN_TANKER
|
||||
-- Check if "Y" mode is selected for aircraft.
|
||||
if Mode~="Y" then
|
||||
self:E({"WARNING: The POSITIONABLE you want to attach the AA Tacan Beacon is an aircraft: Mode should Y !The BEACON is not emitting.", self.Positionable})
|
||||
end
|
||||
end
|
||||
|
||||
-- Attached unit.
|
||||
local UnitID=self.Positionable:GetID()
|
||||
|
||||
-- Debug.
|
||||
self:I({string.format("BEACON Activating TACAN %s: Channel=%d%s, Morse=%s, Bearing=%s, Duration=%s!", tostring(self.name), Channel, Mode, Message, tostring(Bearing), tostring(Duration))})
|
||||
|
||||
-- Start beacon.
|
||||
self.Positionable:CommandActivateBeacon(Type, System, Frequency, UnitID, Channel, Mode, AA, Message, Bearing)
|
||||
|
||||
-- Stop sheduler.
|
||||
if Duration then
|
||||
self.Positionable:DeactivateBeacon(Duration)
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Activates an ICLS BEACON. The unit the BEACON is attached to should be an aircraft carrier supporting this system.
|
||||
-- @param #BEACON self
|
||||
-- @param #number Channel ICLS channel.
|
||||
-- @param #string Callsign The Message that is going to be coded in Morse and broadcasted by the beacon.
|
||||
-- @param #number Duration How long will the beacon last in seconds. Omit for forever.
|
||||
-- @return #BEACON self
|
||||
function BEACON:ActivateICLS(Channel, Callsign, Duration)
|
||||
self:F({Channel=Channel, Callsign=Callsign, Duration=Duration})
|
||||
|
||||
-- Attached unit.
|
||||
local UnitID=self.Positionable:GetID()
|
||||
|
||||
-- Debug
|
||||
self:T2({"ICLS BEACON started!"})
|
||||
|
||||
-- Start beacon.
|
||||
self.Positionable:CommandActivateICLS(Channel, UnitID, Callsign)
|
||||
|
||||
-- Stop sheduler
|
||||
if Duration then -- Schedule the stop of the BEACON if asked by the MD
|
||||
self.Positionable:DeactivateBeacon(Duration)
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
--- Activates a TACAN BEACON on an Aircraft.
|
||||
-- @param #BEACON self
|
||||
-- @param #number TACANChannel (the "10" part in "10Y"). Note that AA TACAN are only available on Y Channels
|
||||
-- @param #string Message The Message that is going to be coded in Morse and broadcasted by the beacon
|
||||
-- @param #boolean Bearing Can the BEACON be homed on ?
|
||||
-- @param #number BeaconDuration How long will the beacon last in seconds. Omit for forever.
|
||||
-- @return #BEACON self
|
||||
-- @usage
|
||||
-- -- Let's create a TACAN Beacon for a tanker
|
||||
-- local myUnit = UNIT:FindByName("MyUnit")
|
||||
-- local myBeacon = myUnit:GetBeacon() -- Creates the beacon
|
||||
--
|
||||
-- myBeacon:AATACAN(20, "TEXACO", true) -- Activate the beacon
|
||||
function BEACON:AATACAN(TACANChannel, Message, Bearing, BeaconDuration)
|
||||
self:F({TACANChannel, Message, Bearing, BeaconDuration})
|
||||
|
||||
local IsValid = true
|
||||
|
||||
if not self.Positionable:IsAir() then
|
||||
self:E({"The POSITIONABLE you want to attach the AA Tacan Beacon is not an aircraft ! The BEACON is not emitting", self.Positionable})
|
||||
IsValid = false
|
||||
end
|
||||
|
||||
local Frequency = self:_TACANToFrequency(TACANChannel, "Y")
|
||||
if not Frequency then
|
||||
self:E({"The passed TACAN channel is invalid, the BEACON is not emitting"})
|
||||
IsValid = false
|
||||
end
|
||||
|
||||
-- I'm using the beacon type 4 (BEACON_TYPE_TACAN). For System, I'm using 5 (TACAN_TANKER_MODE_Y) if the bearing shows its bearing
|
||||
-- or 14 (TACAN_AA_MODE_Y) if it does not
|
||||
local System
|
||||
if Bearing then
|
||||
System = 5
|
||||
else
|
||||
System = 14
|
||||
end
|
||||
|
||||
if IsValid then -- Starts the BEACON
|
||||
self:T2({"AA TACAN BEACON started !"})
|
||||
self.Positionable:SetCommand({
|
||||
id = "ActivateBeacon",
|
||||
params = {
|
||||
type = 4,
|
||||
system = System,
|
||||
callsign = Message,
|
||||
frequency = Frequency,
|
||||
}
|
||||
})
|
||||
|
||||
if BeaconDuration then -- Schedule the stop of the BEACON if asked by the MD
|
||||
SCHEDULER:New(nil,
|
||||
function()
|
||||
self:StopAATACAN()
|
||||
end, {}, BeaconDuration)
|
||||
end
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Stops the AA TACAN BEACON
|
||||
-- @param #BEACON self
|
||||
-- @return #BEACON self
|
||||
function BEACON:StopAATACAN()
|
||||
self:F()
|
||||
if not self.Positionable then
|
||||
self:E({"Start the beacon first before stoping it !"})
|
||||
else
|
||||
self.Positionable:SetCommand({
|
||||
id = 'DeactivateBeacon',
|
||||
params = {
|
||||
}
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- Activates a general pupose Radio Beacon
|
||||
-- This uses the very generic singleton function "trigger.action.radioTransmission()" provided by DCS to broadcast a sound file on a specific frequency.
|
||||
-- Although any frequency could be used, only 2 DCS Modules can home on radio beacons at the time of writing : the Huey and the Mi-8.
|
||||
-- They can home in on these specific frequencies :
|
||||
-- * **Mi8**
|
||||
-- * R-828 -> 20-60MHz
|
||||
-- * ARKUD -> 100-150MHz (canal 1 : 114166, canal 2 : 114333, canal 3 : 114583, canal 4 : 121500, canal 5 : 123100, canal 6 : 124100) AM
|
||||
-- * ARK9 -> 150-1300KHz
|
||||
-- * **Huey**
|
||||
-- * AN/ARC-131 -> 30-76 Mhz FM
|
||||
-- @param #BEACON self
|
||||
-- @param #string FileName The name of the audio file
|
||||
-- @param #number Frequency in MHz
|
||||
-- @param #number Modulation either radio.modulation.AM or radio.modulation.FM
|
||||
-- @param #number Power in W
|
||||
-- @param #number BeaconDuration How long will the beacon last in seconds. Omit for forever.
|
||||
-- @return #BEACON self
|
||||
-- @usage
|
||||
-- -- Let's create a beacon for a unit in distress.
|
||||
-- -- Frequency will be 40MHz FM (home-able by a Huey's AN/ARC-131)
|
||||
-- -- The beacon they use is battery-powered, and only lasts for 5 min
|
||||
-- local UnitInDistress = UNIT:FindByName("Unit1")
|
||||
-- local UnitBeacon = UnitInDistress:GetBeacon()
|
||||
--
|
||||
-- -- Set the beacon and start it
|
||||
-- UnitBeacon:RadioBeacon("MySoundFileSOS.ogg", 40, radio.modulation.FM, 20, 5*60)
|
||||
function BEACON:RadioBeacon(FileName, Frequency, Modulation, Power, BeaconDuration)
|
||||
self:F({FileName, Frequency, Modulation, Power, BeaconDuration})
|
||||
local IsValid = false
|
||||
|
||||
-- Check the filename
|
||||
if type(FileName) == "string" then
|
||||
if FileName:find(".ogg") or FileName:find(".wav") then
|
||||
if not FileName:find("l10n/DEFAULT/") then
|
||||
FileName = "l10n/DEFAULT/" .. FileName
|
||||
end
|
||||
IsValid = true
|
||||
end
|
||||
end
|
||||
if not IsValid then
|
||||
self:E({"File name invalid. Maybe something wrong with the extension ? ", FileName})
|
||||
end
|
||||
|
||||
-- Check the Frequency
|
||||
if type(Frequency) ~= "number" and IsValid then
|
||||
self:E({"Frequency invalid. ", Frequency})
|
||||
IsValid = false
|
||||
end
|
||||
Frequency = Frequency * 1000000 -- Conversion to Hz
|
||||
|
||||
-- Check the modulation
|
||||
if Modulation ~= radio.modulation.AM and Modulation ~= radio.modulation.FM and IsValid then --TODO Maybe make this future proof if ED decides to add an other modulation ?
|
||||
self:E({"Modulation is invalid. Use DCS's enum radio.modulation.", Modulation})
|
||||
IsValid = false
|
||||
end
|
||||
|
||||
-- Check the Power
|
||||
if type(Power) ~= "number" and IsValid then
|
||||
self:E({"Power is invalid. ", Power})
|
||||
IsValid = false
|
||||
end
|
||||
Power = math.floor(math.abs(Power)) --TODO Find what is the maximum power allowed by DCS and limit power to that
|
||||
|
||||
if IsValid then
|
||||
self:T2({"Activating Beacon on ", Frequency, Modulation})
|
||||
-- Note that this is looped. I have to give this transmission a unique name, I use the class ID
|
||||
trigger.action.radioTransmission(FileName, self.Positionable:GetPositionVec3(), Modulation, true, Frequency, Power, tostring(self.ID))
|
||||
|
||||
if BeaconDuration then -- Schedule the stop of the BEACON if asked by the MD
|
||||
SCHEDULER:New( nil,
|
||||
function()
|
||||
self:StopRadioBeacon()
|
||||
end, {}, BeaconDuration)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Stops the AA TACAN BEACON
|
||||
-- @param #BEACON self
|
||||
-- @return #BEACON self
|
||||
function BEACON:StopRadioBeacon()
|
||||
self:F()
|
||||
-- The unique name of the transmission is the class ID
|
||||
trigger.action.stopRadioTransmission(tostring(self.ID))
|
||||
return self
|
||||
end
|
||||
|
||||
--- Converts a TACAN Channel/Mode couple into a frequency in Hz
|
||||
-- @param #BEACON self
|
||||
-- @param #number TACANChannel
|
||||
-- @param #string TACANMode
|
||||
-- @return #number Frequecy
|
||||
-- @return #nil if parameters are invalid
|
||||
function BEACON:_TACANToFrequency(TACANChannel, TACANMode)
|
||||
self:F3({TACANChannel, TACANMode})
|
||||
|
||||
if type(TACANChannel) ~= "number" then
|
||||
if TACANMode ~= "X" and TACANMode ~= "Y" then
|
||||
return nil -- error in arguments
|
||||
end
|
||||
end
|
||||
|
||||
-- This code is largely based on ED's code, in DCS World\Scripts\World\Radio\BeaconTypes.lua, line 137.
|
||||
-- I have no idea what it does but it seems to work
|
||||
local A = 1151 -- 'X', channel >= 64
|
||||
local B = 64 -- channel >= 64
|
||||
|
||||
if TACANChannel < 64 then
|
||||
B = 1
|
||||
end
|
||||
|
||||
if TACANMode == 'Y' then
|
||||
A = 1025
|
||||
if TACANChannel < 64 then
|
||||
A = 1088
|
||||
end
|
||||
else -- 'X'
|
||||
if TACANChannel < 64 then
|
||||
A = 962
|
||||
end
|
||||
end
|
||||
|
||||
return (A + TACANChannel - B) * 1000000
|
||||
end
|
||||
|
||||
|
||||
@@ -1,23 +1,27 @@
|
||||
--- **Core** - Queues Radio Transmissions.
|
||||
--- **Sound** - Queues Radio Transmissions.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ## Features:
|
||||
--
|
||||
-- * Managed Radio Transmissions.
|
||||
-- * Manage Radio Transmissions
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Authors: funkyfranky
|
||||
--
|
||||
-- @module Core.RadioQueue
|
||||
-- @module Sound.RadioQueue
|
||||
-- @image Core_Radio.JPG
|
||||
|
||||
--- Manages radio transmissions.
|
||||
--
|
||||
-- The main goal of the RADIOQUEUE class is to string together multiple sound files to play a complete sentence.
|
||||
-- The underlying problem is that radio transmissions in DCS are not queued but played "on top" of each other.
|
||||
-- Therefore, to achive the goal, it is vital to know the precise duration how long it takes to play the sound file.
|
||||
--
|
||||
-- @type RADIOQUEUE
|
||||
-- @field #string ClassName Name of the class "RADIOQUEUE".
|
||||
-- @field #boolean Debug Debug mode. More info.
|
||||
-- @field #boolean Debugmode Debug mode. More info.
|
||||
-- @field #string lid ID for dcs.log.
|
||||
-- @field #number frequency The radio frequency in Hz.
|
||||
-- @field #number modulation The radio modulation. Either radio.modulation.AM or radio.modulation.FM.
|
||||
@@ -35,10 +39,11 @@
|
||||
-- @field #table numbers Table of number transmission parameters.
|
||||
-- @field #boolean checking Scheduler is checking the radio queue.
|
||||
-- @field #boolean schedonce Call ScheduleOnce instead of normal scheduler.
|
||||
-- @field Sound.SRS#MSRS msrs Moose SRS class.
|
||||
-- @extends Core.Base#BASE
|
||||
RADIOQUEUE = {
|
||||
ClassName = "RADIOQUEUE",
|
||||
Debug = nil,
|
||||
Debugmode = nil,
|
||||
lid = nil,
|
||||
frequency = nil,
|
||||
modulation = nil,
|
||||
@@ -55,7 +60,7 @@ RADIOQUEUE = {
|
||||
power = nil,
|
||||
numbers = {},
|
||||
checking = nil,
|
||||
schedonce = nil,
|
||||
schedonce = false,
|
||||
}
|
||||
|
||||
--- Radio queue transmission data.
|
||||
@@ -69,12 +74,14 @@ RADIOQUEUE = {
|
||||
-- @field #boolean isplaying If true, transmission is currently playing.
|
||||
-- @field #number Tplay Mission time (abs) in seconds when the transmission should be played.
|
||||
-- @field #number interval Interval in seconds before next transmission.
|
||||
-- @field Sound.SoundOutput#SOUNDFILE soundfile Sound file object to play via SRS.
|
||||
-- @field Sound.SoundOutput#SOUNDTEXT soundtext Sound TTS object to play via SRS.
|
||||
|
||||
|
||||
--- Create a new RADIOQUEUE object for a given radio frequency/modulation.
|
||||
-- @param #RADIOQUEUE self
|
||||
-- @param #number frequency The radio frequency in MHz.
|
||||
-- @param #number modulation (Optional) The radio modulation. Default radio.modulation.AM.
|
||||
-- @param #number modulation (Optional) The radio modulation. Default `radio.modulation.AM` (=0).
|
||||
-- @param #string alias (Optional) Name of the radio queue.
|
||||
-- @return #RADIOQUEUE self The RADIOQUEUE object.
|
||||
function RADIOQUEUE:New(frequency, modulation, alias)
|
||||
@@ -125,9 +132,9 @@ function RADIOQUEUE:Start(delay, dt)
|
||||
|
||||
-- Start Scheduler.
|
||||
if self.schedonce then
|
||||
self:_CheckRadioQueueDelayed(delay)
|
||||
self:_CheckRadioQueueDelayed(self.delay)
|
||||
else
|
||||
self.RQid=self.scheduler:Schedule(nil, RADIOQUEUE._CheckRadioQueue, {self}, delay, dt)
|
||||
self.RQid=self.scheduler:Schedule(nil, RADIOQUEUE._CheckRadioQueue, {self}, self.delay, self.dt)
|
||||
end
|
||||
|
||||
return self
|
||||
@@ -170,6 +177,17 @@ function RADIOQUEUE:SetRadioPower(power)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set SRS.
|
||||
-- @param #RADIOQUEUE self
|
||||
-- @param #string PathToSRS Path to SRS.
|
||||
-- @param #number Port SRS port. Default 5002.
|
||||
-- @return #RADIOQUEUE self The RADIOQUEUE object.
|
||||
function RADIOQUEUE:SetSRS(PathToSRS, Port)
|
||||
self.msrs=MSRS:New(PathToSRS, self.frequency/1000000, self.modulation)
|
||||
self.msrs:SetPort(Port)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set parameters of a digit.
|
||||
-- @param #RADIOQUEUE self
|
||||
-- @param #number digit The digit 0-9.
|
||||
@@ -202,7 +220,7 @@ end
|
||||
--- Add a transmission to the radio queue.
|
||||
-- @param #RADIOQUEUE self
|
||||
-- @param #RADIOQUEUE.Transmission transmission The transmission data table.
|
||||
-- @return #RADIOQUEUE self The RADIOQUEUE object.
|
||||
-- @return #RADIOQUEUE self
|
||||
function RADIOQUEUE:AddTransmission(transmission)
|
||||
self:F({transmission=transmission})
|
||||
|
||||
@@ -221,7 +239,7 @@ function RADIOQUEUE:AddTransmission(transmission)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Add a transmission to the radio queue.
|
||||
--- Create a new transmission and add it to the radio queue.
|
||||
-- @param #RADIOQUEUE self
|
||||
-- @param #string filename Name of the sound file. Usually an ogg or wav file type.
|
||||
-- @param #number duration Duration in seconds the file lasts.
|
||||
@@ -230,7 +248,7 @@ end
|
||||
-- @param #number interval Interval in seconds after the last transmission finished.
|
||||
-- @param #string subtitle Subtitle of the transmission.
|
||||
-- @param #number subduration Duration [sec] of the subtitle being displayed. Default 5 sec.
|
||||
-- @return #RADIOQUEUE self The RADIOQUEUE object.
|
||||
-- @return #RADIOQUEUE.Transmission Radio transmission table.
|
||||
function RADIOQUEUE:NewTransmission(filename, duration, path, tstart, interval, subtitle, subduration)
|
||||
|
||||
-- Sanity checks.
|
||||
@@ -269,9 +287,36 @@ function RADIOQUEUE:NewTransmission(filename, duration, path, tstart, interval,
|
||||
-- Add transmission to queue.
|
||||
self:AddTransmission(transmission)
|
||||
|
||||
return transmission
|
||||
end
|
||||
|
||||
--- Add a SOUNDFILE to the radio queue.
|
||||
-- @param #RADIOQUEUE self
|
||||
-- @param Sound.SoundOutput#SOUNDFILE soundfile Sound file object to be added.
|
||||
-- @param #number tstart Start time (abs) seconds. Default now.
|
||||
-- @param #number interval Interval in seconds after the last transmission finished.
|
||||
-- @return #RADIOQUEUE self
|
||||
function RADIOQUEUE:AddSoundFile(soundfile, tstart, interval)
|
||||
--env.info(string.format("FF add soundfile: name=%s%s", soundfile:GetPath(), soundfile:GetFileName()))
|
||||
local transmission=self:NewTransmission(soundfile:GetFileName(), soundfile.duration, soundfile:GetPath(), tstart, interval, soundfile.subtitle, soundfile.subduration)
|
||||
transmission.soundfile=soundfile
|
||||
return self
|
||||
end
|
||||
|
||||
--- Add a SOUNDTEXT to the radio queue.
|
||||
-- @param #RADIOQUEUE self
|
||||
-- @param Sound.SoundOutput#SOUNDTEXT soundtext Text-to-speech text.
|
||||
-- @param #number tstart Start time (abs) seconds. Default now.
|
||||
-- @param #number interval Interval in seconds after the last transmission finished.
|
||||
-- @return #RADIOQUEUE self
|
||||
function RADIOQUEUE:AddSoundText(soundtext, tstart, interval)
|
||||
|
||||
local transmission=self:NewTransmission("SoundText.ogg", soundtext.duration, nil, tstart, interval, soundtext.subtitle, soundtext.subduration)
|
||||
transmission.soundtext=soundtext
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Convert a number (as string) into a radio transmission.
|
||||
-- E.g. for board number or headings.
|
||||
-- @param #RADIOQUEUE self
|
||||
@@ -280,19 +325,9 @@ end
|
||||
-- @param #number interval Interval between the next call.
|
||||
-- @return #number Duration of the call in seconds.
|
||||
function RADIOQUEUE:Number2Transmission(number, delay, interval)
|
||||
|
||||
--- Split string into characters.
|
||||
local function _split(str)
|
||||
local chars={}
|
||||
for i=1,#str do
|
||||
local c=str:sub(i,i)
|
||||
table.insert(chars, c)
|
||||
end
|
||||
return chars
|
||||
end
|
||||
|
||||
-- Split string into characters.
|
||||
local numbers=_split(number)
|
||||
local numbers=UTILS.GetCharacters(number)
|
||||
|
||||
local wait=0
|
||||
for i=1,#numbers do
|
||||
@@ -325,6 +360,11 @@ end
|
||||
-- @param #RADIOQUEUE.Transmission transmission The transmission.
|
||||
function RADIOQUEUE:Broadcast(transmission)
|
||||
|
||||
if ((transmission.soundfile and transmission.soundfile.useSRS) or transmission.soundtext) and self.msrs then
|
||||
self:_BroadcastSRS(transmission)
|
||||
return
|
||||
end
|
||||
|
||||
-- Get unit sending the transmission.
|
||||
local sender=self:_GetRadioSender()
|
||||
|
||||
@@ -375,8 +415,10 @@ function RADIOQUEUE:Broadcast(transmission)
|
||||
sender:SetCommand(commandTransmit)
|
||||
|
||||
-- Debug message.
|
||||
local text=string.format("file=%s, freq=%.2f MHz, duration=%.2f sec, subtitle=%s", filename, self.frequency/1000000, transmission.duration, transmission.subtitle or "")
|
||||
MESSAGE:New(text, 2, "RADIOQUEUE "..self.alias):ToAllIf(self.Debug)
|
||||
if self.Debugmode then
|
||||
local text=string.format("file=%s, freq=%.2f MHz, duration=%.2f sec, subtitle=%s", filename, self.frequency/1000000, transmission.duration, transmission.subtitle or "")
|
||||
MESSAGE:New(text, 2, "RADIOQUEUE "..self.alias):ToAll()
|
||||
end
|
||||
|
||||
else
|
||||
|
||||
@@ -388,10 +430,7 @@ function RADIOQUEUE:Broadcast(transmission)
|
||||
|
||||
-- Try to get positon from sender unit/static.
|
||||
if self.sendername then
|
||||
local coord=self:_GetRadioSenderCoord()
|
||||
if coord then
|
||||
vec3=coord:GetVec3()
|
||||
end
|
||||
vec3=self:_GetRadioSenderCoord()
|
||||
end
|
||||
|
||||
-- Try to get fixed positon.
|
||||
@@ -408,13 +447,28 @@ function RADIOQUEUE:Broadcast(transmission)
|
||||
trigger.action.radioTransmission(filename, vec3, self.modulation, false, self.frequency, self.power)
|
||||
|
||||
-- Debug message.
|
||||
local text=string.format("file=%s, freq=%.2f MHz, duration=%.2f sec, subtitle=%s", filename, self.frequency/1000000, transmission.duration, transmission.subtitle or "")
|
||||
MESSAGE:New(string.format(text, filename, transmission.duration, transmission.subtitle or ""), 5, "RADIOQUEUE "..self.alias):ToAllIf(self.Debug)
|
||||
if self.Debugmode then
|
||||
local text=string.format("file=%s, freq=%.2f MHz, duration=%.2f sec, subtitle=%s", filename, self.frequency/1000000, transmission.duration, transmission.subtitle or "")
|
||||
MESSAGE:New(string.format(text, filename, transmission.duration, transmission.subtitle or ""), 5, "RADIOQUEUE "..self.alias):ToAll()
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
--- Broadcast radio message.
|
||||
-- @param #RADIOQUEUE self
|
||||
-- @param #RADIOQUEUE.Transmission transmission The transmission.
|
||||
function RADIOQUEUE:_BroadcastSRS(transmission)
|
||||
|
||||
if transmission.soundfile and transmission.soundfile.useSRS then
|
||||
self.msrs:PlaySoundFile(transmission.soundfile)
|
||||
elseif transmission.soundtext then
|
||||
self.msrs:PlaySoundText(transmission.soundtext)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Start checking the radio queue.
|
||||
-- @param #RADIOQUEUE self
|
||||
-- @param #number delay Delay in seconds before checking.
|
||||
@@ -524,7 +578,7 @@ end
|
||||
|
||||
--- Get unit from which we want to transmit a radio message. This has to be an aircraft for subtitles to work.
|
||||
-- @param #RADIOQUEUE self
|
||||
-- @return Wrapper.Unit#UNIT Sending aircraft unit or nil if was not setup, is not an aircraft or is not alive.
|
||||
-- @return Wrapper.Unit#UNIT Sending unit or nil if was not setup, is not an aircraft or ground unit or is not alive.
|
||||
function RADIOQUEUE:_GetRadioSender()
|
||||
|
||||
-- Check if we have a sending aircraft.
|
||||
@@ -532,11 +586,12 @@ function RADIOQUEUE:_GetRadioSender()
|
||||
|
||||
-- Try the general default.
|
||||
if self.sendername then
|
||||
|
||||
-- First try to find a unit
|
||||
sender=UNIT:FindByName(self.sendername)
|
||||
|
||||
-- Check that sender is alive and an aircraft.
|
||||
if sender and sender:IsAlive() and sender:IsAir() then
|
||||
if sender and sender:IsAlive() and (sender:IsAir() or sender:IsGround()) then
|
||||
return sender
|
||||
end
|
||||
|
||||
@@ -545,9 +600,9 @@ function RADIOQUEUE:_GetRadioSender()
|
||||
return nil
|
||||
end
|
||||
|
||||
--- Get unit from which we want to transmit a radio message. This has to be an aircraft for subtitles to work.
|
||||
--- Get unit from which we want to transmit a radio message. This has to be an aircraft or ground unit for subtitles to work.
|
||||
-- @param #RADIOQUEUE self
|
||||
-- @return Core.Point#COORDINATE Coordinate of the sender unit.
|
||||
-- @return DCS#Vec3 Vector 3D.
|
||||
function RADIOQUEUE:_GetRadioSenderCoord()
|
||||
|
||||
local vec3=nil
|
||||
@@ -560,7 +615,7 @@ function RADIOQUEUE:_GetRadioSenderCoord()
|
||||
|
||||
-- Check that sender is alive and an aircraft.
|
||||
if sender and sender:IsAlive() then
|
||||
return sender:GetCoordinate()
|
||||
return sender:GetVec3()
|
||||
end
|
||||
|
||||
-- Now try a static.
|
||||
@@ -568,7 +623,7 @@ function RADIOQUEUE:_GetRadioSenderCoord()
|
||||
|
||||
-- Check that sender is alive and an aircraft.
|
||||
if sender then
|
||||
return sender:GetCoordinate()
|
||||
return sender:GetVec3()
|
||||
end
|
||||
|
||||
end
|
||||
@@ -1,9 +1,9 @@
|
||||
--- **Core** - Makes the radio talk.
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- ## Features:
|
||||
--
|
||||
--
|
||||
-- * Send text strings using a vocabulary that is converted in spoken language.
|
||||
-- * Possiblity to implement multiple language.
|
||||
--
|
||||
@@ -11,14 +11,14 @@
|
||||
--
|
||||
-- ### Authors: FlightControl
|
||||
--
|
||||
-- @module Core.RadioSpeech
|
||||
-- @module Sound.RadioSpeech
|
||||
-- @image Core_Radio.JPG
|
||||
|
||||
--- Makes the radio speak.
|
||||
--
|
||||
--
|
||||
-- # RADIOSPEECH usage
|
||||
--
|
||||
--
|
||||
--
|
||||
--
|
||||
-- @type RADIOSPEECH
|
||||
-- @extends Core.RadioQueue#RADIOQUEUE
|
||||
RADIOSPEECH = {
|
||||
@@ -59,24 +59,24 @@ RADIOSPEECH.Vocabulary.EN = {
|
||||
["70"] = { "70", 0.48 },
|
||||
["80"] = { "80", 0.26 },
|
||||
["90"] = { "90", 0.36 },
|
||||
["100"] = { "100", 0.55 },
|
||||
["200"] = { "200", 0.55 },
|
||||
["300"] = { "300", 0.61 },
|
||||
["400"] = { "400", 0.60 },
|
||||
["500"] = { "500", 0.61 },
|
||||
["600"] = { "600", 0.65 },
|
||||
["700"] = { "700", 0.70 },
|
||||
["800"] = { "800", 0.54 },
|
||||
["900"] = { "900", 0.60 },
|
||||
["1000"] = { "1000", 0.60 },
|
||||
["2000"] = { "2000", 0.61 },
|
||||
["3000"] = { "3000", 0.64 },
|
||||
["4000"] = { "4000", 0.62 },
|
||||
["5000"] = { "5000", 0.69 },
|
||||
["6000"] = { "6000", 0.69 },
|
||||
["7000"] = { "7000", 0.75 },
|
||||
["8000"] = { "8000", 0.59 },
|
||||
["9000"] = { "9000", 0.65 },
|
||||
["100"] = { "100", 0.55 },
|
||||
["200"] = { "200", 0.55 },
|
||||
["300"] = { "300", 0.61 },
|
||||
["400"] = { "400", 0.60 },
|
||||
["500"] = { "500", 0.61 },
|
||||
["600"] = { "600", 0.65 },
|
||||
["700"] = { "700", 0.70 },
|
||||
["800"] = { "800", 0.54 },
|
||||
["900"] = { "900", 0.60 },
|
||||
["1000"] = { "1000", 0.60 },
|
||||
["2000"] = { "2000", 0.61 },
|
||||
["3000"] = { "3000", 0.64 },
|
||||
["4000"] = { "4000", 0.62 },
|
||||
["5000"] = { "5000", 0.69 },
|
||||
["6000"] = { "6000", 0.69 },
|
||||
["7000"] = { "7000", 0.75 },
|
||||
["8000"] = { "8000", 0.59 },
|
||||
["9000"] = { "9000", 0.65 },
|
||||
|
||||
["chevy"] = { "chevy", 0.35 },
|
||||
["colt"] = { "colt", 0.35 },
|
||||
@@ -94,10 +94,10 @@ RADIOSPEECH.Vocabulary.EN = {
|
||||
["meters"] = { "meters", 0.41 },
|
||||
["mi"] = { "miles", 0.45 },
|
||||
["feet"] = { "feet", 0.29 },
|
||||
|
||||
|
||||
["br"] = { "br", 1.1 },
|
||||
["bra"] = { "bra", 0.3 },
|
||||
|
||||
|
||||
|
||||
["returning to base"] = { "returning_to_base", 0.85 },
|
||||
["on route to ground target"] = { "on_route_to_ground_target", 1.05 },
|
||||
@@ -143,52 +143,52 @@ RADIOSPEECH.Vocabulary.RU = {
|
||||
["70"] = { "70", 0.68 },
|
||||
["80"] = { "80", 0.84 },
|
||||
["90"] = { "90", 0.71 },
|
||||
["100"] = { "100", 0.35 },
|
||||
["200"] = { "200", 0.59 },
|
||||
["300"] = { "300", 0.53 },
|
||||
["400"] = { "400", 0.70 },
|
||||
["500"] = { "500", 0.50 },
|
||||
["600"] = { "600", 0.58 },
|
||||
["700"] = { "700", 0.64 },
|
||||
["800"] = { "800", 0.77 },
|
||||
["900"] = { "900", 0.75 },
|
||||
["1000"] = { "1000", 0.87 },
|
||||
["2000"] = { "2000", 0.83 },
|
||||
["3000"] = { "3000", 0.84 },
|
||||
["4000"] = { "4000", 1.00 },
|
||||
["5000"] = { "5000", 0.77 },
|
||||
["6000"] = { "6000", 0.90 },
|
||||
["7000"] = { "7000", 0.77 },
|
||||
["8000"] = { "8000", 0.92 },
|
||||
["9000"] = { "9000", 0.87 },
|
||||
["100"] = { "100", 0.35 },
|
||||
["200"] = { "200", 0.59 },
|
||||
["300"] = { "300", 0.53 },
|
||||
["400"] = { "400", 0.70 },
|
||||
["500"] = { "500", 0.50 },
|
||||
["600"] = { "600", 0.58 },
|
||||
["700"] = { "700", 0.64 },
|
||||
["800"] = { "800", 0.77 },
|
||||
["900"] = { "900", 0.75 },
|
||||
["1000"] = { "1000", 0.87 },
|
||||
["2000"] = { "2000", 0.83 },
|
||||
["3000"] = { "3000", 0.84 },
|
||||
["4000"] = { "4000", 1.00 },
|
||||
["5000"] = { "5000", 0.77 },
|
||||
["6000"] = { "6000", 0.90 },
|
||||
["7000"] = { "7000", 0.77 },
|
||||
["8000"] = { "8000", 0.92 },
|
||||
["9000"] = { "9000", 0.87 },
|
||||
|
||||
["степени"] = { "degrees", 0.5 },
|
||||
["километров"] = { "kilometers", 0.65 },
|
||||
["градусы"] = { "degrees", 0.5 },
|
||||
["километры"] = { "kilometers", 0.65 },
|
||||
["km"] = { "kilometers", 0.65 },
|
||||
["миль"] = { "miles", 0.45 },
|
||||
["мили"] = { "miles", 0.45 },
|
||||
["mi"] = { "miles", 0.45 },
|
||||
["метры"] = { "meters", 0.41 },
|
||||
["метров"] = { "meters", 0.41 },
|
||||
["m"] = { "meters", 0.41 },
|
||||
["ноги"] = { "feet", 0.37 },
|
||||
|
||||
|
||||
["br"] = { "br", 1.1 },
|
||||
["bra"] = { "bra", 0.3 },
|
||||
|
||||
|
||||
["возвращаясь на базу"] = { "returning_to_base", 1.40 },
|
||||
|
||||
["возвращение на базу"] = { "returning_to_base", 1.40 },
|
||||
["на пути к наземной цели"] = { "on_route_to_ground_target", 1.45 },
|
||||
["перехват самолетов"] = { "intercepting_bogeys", 1.22 },
|
||||
["перехват боги"] = { "intercepting_bogeys", 1.22 },
|
||||
["поражение наземной цели"] = { "engaging_ground_target", 1.53 },
|
||||
["захватывающие самолеты"] = { "engaging_bogeys", 1.68 },
|
||||
["колеса вверх"] = { "wheels_up", 0.92 },
|
||||
["привлечение болотных птиц"] = { "engaging_bogeys", 1.68 },
|
||||
["колёса вверх..."] = { "wheels_up", 0.92 },
|
||||
["посадка на базу"] = { "landing at base", 1.04 },
|
||||
["патрулирующий"] = { "patrolling", 0.96 },
|
||||
["патрулирование"] = { "patrolling", 0.96 },
|
||||
|
||||
["за"] = { "for", 0.27 },
|
||||
["для"] = { "for", 0.27 },
|
||||
["и"] = { "and", 0.17 },
|
||||
["в"] = { "at", 0.19 },
|
||||
["dot"] = { "dot", 0.51 },
|
||||
["defender"] = { "defender", 0.45 },
|
||||
["на сайте"] = { "at", 0.19 },
|
||||
["точка"] = { "dot", 0.51 },
|
||||
["защитник"] = { "defender", 0.45 },
|
||||
}
|
||||
|
||||
--- Create a new RADIOSPEECH object for a given radio frequency/modulation.
|
||||
@@ -200,11 +200,11 @@ function RADIOSPEECH:New(frequency, modulation)
|
||||
|
||||
-- Inherit base
|
||||
local self = BASE:Inherit( self, RADIOQUEUE:New( frequency, modulation ) ) -- #RADIOSPEECH
|
||||
|
||||
|
||||
self.Language = "EN"
|
||||
|
||||
|
||||
self:BuildTree()
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -262,7 +262,7 @@ end
|
||||
function RADIOSPEECH:BuildTree()
|
||||
|
||||
self.Speech = {}
|
||||
|
||||
|
||||
for Language, Sentences in pairs( self.Vocabulary ) do
|
||||
self:I( { Language = Language, Sentences = Sentences })
|
||||
self.Speech[Language] = {}
|
||||
@@ -271,7 +271,7 @@ function RADIOSPEECH:BuildTree()
|
||||
self:AddSentenceToSpeech( Sentence, self.Speech[Language], Sentence, Data )
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
self:I( { Speech = self.Speech } )
|
||||
|
||||
return self
|
||||
@@ -290,7 +290,7 @@ function RADIOSPEECH:SpeakWords( Sentence, Speech, Language )
|
||||
local Word, RemainderSentence = Sentence:match( "^[., ]*([^ .,]+)(.*)" )
|
||||
|
||||
self:I( { Word = Word, Speech = Speech[Word], RemainderSentence = RemainderSentence } )
|
||||
|
||||
|
||||
|
||||
if Word then
|
||||
if Word ~= "" and tonumber(Word) == nil then
|
||||
@@ -302,7 +302,7 @@ function RADIOSPEECH:SpeakWords( Sentence, Speech, Language )
|
||||
if Speech[Word].Next == nil then
|
||||
self:I( { Sentence = Speech[Word].Sentence, Data = Speech[Word].Data } )
|
||||
self:NewTransmission( Speech[Word].Data[1] .. ".wav", Speech[Word].Data[2], Language .. "/" )
|
||||
else
|
||||
else
|
||||
if RemainderSentence and RemainderSentence ~= "" then
|
||||
return self:SpeakWords( RemainderSentence, Speech[Word].Next, Language )
|
||||
end
|
||||
@@ -310,11 +310,11 @@ function RADIOSPEECH:SpeakWords( Sentence, Speech, Language )
|
||||
end
|
||||
return RemainderSentence
|
||||
end
|
||||
return OriginalSentence
|
||||
return OriginalSentence
|
||||
else
|
||||
return ""
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Speak a sentence.
|
||||
@@ -333,7 +333,7 @@ function RADIOSPEECH:SpeakDigits( Sentence, Speech, Langauge )
|
||||
|
||||
if Digits then
|
||||
if Digits ~= "" and tonumber( Digits ) ~= nil then
|
||||
|
||||
|
||||
-- Construct numbers
|
||||
local Number = tonumber( Digits )
|
||||
local Multiple = nil
|
||||
@@ -357,7 +357,7 @@ function RADIOSPEECH:SpeakDigits( Sentence, Speech, Langauge )
|
||||
end
|
||||
return RemainderSentence
|
||||
end
|
||||
return OriginalSentence
|
||||
return OriginalSentence
|
||||
else
|
||||
return ""
|
||||
end
|
||||
@@ -374,26 +374,26 @@ function RADIOSPEECH:Speak( Sentence, Language )
|
||||
self:I( { Sentence, Language } )
|
||||
|
||||
local Language = Language or "EN"
|
||||
|
||||
|
||||
self:I( { Language = Language } )
|
||||
|
||||
|
||||
-- If there is no node for Speech, then we start at the first nodes of the language.
|
||||
local Speech = self.Speech[Language]
|
||||
|
||||
|
||||
self:I( { Speech = Speech, Language = Language } )
|
||||
|
||||
|
||||
self:NewTransmission( "_In.wav", 0.52, Language .. "/" )
|
||||
|
||||
|
||||
repeat
|
||||
|
||||
Sentence = self:SpeakWords( Sentence, Speech, Language )
|
||||
|
||||
|
||||
self:I( { Sentence = Sentence } )
|
||||
|
||||
Sentence = self:SpeakDigits( Sentence, Speech, Language )
|
||||
|
||||
self:I( { Sentence = Sentence } )
|
||||
|
||||
|
||||
-- Sentence = self:SpeakSymbols( Sentence, Speech )
|
||||
--
|
||||
-- self:I( { Sentence = Sentence } )
|
||||
1201
Moose Development/Moose/Sound/SRS.lua
Normal file
1201
Moose Development/Moose/Sound/SRS.lua
Normal file
File diff suppressed because it is too large
Load Diff
412
Moose Development/Moose/Sound/SoundOutput.lua
Normal file
412
Moose Development/Moose/Sound/SoundOutput.lua
Normal file
@@ -0,0 +1,412 @@
|
||||
--- **Sound** - Sound output classes.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ## Features:
|
||||
--
|
||||
-- * Create a SOUNDFILE object (mp3 or ogg) to be played via DCS or SRS transmissions
|
||||
-- * Create a SOUNDTEXT object for text-to-speech output vis SRS Simple-Text-To-Speech (STTS)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **funkyfranky**
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- There are two classes, SOUNDFILE and SOUNDTEXT, defined in this section that deal with playing
|
||||
-- sound files or arbitrary text (via SRS Simple-Text-To-Speech), respectively.
|
||||
--
|
||||
-- The SOUNDFILE and SOUNDTEXT objects can be defined and used in other MOOSE classes.
|
||||
--
|
||||
--
|
||||
-- @module Sound.SoundOutput
|
||||
-- @image Sound_SoundOutput.png
|
||||
|
||||
do -- Sound Base
|
||||
|
||||
--- @type SOUNDBASE
|
||||
-- @field #string ClassName Name of the class.
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
|
||||
--- Basic sound output inherited by other classes suche as SOUNDFILE and SOUNDTEXT.
|
||||
--
|
||||
-- This class is **not** meant to be used by "ordinary" users.
|
||||
--
|
||||
-- @field #SOUNDBASE
|
||||
SOUNDBASE={
|
||||
ClassName = "SOUNDBASE",
|
||||
}
|
||||
|
||||
--- Constructor to create a new SOUNDBASE object.
|
||||
-- @param #SOUNDBASE self
|
||||
-- @return #SOUNDBASE self
|
||||
function SOUNDBASE:New()
|
||||
|
||||
-- Inherit BASE.
|
||||
local self=BASE:Inherit(self, BASE:New()) -- #SOUNDBASE
|
||||
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Function returns estimated speech time in seconds.
|
||||
-- Assumptions for time calc: 100 Words per min, avarage of 5 letters for english word so
|
||||
--
|
||||
-- * 5 chars * 100wpm = 500 characters per min = 8.3 chars per second
|
||||
--
|
||||
-- So lengh of msg / 8.3 = number of seconds needed to read it. rounded down to 8 chars per sec map function:
|
||||
--
|
||||
-- * (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min
|
||||
--
|
||||
-- @param #string Text The text string to analyze.
|
||||
-- @param #number Speed Speed factor. Default 1.
|
||||
-- @param #boolean isGoogle If true, google text-to-speech is used.
|
||||
function SOUNDBASE:GetSpeechTime(length,speed,isGoogle)
|
||||
|
||||
local maxRateRatio = 3
|
||||
|
||||
speed = speed or 1.0
|
||||
isGoogle = isGoogle or false
|
||||
|
||||
local speedFactor = 1.0
|
||||
if isGoogle then
|
||||
speedFactor = speed
|
||||
else
|
||||
if speed ~= 0 then
|
||||
speedFactor = math.abs(speed) * (maxRateRatio - 1) / 10 + 1
|
||||
end
|
||||
if speed < 0 then
|
||||
speedFactor = 1/speedFactor
|
||||
end
|
||||
end
|
||||
|
||||
-- Words per minute.
|
||||
local wpm = math.ceil(100 * speedFactor)
|
||||
|
||||
-- Characters per second.
|
||||
local cps = math.floor((wpm * 5)/60)
|
||||
|
||||
if type(length) == "string" then
|
||||
length = string.len(length)
|
||||
end
|
||||
|
||||
return math.ceil(length/cps)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
do -- Sound File
|
||||
|
||||
--- @type SOUNDFILE
|
||||
-- @field #string ClassName Name of the class
|
||||
-- @field #string filename Name of the flag.
|
||||
-- @field #string path Directory path, where the sound file is located. This includes the final slash "/".
|
||||
-- @field #string duration Duration of the sound file in seconds.
|
||||
-- @field #string subtitle Subtitle of the transmission.
|
||||
-- @field #number subduration Duration in seconds how long the subtitle is displayed.
|
||||
-- @field #boolean useSRS If true, sound file is played via SRS. Sound file needs to be on local disk not inside the miz file!
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
|
||||
--- Sound files used by other classes.
|
||||
--
|
||||
-- # The SOUNDFILE Concept
|
||||
--
|
||||
-- A SOUNDFILE object hold the important properties that are necessary to play the sound file, e.g. its file name, path, duration.
|
||||
--
|
||||
-- It can be created with the @{#SOUNDFILE.New}(*FileName*, *Path*, *Duration*) function:
|
||||
--
|
||||
-- local soundfile=SOUNDFILE:New("My Soundfile.ogg", "Sound File/", 3.5)
|
||||
--
|
||||
-- ## SRS
|
||||
--
|
||||
-- If sound files are supposed to be played via SRS, you need to use the @{#SOUNDFILE.SetPlayWithSRS}() function.
|
||||
--
|
||||
-- # Location/Path
|
||||
--
|
||||
-- ## DCS
|
||||
--
|
||||
-- DCS can only play sound files that are located inside the mission (.miz) file. In particular, DCS cannot make use of files that are stored on
|
||||
-- your hard drive.
|
||||
--
|
||||
-- The default location where sound files are stored in DCS is the directory "l10n/DEFAULT/". This is where sound files are placed, if they are
|
||||
-- added via the mission editor (TRIGGERS-->ACTIONS-->SOUND TO ALL). Note however, that sound files which are not added with a trigger command,
|
||||
-- will be deleted each time the mission is saved! Therefore, this directory is not ideal to be used especially if many sound files are to
|
||||
-- be included since for each file a trigger action needs to be created. Which is cumbersome, to say the least.
|
||||
--
|
||||
-- The recommended way is to create a new folder inside the mission (.miz) file (a miz file is essentially zip file and can be opened, e.g., with 7-Zip)
|
||||
-- and to place the sound files in there. Sound files in these folders are not wiped out by DCS on the next save.
|
||||
--
|
||||
-- ## SRS
|
||||
--
|
||||
-- SRS sound files need to be located on your local drive (not inside the miz). Therefore, you need to specify the full path.
|
||||
--
|
||||
-- @field #SOUNDFILE
|
||||
SOUNDFILE={
|
||||
ClassName = "SOUNDFILE",
|
||||
filename = nil,
|
||||
path = "l10n/DEFAULT/",
|
||||
duration = 3,
|
||||
subtitle = nil,
|
||||
subduration = 0,
|
||||
useSRS = false,
|
||||
}
|
||||
|
||||
--- Constructor to create a new SOUNDFILE object.
|
||||
-- @param #SOUNDFILE self
|
||||
-- @param #string FileName The name of the sound file, e.g. "Hello World.ogg".
|
||||
-- @param #string Path The path of the directory, where the sound file is located. Default is "l10n/DEFAULT/" within the miz file.
|
||||
-- @param #number Duration Duration in seconds, how long it takes to play the sound file. Default is 3 seconds.
|
||||
-- @return #SOUNDFILE self
|
||||
function SOUNDFILE:New(FileName, Path, Duration)
|
||||
|
||||
-- Inherit BASE.
|
||||
local self=BASE:Inherit(self, BASE:New()) -- #SOUNDFILE
|
||||
|
||||
-- Set file name.
|
||||
self:SetFileName(FileName)
|
||||
|
||||
-- Set path.
|
||||
self:SetPath(Path)
|
||||
|
||||
-- Set duration.
|
||||
self:SetDuration(Duration)
|
||||
|
||||
-- Debug info:
|
||||
self:T(string.format("New SOUNDFILE: file name=%s, path=%s", self.filename, self.path))
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set path, where the sound file is located.
|
||||
-- @param #SOUNDFILE self
|
||||
-- @param #string Path Path to the directory, where the sound file is located. In case this is nil, it defaults to the DCS mission temp directory.
|
||||
-- @return #SOUNDFILE self
|
||||
function SOUNDFILE:SetPath(Path)
|
||||
|
||||
-- Init path.
|
||||
self.path=Path or "l10n/DEFAULT/"
|
||||
|
||||
if not Path and self.useSRS then -- use path to mission temp dir
|
||||
self.path = os.getenv('TMP') .. "\\DCS\\Mission\\l10n\\DEFAULT"
|
||||
end
|
||||
|
||||
-- Remove (back)slashes.
|
||||
local nmax=1000 ; local n=1
|
||||
while (self.path:sub(-1)=="/" or self.path:sub(-1)==[[\]]) and n<=nmax do
|
||||
self.path=self.path:sub(1,#self.path-1)
|
||||
n=n+1
|
||||
end
|
||||
|
||||
-- Append slash.
|
||||
self.path=self.path.."/"
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get path of the directory, where the sound file is located.
|
||||
-- @param #SOUNDFILE self
|
||||
-- @return #string Path.
|
||||
function SOUNDFILE:GetPath()
|
||||
local path=self.path or "l10n/DEFAULT/"
|
||||
return path
|
||||
end
|
||||
|
||||
--- Set sound file name. This must be a .ogg or .mp3 file!
|
||||
-- @param #SOUNDFILE self
|
||||
-- @param #string FileName Name of the file. Default is "Hello World.mp3".
|
||||
-- @return #SOUNDFILE self
|
||||
function SOUNDFILE:SetFileName(FileName)
|
||||
--TODO: check that sound file is really .ogg or .mp3
|
||||
self.filename=FileName or "Hello World.mp3"
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get the sound file name.
|
||||
-- @param #SOUNDFILE self
|
||||
-- @return #string Name of the soud file. This does *not* include its path.
|
||||
function SOUNDFILE:GetFileName()
|
||||
return self.filename
|
||||
end
|
||||
|
||||
|
||||
--- Set duration how long it takes to play the sound file.
|
||||
-- @param #SOUNDFILE self
|
||||
-- @param #string Duration Duration in seconds. Default 3 seconds.
|
||||
-- @return #SOUNDFILE self
|
||||
function SOUNDFILE:SetDuration(Duration)
|
||||
self.duration=Duration or 3
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get duration how long the sound file takes to play.
|
||||
-- @param #SOUNDFILE self
|
||||
-- @return #number Duration in seconds.
|
||||
function SOUNDFILE:GetDuration()
|
||||
return self.duration or 3
|
||||
end
|
||||
|
||||
--- Get the complete sound file name inlcuding its path.
|
||||
-- @param #SOUNDFILE self
|
||||
-- @return #string Name of the sound file.
|
||||
function SOUNDFILE:GetName()
|
||||
local path=self:GetPath()
|
||||
local filename=self:GetFileName()
|
||||
local name=string.format("%s%s", path, filename)
|
||||
return name
|
||||
end
|
||||
|
||||
--- Set whether sound files should be played via SRS.
|
||||
-- @param #SOUNDFILE self
|
||||
-- @param #boolean Switch If true or nil, use SRS. If false, use DCS transmission.
|
||||
-- @return #SOUNDFILE self
|
||||
function SOUNDFILE:SetPlayWithSRS(Switch)
|
||||
if Switch==true or Switch==nil then
|
||||
self.useSRS=true
|
||||
else
|
||||
self.useSRS=false
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
do -- Text-To-Speech
|
||||
|
||||
--- @type SOUNDTEXT
|
||||
-- @field #string ClassName Name of the class
|
||||
-- @field #string text Text to speak.
|
||||
-- @field #number duration Duration in seconds.
|
||||
-- @field #string gender Gender: "male", "female".
|
||||
-- @field #string culture Culture, e.g. "en-GB".
|
||||
-- @field #string voice Specific voice to use. Overrules `gender` and `culture` settings.
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
|
||||
--- Text-to-speech objects for other classes.
|
||||
--
|
||||
-- # The SOUNDTEXT Concept
|
||||
--
|
||||
-- A SOUNDTEXT object holds all necessary information to play a general text via SRS Simple-Text-To-Speech.
|
||||
--
|
||||
-- It can be created with the @{#SOUNDTEXT.New}(*Text*, *Duration*) function.
|
||||
--
|
||||
-- * @{#SOUNDTEXT.New}(*Text, Duration*): Creates a new SOUNDTEXT object.
|
||||
--
|
||||
-- # Options
|
||||
--
|
||||
-- ## Gender
|
||||
--
|
||||
-- You can choose a gender ("male" or "femal") with the @{#SOUNDTEXT.SetGender}(*Gender*) function.
|
||||
-- Note that the gender voice needs to be installed on your windows machine for the used culture (see below).
|
||||
--
|
||||
-- ## Culture
|
||||
--
|
||||
-- You can choose a "culture" (accent) with the @{#SOUNDTEXT.SetCulture}(*Culture*) function, where the default (SRS) culture is "en-GB".
|
||||
--
|
||||
-- Other examples for culture are: "en-US" (US accent), "de-DE" (German), "it-IT" (Italian), "ru-RU" (Russian), "zh-CN" (Chinese).
|
||||
--
|
||||
-- Note that the chosen culture needs to be installed on your windows machine.
|
||||
--
|
||||
-- ## Specific Voice
|
||||
--
|
||||
-- You can use a specific voice for the transmission with the @{SOUNDTEXT.SetVoice}(*VoiceName*) function. Here are some examples
|
||||
--
|
||||
-- * Name: Microsoft Hazel Desktop, Culture: en-GB, Gender: Female, Age: Adult, Desc: Microsoft Hazel Desktop - English (Great Britain)
|
||||
-- * Name: Microsoft David Desktop, Culture: en-US, Gender: Male, Age: Adult, Desc: Microsoft David Desktop - English (United States)
|
||||
-- * Name: Microsoft Zira Desktop, Culture: en-US, Gender: Female, Age: Adult, Desc: Microsoft Zira Desktop - English (United States)
|
||||
-- * Name: Microsoft Hedda Desktop, Culture: de-DE, Gender: Female, Age: Adult, Desc: Microsoft Hedda Desktop - German
|
||||
-- * Name: Microsoft Helena Desktop, Culture: es-ES, Gender: Female, Age: Adult, Desc: Microsoft Helena Desktop - Spanish (Spain)
|
||||
-- * Name: Microsoft Hortense Desktop, Culture: fr-FR, Gender: Female, Age: Adult, Desc: Microsoft Hortense Desktop - French
|
||||
-- * Name: Microsoft Elsa Desktop, Culture: it-IT, Gender: Female, Age: Adult, Desc: Microsoft Elsa Desktop - Italian (Italy)
|
||||
-- * Name: Microsoft Irina Desktop, Culture: ru-RU, Gender: Female, Age: Adult, Desc: Microsoft Irina Desktop - Russian
|
||||
-- * Name: Microsoft Huihui Desktop, Culture: zh-CN, Gender: Female, Age: Adult, Desc: Microsoft Huihui Desktop - Chinese (Simplified)
|
||||
--
|
||||
-- Note that this must be installed on your windos machine. Also note that this overrides any culture and gender settings.
|
||||
--
|
||||
-- @field #SOUNDTEXT
|
||||
SOUNDTEXT={
|
||||
ClassName = "SOUNDTEXT",
|
||||
}
|
||||
|
||||
--- Constructor to create a new SOUNDTEXT object.
|
||||
-- @param #SOUNDTEXT self
|
||||
-- @param #string Text The text to speak.
|
||||
-- @param #number Duration Duration in seconds, how long it takes to play the text. Default is 3 seconds.
|
||||
-- @return #SOUNDTEXT self
|
||||
function SOUNDTEXT:New(Text, Duration)
|
||||
|
||||
-- Inherit BASE.
|
||||
local self=BASE:Inherit(self, BASE:New()) -- #SOUNDTEXT
|
||||
|
||||
self:SetText(Text)
|
||||
self:SetDuration(Duration or STTS.getSpeechTime(Text))
|
||||
--self:SetGender()
|
||||
--self:SetCulture()
|
||||
|
||||
-- Debug info:
|
||||
self:T(string.format("New SOUNDTEXT: text=%s, duration=%.1f sec", self.text, self.duration))
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set text.
|
||||
-- @param #SOUNDTEXT self
|
||||
-- @param #string Text Text to speak. Default "Hello World!".
|
||||
-- @return #SOUNDTEXT self
|
||||
function SOUNDTEXT:SetText(Text)
|
||||
|
||||
self.text=Text or "Hello World!"
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set duration, how long it takes to speak the text.
|
||||
-- @param #SOUNDTEXT self
|
||||
-- @param #number Duration Duration in seconds. Default 3 seconds.
|
||||
-- @return #SOUNDTEXT self
|
||||
function SOUNDTEXT:SetDuration(Duration)
|
||||
|
||||
self.duration=Duration or 3
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set gender.
|
||||
-- @param #SOUNDTEXT self
|
||||
-- @param #string Gender Gender: "male" or "female" (default).
|
||||
-- @return #SOUNDTEXT self
|
||||
function SOUNDTEXT:SetGender(Gender)
|
||||
|
||||
self.gender=Gender or "female"
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set TTS culture - local for the voice.
|
||||
-- @param #SOUNDTEXT self
|
||||
-- @param #string Culture TTS culture. Default "en-GB".
|
||||
-- @return #SOUNDTEXT self
|
||||
function SOUNDTEXT:SetCulture(Culture)
|
||||
|
||||
self.culture=Culture or "en-GB"
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set to use a specific voice name.
|
||||
-- See the list from `DCS-SR-ExternalAudio.exe --help` or if using google see [google voices](https://cloud.google.com/text-to-speech/docs/voices).
|
||||
-- @param #SOUNDTEXT self
|
||||
-- @param #string VoiceName Voice name. Note that this will overrule `Gender` and `Culture`.
|
||||
-- @return #SOUNDTEXT self
|
||||
function SOUNDTEXT:SetVoice(VoiceName)
|
||||
|
||||
self.voice=VoiceName
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
end
|
||||
@@ -1,4 +1,4 @@
|
||||
--- **Core** - Manage user sound.
|
||||
--- **Sound** - Manage user sound.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
@@ -16,7 +16,7 @@
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module Core.UserSound
|
||||
-- @module Sound.UserSound
|
||||
-- @image Core_Usersound.JPG
|
||||
|
||||
do -- UserSound
|
||||
@@ -40,7 +40,7 @@ do -- UserSound
|
||||
-- @param #USERSOUND self
|
||||
-- @param #string UserSoundFileName The filename of the usersound.
|
||||
-- @return #USERSOUND
|
||||
function USERSOUND:New( UserSoundFileName ) --R2.3
|
||||
function USERSOUND:New( UserSoundFileName )
|
||||
|
||||
local self = BASE:Inherit( self, BASE:New() ) -- #USERSOUND
|
||||
|
||||
@@ -58,7 +58,7 @@ do -- UserSound
|
||||
-- local BlueVictory = USERSOUND:New( "BlueVictory.ogg" )
|
||||
-- BlueVictory:SetFileName( "BlueVictoryLoud.ogg" ) -- Set the BlueVictory to change the file name to play a louder sound.
|
||||
--
|
||||
function USERSOUND:SetFileName( UserSoundFileName ) --R2.3
|
||||
function USERSOUND:SetFileName( UserSoundFileName )
|
||||
|
||||
self.UserSoundFileName = UserSoundFileName
|
||||
|
||||
@@ -75,7 +75,7 @@ do -- UserSound
|
||||
-- local BlueVictory = USERSOUND:New( "BlueVictory.ogg" )
|
||||
-- BlueVictory:ToAll() -- Play the sound that Blue has won.
|
||||
--
|
||||
function USERSOUND:ToAll() --R2.3
|
||||
function USERSOUND:ToAll()
|
||||
|
||||
trigger.action.outSound( self.UserSoundFileName )
|
||||
|
||||
@@ -91,7 +91,7 @@ do -- UserSound
|
||||
-- local BlueVictory = USERSOUND:New( "BlueVictory.ogg" )
|
||||
-- BlueVictory:ToCoalition( coalition.side.BLUE ) -- Play the sound that Blue has won to the blue coalition.
|
||||
--
|
||||
function USERSOUND:ToCoalition( Coalition ) --R2.3
|
||||
function USERSOUND:ToCoalition( Coalition )
|
||||
|
||||
trigger.action.outSoundForCoalition(Coalition, self.UserSoundFileName )
|
||||
|
||||
@@ -107,7 +107,7 @@ do -- UserSound
|
||||
-- local BlueVictory = USERSOUND:New( "BlueVictory.ogg" )
|
||||
-- BlueVictory:ToCountry( country.id.USA ) -- Play the sound that Blue has won to the USA country.
|
||||
--
|
||||
function USERSOUND:ToCountry( Country ) --R2.3
|
||||
function USERSOUND:ToCountry( Country )
|
||||
|
||||
trigger.action.outSoundForCountry( Country, self.UserSoundFileName )
|
||||
|
||||
@@ -123,9 +123,9 @@ do -- UserSound
|
||||
-- @usage
|
||||
-- local BlueVictory = USERSOUND:New( "BlueVictory.ogg" )
|
||||
-- local PlayerGroup = GROUP:FindByName( "PlayerGroup" ) -- Search for the active group named "PlayerGroup", that contains a human player.
|
||||
-- BlueVictory:ToGroup( PlayerGroup ) -- Play the sound that Blue has won to the player group.
|
||||
-- BlueVictory:ToGroup( PlayerGroup ) -- Play the victory sound to the player group.
|
||||
--
|
||||
function USERSOUND:ToGroup( Group, Delay ) --R2.3
|
||||
function USERSOUND:ToGroup( Group, Delay )
|
||||
|
||||
Delay=Delay or 0
|
||||
if Delay>0 then
|
||||
@@ -136,5 +136,27 @@ do -- UserSound
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Play the usersound to the given @{Wrapper.Unit}.
|
||||
-- @param #USERSOUND self
|
||||
-- @param Wrapper.Unit#UNIT Unit The @{Wrapper.Unit} to play the usersound to.
|
||||
-- @param #number Delay (Optional) Delay in seconds, before the sound is played. Default 0.
|
||||
-- @return #USERSOUND The usersound instance.
|
||||
-- @usage
|
||||
-- local BlueVictory = USERSOUND:New( "BlueVictory.ogg" )
|
||||
-- local PlayerUnit = UNIT:FindByName( "PlayerUnit" ) -- Search for the active unit named "PlayerUnit", a human player.
|
||||
-- BlueVictory:ToUnit( PlayerUnit ) -- Play the victory sound to the player unit.
|
||||
--
|
||||
function USERSOUND:ToUnit( Unit, Delay )
|
||||
|
||||
Delay=Delay or 0
|
||||
if Delay>0 then
|
||||
SCHEDULER:New(nil, USERSOUND.ToUnit,{self, Unit}, Delay)
|
||||
else
|
||||
trigger.action.outSoundForUnit( Unit:GetID(), self.UserSoundFileName )
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
end
|
||||
@@ -202,6 +202,7 @@ function COMMANDCENTER:New( CommandCenterPositionable, CommandCenterName )
|
||||
self:SetAutoAcceptTasks( true )
|
||||
self:SetAutoAssignMethod( COMMANDCENTER.AutoAssignMethods.Distance )
|
||||
self:SetFlashStatus( false )
|
||||
self:SetMessageDuration(10)
|
||||
|
||||
self:HandleEvent( EVENTS.Birth,
|
||||
--- @param #COMMANDCENTER self
|
||||
@@ -215,7 +216,7 @@ function COMMANDCENTER:New( CommandCenterPositionable, CommandCenterName )
|
||||
local MenuReporting = MENU_GROUP:New( EventGroup, "Missions Reports", CommandCenterMenu )
|
||||
local MenuMissionsSummary = MENU_GROUP_COMMAND:New( EventGroup, "Missions Status Report", MenuReporting, self.ReportSummary, self, EventGroup )
|
||||
local MenuMissionsDetails = MENU_GROUP_COMMAND:New( EventGroup, "Missions Players Report", MenuReporting, self.ReportMissionsPlayers, self, EventGroup )
|
||||
self:ReportSummary( EventGroup )
|
||||
--self:ReportSummary( EventGroup )
|
||||
local PlayerUnit = EventData.IniUnit
|
||||
for MissionID, Mission in pairs( self:GetMissions() ) do
|
||||
local Mission = Mission -- Tasking.Mission#MISSION
|
||||
@@ -559,9 +560,11 @@ function COMMANDCENTER:SetAutoAssignTasks( AutoAssign )
|
||||
self.AutoAssignTasks = AutoAssign or false
|
||||
|
||||
if self.AutoAssignTasks == true then
|
||||
self:ScheduleRepeat( 10, 30, 0, nil, self.AssignTasks, self )
|
||||
self.autoAssignTasksScheduleID=self:ScheduleRepeat( 10, 30, 0, nil, self.AssignTasks, self )
|
||||
else
|
||||
self:ScheduleStop( self.AssignTasks )
|
||||
self:ScheduleStop()
|
||||
-- FF this is not the schedule ID
|
||||
--self:ScheduleStop( self.AssignTasks )
|
||||
end
|
||||
|
||||
end
|
||||
@@ -682,7 +685,7 @@ end
|
||||
-- @param #string Message The message text.
|
||||
function COMMANDCENTER:MessageToAll( Message )
|
||||
|
||||
self:GetPositionable():MessageToAll( Message, 20, self:GetName() )
|
||||
self:GetPositionable():MessageToAll( Message, self.MessageDuration, self:GetName() )
|
||||
|
||||
end
|
||||
|
||||
@@ -692,7 +695,7 @@ end
|
||||
-- @param Wrapper.Group#GROUP MessageGroup The group to receive the message.
|
||||
function COMMANDCENTER:MessageToGroup( Message, MessageGroup )
|
||||
|
||||
self:GetPositionable():MessageToGroup( Message, 15, MessageGroup, self:GetShortText() )
|
||||
self:GetPositionable():MessageToGroup( Message, self.MessageDuration, MessageGroup, self:GetShortText() )
|
||||
|
||||
end
|
||||
|
||||
@@ -715,7 +718,7 @@ function COMMANDCENTER:MessageToCoalition( Message )
|
||||
local CCCoalition = self:GetPositionable():GetCoalition()
|
||||
--TODO: Fix coalition bug!
|
||||
|
||||
self:GetPositionable():MessageToCoalition( Message, 15, CCCoalition, self:GetShortText() )
|
||||
self:GetPositionable():MessageToCoalition( Message, self.MessageDuration, CCCoalition, self:GetShortText() )
|
||||
|
||||
end
|
||||
|
||||
@@ -795,9 +798,18 @@ end
|
||||
|
||||
--- Let the command center flash a report of the status of the subscribed task to a group.
|
||||
-- @param #COMMANDCENTER self
|
||||
-- @param Flash #boolean
|
||||
function COMMANDCENTER:SetFlashStatus( Flash )
|
||||
self:F()
|
||||
|
||||
self.FlashStatus = Flash or true
|
||||
|
||||
self.FlashStatus = Flash and true
|
||||
end
|
||||
|
||||
--- Duration a command center message is shown.
|
||||
-- @param #COMMANDCENTER self
|
||||
-- @param seconds #number
|
||||
function COMMANDCENTER:SetMessageDuration(seconds)
|
||||
self:F()
|
||||
|
||||
self.MessageDuration = 10 or seconds
|
||||
end
|
||||
|
||||
@@ -132,7 +132,7 @@ MISSION = {
|
||||
-- @param #string MissionName Name of the mission. This name will be used to reference the status of each mission by the players.
|
||||
-- @param #string MissionPriority String indicating the "priority" of the Mission. e.g. "Primary", "Secondary". It is free format and up to the Mission designer to choose. There are no rules behind this field.
|
||||
-- @param #string MissionBriefing String indicating the mission briefing to be shown when a player joins a @{CLIENT}.
|
||||
-- @param DCS#coaliton.side MissionCoalition Side of the coalition, i.e. and enumerator @{#DCS.coalition.side} corresponding to RED, BLUE or NEUTRAL.
|
||||
-- @param DCS#coalition.side MissionCoalition Side of the coalition, i.e. and enumerator @{#DCS.coalition.side} corresponding to RED, BLUE or NEUTRAL.
|
||||
-- @return #MISSION self
|
||||
function MISSION:New( CommandCenter, MissionName, MissionPriority, MissionBriefing, MissionCoalition )
|
||||
|
||||
|
||||
@@ -82,7 +82,7 @@
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- A statistic report on the progress of the mission. Each task achievement will increase the %-tage to 100% as a goal to complete the task.
|
||||
-- A statistic report on the progress of the mission. Each task achievement will increase the % to 100% as a goal to complete the task.
|
||||
--
|
||||
-- ## 1.3) Join a Task.
|
||||
--
|
||||
@@ -881,6 +881,9 @@ do -- Group Assignment
|
||||
local Mission = self:GetMission()
|
||||
local CommandCenter = Mission:GetCommandCenter()
|
||||
CommandCenter:SetMenu()
|
||||
|
||||
self:MenuFlashTaskStatus( TaskGroup, false ) -- stop message flashing, if any #1383 & #1312
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1252,7 +1255,7 @@ function TASK:MenuFlashTaskStatus( TaskGroup, Flash )
|
||||
self.FlashTaskStatus = Flash
|
||||
|
||||
if self.FlashTaskStatus then
|
||||
self.FlashTaskScheduler, self.FlashTaskScheduleID = SCHEDULER:New( self, self.MenuTaskStatus, { TaskGroup }, 0, 60 )
|
||||
self.FlashTaskScheduler, self.FlashTaskScheduleID = SCHEDULER:New( self, self.MenuTaskStatus, { TaskGroup }, 0, 60) --Issue #1383 never ending flash messages
|
||||
else
|
||||
if self.FlashTaskScheduler then
|
||||
self.FlashTaskScheduler:Stop( self.FlashTaskScheduleID )
|
||||
|
||||
@@ -344,7 +344,9 @@ function TASKINFO:Report( Report, Detail, ReportGroup, Task )
|
||||
Text = DataText
|
||||
else
|
||||
local DataText = Data.Data -- #string
|
||||
Text = DataText
|
||||
if type(DataText) == "string" then --Issue #1388 - don't just assume this is a string
|
||||
Text = DataText
|
||||
end
|
||||
end
|
||||
|
||||
if Line < math.floor( Data.Order / 10 ) then
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
--- **Tasking** - The TASK_A2A models tasks for players in Air to Air engagements.
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
--
|
||||
-- ### Contributions:
|
||||
--
|
||||
--
|
||||
-- ### Contributions:
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- @module Tasking.Task_A2A
|
||||
-- @image MOOSE.JPG
|
||||
|
||||
@@ -35,12 +35,12 @@ do -- TASK_A2A
|
||||
-- * @{#TASK_A2A.SetScoreOnDestroy}(): Set a score when a target in scope of the A2A attack, has been destroyed.
|
||||
-- * @{#TASK_A2A.SetScoreOnSuccess}(): Set a score when all the targets in scope of the A2A attack, have been destroyed.
|
||||
-- * @{#TASK_A2A.SetPenaltyOnFailed}(): Set a penalty when the A2A attack has failed.
|
||||
--
|
||||
--
|
||||
-- @field #TASK_A2A
|
||||
TASK_A2A = {
|
||||
ClassName = "TASK_A2A",
|
||||
ClassName = "TASK_A2A"
|
||||
}
|
||||
|
||||
|
||||
--- Instantiates a new TASK_A2A.
|
||||
-- @param #TASK_A2A self
|
||||
-- @param Tasking.Mission#MISSION Mission
|
||||
@@ -54,51 +54,49 @@ do -- TASK_A2A
|
||||
function TASK_A2A:New( Mission, SetAttack, TaskName, TargetSetUnit, TaskType, TaskBriefing )
|
||||
local self = BASE:Inherit( self, TASK:New( Mission, SetAttack, TaskName, TaskType, TaskBriefing ) ) -- Tasking.Task#TASK_A2A
|
||||
self:F()
|
||||
|
||||
|
||||
self.TargetSetUnit = TargetSetUnit
|
||||
self.TaskType = TaskType
|
||||
|
||||
local Fsm = self:GetUnitProcess()
|
||||
|
||||
|
||||
Fsm:AddTransition( "Assigned", "RouteToRendezVous", "RoutingToRendezVous" )
|
||||
Fsm:AddProcess ( "RoutingToRendezVous", "RouteToRendezVousPoint", ACT_ROUTE_POINT:New(), { Arrived = "ArriveAtRendezVous" } )
|
||||
Fsm:AddProcess ( "RoutingToRendezVous", "RouteToRendezVousZone", ACT_ROUTE_ZONE:New(), { Arrived = "ArriveAtRendezVous" } )
|
||||
|
||||
Fsm:AddProcess( "RoutingToRendezVous", "RouteToRendezVousPoint", ACT_ROUTE_POINT:New(), { Arrived = "ArriveAtRendezVous" } )
|
||||
Fsm:AddProcess( "RoutingToRendezVous", "RouteToRendezVousZone", ACT_ROUTE_ZONE:New(), { Arrived = "ArriveAtRendezVous" } )
|
||||
|
||||
Fsm:AddTransition( { "Arrived", "RoutingToRendezVous" }, "ArriveAtRendezVous", "ArrivedAtRendezVous" )
|
||||
|
||||
|
||||
Fsm:AddTransition( { "ArrivedAtRendezVous", "HoldingAtRendezVous" }, "Engage", "Engaging" )
|
||||
Fsm:AddTransition( { "ArrivedAtRendezVous", "HoldingAtRendezVous" }, "HoldAtRendezVous", "HoldingAtRendezVous" )
|
||||
|
||||
Fsm:AddProcess ( "Engaging", "Account", ACT_ACCOUNT_DEADS:New(), {} )
|
||||
|
||||
Fsm:AddProcess( "Engaging", "Account", ACT_ACCOUNT_DEADS:New(), {} )
|
||||
Fsm:AddTransition( "Engaging", "RouteToTarget", "Engaging" )
|
||||
Fsm:AddProcess( "Engaging", "RouteToTargetZone", ACT_ROUTE_ZONE:New(), {} )
|
||||
Fsm:AddProcess( "Engaging", "RouteToTargetPoint", ACT_ROUTE_POINT:New(), {} )
|
||||
Fsm:AddTransition( "Engaging", "RouteToTargets", "Engaging" )
|
||||
|
||||
-- Fsm:AddTransition( "Accounted", "DestroyedAll", "Accounted" )
|
||||
-- Fsm:AddTransition( "Accounted", "Success", "Success" )
|
||||
|
||||
-- Fsm:AddTransition( "Accounted", "DestroyedAll", "Accounted" )
|
||||
-- Fsm:AddTransition( "Accounted", "Success", "Success" )
|
||||
Fsm:AddTransition( "Rejected", "Reject", "Aborted" )
|
||||
Fsm:AddTransition( "Failed", "Fail", "Failed" )
|
||||
|
||||
|
||||
---- @param #FSM_PROCESS self
|
||||
-- @param #FSM_PROCESS self
|
||||
-- @param Wrapper.Unit#UNIT TaskUnit
|
||||
-- @param #TASK_CARGO Task
|
||||
function Fsm:OnLeaveAssigned( TaskUnit, Task )
|
||||
self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } )
|
||||
|
||||
|
||||
self:SelectAction()
|
||||
end
|
||||
|
||||
--- Test
|
||||
|
||||
--- Test
|
||||
-- @param #FSM_PROCESS self
|
||||
-- @param Wrapper.Unit#UNIT TaskUnit
|
||||
-- @param Tasking.Task_A2A#TASK_A2A Task
|
||||
function Fsm:onafterRouteToRendezVous( TaskUnit, Task )
|
||||
self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } )
|
||||
-- Determine the first Unit from the self.RendezVousSetUnit
|
||||
|
||||
|
||||
if Task:GetRendezVousZone( TaskUnit ) then
|
||||
self:__RouteToRendezVousZone( 0.1 )
|
||||
else
|
||||
@@ -110,36 +108,36 @@ do -- TASK_A2A
|
||||
end
|
||||
end
|
||||
|
||||
--- Test
|
||||
--- Test
|
||||
-- @param #FSM_PROCESS self
|
||||
-- @param Wrapper.Unit#UNIT TaskUnit
|
||||
-- @param Tasking.Task#TASK_A2A Task
|
||||
function Fsm:OnAfterArriveAtRendezVous( TaskUnit, Task )
|
||||
self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } )
|
||||
-- Determine the first Unit from the self.TargetSetUnit
|
||||
|
||||
self:__Engage( 0.1 )
|
||||
|
||||
self:__Engage( 0.1 )
|
||||
end
|
||||
|
||||
--- Test
|
||||
|
||||
--- Test
|
||||
-- @param #FSM_PROCESS self
|
||||
-- @param Wrapper.Unit#UNIT TaskUnit
|
||||
-- @param Tasking.Task#TASK_A2A Task
|
||||
function Fsm:onafterEngage( TaskUnit, Task )
|
||||
self:F( { self } )
|
||||
self:__Account( 0.1 )
|
||||
self:__RouteToTarget(0.1 )
|
||||
self:__RouteToTarget( 0.1 )
|
||||
self:__RouteToTargets( -10 )
|
||||
end
|
||||
|
||||
--- Test
|
||||
|
||||
--- Test
|
||||
-- @param #FSM_PROCESS self
|
||||
-- @param Wrapper.Unit#UNIT TaskUnit
|
||||
-- @param Tasking.Task_A2A#TASK_A2A Task
|
||||
function Fsm:onafterRouteToTarget( TaskUnit, Task )
|
||||
self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } )
|
||||
-- Determine the first Unit from the self.TargetSetUnit
|
||||
|
||||
|
||||
if Task:GetTargetZone( TaskUnit ) then
|
||||
self:__RouteToTargetZone( 0.1 )
|
||||
else
|
||||
@@ -152,8 +150,8 @@ do -- TASK_A2A
|
||||
self:__RouteToTargetPoint( 0.1 )
|
||||
end
|
||||
end
|
||||
|
||||
--- Test
|
||||
|
||||
--- Test
|
||||
-- @param #FSM_PROCESS self
|
||||
-- @param Wrapper.Unit#UNIT TaskUnit
|
||||
-- @param Tasking.Task_A2A#TASK_A2A Task
|
||||
@@ -165,20 +163,18 @@ do -- TASK_A2A
|
||||
end
|
||||
self:__RouteToTargets( -10 )
|
||||
end
|
||||
|
||||
|
||||
return self
|
||||
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- @param #TASK_A2A self
|
||||
-- @param Core.Set#SET_UNIT TargetSetUnit The set of targets.
|
||||
function TASK_A2A:SetTargetSetUnit( TargetSetUnit )
|
||||
|
||||
|
||||
self.TargetSetUnit = TargetSetUnit
|
||||
end
|
||||
|
||||
|
||||
|
||||
--- @param #TASK_A2A self
|
||||
function TASK_A2A:GetPlannedMenuText()
|
||||
return self:GetStateString() .. " - " .. self:GetTaskName() .. " ( " .. self.TargetSetUnit:GetUnitTypesText() .. " )"
|
||||
@@ -188,34 +184,32 @@ do -- TASK_A2A
|
||||
-- @param Core.Point#COORDINATE RendezVousCoordinate The Coordinate object referencing to the 2D point where the RendezVous point is located on the map.
|
||||
-- @param #number RendezVousRange The RendezVousRange that defines when the player is considered to have arrived at the RendezVous point.
|
||||
-- @param Wrapper.Unit#UNIT TaskUnit
|
||||
function TASK_A2A:SetRendezVousCoordinate( RendezVousCoordinate, RendezVousRange, TaskUnit )
|
||||
|
||||
function TASK_A2A:SetRendezVousCoordinate( RendezVousCoordinate, RendezVousRange, TaskUnit )
|
||||
|
||||
local ProcessUnit = self:GetUnitProcess( TaskUnit )
|
||||
|
||||
|
||||
local ActRouteRendezVous = ProcessUnit:GetProcess( "RoutingToRendezVous", "RouteToRendezVousPoint" ) -- Actions.Act_Route#ACT_ROUTE_POINT
|
||||
ActRouteRendezVous:SetCoordinate( RendezVousCoordinate )
|
||||
ActRouteRendezVous:SetRange( RendezVousRange )
|
||||
end
|
||||
|
||||
|
||||
--- @param #TASK_A2A self
|
||||
-- @param Wrapper.Unit#UNIT TaskUnit
|
||||
-- @return Core.Point#COORDINATE The Coordinate object referencing to the 2D point where the RendezVous point is located on the map.
|
||||
-- @return #number The RendezVousRange that defines when the player is considered to have arrived at the RendezVous point.
|
||||
function TASK_A2A:GetRendezVousCoordinate( TaskUnit )
|
||||
|
||||
|
||||
local ProcessUnit = self:GetUnitProcess( TaskUnit )
|
||||
|
||||
local ActRouteRendezVous = ProcessUnit:GetProcess( "RoutingToRendezVous", "RouteToRendezVousPoint" ) -- Actions.Act_Route#ACT_ROUTE_POINT
|
||||
return ActRouteRendezVous:GetCoordinate(), ActRouteRendezVous:GetRange()
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
--- @param #TASK_A2A self
|
||||
-- @param Core.Zone#ZONE_BASE RendezVousZone The Zone object where the RendezVous is located on the map.
|
||||
-- @param Wrapper.Unit#UNIT TaskUnit
|
||||
function TASK_A2A:SetRendezVousZone( RendezVousZone, TaskUnit )
|
||||
|
||||
|
||||
local ProcessUnit = self:GetUnitProcess( TaskUnit )
|
||||
|
||||
local ActRouteRendezVous = ProcessUnit:GetProcess( "RoutingToRendezVous", "RouteToRendezVousZone" ) -- Actions.Act_Route#ACT_ROUTE_ZONE
|
||||
@@ -232,18 +226,17 @@ do -- TASK_A2A
|
||||
local ActRouteRendezVous = ProcessUnit:GetProcess( "RoutingToRendezVous", "RouteToRendezVousZone" ) -- Actions.Act_Route#ACT_ROUTE_ZONE
|
||||
return ActRouteRendezVous:GetZone()
|
||||
end
|
||||
|
||||
|
||||
--- @param #TASK_A2A self
|
||||
-- @param Core.Point#COORDINATE TargetCoordinate The Coordinate object where the Target is located on the map.
|
||||
-- @param Wrapper.Unit#UNIT TaskUnit
|
||||
function TASK_A2A:SetTargetCoordinate( TargetCoordinate, TaskUnit )
|
||||
|
||||
|
||||
local ProcessUnit = self:GetUnitProcess( TaskUnit )
|
||||
|
||||
local ActRouteTarget = ProcessUnit:GetProcess( "Engaging", "RouteToTargetPoint" ) -- Actions.Act_Route#ACT_ROUTE_POINT
|
||||
ActRouteTarget:SetCoordinate( TargetCoordinate )
|
||||
end
|
||||
|
||||
|
||||
--- @param #TASK_A2A self
|
||||
-- @param Wrapper.Unit#UNIT TaskUnit
|
||||
@@ -256,18 +249,16 @@ do -- TASK_A2A
|
||||
return ActRouteTarget:GetCoordinate()
|
||||
end
|
||||
|
||||
|
||||
--- @param #TASK_A2A self
|
||||
-- @param Core.Zone#ZONE_BASE TargetZone The Zone object where the Target is located on the map.
|
||||
-- @param Wrapper.Unit#UNIT TaskUnit
|
||||
function TASK_A2A:SetTargetZone( TargetZone, Altitude, Heading, TaskUnit )
|
||||
|
||||
|
||||
local ProcessUnit = self:GetUnitProcess( TaskUnit )
|
||||
|
||||
local ActRouteTarget = ProcessUnit:GetProcess( "Engaging", "RouteToTargetZone" ) -- Actions.Act_Route#ACT_ROUTE_ZONE
|
||||
ActRouteTarget:SetZone( TargetZone, Altitude, Heading )
|
||||
end
|
||||
|
||||
|
||||
--- @param #TASK_A2A self
|
||||
-- @param Wrapper.Unit#UNIT TaskUnit
|
||||
@@ -281,45 +272,43 @@ do -- TASK_A2A
|
||||
end
|
||||
|
||||
function TASK_A2A:SetGoalTotal()
|
||||
|
||||
|
||||
self.GoalTotal = self.TargetSetUnit:Count()
|
||||
end
|
||||
|
||||
function TASK_A2A:GetGoalTotal()
|
||||
|
||||
|
||||
return self.GoalTotal
|
||||
end
|
||||
|
||||
--- Return the relative distance to the target vicinity from the player, in order to sort the targets in the reports per distance from the threats.
|
||||
-- @param #TASK_A2A self
|
||||
function TASK_A2A:ReportOrder( ReportGroup )
|
||||
self:UpdateTaskInfo( self.DetectedItem )
|
||||
|
||||
self:UpdateTaskInfo( self.DetectedItem )
|
||||
|
||||
local Coordinate = self.TaskInfo:GetData( "Coordinate" )
|
||||
local Distance = ReportGroup:GetCoordinate():Get2DDistance( Coordinate )
|
||||
|
||||
|
||||
return Distance
|
||||
end
|
||||
|
||||
|
||||
|
||||
--- This method checks every 10 seconds if the goal has been reached of the task.
|
||||
-- @param #TASK_A2A self
|
||||
function TASK_A2A:onafterGoal( TaskUnit, From, Event, To )
|
||||
local TargetSetUnit = self.TargetSetUnit -- Core.Set#SET_UNIT
|
||||
|
||||
|
||||
if TargetSetUnit:Count() == 0 then
|
||||
self:Success()
|
||||
end
|
||||
|
||||
|
||||
self:__Goal( -10 )
|
||||
end
|
||||
|
||||
|
||||
--- @param #TASK_A2A self
|
||||
function TASK_A2A:UpdateTaskInfo( DetectedItem )
|
||||
|
||||
if self:IsStatePlanned() or self:IsStateAssigned() then
|
||||
local TargetCoordinate = DetectedItem and self.Detection:GetDetectedItemCoordinate( DetectedItem ) or self.TargetSetUnit:GetFirst():GetCoordinate()
|
||||
local TargetCoordinate = DetectedItem and self.Detection:GetDetectedItemCoordinate( DetectedItem ) or self.TargetSetUnit:GetFirst():GetCoordinate()
|
||||
self.TaskInfo:AddTaskName( 0, "MSOD" )
|
||||
self.TaskInfo:AddCoordinate( TargetCoordinate, 1, "SOD" )
|
||||
|
||||
@@ -343,12 +332,12 @@ do -- TASK_A2A
|
||||
end
|
||||
end
|
||||
self.TaskInfo:AddTargetCount( DetectedItemsCount, 11, "O", true )
|
||||
self.TaskInfo:AddTargets( DetectedItemsCount, ReportTypes:Text( ", " ), 20, "D", true )
|
||||
self.TaskInfo:AddTargets( DetectedItemsCount, ReportTypes:Text( ", " ), 20, "D", true )
|
||||
else
|
||||
local DetectedItemsCount = self.TargetSetUnit:Count()
|
||||
local DetectedItemsTypes = self.TargetSetUnit:GetTypeNames()
|
||||
self.TaskInfo:AddTargetCount( DetectedItemsCount, 11, "O", true )
|
||||
self.TaskInfo:AddTargets( DetectedItemsCount, DetectedItemsTypes, 20, "D", true )
|
||||
self.TaskInfo:AddTargets( DetectedItemsCount, DetectedItemsTypes, 20, "D", true )
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -359,8 +348,8 @@ do -- TASK_A2A
|
||||
-- @param Tasking.CommandCenter#COMMANDCENTER CommandCenter The command center.
|
||||
-- @param Wrapper.Group#GROUP TaskGroup The player group.
|
||||
function TASK_A2A:GetAutoAssignPriority( AutoAssignMethod, CommandCenter, TaskGroup )
|
||||
|
||||
if AutoAssignMethod == COMMANDCENTER.AutoAssignMethods.Random then
|
||||
|
||||
if AutoAssignMethod == COMMANDCENTER.AutoAssignMethods.Random then
|
||||
return math.random( 1, 9 )
|
||||
elseif AutoAssignMethod == COMMANDCENTER.AutoAssignMethods.Distance then
|
||||
local Coordinate = self.TaskInfo:GetData( "Coordinate" )
|
||||
@@ -373,8 +362,7 @@ do -- TASK_A2A
|
||||
return 0
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
do -- TASK_A2A_INTERCEPT
|
||||
|
||||
@@ -384,44 +372,39 @@ do -- TASK_A2A_INTERCEPT
|
||||
-- @extends Tasking.Task#TASK
|
||||
|
||||
--- Defines an intercept task for a human player to be executed.
|
||||
-- When enemy planes need to be intercepted by human players, use this task type to urgen the players to get out there!
|
||||
--
|
||||
-- The TASK_A2A_INTERCEPT is used by the @{Tasking.Task_A2A_Dispatcher#TASK_A2A_DISPATCHER} to automatically create intercept tasks
|
||||
-- When enemy planes need to be intercepted by human players, use this task type to urge the players to get out there!
|
||||
--
|
||||
-- The TASK_A2A_INTERCEPT is used by the @{Tasking.Task_A2A_Dispatcher#TASK_A2A_DISPATCHER} to automatically create intercept tasks
|
||||
-- based on detected airborne enemy targets intruding friendly airspace.
|
||||
--
|
||||
--
|
||||
-- The task is defined for a @{Tasking.Mission#MISSION}, where a friendly @{Core.Set#SET_GROUP} consisting of GROUPs with one human players each, is intercepting the targets.
|
||||
-- The task is given a name and a briefing, that is used in the menu structure and in the reporting.
|
||||
--
|
||||
--
|
||||
-- @field #TASK_A2A_INTERCEPT
|
||||
TASK_A2A_INTERCEPT = {
|
||||
ClassName = "TASK_A2A_INTERCEPT",
|
||||
ClassName = "TASK_A2A_INTERCEPT"
|
||||
}
|
||||
|
||||
|
||||
|
||||
--- Instantiates a new TASK_A2A_INTERCEPT.
|
||||
-- @param #TASK_A2A_INTERCEPT self
|
||||
-- @param Tasking.Mission#MISSION Mission
|
||||
-- @param Core.Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned.
|
||||
-- @param #string TaskName The name of the Task.
|
||||
-- @param Core.Set#SET_UNIT TargetSetUnit
|
||||
-- @param Core.Set#SET_UNIT TargetSetUnit
|
||||
-- @param #string TaskBriefing The briefing of the task.
|
||||
-- @return #TASK_A2A_INTERCEPT
|
||||
function TASK_A2A_INTERCEPT:New( Mission, SetGroup, TaskName, TargetSetUnit, TaskBriefing )
|
||||
local self = BASE:Inherit( self, TASK_A2A:New( Mission, SetGroup, TaskName, TargetSetUnit, "INTERCEPT", TaskBriefing ) ) -- #TASK_A2A_INTERCEPT
|
||||
self:F()
|
||||
|
||||
|
||||
Mission:AddTask( self )
|
||||
|
||||
self:SetBriefing(
|
||||
TaskBriefing or
|
||||
"Intercept incoming intruders.\n"
|
||||
)
|
||||
|
||||
self:SetBriefing( TaskBriefing or "Intercept incoming intruders.\n" )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set a score when a target in scope of the A2A attack, has been destroyed .
|
||||
|
||||
--- Set a score when a target in scope of the A2A attack, has been destroyed.
|
||||
-- @param #TASK_A2A_INTERCEPT self
|
||||
-- @param #string PlayerName The name of the player.
|
||||
-- @param #number Score The score in points to be granted when task process has been achieved.
|
||||
@@ -433,7 +416,7 @@ do -- TASK_A2A_INTERCEPT
|
||||
local ProcessUnit = self:GetUnitProcess( TaskUnit )
|
||||
|
||||
ProcessUnit:AddScoreProcess( "Engaging", "Account", "AccountForPlayer", "Player " .. PlayerName .. " has intercepted a target.", Score )
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -449,7 +432,7 @@ do -- TASK_A2A_INTERCEPT
|
||||
local ProcessUnit = self:GetUnitProcess( TaskUnit )
|
||||
|
||||
ProcessUnit:AddScore( "Success", "All targets have been successfully intercepted!", Score )
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -465,14 +448,12 @@ do -- TASK_A2A_INTERCEPT
|
||||
local ProcessUnit = self:GetUnitProcess( TaskUnit )
|
||||
|
||||
ProcessUnit:AddScore( "Failed", "The intercept has failed!", Penalty )
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
|
||||
do -- TASK_A2A_SWEEP
|
||||
|
||||
--- The TASK_A2A_SWEEP class
|
||||
@@ -484,20 +465,18 @@ do -- TASK_A2A_SWEEP
|
||||
-- A sweep task needs to be given when targets were detected but somehow the detection was lost.
|
||||
-- Most likely, these enemy planes are hidden in the mountains or are flying under radar.
|
||||
-- These enemy planes need to be sweeped by human players, and use this task type to urge the players to get out there and find those enemy fighters.
|
||||
--
|
||||
-- The TASK_A2A_SWEEP is used by the @{Tasking.Task_A2A_Dispatcher#TASK_A2A_DISPATCHER} to automatically create sweep tasks
|
||||
--
|
||||
-- The TASK_A2A_SWEEP is used by the @{Tasking.Task_A2A_Dispatcher#TASK_A2A_DISPATCHER} to automatically create sweep tasks
|
||||
-- based on detected airborne enemy targets intruding friendly airspace, for which the detection has been lost for more than 60 seconds.
|
||||
--
|
||||
--
|
||||
-- The task is defined for a @{Tasking.Mission#MISSION}, where a friendly @{Core.Set#SET_GROUP} consisting of GROUPs with one human players each, is sweeping the targets.
|
||||
-- The task is given a name and a briefing, that is used in the menu structure and in the reporting.
|
||||
--
|
||||
--
|
||||
-- @field #TASK_A2A_SWEEP
|
||||
TASK_A2A_SWEEP = {
|
||||
ClassName = "TASK_A2A_SWEEP",
|
||||
ClassName = "TASK_A2A_SWEEP"
|
||||
}
|
||||
|
||||
|
||||
|
||||
--- Instantiates a new TASK_A2A_SWEEP.
|
||||
-- @param #TASK_A2A_SWEEP self
|
||||
-- @param Tasking.Mission#MISSION Mission
|
||||
@@ -509,29 +488,26 @@ do -- TASK_A2A_SWEEP
|
||||
function TASK_A2A_SWEEP:New( Mission, SetGroup, TaskName, TargetSetUnit, TaskBriefing )
|
||||
local self = BASE:Inherit( self, TASK_A2A:New( Mission, SetGroup, TaskName, TargetSetUnit, "SWEEP", TaskBriefing ) ) -- #TASK_A2A_SWEEP
|
||||
self:F()
|
||||
|
||||
|
||||
Mission:AddTask( self )
|
||||
|
||||
self:SetBriefing(
|
||||
TaskBriefing or
|
||||
"Perform a fighter sweep. Incoming intruders were detected and could be hiding at the location.\n"
|
||||
)
|
||||
|
||||
self:SetBriefing( TaskBriefing or "Perform a fighter sweep. Incoming intruders were detected and could be hiding at the location.\n" )
|
||||
|
||||
return self
|
||||
end
|
||||
end
|
||||
|
||||
--- @param #TASK_A2A_SWEEP self
|
||||
function TASK_A2A_SWEEP:onafterGoal( TaskUnit, From, Event, To )
|
||||
local TargetSetUnit = self.TargetSetUnit -- Core.Set#SET_UNIT
|
||||
|
||||
|
||||
if TargetSetUnit:Count() == 0 then
|
||||
self:Success()
|
||||
end
|
||||
|
||||
|
||||
self:__Goal( -10 )
|
||||
end
|
||||
|
||||
--- Set a score when a target in scope of the A2A attack, has been destroyed .
|
||||
--- Set a score when a target in scope of the A2A attack, has been destroyed.
|
||||
-- @param #TASK_A2A_SWEEP self
|
||||
-- @param #string PlayerName The name of the player.
|
||||
-- @param #number Score The score in points to be granted when task process has been achieved.
|
||||
@@ -543,7 +519,7 @@ do -- TASK_A2A_SWEEP
|
||||
local ProcessUnit = self:GetUnitProcess( TaskUnit )
|
||||
|
||||
ProcessUnit:AddScoreProcess( "Engaging", "Account", "AccountForPlayer", "Player " .. PlayerName .. " has sweeped a target.", Score )
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -559,7 +535,7 @@ do -- TASK_A2A_SWEEP
|
||||
local ProcessUnit = self:GetUnitProcess( TaskUnit )
|
||||
|
||||
ProcessUnit:AddScore( "Success", "All targets have been successfully sweeped!", Score )
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -575,13 +551,12 @@ do -- TASK_A2A_SWEEP
|
||||
local ProcessUnit = self:GetUnitProcess( TaskUnit )
|
||||
|
||||
ProcessUnit:AddScore( "Failed", "The sweep has failed!", Penalty )
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
do -- TASK_A2A_ENGAGE
|
||||
|
||||
--- The TASK_A2A_ENGAGE class
|
||||
@@ -591,42 +566,37 @@ do -- TASK_A2A_ENGAGE
|
||||
|
||||
--- Defines an engage task for a human player to be executed.
|
||||
-- When enemy planes are close to human players, use this task type is used urge the players to get out there!
|
||||
--
|
||||
-- The TASK_A2A_ENGAGE is used by the @{Tasking.Task_A2A_Dispatcher#TASK_A2A_DISPATCHER} to automatically create engage tasks
|
||||
--
|
||||
-- The TASK_A2A_ENGAGE is used by the @{Tasking.Task_A2A_Dispatcher#TASK_A2A_DISPATCHER} to automatically create engage tasks
|
||||
-- based on detected airborne enemy targets intruding friendly airspace.
|
||||
--
|
||||
--
|
||||
-- The task is defined for a @{Tasking.Mission#MISSION}, where a friendly @{Core.Set#SET_GROUP} consisting of GROUPs with one human players each, is engaging the targets.
|
||||
-- The task is given a name and a briefing, that is used in the menu structure and in the reporting.
|
||||
--
|
||||
--
|
||||
-- @field #TASK_A2A_ENGAGE
|
||||
TASK_A2A_ENGAGE = {
|
||||
ClassName = "TASK_A2A_ENGAGE",
|
||||
ClassName = "TASK_A2A_ENGAGE"
|
||||
}
|
||||
|
||||
|
||||
|
||||
--- Instantiates a new TASK_A2A_ENGAGE.
|
||||
-- @param #TASK_A2A_ENGAGE self
|
||||
-- @param Tasking.Mission#MISSION Mission
|
||||
-- @param Core.Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned.
|
||||
-- @param #string TaskName The name of the Task.
|
||||
-- @param Core.Set#SET_UNIT TargetSetUnit
|
||||
-- @param Core.Set#SET_UNIT TargetSetUnit
|
||||
-- @param #string TaskBriefing The briefing of the task.
|
||||
-- @return #TASK_A2A_ENGAGE self
|
||||
function TASK_A2A_ENGAGE:New( Mission, SetGroup, TaskName, TargetSetUnit, TaskBriefing )
|
||||
local self = BASE:Inherit( self, TASK_A2A:New( Mission, SetGroup, TaskName, TargetSetUnit, "ENGAGE", TaskBriefing ) ) -- #TASK_A2A_ENGAGE
|
||||
self:F()
|
||||
|
||||
|
||||
Mission:AddTask( self )
|
||||
|
||||
self:SetBriefing(
|
||||
TaskBriefing or
|
||||
"Bogeys are nearby! Players close by are ordered to ENGAGE the intruders!\n"
|
||||
)
|
||||
|
||||
self:SetBriefing( TaskBriefing or "Bogeys are nearby! Players close by are ordered to ENGAGE the intruders!\n" )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Set a score when a target in scope of the A2A attack, has been destroyed .
|
||||
-- @param #TASK_A2A_ENGAGE self
|
||||
-- @param #string PlayerName The name of the player.
|
||||
@@ -639,7 +609,7 @@ do -- TASK_A2A_ENGAGE
|
||||
local ProcessUnit = self:GetUnitProcess( TaskUnit )
|
||||
|
||||
ProcessUnit:AddScoreProcess( "Engaging", "Account", "AccountForPlayer", "Player " .. PlayerName .. " has engaged and destroyed a target.", Score )
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -655,7 +625,7 @@ do -- TASK_A2A_ENGAGE
|
||||
local ProcessUnit = self:GetUnitProcess( TaskUnit )
|
||||
|
||||
ProcessUnit:AddScore( "Success", "All targets have been successfully engaged!", Score )
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -671,7 +641,7 @@ do -- TASK_A2A_ENGAGE
|
||||
local ProcessUnit = self:GetUnitProcess( TaskUnit )
|
||||
|
||||
ProcessUnit:AddScore( "Failed", "The target engagement has failed!", Penalty )
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
--- **Tasking** - Dynamically allocates A2A tasks to human players, based on detected airborne targets through an EWR network.
|
||||
--
|
||||
--
|
||||
-- **Features:**
|
||||
--
|
||||
--
|
||||
-- * Dynamically assign tasks to human players based on detected targets.
|
||||
-- * Dynamically change the tasks as the tactical situation evolves during the mission.
|
||||
-- * Dynamically assign (CAP) Control Air Patrols tasks for human players to perform CAP.
|
||||
@@ -11,15 +11,15 @@
|
||||
-- * Define different ranges to engage upon intruders.
|
||||
-- * Keep task achievements.
|
||||
-- * Score task achievements.
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
--
|
||||
-- ### Contributions:
|
||||
--
|
||||
--
|
||||
-- ### Contributions:
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- @module Tasking.Task_A2A_Dispatcher
|
||||
-- @image Task_A2A_Dispatcher.JPG
|
||||
|
||||
@@ -30,72 +30,72 @@ do -- TASK_A2A_DISPATCHER
|
||||
-- @extends Tasking.DetectionManager#DETECTION_MANAGER
|
||||
|
||||
--- Orchestrates the dynamic dispatching of tasks upon groups of detected units determined a @{Set} of EWR installation groups.
|
||||
--
|
||||
--
|
||||
-- 
|
||||
--
|
||||
--
|
||||
-- The EWR will detect units, will group them, and will dispatch @{Task}s to groups. Depending on the type of target detected, different tasks will be dispatched.
|
||||
-- Find a summary below describing for which situation a task type is created:
|
||||
--
|
||||
--
|
||||
-- 
|
||||
--
|
||||
--
|
||||
-- * **INTERCEPT Task**: Is created when the target is known, is detected and within a danger zone, and there is no friendly airborne in range.
|
||||
-- * **SWEEP Task**: Is created when the target is unknown, was detected and the last position is only known, and within a danger zone, and there is no friendly airborne in range.
|
||||
-- * **ENGAGE Task**: Is created when the target is known, is detected and within a danger zone, and there is a friendly airborne in range, that will receive this task.
|
||||
--
|
||||
--
|
||||
-- ## 1. TASK\_A2A\_DISPATCHER constructor:
|
||||
--
|
||||
--
|
||||
-- The @{#TASK_A2A_DISPATCHER.New}() method creates a new TASK\_A2A\_DISPATCHER instance.
|
||||
--
|
||||
--
|
||||
-- ### 1.1. Define or set the **Mission**:
|
||||
--
|
||||
--
|
||||
-- Tasking is executed to accomplish missions. Therefore, a MISSION object needs to be given as the first parameter.
|
||||
--
|
||||
--
|
||||
-- local HQ = GROUP:FindByName( "HQ", "Bravo" )
|
||||
-- local CommandCenter = COMMANDCENTER:New( HQ, "Lima" )
|
||||
-- local Mission = MISSION:New( CommandCenter, "A2A Mission", "High", "Watch the air enemy units being detected.", coalition.side.RED )
|
||||
--
|
||||
--
|
||||
-- Missions are governed by COMMANDCENTERS, so, ensure you have a COMMANDCENTER object installed and setup within your mission.
|
||||
-- Create the MISSION object, and hook it under the command center.
|
||||
--
|
||||
--
|
||||
-- ### 1.2. Build a set of the groups seated by human players:
|
||||
--
|
||||
--
|
||||
-- 
|
||||
--
|
||||
--
|
||||
-- A set or collection of the groups wherein human players can be seated, these can be clients or units that can be joined as a slot or jumping into.
|
||||
--
|
||||
--
|
||||
-- local AttackGroups = SET_GROUP:New():FilterCoalitions( "red" ):FilterPrefixes( "Defender" ):FilterStart()
|
||||
--
|
||||
--
|
||||
-- The set is built using the SET_GROUP class. Apply any filter criteria to identify the correct groups for your mission.
|
||||
-- Only these slots or units will be able to execute the mission and will receive tasks for this mission, once available.
|
||||
--
|
||||
--
|
||||
-- ### 1.3. Define the **EWR network**:
|
||||
--
|
||||
--
|
||||
-- As part of the TASK\_A2A\_DISPATCHER constructor, an EWR network must be given as the third parameter.
|
||||
-- An EWR network, or, Early Warning Radar network, is used to early detect potential airborne targets and to understand the position of patrolling targets of the enemy.
|
||||
--
|
||||
--
|
||||
-- 
|
||||
--
|
||||
--
|
||||
-- Typically EWR networks are setup using 55G6 EWR, 1L13 EWR, Hawk sr and Patriot str ground based radar units.
|
||||
-- These radars have different ranges and 55G6 EWR and 1L13 EWR radars are Eastern Bloc units (eg Russia, Ukraine, Georgia) while the Hawk and Patriot radars are Western (eg US).
|
||||
-- Additionally, ANY other radar capable unit can be part of the EWR network! Also AWACS airborne units, planes, helicopters can help to detect targets, as long as they have radar.
|
||||
-- The position of these units is very important as they need to provide enough coverage
|
||||
-- to pick up enemy aircraft as they approach so that CAP and GCI flights can be tasked to intercept them.
|
||||
--
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- Additionally in a hot war situation where the border is no longer respected the placement of radars has a big effect on how fast the war escalates.
|
||||
-- For example if they are a long way forward and can detect enemy planes on the ground and taking off
|
||||
-- they will start to vector CAP and GCI flights to attack them straight away which will immediately draw a response from the other coalition.
|
||||
-- Having the radars further back will mean a slower escalation because fewer targets will be detected and
|
||||
-- therefore less CAP and GCI flights will spawn and this will tend to make just the border area active rather than a melee over the whole map.
|
||||
-- It all depends on what the desired effect is.
|
||||
--
|
||||
--
|
||||
-- Additionally in a hot war situation where the border is no longer respected the placement of radars has a big effect on how fast the war escalates.
|
||||
-- For example if they are a long way forward and can detect enemy planes on the ground and taking off
|
||||
-- they will start to vector CAP and GCI flights to attack them straight away which will immediately draw a response from the other coalition.
|
||||
-- Having the radars further back will mean a slower escalation because fewer targets will be detected and
|
||||
-- therefore less CAP and GCI flights will spawn and this will tend to make just the border area active rather than a melee over the whole map.
|
||||
-- It all depends on what the desired effect is.
|
||||
--
|
||||
-- EWR networks are **dynamically constructed**, that is, they form part of the @{Functional.Detection#DETECTION_BASE} object that is given as the input parameter of the TASK\_A2A\_DISPATCHER class.
|
||||
-- By defining in a **smart way the names or name prefixes of the groups** with EWR capable units, these groups will be **automatically added or deleted** from the EWR network,
|
||||
-- By defining in a **smart way the names or name prefixes of the groups** with EWR capable units, these groups will be **automatically added or deleted** from the EWR network,
|
||||
-- increasing or decreasing the radar coverage of the Early Warning System.
|
||||
--
|
||||
--
|
||||
-- See the following example to setup an EWR network containing EWR stations and AWACS.
|
||||
--
|
||||
--
|
||||
-- local EWRSet = SET_GROUP:New():FilterPrefixes( "EWR" ):FilterCoalitions("red"):FilterStart()
|
||||
--
|
||||
-- local EWRDetection = DETECTION_AREAS:New( EWRSet, 6000 )
|
||||
@@ -104,50 +104,50 @@ do -- TASK_A2A_DISPATCHER
|
||||
--
|
||||
-- -- Setup the A2A dispatcher, and initialize it.
|
||||
-- A2ADispatcher = TASK_A2A_DISPATCHER:New( Mission, AttackGroups, EWRDetection )
|
||||
--
|
||||
--
|
||||
-- The above example creates a SET_GROUP instance, and stores this in the variable (object) **EWRSet**.
|
||||
-- **EWRSet** is then being configured to filter all active groups with a group name starting with **EWR** to be included in the Set.
|
||||
-- **EWRSet** is then being ordered to start the dynamic filtering. Note that any destroy or new spawn of a group with the above names will be removed or added to the Set.
|
||||
-- Then a new **EWRDetection** object is created from the class DETECTION_AREAS. A grouping radius of 6000 is choosen, which is 6km.
|
||||
-- Then a new **EWRDetection** object is created from the class DETECTION_AREAS. A grouping radius of 6000 is chosen, which is 6 km.
|
||||
-- The **EWRDetection** object is then passed to the @{#TASK_A2A_DISPATCHER.New}() method to indicate the EWR network configuration and setup the A2A tasking and detection mechanism.
|
||||
--
|
||||
--
|
||||
-- ### 2. Define the detected **target grouping radius**:
|
||||
--
|
||||
--
|
||||
-- 
|
||||
--
|
||||
--
|
||||
-- The target grouping radius is a property of the Detection object, that was passed to the AI\_A2A\_DISPATCHER object, but can be changed.
|
||||
-- The grouping radius should not be too small, but also depends on the types of planes and the era of the simulation.
|
||||
-- Fast planes like in the 80s, need a larger radius than WWII planes.
|
||||
-- Fast planes like in the 80s, need a larger radius than WWII planes.
|
||||
-- Typically I suggest to use 30000 for new generation planes and 10000 for older era aircraft.
|
||||
--
|
||||
--
|
||||
-- Note that detected targets are constantly re-grouped, that is, when certain detected aircraft are moving further than the group radius, then these aircraft will become a separate
|
||||
-- group being detected. This may result in additional GCI being started by the dispatcher! So don't make this value too small!
|
||||
--
|
||||
--
|
||||
-- ## 3. Set the **Engage radius**:
|
||||
--
|
||||
--
|
||||
-- Define the radius to engage any target by airborne friendlies, which are executing cap or returning from an intercept mission.
|
||||
--
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- So, if there is a target area detected and reported,
|
||||
-- then any friendlies that are airborne near this target area,
|
||||
--
|
||||
-- So, if there is a target area detected and reported,
|
||||
-- then any friendlies that are airborne near this target area,
|
||||
-- will be commanded to (re-)engage that target when available (if no other tasks were commanded).
|
||||
-- For example, if 100000 is given as a value, then any friendly that is airborne within 100km from the detected target,
|
||||
-- For example, if 100000 is given as a value, then any friendly that is airborne within 100km from the detected target,
|
||||
-- will be considered to receive the command to engage that target area.
|
||||
-- You need to evaluate the value of this parameter carefully.
|
||||
-- If too small, more intercept missions may be triggered upon detected target areas.
|
||||
-- If too large, any airborne cap may not be able to reach the detected target area in time, because it is too far.
|
||||
--
|
||||
--
|
||||
-- ## 4. Set **Scoring** and **Messages**:
|
||||
--
|
||||
--
|
||||
-- The TASK\_A2A\_DISPATCHER is a state machine. It triggers the event Assign when a new player joins a @{Task} dispatched by the TASK\_A2A\_DISPATCHER.
|
||||
-- An _event handler_ can be defined to catch the **Assign** event, and add **additional processing** to set _scoring_ and to _define messages_,
|
||||
-- when the player reaches certain achievements in the task.
|
||||
--
|
||||
--
|
||||
-- The prototype to handle the **Assign** event needs to be developed as follows:
|
||||
--
|
||||
--
|
||||
-- TaskDispatcher = TASK_A2A_DISPATCHER:New( ... )
|
||||
--
|
||||
--
|
||||
-- --- @param #TaskDispatcher self
|
||||
-- -- @param #string From Contains the name of the state from where the Event was triggered.
|
||||
-- -- @param #string Event Contains the name of the event that was triggered. In this case Assign.
|
||||
@@ -160,22 +160,22 @@ do -- TASK_A2A_DISPATCHER
|
||||
-- Task:SetScoreOnSuccess( PlayerName, 200, TaskUnit )
|
||||
-- Task:SetScoreOnFail( PlayerName, -100, TaskUnit )
|
||||
-- end
|
||||
--
|
||||
--
|
||||
-- The **OnAfterAssign** method (function) is added to the TaskDispatcher object.
|
||||
-- This method will be called when a new player joins a unit in the set of groups in scope of the dispatcher.
|
||||
-- So, this method will be called only **ONCE** when a player joins a unit in scope of the task.
|
||||
--
|
||||
--
|
||||
-- The TASK class implements various methods to additional **set scoring** for player achievements:
|
||||
--
|
||||
--
|
||||
-- * @{Tasking.Task#TASK.SetScoreOnProgress}() will add additional scores when a player achieves **Progress** while executing the task.
|
||||
-- Examples of **task progress** can be destroying units, arriving at zones etc.
|
||||
--
|
||||
-- * @{Tasking.Task#TASK.SetScoreOnSuccess}() will add additional scores when the task goes into **Success** state.
|
||||
--
|
||||
-- * @{Tasking.Task#TASK.SetScoreOnSuccess}() will add additional scores when the task goes into **Success** state.
|
||||
-- This means the **task has been successfully completed**.
|
||||
--
|
||||
-- * @{Tasking.Task#TASK.SetScoreOnSuccess}() will add additional (negative) scores when the task goes into **Failed** state.
|
||||
--
|
||||
-- * @{Tasking.Task#TASK.SetScoreOnSuccess}() will add additional (negative) scores when the task goes into **Failed** state.
|
||||
-- This means the **task has not been successfully completed**, and the scores must be given with a negative value!
|
||||
--
|
||||
--
|
||||
-- @field #TASK_A2A_DISPATCHER
|
||||
TASK_A2A_DISPATCHER = {
|
||||
ClassName = "TASK_A2A_DISPATCHER",
|
||||
@@ -184,8 +184,7 @@ do -- TASK_A2A_DISPATCHER
|
||||
Tasks = {},
|
||||
SweepZones = {},
|
||||
}
|
||||
|
||||
|
||||
|
||||
--- TASK_A2A_DISPATCHER constructor.
|
||||
-- @param #TASK_A2A_DISPATCHER self
|
||||
-- @param Tasking.Mission#MISSION Mission The mission for which the task dispatching is done.
|
||||
@@ -193,22 +192,21 @@ do -- TASK_A2A_DISPATCHER
|
||||
-- @param Functional.Detection#DETECTION_BASE Detection The detection results that are used to dynamically assign new tasks to human players.
|
||||
-- @return #TASK_A2A_DISPATCHER self
|
||||
function TASK_A2A_DISPATCHER:New( Mission, SetGroup, Detection )
|
||||
|
||||
|
||||
-- Inherits from DETECTION_MANAGER
|
||||
local self = BASE:Inherit( self, DETECTION_MANAGER:New( SetGroup, Detection ) ) -- #TASK_A2A_DISPATCHER
|
||||
|
||||
|
||||
self.Detection = Detection
|
||||
self.Mission = Mission
|
||||
|
||||
|
||||
self.FlashNewTask = false
|
||||
|
||||
-- TODO: Check detection through radar.
|
||||
self.Detection:FilterCategories( Unit.Category.AIRPLANE, Unit.Category.HELICOPTER )
|
||||
self.Detection:InitDetectRadar( true )
|
||||
self.Detection:SetRefreshTimeInterval( 30 )
|
||||
|
||||
|
||||
self:AddTransition( "Started", "Assign", "Started" )
|
||||
|
||||
|
||||
--- OnAfter Transition Handler for Event Assign.
|
||||
-- @function [parent=#TASK_A2A_DISPATCHER] OnAfterAssign
|
||||
-- @param #TASK_A2A_DISPATCHER self
|
||||
@@ -220,14 +218,13 @@ do -- TASK_A2A_DISPATCHER
|
||||
-- @param #string PlayerName
|
||||
|
||||
self:__Start( 5 )
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Define the radius to when an ENGAGE task will be generated for any nearby by airborne friendlies, which are executing cap or returning from an intercept mission.
|
||||
-- So, if there is a target area detected and reported,
|
||||
-- then any friendlies that are airborne near this target area,
|
||||
-- So, if there is a target area detected and reported,
|
||||
-- then any friendlies that are airborne near this target area,
|
||||
-- will be commanded to (re-)engage that target when available (if no other tasks were commanded).
|
||||
-- An ENGAGE task will be created for those pilots.
|
||||
-- For example, if 100000 is given as a value, then any friendly that is airborne within 100km from the detected target,
|
||||
@@ -239,22 +236,27 @@ do -- TASK_A2A_DISPATCHER
|
||||
-- @param #number EngageRadius (Optional, Default = 100000) The radius to report friendlies near the target.
|
||||
-- @return #TASK_A2A_DISPATCHER
|
||||
-- @usage
|
||||
--
|
||||
--
|
||||
-- -- Set 50km as the radius to engage any target by airborne friendlies.
|
||||
-- TaskA2ADispatcher:SetEngageRadius( 50000 )
|
||||
--
|
||||
--
|
||||
-- -- Set 100km as the radius to engage any target by airborne friendlies.
|
||||
-- TaskA2ADispatcher:SetEngageRadius() -- 100000 is the default value.
|
||||
--
|
||||
--
|
||||
function TASK_A2A_DISPATCHER:SetEngageRadius( EngageRadius )
|
||||
|
||||
self.Detection:SetFriendliesRange( EngageRadius or 100000 )
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
--- Set flashing player messages on or off
|
||||
-- @param #TASK_A2A_DISPATCHER self
|
||||
-- @param #boolean onoff Set messages on (true) or off (false)
|
||||
function TASK_A2A_DISPATCHER:SetSendMessages( onoff )
|
||||
self.FlashNewTask = onoff
|
||||
end
|
||||
|
||||
--- Creates an INTERCEPT task when there are targets for it.
|
||||
-- @param #TASK_A2A_DISPATCHER self
|
||||
-- @param Functional.Detection#DETECTION_BASE.DetectedItem DetectedItem
|
||||
@@ -262,26 +264,25 @@ do -- TASK_A2A_DISPATCHER
|
||||
-- @return #nil If there are no targets to be set.
|
||||
function TASK_A2A_DISPATCHER:EvaluateINTERCEPT( DetectedItem )
|
||||
self:F( { DetectedItem.ItemID } )
|
||||
|
||||
|
||||
local DetectedSet = DetectedItem.Set
|
||||
local DetectedZone = DetectedItem.Zone
|
||||
|
||||
-- Check if there is at least one UNIT in the DetectedSet is visible.
|
||||
|
||||
|
||||
if DetectedItem.IsDetected == true then
|
||||
|
||||
-- Here we're doing something advanced... We're copying the DetectedSet.
|
||||
local TargetSetUnit = SET_UNIT:New()
|
||||
TargetSetUnit:SetDatabase( DetectedSet )
|
||||
TargetSetUnit:FilterOnce() -- Filter but don't do any events!!! Elements are added manually upon each detection.
|
||||
|
||||
|
||||
return TargetSetUnit
|
||||
end
|
||||
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
--- Creates an SWEEP task when there are targets for it.
|
||||
-- @param #TASK_A2A_DISPATCHER self
|
||||
-- @param Functional.Detection#DETECTION_BASE.DetectedItem DetectedItem
|
||||
@@ -289,10 +290,9 @@ do -- TASK_A2A_DISPATCHER
|
||||
-- @return #nil If there are no targets to be set.
|
||||
function TASK_A2A_DISPATCHER:EvaluateSWEEP( DetectedItem )
|
||||
self:F( { DetectedItem.ItemID } )
|
||||
|
||||
local DetectedSet = DetectedItem.Set
|
||||
local DetectedZone = DetectedItem.Zone
|
||||
|
||||
local DetectedSet = DetectedItem.Set
|
||||
local DetectedZone = DetectedItem.Zone -- TODO: This seems unused, remove?
|
||||
|
||||
if DetectedItem.IsDetected == false then
|
||||
|
||||
@@ -300,14 +300,13 @@ do -- TASK_A2A_DISPATCHER
|
||||
local TargetSetUnit = SET_UNIT:New()
|
||||
TargetSetUnit:SetDatabase( DetectedSet )
|
||||
TargetSetUnit:FilterOnce() -- Filter but don't do any events!!! Elements are added manually upon each detection.
|
||||
|
||||
|
||||
return TargetSetUnit
|
||||
end
|
||||
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
--- Creates an ENGAGE task when there are human friendlies airborne near the targets.
|
||||
-- @param #TASK_A2A_DISPATCHER self
|
||||
-- @param Functional.Detection#DETECTION_BASE.DetectedItem DetectedItem
|
||||
@@ -315,13 +314,12 @@ do -- TASK_A2A_DISPATCHER
|
||||
-- @return #nil If there are no targets to be set.
|
||||
function TASK_A2A_DISPATCHER:EvaluateENGAGE( DetectedItem )
|
||||
self:F( { DetectedItem.ItemID } )
|
||||
|
||||
|
||||
local DetectedSet = DetectedItem.Set
|
||||
local DetectedZone = DetectedItem.Zone
|
||||
local DetectedZone = DetectedItem.Zone -- TODO: This seems unused, remove?
|
||||
|
||||
local PlayersCount, PlayersReport = self:GetPlayerFriendliesNearBy( DetectedItem )
|
||||
|
||||
|
||||
-- Only allow ENGAGE when there are Players near the zone, and when the Area has detected items since the last run in a 60 seconds time zone.
|
||||
if PlayersCount > 0 and DetectedItem.IsDetected == true then
|
||||
|
||||
@@ -329,16 +327,13 @@ do -- TASK_A2A_DISPATCHER
|
||||
local TargetSetUnit = SET_UNIT:New()
|
||||
TargetSetUnit:SetDatabase( DetectedSet )
|
||||
TargetSetUnit:FilterOnce() -- Filter but don't do any events!!! Elements are added manually upon each detection.
|
||||
|
||||
|
||||
return TargetSetUnit
|
||||
end
|
||||
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
--- Evaluates the removal of the Task from the Mission.
|
||||
-- Can only occur when the DetectedItem is Changed AND the state of the Task is "Planned".
|
||||
-- @param #TASK_A2A_DISPATCHER self
|
||||
@@ -349,24 +344,24 @@ do -- TASK_A2A_DISPATCHER
|
||||
-- @param #boolean DetectedItemChange
|
||||
-- @return Tasking.Task#TASK
|
||||
function TASK_A2A_DISPATCHER:EvaluateRemoveTask( Mission, Task, Detection, DetectedItem, DetectedItemIndex, DetectedItemChanged )
|
||||
|
||||
|
||||
if Task then
|
||||
|
||||
if Task:IsStatePlanned() then
|
||||
local TaskName = Task:GetName()
|
||||
local TaskType = TaskName:match( "(%u+)%.%d+" )
|
||||
|
||||
|
||||
self:T2( { TaskType = TaskType } )
|
||||
|
||||
|
||||
local Remove = false
|
||||
|
||||
|
||||
local IsPlayers = Detection:IsPlayersNearBy( DetectedItem )
|
||||
if TaskType == "ENGAGE" then
|
||||
if IsPlayers == false then
|
||||
Remove = true
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
if TaskType == "INTERCEPT" then
|
||||
if IsPlayers == true then
|
||||
Remove = true
|
||||
@@ -375,7 +370,7 @@ do -- TASK_A2A_DISPATCHER
|
||||
Remove = true
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
if TaskType == "SWEEP" then
|
||||
if DetectedItem.IsDetected == true then
|
||||
Remove = true
|
||||
@@ -383,18 +378,18 @@ do -- TASK_A2A_DISPATCHER
|
||||
end
|
||||
|
||||
local DetectedSet = DetectedItem.Set -- Core.Set#SET_UNIT
|
||||
--DetectedSet:Flush( self )
|
||||
--self:F( { DetectedSetCount = DetectedSet:Count() } )
|
||||
-- DetectedSet:Flush( self )
|
||||
-- self:F( { DetectedSetCount = DetectedSet:Count() } )
|
||||
if DetectedSet:Count() == 0 then
|
||||
Remove = true
|
||||
end
|
||||
|
||||
|
||||
if DetectedItemChanged == true or Remove then
|
||||
Task = self:RemoveTask( DetectedItemIndex )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
return Task
|
||||
end
|
||||
|
||||
@@ -403,10 +398,10 @@ do -- TASK_A2A_DISPATCHER
|
||||
-- @param DetectedItem
|
||||
-- @return #number, Core.CommandCenter#REPORT
|
||||
function TASK_A2A_DISPATCHER:GetFriendliesNearBy( DetectedItem )
|
||||
|
||||
|
||||
local DetectedSet = DetectedItem.Set
|
||||
local FriendlyUnitsNearBy = self.Detection:GetFriendliesNearBy( DetectedItem, Unit.Category.AIRPLANE )
|
||||
|
||||
|
||||
local FriendlyTypes = {}
|
||||
local FriendliesCount = 0
|
||||
|
||||
@@ -418,27 +413,26 @@ do -- TASK_A2A_DISPATCHER
|
||||
local FriendlyUnitThreatLevel = FriendlyUnit:GetThreatLevel()
|
||||
FriendliesCount = FriendliesCount + 1
|
||||
local FriendlyType = FriendlyUnit:GetTypeName()
|
||||
FriendlyTypes[FriendlyType] = FriendlyTypes[FriendlyType] and ( FriendlyTypes[FriendlyType] + 1 ) or 1
|
||||
FriendlyTypes[FriendlyType] = FriendlyTypes[FriendlyType] and (FriendlyTypes[FriendlyType] + 1) or 1
|
||||
if DetectedTreatLevel < FriendlyUnitThreatLevel + 2 then
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
--self:F( { FriendliesCount = FriendliesCount } )
|
||||
|
||||
-- self:F( { FriendliesCount = FriendliesCount } )
|
||||
|
||||
local FriendlyTypesReport = REPORT:New()
|
||||
|
||||
|
||||
if FriendliesCount > 0 then
|
||||
for FriendlyType, FriendlyTypeCount in pairs( FriendlyTypes ) do
|
||||
FriendlyTypesReport:Add( string.format("%d of %s", FriendlyTypeCount, FriendlyType ) )
|
||||
FriendlyTypesReport:Add( string.format( "%d of %s", FriendlyTypeCount, FriendlyType ) )
|
||||
end
|
||||
else
|
||||
FriendlyTypesReport:Add( "-" )
|
||||
end
|
||||
|
||||
|
||||
|
||||
return FriendliesCount, FriendlyTypesReport
|
||||
end
|
||||
|
||||
@@ -447,10 +441,10 @@ do -- TASK_A2A_DISPATCHER
|
||||
-- @param DetectedItem
|
||||
-- @return #number, Core.CommandCenter#REPORT
|
||||
function TASK_A2A_DISPATCHER:GetPlayerFriendliesNearBy( DetectedItem )
|
||||
|
||||
|
||||
local DetectedSet = DetectedItem.Set
|
||||
local PlayersNearBy = self.Detection:GetPlayersNearBy( DetectedItem )
|
||||
|
||||
|
||||
local PlayerTypes = {}
|
||||
local PlayersCount = 0
|
||||
|
||||
@@ -459,7 +453,7 @@ do -- TASK_A2A_DISPATCHER
|
||||
for PlayerUnitName, PlayerUnitData in pairs( PlayersNearBy ) do
|
||||
local PlayerUnit = PlayerUnitData -- Wrapper.Unit#UNIT
|
||||
local PlayerName = PlayerUnit:GetPlayerName()
|
||||
--self:F( { PlayerName = PlayerName, PlayerUnit = PlayerUnit } )
|
||||
-- self:F( { PlayerName = PlayerName, PlayerUnit = PlayerUnit } )
|
||||
if PlayerUnit:IsAirPlane() and PlayerName ~= nil then
|
||||
local FriendlyUnitThreatLevel = PlayerUnit:GetThreatLevel()
|
||||
PlayersCount = PlayersCount + 1
|
||||
@@ -469,20 +463,19 @@ do -- TASK_A2A_DISPATCHER
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
local PlayerTypesReport = REPORT:New()
|
||||
|
||||
|
||||
if PlayersCount > 0 then
|
||||
for PlayerName, PlayerType in pairs( PlayerTypes ) do
|
||||
PlayerTypesReport:Add( string.format('"%s" in %s', PlayerName, PlayerType ) )
|
||||
PlayerTypesReport:Add( string.format( '"%s" in %s', PlayerName, PlayerType ) )
|
||||
end
|
||||
else
|
||||
PlayerTypesReport:Add( "-" )
|
||||
end
|
||||
|
||||
|
||||
|
||||
return PlayersCount, PlayerTypesReport
|
||||
end
|
||||
|
||||
@@ -491,24 +484,23 @@ do -- TASK_A2A_DISPATCHER
|
||||
self.Tasks[TaskIndex] = nil
|
||||
end
|
||||
|
||||
|
||||
--- Assigns tasks in relation to the detected items to the @{Core.Set#SET_GROUP}.
|
||||
-- @param #TASK_A2A_DISPATCHER self
|
||||
-- @param Functional.Detection#DETECTION_BASE Detection The detection created by the @{Functional.Detection#DETECTION_BASE} derived object.
|
||||
-- @return #boolean Return true if you want the task assigning to continue... false will cancel the loop.
|
||||
function TASK_A2A_DISPATCHER:ProcessDetected( Detection )
|
||||
self:F()
|
||||
|
||||
|
||||
local AreaMsg = {}
|
||||
local TaskMsg = {}
|
||||
local ChangeMsg = {}
|
||||
|
||||
|
||||
local Mission = self.Mission
|
||||
|
||||
|
||||
if Mission:IsIDLE() or Mission:IsENGAGED() then
|
||||
|
||||
|
||||
local TaskReport = REPORT:New()
|
||||
|
||||
|
||||
-- Checking the task queue for the dispatcher, and removing any obsolete task!
|
||||
for TaskIndex, TaskData in pairs( self.Tasks ) do
|
||||
local Task = TaskData -- Tasking.Task#TASK
|
||||
@@ -526,18 +518,18 @@ do -- TASK_A2A_DISPATCHER
|
||||
|
||||
-- Now that all obsolete tasks are removed, loop through the detected targets.
|
||||
for DetectedItemID, DetectedItem in pairs( Detection:GetDetectedItems() ) do
|
||||
|
||||
|
||||
local DetectedItem = DetectedItem -- Functional.Detection#DETECTION_BASE.DetectedItem
|
||||
local DetectedSet = DetectedItem.Set -- Core.Set#SET_UNIT
|
||||
local DetectedCount = DetectedSet:Count()
|
||||
local DetectedZone = DetectedItem.Zone
|
||||
--self:F( { "Targets in DetectedItem", DetectedItem.ItemID, DetectedSet:Count(), tostring( DetectedItem ) } )
|
||||
--DetectedSet:Flush( self )
|
||||
|
||||
-- self:F( { "Targets in DetectedItem", DetectedItem.ItemID, DetectedSet:Count(), tostring( DetectedItem ) } )
|
||||
-- DetectedSet:Flush( self )
|
||||
|
||||
local DetectedID = DetectedItem.ID
|
||||
local TaskIndex = DetectedItem.Index
|
||||
local DetectedItemChanged = DetectedItem.Changed
|
||||
|
||||
|
||||
local Task = self.Tasks[TaskIndex]
|
||||
Task = self:EvaluateRemoveTask( Mission, Task, Detection, DetectedItem, TaskIndex, DetectedItemChanged ) -- Task will be removed if it is planned and changed.
|
||||
|
||||
@@ -560,7 +552,7 @@ do -- TASK_A2A_DISPATCHER
|
||||
Task = TASK_A2A_SWEEP:New( Mission, self.SetGroup, string.format( "SWEEP.%03d", DetectedID ), TargetSetUnit )
|
||||
Task:SetDetection( Detection, DetectedItem )
|
||||
Task:UpdateTaskInfo( DetectedItem )
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -577,7 +569,7 @@ do -- TASK_A2A_DISPATCHER
|
||||
function Task.OnEnterCancelled( Task, From, Event, To )
|
||||
self:Cancelled( Task )
|
||||
end
|
||||
|
||||
|
||||
function Task.OnEnterFailed( Task, From, Event, To )
|
||||
self:Failed( Task )
|
||||
end
|
||||
@@ -585,38 +577,38 @@ do -- TASK_A2A_DISPATCHER
|
||||
function Task.OnEnterAborted( Task, From, Event, To )
|
||||
self:Aborted( Task )
|
||||
end
|
||||
|
||||
|
||||
TaskReport:Add( Task:GetName() )
|
||||
else
|
||||
self:F("This should not happen")
|
||||
self:F( "This should not happen" )
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
if Task then
|
||||
local FriendliesCount, FriendliesReport = self:GetFriendliesNearBy( DetectedItem, Unit.Category.AIRPLANE )
|
||||
Task.TaskInfo:AddText( "Friendlies", string.format( "%d ( %s )", FriendliesCount, FriendliesReport:Text( "," ) ), 40, "MOD" )
|
||||
Task.TaskInfo:AddText( "Friendlies", string.format( "%d ( %s )", FriendliesCount, FriendliesReport:Text( "," ) ), 40, "MOD" )
|
||||
local PlayersCount, PlayersReport = self:GetPlayerFriendliesNearBy( DetectedItem )
|
||||
Task.TaskInfo:AddText( "Players", string.format( "%d ( %s )", PlayersCount, PlayersReport:Text( "," ) ), 40, "MOD" )
|
||||
Task.TaskInfo:AddText( "Players", string.format( "%d ( %s )", PlayersCount, PlayersReport:Text( "," ) ), 40, "MOD" )
|
||||
end
|
||||
|
||||
|
||||
-- OK, so the tasking has been done, now delete the changes reported for the area.
|
||||
Detection:AcceptChanges( DetectedItem )
|
||||
end
|
||||
|
||||
|
||||
-- TODO set menus using the HQ coordinator
|
||||
Mission:GetCommandCenter():SetMenu()
|
||||
|
||||
local TaskText = TaskReport:Text(", ")
|
||||
|
||||
local TaskText = TaskReport:Text( ", " )
|
||||
|
||||
for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do
|
||||
if ( not Mission:IsGroupAssigned(TaskGroup) ) and TaskText ~= "" then
|
||||
if (not Mission:IsGroupAssigned( TaskGroup )) and TaskText ~= "" and (self.FlashNewTask) then
|
||||
Mission:GetCommandCenter():MessageToGroup( string.format( "%s has tasks %s. Subscribe to a task using the radio menu.", Mission:GetShortText(), TaskText ), TaskGroup )
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
--- **Tasking** - The TASK_A2G models tasks for players in Air to Ground engagements.
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
--
|
||||
-- ### Contributions:
|
||||
--
|
||||
--
|
||||
-- ### Contributions:
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- @module Tasking.Task_A2G
|
||||
-- @image MOOSE.JPG
|
||||
|
||||
@@ -18,29 +18,29 @@ do -- TASK_A2G
|
||||
-- @field Core.Set#SET_UNIT TargetSetUnit
|
||||
-- @extends Tasking.Task#TASK
|
||||
|
||||
--- The TASK_A2G class defines Air To Ground tasks for a @{Set} of Target Units,
|
||||
--- The TASK_A2G class defines Air To Ground tasks for a @{Set} of Target Units,
|
||||
-- based on the tasking capabilities defined in @{Tasking.Task#TASK}.
|
||||
-- The TASK_A2G is implemented using a @{Core.Fsm#FSM_TASK}, and has the following statuses:
|
||||
--
|
||||
--
|
||||
-- * **None**: Start of the process
|
||||
-- * **Planned**: The A2G task is planned.
|
||||
-- * **Assigned**: The A2G task is assigned to a @{Wrapper.Group#GROUP}.
|
||||
-- * **Success**: The A2G task is successfully completed.
|
||||
-- * **Failed**: The A2G task has failed. This will happen if the player exists the task early, without communicating a possible cancellation to HQ.
|
||||
--
|
||||
--
|
||||
-- ## 1) Set the scoring of achievements in an A2G attack.
|
||||
--
|
||||
--
|
||||
-- Scoring or penalties can be given in the following circumstances:
|
||||
--
|
||||
--
|
||||
-- * @{#TASK_A2G.SetScoreOnDestroy}(): Set a score when a target in scope of the A2G attack, has been destroyed.
|
||||
-- * @{#TASK_A2G.SetScoreOnSuccess}(): Set a score when all the targets in scope of the A2G attack, have been destroyed.
|
||||
-- * @{#TASK_A2G.SetPenaltyOnFailed}(): Set a penalty when the A2G attack has failed.
|
||||
--
|
||||
--
|
||||
-- @field #TASK_A2G
|
||||
TASK_A2G = {
|
||||
ClassName = "TASK_A2G",
|
||||
ClassName = "TASK_A2G"
|
||||
}
|
||||
|
||||
|
||||
--- Instantiates a new TASK_A2G.
|
||||
-- @param #TASK_A2G self
|
||||
-- @param Tasking.Mission#MISSION Mission
|
||||
@@ -54,53 +54,51 @@ do -- TASK_A2G
|
||||
function TASK_A2G:New( Mission, SetGroup, TaskName, TargetSetUnit, TaskType, TaskBriefing )
|
||||
local self = BASE:Inherit( self, TASK:New( Mission, SetGroup, TaskName, TaskType, TaskBriefing ) ) -- Tasking.Task#TASK_A2G
|
||||
self:F()
|
||||
|
||||
|
||||
self.TargetSetUnit = TargetSetUnit
|
||||
self.TaskType = TaskType
|
||||
|
||||
|
||||
local Fsm = self:GetUnitProcess()
|
||||
|
||||
|
||||
Fsm:AddTransition( "Assigned", "RouteToRendezVous", "RoutingToRendezVous" )
|
||||
Fsm:AddProcess ( "RoutingToRendezVous", "RouteToRendezVousPoint", ACT_ROUTE_POINT:New(), { Arrived = "ArriveAtRendezVous" } )
|
||||
Fsm:AddProcess ( "RoutingToRendezVous", "RouteToRendezVousZone", ACT_ROUTE_ZONE:New(), { Arrived = "ArriveAtRendezVous" } )
|
||||
|
||||
Fsm:AddProcess( "RoutingToRendezVous", "RouteToRendezVousPoint", ACT_ROUTE_POINT:New(), { Arrived = "ArriveAtRendezVous" } )
|
||||
Fsm:AddProcess( "RoutingToRendezVous", "RouteToRendezVousZone", ACT_ROUTE_ZONE:New(), { Arrived = "ArriveAtRendezVous" } )
|
||||
|
||||
Fsm:AddTransition( { "Arrived", "RoutingToRendezVous" }, "ArriveAtRendezVous", "ArrivedAtRendezVous" )
|
||||
|
||||
|
||||
Fsm:AddTransition( { "ArrivedAtRendezVous", "HoldingAtRendezVous" }, "Engage", "Engaging" )
|
||||
Fsm:AddTransition( { "ArrivedAtRendezVous", "HoldingAtRendezVous" }, "HoldAtRendezVous", "HoldingAtRendezVous" )
|
||||
|
||||
Fsm:AddProcess ( "Engaging", "Account", ACT_ACCOUNT_DEADS:New(), {} )
|
||||
|
||||
Fsm:AddProcess( "Engaging", "Account", ACT_ACCOUNT_DEADS:New(), {} )
|
||||
Fsm:AddTransition( "Engaging", "RouteToTarget", "Engaging" )
|
||||
Fsm:AddProcess( "Engaging", "RouteToTargetZone", ACT_ROUTE_ZONE:New(), {} )
|
||||
Fsm:AddProcess( "Engaging", "RouteToTargetPoint", ACT_ROUTE_POINT:New(), {} )
|
||||
Fsm:AddTransition( "Engaging", "RouteToTargets", "Engaging" )
|
||||
|
||||
--Fsm:AddTransition( "Accounted", "DestroyedAll", "Accounted" )
|
||||
--Fsm:AddTransition( "Accounted", "Success", "Success" )
|
||||
|
||||
-- Fsm:AddTransition( "Accounted", "DestroyedAll", "Accounted" )
|
||||
-- Fsm:AddTransition( "Accounted", "Success", "Success" )
|
||||
Fsm:AddTransition( "Rejected", "Reject", "Aborted" )
|
||||
Fsm:AddTransition( "Failed", "Fail", "Failed" )
|
||||
|
||||
|
||||
|
||||
--- Test
|
||||
--- Test
|
||||
-- @param #FSM_PROCESS self
|
||||
-- @param Wrapper.Unit#UNIT TaskUnit
|
||||
-- @param Tasking.Task_A2G#TASK_A2G Task
|
||||
function Fsm:onafterAssigned( TaskUnit, Task )
|
||||
self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } )
|
||||
-- Determine the first Unit from the self.RendezVousSetUnit
|
||||
|
||||
|
||||
self:RouteToRendezVous()
|
||||
end
|
||||
|
||||
--- Test
|
||||
|
||||
--- Test
|
||||
-- @param #FSM_PROCESS self
|
||||
-- @param Wrapper.Unit#UNIT TaskUnit
|
||||
-- @param Tasking.Task_A2G#TASK_A2G Task
|
||||
function Fsm:onafterRouteToRendezVous( TaskUnit, Task )
|
||||
self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } )
|
||||
-- Determine the first Unit from the self.RendezVousSetUnit
|
||||
|
||||
|
||||
if Task:GetRendezVousZone( TaskUnit ) then
|
||||
self:__RouteToRendezVousZone( 0.1 )
|
||||
else
|
||||
@@ -112,36 +110,36 @@ do -- TASK_A2G
|
||||
end
|
||||
end
|
||||
|
||||
--- Test
|
||||
--- Test
|
||||
-- @param #FSM_PROCESS self
|
||||
-- @param Wrapper.Unit#UNIT TaskUnit
|
||||
-- @param Tasking.Task#TASK_A2G Task
|
||||
function Fsm:OnAfterArriveAtRendezVous( TaskUnit, Task )
|
||||
self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } )
|
||||
-- Determine the first Unit from the self.TargetSetUnit
|
||||
|
||||
self:__Engage( 0.1 )
|
||||
|
||||
self:__Engage( 0.1 )
|
||||
end
|
||||
|
||||
--- Test
|
||||
|
||||
--- Test
|
||||
-- @param #FSM_PROCESS self
|
||||
-- @param Wrapper.Unit#UNIT TaskUnit
|
||||
-- @param Tasking.Task#TASK_A2G Task
|
||||
function Fsm:onafterEngage( TaskUnit, Task )
|
||||
self:F( { self } )
|
||||
self:__Account( 0.1 )
|
||||
self:__RouteToTarget(0.1 )
|
||||
self:__RouteToTarget( 0.1 )
|
||||
self:__RouteToTargets( -10 )
|
||||
end
|
||||
|
||||
--- Test
|
||||
|
||||
--- Test
|
||||
-- @param #FSM_PROCESS self
|
||||
-- @param Wrapper.Unit#UNIT TaskUnit
|
||||
-- @param Tasking.Task_A2G#TASK_A2G Task
|
||||
function Fsm:onafterRouteToTarget( TaskUnit, Task )
|
||||
self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } )
|
||||
-- Determine the first Unit from the self.TargetSetUnit
|
||||
|
||||
|
||||
if Task:GetTargetZone( TaskUnit ) then
|
||||
self:__RouteToTargetZone( 0.1 )
|
||||
else
|
||||
@@ -154,8 +152,8 @@ do -- TASK_A2G
|
||||
self:__RouteToTargetPoint( 0.1 )
|
||||
end
|
||||
end
|
||||
|
||||
--- Test
|
||||
|
||||
--- Test
|
||||
-- @param #FSM_PROCESS self
|
||||
-- @param Wrapper.Unit#UNIT TaskUnit
|
||||
-- @param Tasking.Task_A2G#TASK_A2G Task
|
||||
@@ -167,20 +165,18 @@ do -- TASK_A2G
|
||||
end
|
||||
self:__RouteToTargets( -10 )
|
||||
end
|
||||
|
||||
|
||||
return self
|
||||
|
||||
|
||||
end
|
||||
|
||||
--- @param #TASK_A2G self
|
||||
-- @param Core.Set#SET_UNIT TargetSetUnit The set of targets.
|
||||
function TASK_A2G:SetTargetSetUnit( TargetSetUnit )
|
||||
|
||||
|
||||
self.TargetSetUnit = TargetSetUnit
|
||||
end
|
||||
|
||||
|
||||
|
||||
--- @param #TASK_A2G self
|
||||
function TASK_A2G:GetPlannedMenuText()
|
||||
return self:GetStateString() .. " - " .. self:GetTaskName() .. " ( " .. self.TargetSetUnit:GetUnitTypesText() .. " )"
|
||||
@@ -190,34 +186,32 @@ do -- TASK_A2G
|
||||
-- @param Core.Point#COORDINATE RendezVousCoordinate The Coordinate object referencing to the 2D point where the RendezVous point is located on the map.
|
||||
-- @param #number RendezVousRange The RendezVousRange that defines when the player is considered to have arrived at the RendezVous point.
|
||||
-- @param Wrapper.Unit#UNIT TaskUnit
|
||||
function TASK_A2G:SetRendezVousCoordinate( RendezVousCoordinate, RendezVousRange, TaskUnit )
|
||||
|
||||
function TASK_A2G:SetRendezVousCoordinate( RendezVousCoordinate, RendezVousRange, TaskUnit )
|
||||
|
||||
local ProcessUnit = self:GetUnitProcess( TaskUnit )
|
||||
|
||||
|
||||
local ActRouteRendezVous = ProcessUnit:GetProcess( "RoutingToRendezVous", "RouteToRendezVousPoint" ) -- Actions.Act_Route#ACT_ROUTE_POINT
|
||||
ActRouteRendezVous:SetCoordinate( RendezVousCoordinate )
|
||||
ActRouteRendezVous:SetRange( RendezVousRange )
|
||||
end
|
||||
|
||||
|
||||
--- @param #TASK_A2G self
|
||||
-- @param Wrapper.Unit#UNIT TaskUnit
|
||||
-- @return Core.Point#COORDINATE The Coordinate object referencing to the 2D point where the RendezVous point is located on the map.
|
||||
-- @return #number The RendezVousRange that defines when the player is considered to have arrived at the RendezVous point.
|
||||
function TASK_A2G:GetRendezVousCoordinate( TaskUnit )
|
||||
|
||||
|
||||
local ProcessUnit = self:GetUnitProcess( TaskUnit )
|
||||
|
||||
local ActRouteRendezVous = ProcessUnit:GetProcess( "RoutingToRendezVous", "RouteToRendezVousPoint" ) -- Actions.Act_Route#ACT_ROUTE_POINT
|
||||
return ActRouteRendezVous:GetCoordinate(), ActRouteRendezVous:GetRange()
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
--- @param #TASK_A2G self
|
||||
-- @param Core.Zone#ZONE_BASE RendezVousZone The Zone object where the RendezVous is located on the map.
|
||||
-- @param Wrapper.Unit#UNIT TaskUnit
|
||||
function TASK_A2G:SetRendezVousZone( RendezVousZone, TaskUnit )
|
||||
|
||||
|
||||
local ProcessUnit = self:GetUnitProcess( TaskUnit )
|
||||
|
||||
local ActRouteRendezVous = ProcessUnit:GetProcess( "RoutingToRendezVous", "RouteToRendezVousZone" ) -- Actions.Act_Route#ACT_ROUTE_ZONE
|
||||
@@ -234,18 +228,17 @@ do -- TASK_A2G
|
||||
local ActRouteRendezVous = ProcessUnit:GetProcess( "RoutingToRendezVous", "RouteToRendezVousZone" ) -- Actions.Act_Route#ACT_ROUTE_ZONE
|
||||
return ActRouteRendezVous:GetZone()
|
||||
end
|
||||
|
||||
|
||||
--- @param #TASK_A2G self
|
||||
-- @param Core.Point#COORDINATE TargetCoordinate The Coordinate object where the Target is located on the map.
|
||||
-- @param Wrapper.Unit#UNIT TaskUnit
|
||||
function TASK_A2G:SetTargetCoordinate( TargetCoordinate, TaskUnit )
|
||||
|
||||
|
||||
local ProcessUnit = self:GetUnitProcess( TaskUnit )
|
||||
|
||||
local ActRouteTarget = ProcessUnit:GetProcess( "Engaging", "RouteToTargetPoint" ) -- Actions.Act_Route#ACT_ROUTE_POINT
|
||||
ActRouteTarget:SetCoordinate( TargetCoordinate )
|
||||
end
|
||||
|
||||
|
||||
--- @param #TASK_A2G self
|
||||
-- @param Wrapper.Unit#UNIT TaskUnit
|
||||
@@ -258,18 +251,16 @@ do -- TASK_A2G
|
||||
return ActRouteTarget:GetCoordinate()
|
||||
end
|
||||
|
||||
|
||||
--- @param #TASK_A2G self
|
||||
-- @param Core.Zone#ZONE_BASE TargetZone The Zone object where the Target is located on the map.
|
||||
-- @param Wrapper.Unit#UNIT TaskUnit
|
||||
function TASK_A2G:SetTargetZone( TargetZone, TaskUnit )
|
||||
|
||||
|
||||
local ProcessUnit = self:GetUnitProcess( TaskUnit )
|
||||
|
||||
local ActRouteTarget = ProcessUnit:GetProcess( "Engaging", "RouteToTargetZone" ) -- Actions.Act_Route#ACT_ROUTE_ZONE
|
||||
ActRouteTarget:SetZone( TargetZone )
|
||||
end
|
||||
|
||||
|
||||
--- @param #TASK_A2G self
|
||||
-- @param Wrapper.Unit#UNIT TaskUnit
|
||||
@@ -283,47 +274,46 @@ do -- TASK_A2G
|
||||
end
|
||||
|
||||
function TASK_A2G:SetGoalTotal()
|
||||
|
||||
|
||||
self.GoalTotal = self.TargetSetUnit:Count()
|
||||
end
|
||||
|
||||
function TASK_A2G:GetGoalTotal()
|
||||
|
||||
|
||||
return self.GoalTotal
|
||||
end
|
||||
|
||||
|
||||
--- Return the relative distance to the target vicinity from the player, in order to sort the targets in the reports per distance from the threats.
|
||||
-- @param #TASK_A2G self
|
||||
function TASK_A2G:ReportOrder( ReportGroup )
|
||||
self:UpdateTaskInfo( self.DetectedItem )
|
||||
|
||||
function TASK_A2G:ReportOrder( ReportGroup )
|
||||
self:UpdateTaskInfo( self.DetectedItem )
|
||||
|
||||
local Coordinate = self.TaskInfo:GetData( "Coordinate" )
|
||||
local Distance = ReportGroup:GetCoordinate():Get2DDistance( Coordinate )
|
||||
|
||||
|
||||
return Distance
|
||||
end
|
||||
|
||||
|
||||
|
||||
--- This method checks every 10 seconds if the goal has been reached of the task.
|
||||
-- @param #TASK_A2G self
|
||||
function TASK_A2G:onafterGoal( TaskUnit, From, Event, To )
|
||||
local TargetSetUnit = self.TargetSetUnit -- Core.Set#SET_UNIT
|
||||
|
||||
|
||||
if TargetSetUnit:Count() == 0 then
|
||||
self:Success()
|
||||
end
|
||||
|
||||
|
||||
self:__Goal( -10 )
|
||||
end
|
||||
|
||||
--- @param #TASK_A2G self
|
||||
function TASK_A2G:UpdateTaskInfo( DetectedItem )
|
||||
|
||||
|
||||
if self:IsStatePlanned() or self:IsStateAssigned() then
|
||||
local TargetCoordinate = DetectedItem and self.Detection:GetDetectedItemCoordinate( DetectedItem ) or self.TargetSetUnit:GetFirst():GetCoordinate()
|
||||
local TargetCoordinate = DetectedItem and self.Detection:GetDetectedItemCoordinate( DetectedItem ) or self.TargetSetUnit:GetFirst():GetCoordinate()
|
||||
self.TaskInfo:AddTaskName( 0, "MSOD" )
|
||||
self.TaskInfo:AddCoordinate( TargetCoordinate, 1, "SOD" )
|
||||
|
||||
|
||||
local ThreatLevel, ThreatText
|
||||
if DetectedItem then
|
||||
ThreatLevel, ThreatText = self.Detection:GetDetectedItemThreatLevel( DetectedItem )
|
||||
@@ -331,7 +321,7 @@ do -- TASK_A2G
|
||||
ThreatLevel, ThreatText = self.TargetSetUnit:CalculateThreatLevelA2G()
|
||||
end
|
||||
self.TaskInfo:AddThreat( ThreatText, ThreatLevel, 10, "MOD", true )
|
||||
|
||||
|
||||
if self.Detection then
|
||||
local DetectedItemsCount = self.TargetSetUnit:Count()
|
||||
local ReportTypes = REPORT:New()
|
||||
@@ -344,33 +334,33 @@ do -- TASK_A2G
|
||||
end
|
||||
end
|
||||
self.TaskInfo:AddTargetCount( DetectedItemsCount, 11, "O", true )
|
||||
self.TaskInfo:AddTargets( DetectedItemsCount, ReportTypes:Text( ", " ), 20, "D", true )
|
||||
self.TaskInfo:AddTargets( DetectedItemsCount, ReportTypes:Text( ", " ), 20, "D", true )
|
||||
else
|
||||
local DetectedItemsCount = self.TargetSetUnit:Count()
|
||||
local DetectedItemsTypes = self.TargetSetUnit:GetTypeNames()
|
||||
self.TaskInfo:AddTargetCount( DetectedItemsCount, 11, "O", true )
|
||||
self.TaskInfo:AddTargets( DetectedItemsCount, DetectedItemsTypes, 20, "D", true )
|
||||
self.TaskInfo:AddTargets( DetectedItemsCount, DetectedItemsTypes, 20, "D", true )
|
||||
end
|
||||
self.TaskInfo:AddQFEAtCoordinate( TargetCoordinate, 30, "MOD" )
|
||||
self.TaskInfo:AddTemperatureAtCoordinate( TargetCoordinate, 31, "MD" )
|
||||
self.TaskInfo:AddWindAtCoordinate( TargetCoordinate, 32, "MD" )
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- This function is called from the @{Tasking.CommandCenter#COMMANDCENTER} to determine the method of automatic task selection.
|
||||
-- @param #TASK_A2G self
|
||||
-- @param #number AutoAssignMethod The method to be applied to the task.
|
||||
-- @param Tasking.CommandCenter#COMMANDCENTER CommandCenter The command center.
|
||||
-- @param Wrapper.Group#GROUP TaskGroup The player group.
|
||||
function TASK_A2G:GetAutoAssignPriority( AutoAssignMethod, CommandCenter, TaskGroup )
|
||||
|
||||
if AutoAssignMethod == COMMANDCENTER.AutoAssignMethods.Random then
|
||||
|
||||
if AutoAssignMethod == COMMANDCENTER.AutoAssignMethods.Random then
|
||||
return math.random( 1, 9 )
|
||||
elseif AutoAssignMethod == COMMANDCENTER.AutoAssignMethods.Distance then
|
||||
local Coordinate = self.TaskInfo:GetData( "Coordinate" )
|
||||
local Distance = Coordinate:Get2DDistance( CommandCenter:GetPositionable():GetCoordinate() )
|
||||
self:F({Distance=Distance})
|
||||
self:F( { Distance = Distance } )
|
||||
return math.floor( Distance )
|
||||
elseif AutoAssignMethod == COMMANDCENTER.AutoAssignMethods.Priority then
|
||||
return 1
|
||||
@@ -379,8 +369,7 @@ do -- TASK_A2G
|
||||
return 0
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
do -- TASK_A2G_SEAD
|
||||
|
||||
@@ -397,9 +386,9 @@ do -- TASK_A2G_SEAD
|
||||
--
|
||||
-- @field #TASK_A2G_SEAD
|
||||
TASK_A2G_SEAD = {
|
||||
ClassName = "TASK_A2G_SEAD",
|
||||
ClassName = "TASK_A2G_SEAD"
|
||||
}
|
||||
|
||||
|
||||
--- Instantiates a new TASK_A2G_SEAD.
|
||||
-- @param #TASK_A2G_SEAD self
|
||||
-- @param Tasking.Mission#MISSION Mission
|
||||
@@ -408,19 +397,16 @@ do -- TASK_A2G_SEAD
|
||||
-- @param Core.Set#SET_UNIT TargetSetUnit
|
||||
-- @param #string TaskBriefing The briefing of the task.
|
||||
-- @return #TASK_A2G_SEAD self
|
||||
function TASK_A2G_SEAD:New( Mission, SetGroup, TaskName, TargetSetUnit, TaskBriefing)
|
||||
function TASK_A2G_SEAD:New( Mission, SetGroup, TaskName, TargetSetUnit, TaskBriefing )
|
||||
local self = BASE:Inherit( self, TASK_A2G:New( Mission, SetGroup, TaskName, TargetSetUnit, "SEAD", TaskBriefing ) ) -- #TASK_A2G_SEAD
|
||||
self:F()
|
||||
|
||||
|
||||
Mission:AddTask( self )
|
||||
|
||||
self:SetBriefing(
|
||||
TaskBriefing or
|
||||
"Execute a Suppression of Enemy Air Defenses."
|
||||
)
|
||||
|
||||
self:SetBriefing( TaskBriefing or "Execute a Suppression of Enemy Air Defenses." )
|
||||
|
||||
return self
|
||||
end
|
||||
end
|
||||
|
||||
--- Set a score when a target in scope of the A2G attack, has been destroyed .
|
||||
-- @param #TASK_A2G_SEAD self
|
||||
@@ -434,7 +420,7 @@ do -- TASK_A2G_SEAD
|
||||
local ProcessUnit = self:GetUnitProcess( TaskUnit )
|
||||
|
||||
ProcessUnit:AddScoreProcess( "Engaging", "Account", "AccountForPlayer", "Player " .. PlayerName .. " has SEADed a target.", Score )
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -450,7 +436,7 @@ do -- TASK_A2G_SEAD
|
||||
local ProcessUnit = self:GetUnitProcess( TaskUnit )
|
||||
|
||||
ProcessUnit:AddScore( "Success", "All radar emitting targets have been successfully SEADed!", Score )
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -466,11 +452,10 @@ do -- TASK_A2G_SEAD
|
||||
local ProcessUnit = self:GetUnitProcess( TaskUnit )
|
||||
|
||||
ProcessUnit:AddScore( "Failed", "The SEADing has failed!", Penalty )
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
do -- TASK_A2G_BAI
|
||||
@@ -488,10 +473,8 @@ do -- TASK_A2G_BAI
|
||||
-- based on detected enemy ground targets.
|
||||
--
|
||||
-- @field #TASK_A2G_BAI
|
||||
TASK_A2G_BAI = {
|
||||
ClassName = "TASK_A2G_BAI",
|
||||
}
|
||||
|
||||
TASK_A2G_BAI = { ClassName = "TASK_A2G_BAI" }
|
||||
|
||||
--- Instantiates a new TASK_A2G_BAI.
|
||||
-- @param #TASK_A2G_BAI self
|
||||
-- @param Tasking.Mission#MISSION Mission
|
||||
@@ -503,14 +486,11 @@ do -- TASK_A2G_BAI
|
||||
function TASK_A2G_BAI:New( Mission, SetGroup, TaskName, TargetSetUnit, TaskBriefing )
|
||||
local self = BASE:Inherit( self, TASK_A2G:New( Mission, SetGroup, TaskName, TargetSetUnit, "BAI", TaskBriefing ) ) -- #TASK_A2G_BAI
|
||||
self:F()
|
||||
|
||||
|
||||
Mission:AddTask( self )
|
||||
|
||||
self:SetBriefing(
|
||||
TaskBriefing or
|
||||
"Execute a Battlefield Air Interdiction of a group of enemy targets."
|
||||
)
|
||||
|
||||
|
||||
self:SetBriefing( TaskBriefing or "Execute a Battlefield Air Interdiction of a group of enemy targets." )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -526,7 +506,7 @@ do -- TASK_A2G_BAI
|
||||
local ProcessUnit = self:GetUnitProcess( TaskUnit )
|
||||
|
||||
ProcessUnit:AddScoreProcess( "Engaging", "Account", "AccountForPlayer", "Player " .. PlayerName .. " has destroyed a target in Battlefield Air Interdiction (BAI).", Score )
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -542,7 +522,7 @@ do -- TASK_A2G_BAI
|
||||
local ProcessUnit = self:GetUnitProcess( TaskUnit )
|
||||
|
||||
ProcessUnit:AddScore( "Success", "All targets have been successfully destroyed! The Battlefield Air Interdiction (BAI) is a success!", Score )
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -558,15 +538,12 @@ do -- TASK_A2G_BAI
|
||||
local ProcessUnit = self:GetUnitProcess( TaskUnit )
|
||||
|
||||
ProcessUnit:AddScore( "Failed", "The Battlefield Air Interdiction (BAI) has failed!", Penalty )
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
do -- TASK_A2G_CAS
|
||||
|
||||
--- The TASK_A2G_CAS class
|
||||
@@ -581,10 +558,8 @@ do -- TASK_A2G_CAS
|
||||
-- based on detected enemy ground targets.
|
||||
--
|
||||
-- @field #TASK_A2G_CAS
|
||||
TASK_A2G_CAS = {
|
||||
ClassName = "TASK_A2G_CAS",
|
||||
}
|
||||
|
||||
TASK_A2G_CAS = { ClassName = "TASK_A2G_CAS" }
|
||||
|
||||
--- Instantiates a new TASK_A2G_CAS.
|
||||
-- @param #TASK_A2G_CAS self
|
||||
-- @param Tasking.Mission#MISSION Mission
|
||||
@@ -596,19 +571,13 @@ do -- TASK_A2G_CAS
|
||||
function TASK_A2G_CAS:New( Mission, SetGroup, TaskName, TargetSetUnit, TaskBriefing )
|
||||
local self = BASE:Inherit( self, TASK_A2G:New( Mission, SetGroup, TaskName, TargetSetUnit, "CAS", TaskBriefing ) ) -- #TASK_A2G_CAS
|
||||
self:F()
|
||||
|
||||
Mission:AddTask( self )
|
||||
|
||||
self:SetBriefing(
|
||||
TaskBriefing or
|
||||
"Execute a Close Air Support for a group of enemy targets. " ..
|
||||
"Beware of friendlies at the vicinity! "
|
||||
)
|
||||
|
||||
|
||||
Mission:AddTask( self )
|
||||
|
||||
self:SetBriefing( TaskBriefing or ( "Execute a Close Air Support for a group of enemy targets. " .. "Beware of friendlies at the vicinity! " ) )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Set a score when a target in scope of the A2G attack, has been destroyed .
|
||||
-- @param #TASK_A2G_CAS self
|
||||
@@ -622,7 +591,7 @@ do -- TASK_A2G_CAS
|
||||
local ProcessUnit = self:GetUnitProcess( TaskUnit )
|
||||
|
||||
ProcessUnit:AddScoreProcess( "Engaging", "Account", "AccountForPlayer", "Player " .. PlayerName .. " has destroyed a target in Close Air Support (CAS).", Score )
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -638,7 +607,7 @@ do -- TASK_A2G_CAS
|
||||
local ProcessUnit = self:GetUnitProcess( TaskUnit )
|
||||
|
||||
ProcessUnit:AddScore( "Success", "All targets have been successfully destroyed! The Close Air Support (CAS) was a success!", Score )
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -654,9 +623,8 @@ do -- TASK_A2G_CAS
|
||||
local ProcessUnit = self:GetUnitProcess( TaskUnit )
|
||||
|
||||
ProcessUnit:AddScore( "Failed", "The Close Air Support (CAS) has failed!", Penalty )
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
@@ -1,24 +1,25 @@
|
||||
--- **Tasking** -- Dynamically allocates A2G tasks to human players, based on detected ground targets through reconnaissance.
|
||||
--
|
||||
--- **Tasking** -- Dynamically allocates A2G tasks to human players, based on detected ground targets through reconnaissance.
|
||||
--
|
||||
-- **Features:**
|
||||
--
|
||||
--
|
||||
-- * Dynamically assign tasks to human players based on detected targets.
|
||||
-- * Dynamically change the tasks as the tactical situation evolves during the mission.
|
||||
-- * Dynamically assign (CAS) Close Air Support tasks for human players.
|
||||
-- * Dynamically assign (BAI) Battlefield Air Interdiction tasks for human players.
|
||||
-- * Dynamically assign (SEAD) Supression of Enemy Air Defense tasks for human players to eliminate G2A missile threats.
|
||||
-- * Dynamically assign (SEAD) Suppression of Enemy Air Defense tasks for human players to eliminate G2A missile threats.
|
||||
-- * Define and use an EWR (Early Warning Radar) network.
|
||||
-- * Define different ranges to engage upon intruders.
|
||||
-- * Keep task achievements.
|
||||
-- * Score task achievements.--
|
||||
-- * Score task achievements.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
--
|
||||
-- ### Contributions:
|
||||
--
|
||||
--
|
||||
-- ### Contributions:
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- @module Tasking.Task_A2G_Dispatcher
|
||||
-- @image Task_A2G_Dispatcher.JPG
|
||||
|
||||
@@ -32,151 +33,151 @@ do -- TASK_A2G_DISPATCHER
|
||||
-- @extends Tasking.DetectionManager#DETECTION_MANAGER
|
||||
|
||||
--- Orchestrates dynamic **A2G Task Dispatching** based on the detection results of a linked @{Detection} object.
|
||||
--
|
||||
--
|
||||
-- It uses the Tasking System within the MOOSE framework, which is a multi-player Tasking Orchestration system.
|
||||
-- It provides a truly dynamic battle environment for pilots and ground commanders to engage upon,
|
||||
-- in a true co-operation environment wherein **Multiple Teams** will collaborate in Missions to **achieve a common Mission Goal**.
|
||||
--
|
||||
--
|
||||
-- The A2G dispatcher will dispatch the A2G Tasks to a defined @{Set} of @{Wrapper.Group}s that will be manned by **Players**.
|
||||
-- We call this the **AttackSet** of the A2G dispatcher. So, the Players are seated in the @{Client}s of the @{Wrapper.Group} @{Set}.
|
||||
--
|
||||
--
|
||||
-- Depending on the actions of the enemy, preventive tasks are dispatched to the players to orchestrate the engagement in a true co-operation.
|
||||
-- The detection object will group the detected targets by its grouping method, and integrates a @{Set} of @{Wrapper.Group}s that are Recce vehicles or air units.
|
||||
-- We call this the **RecceSet** of the A2G dispatcher.
|
||||
--
|
||||
--
|
||||
-- Depending on the current detected tactical situation, different task types will be dispatched to the Players seated in the AttackSet..
|
||||
-- There are currently 3 **Task Types** implemented in the TASK\_A2G\_DISPATCHER:
|
||||
--
|
||||
--
|
||||
-- - **SEAD Task**: Dispatched when there are ground based Radar Emitters detected within an area.
|
||||
-- - **CAS Task**: Dispatched when there are no ground based Radar Emitters within the area, but there are friendly ground Units within 6 km from the enemy.
|
||||
-- - **BAI Task**: Dispatched when there are no ground based Radar Emitters within the area, and there aren't friendly ground Units within 6 km from the enemy.
|
||||
--
|
||||
-- # 0. Tactical Situations
|
||||
--
|
||||
--
|
||||
-- This chapters provides some insights in the tactical situations when certain Task Types are created.
|
||||
-- The Task Types are depending on the enemy positions that were detected, and the current location of friendly units.
|
||||
--
|
||||
--
|
||||
-- 
|
||||
--
|
||||
--
|
||||
-- In the demonstration mission [TAD-A2G-000 - AREAS - Detection test],
|
||||
-- the tactical situation is a demonstration how the A2G detection works.
|
||||
-- This example will be taken further in the explanation in the following chapters.
|
||||
--
|
||||
--
|
||||
-- 
|
||||
--
|
||||
--
|
||||
-- The red coalition are the players, the blue coalition is the enemy.
|
||||
--
|
||||
--
|
||||
-- Red reconnaissance vehicles and airborne units are detecting the targets.
|
||||
-- We call this the RecceSet as explained above, which is a Set of Groups that
|
||||
-- have a group name starting with `Recce` (configured in the mission script).
|
||||
--
|
||||
--
|
||||
-- Red attack units are responsible for executing the mission for the command center.
|
||||
-- We call this the AttackSet, which is a Set of Groups with a group name starting with `Attack` (configured in the mission script).
|
||||
-- These units are setup in this demonstration mission to be ground vehicles and airplanes.
|
||||
-- For demonstration purposes, the attack airplane is stationed on the ground to explain
|
||||
-- the messages and the menus properly.
|
||||
-- Further test missions demonstrate the A2G task dispatcher from within air.
|
||||
--
|
||||
--
|
||||
-- Depending upon the detection results, the A2G dispatcher will create different tasks.
|
||||
--
|
||||
--
|
||||
-- # 0.1. SEAD Task
|
||||
--
|
||||
--
|
||||
-- A SEAD Task is dispatched when there are ground based Radar Emitters detected within an area.
|
||||
--
|
||||
--
|
||||
-- 
|
||||
--
|
||||
--
|
||||
-- - Once all Radar Emitting Units have been destroyed, the Task will convert into a BAI or CAS task!
|
||||
-- - A CAS and BAI task may be converted into a SEAD task, once a radar has been detected within the area!
|
||||
--
|
||||
--
|
||||
-- # 0.2. CAS Task
|
||||
--
|
||||
--
|
||||
-- A CAS Task is dispatched when there are no ground based Radar Emitters within the area, but there are friendly ground Units within 6 km from the enemy.
|
||||
--
|
||||
--
|
||||
-- 
|
||||
--
|
||||
--
|
||||
-- - After the detection of the CAS task, if the friendly Units are destroyed, the CAS task will convert into a BAI task!
|
||||
-- - Only ground Units are taken into account. Airborne units are ships are not considered friendlies that require Close Air Support.
|
||||
--
|
||||
--
|
||||
-- # 0.3. BAI Task
|
||||
--
|
||||
--
|
||||
-- A BAI Task is dispatched when there are no ground based Radar Emitters within the area, and there aren't friendly ground Units within 6 km from the enemy.
|
||||
--
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- - A BAI task may be converted into a CAS task if friendly Ground Units approach within 6 km range!
|
||||
--
|
||||
-- # 1. Player Experience
|
||||
--
|
||||
--
|
||||
-- The A2G dispatcher is residing under a @{CommandCenter}, which is orchestrating a @{Mission}.
|
||||
-- As a result, you'll find for DCS World missions that implement the A2G dispatcher a **Command Center Menu** and under this one or more **Mission Menus**.
|
||||
--
|
||||
--
|
||||
-- For example, if there are 2 Command Centers (CC).
|
||||
-- Each CC is controlling a couple of Missions, the Radio Menu Structure could look like this:
|
||||
--
|
||||
--
|
||||
-- Radio MENU Structure (F10. Other)
|
||||
--
|
||||
--
|
||||
-- F1. Command Center [Gori]
|
||||
-- F1. Mission "Alpha (Primary)"
|
||||
-- F2. Mission "Beta (Secondary)"
|
||||
-- F3. Mission "Gamma (Tactical)"
|
||||
-- F1. Command Center [Lima]
|
||||
-- F1. Mission "Overlord (High)"
|
||||
--
|
||||
-- Command Center [Gori] is controlling Mission "Alpha", "Beta", "Gamma". Alpha is the Primary mission, Beta the Secondary and there is a Tacical mission Gamma.
|
||||
--
|
||||
-- Command Center [Gori] is controlling Mission "Alpha", "Beta", "Gamma". Alpha is the Primary mission, Beta the Secondary and there is a Tactical mission Gamma.
|
||||
-- Command Center [Lima] is controlling Missions "Overlord", which needs to be executed with High priority.
|
||||
--
|
||||
-- ## 1.1. Mission Menu (Under the Command Center Menu)
|
||||
--
|
||||
--
|
||||
-- The Mission Menu controls the information of the mission, including the:
|
||||
--
|
||||
--
|
||||
-- - **Mission Briefing**: A briefing of the Mission in text, which will be shown as a message.
|
||||
-- - **Mark Task Locations**: A summary of each Task will be shown on the map as a marker.
|
||||
-- - **Create Task Reports**: A menu to create various reports of the current tasks dispatched by the A2G dispatcher.
|
||||
-- - **Create Mission Reports**: A menu to create various reports on the current mission.
|
||||
--
|
||||
--
|
||||
-- For CC [Lima], Mission "Overlord", the menu structure could look like this:
|
||||
--
|
||||
--
|
||||
-- Radio MENU Structure (F10. Other)
|
||||
--
|
||||
--
|
||||
-- F1. Command Center [Lima]
|
||||
-- F1. Mission "Overlord"
|
||||
-- F1. Mission Briefing
|
||||
-- F2. Mark Task Locations on Map
|
||||
-- F3. Task Reports
|
||||
-- F4. Mission Reports
|
||||
--
|
||||
--
|
||||
-- 
|
||||
--
|
||||
--
|
||||
-- ### 1.1.1. Mission Briefing Menu
|
||||
--
|
||||
--
|
||||
-- The Mission Briefing Menu will show in text a summary description of the overall mission objectives and expectations.
|
||||
-- Note that the Mission Briefing is not the briefing of a specific task, but rather provides an overall strategy and tactical situation,
|
||||
-- and explains the mission goals.
|
||||
--
|
||||
--
|
||||
-- and explains the mission goals.
|
||||
--
|
||||
--
|
||||
-- ### 1.1.2. Mark Task Locations Menu
|
||||
--
|
||||
--
|
||||
-- The Mark Task Locations Menu will mark the location indications of the Tasks on the map, if this intelligence is known by the Command Center.
|
||||
-- For A2G tasks this information will always be know, but it can be that for other tasks a location intelligence will be less relevant.
|
||||
-- Note that each Planned task and each Engaged task will be marked. Completed, Failed and Cancelled tasks are not marked.
|
||||
-- Depending on the task type, a summary information is shown to bring to the player the relevant information for situational awareness.
|
||||
--
|
||||
--
|
||||
-- ### 1.1.3. Task Reports Menu
|
||||
--
|
||||
--
|
||||
-- The Task Reports Menu is a sub menu, that allows to create various reports:
|
||||
--
|
||||
--
|
||||
-- - **Tasks Summary**: This report will list all the Tasks that are or were active within the mission, indicating its status.
|
||||
-- - **Planned Tasks**: This report will list all the Tasks that are in status Planned, which are Tasks not assigned to any player, and are ready to be executed.
|
||||
-- - **Assigned Tasks**: This report will list all the Tasks that are in status Assigned, which are Tasks assigned to (a) player(s) and are currently executed.
|
||||
-- - **Successful Tasks**: This report will list all the Tasks that are in status Success, which are Tasks executed by (a) player(s) and are completed successfully.
|
||||
-- - **Failed Tasks**: This report will list all the Tasks that are in status Success, which are Tasks executed by (a) player(s) and that have failed.
|
||||
--
|
||||
--
|
||||
-- The information shown of the tasks will vary according the underlying task type, but are self explanatory.
|
||||
--
|
||||
-- For CC [Gori], Mission "Alpha", the Task Reports menu structure could look like this:
|
||||
--
|
||||
--
|
||||
-- Radio MENU Structure (F10. Other)
|
||||
--
|
||||
--
|
||||
-- F1. Command Center [Gori]
|
||||
-- F1. Mission "Alpha"
|
||||
-- F1. Mission Briefing
|
||||
@@ -188,21 +189,21 @@ do -- TASK_A2G_DISPATCHER
|
||||
-- F4. Successful Tasks
|
||||
-- F5. Failed Tasks
|
||||
-- F4. Mission Reports
|
||||
--
|
||||
--
|
||||
-- Note that these reports provide an "overview" of the tasks. Detailed information of the task can be retrieved using the Detailed Report on the Task Menu.
|
||||
-- (See later).
|
||||
--
|
||||
--
|
||||
-- ### 1.1.4. Mission Reports Menu
|
||||
--
|
||||
--
|
||||
-- The Mission Reports Menu is a sub menu, that provides options to retrieve further information on the current Mission:
|
||||
--
|
||||
-- - **Report Mission Progress**: Shows the progress of the current Mission. Each Task has a %-tage of completion.
|
||||
--
|
||||
-- - **Report Mission Progress**: Shows the progress of the current Mission. Each Task has a % of completion.
|
||||
-- - **Report Players per Task**: Show which players are engaged on which Task within the Mission.
|
||||
--
|
||||
--
|
||||
-- For CC |Gori|, Mission "Alpha", the Mission Reports menu structure could look like this:
|
||||
--
|
||||
--
|
||||
-- Radio MENU Structure (F10. Other)
|
||||
--
|
||||
--
|
||||
-- F1. Command Center [Gori]
|
||||
-- F1. Mission "Alpha"
|
||||
-- F1. Mission Briefing
|
||||
@@ -211,25 +212,25 @@ do -- TASK_A2G_DISPATCHER
|
||||
-- F4. Mission Reports
|
||||
-- F1. Report Mission Progress
|
||||
-- F2. Report Players per Task
|
||||
--
|
||||
--
|
||||
--
|
||||
--
|
||||
-- ## 1.2. Task Management Menus
|
||||
--
|
||||
--
|
||||
-- Very important to remember is: **Multiple Players can be assigned to the same Task, but from the player perspective, the Player can only be assigned to one Task per Mission at the same time!**
|
||||
-- Consider this like the two major modes in which a player can be in. He can be free of tasks or he can be assigned to a Task.
|
||||
-- Depending on whether a Task has been Planned or Assigned to a Player (Group),
|
||||
-- **the Mission Menu will contain extra Menus to control specific Tasks.**
|
||||
--
|
||||
--
|
||||
-- #### 1.2.1. Join a Planned Task
|
||||
--
|
||||
--
|
||||
-- If the Player has not yet been assigned to a Task within the Mission, the Mission Menu will contain additionally a:
|
||||
--
|
||||
--
|
||||
-- - Join Planned Task Menu: This menu structure allows the player to join a planned task (a Task with status Planned).
|
||||
--
|
||||
--
|
||||
-- For CC |Gori|, Mission "Alpha", the menu structure could look like this:
|
||||
--
|
||||
--
|
||||
-- Radio MENU Structure (F10. Other)
|
||||
--
|
||||
--
|
||||
-- F1. Command Center [Gori]
|
||||
-- F1. Mission "Alpha"
|
||||
-- F1. Mission Briefing
|
||||
@@ -237,23 +238,23 @@ do -- TASK_A2G_DISPATCHER
|
||||
-- F3. Task Reports
|
||||
-- F4. Mission Reports
|
||||
-- F5. Join Planned Task
|
||||
--
|
||||
--
|
||||
-- **The F5. Join Planned Task allows the player to join a Planned Task and take an engagement in the running Mission.**
|
||||
--
|
||||
-- #### 1.2.2. Manage an Assigned Task
|
||||
--
|
||||
--
|
||||
-- #### 1.2.2. Manage an Assigned Task
|
||||
--
|
||||
-- If the Player has been assigned to one Task within the Mission, the Mission Menu will contain an extra:
|
||||
--
|
||||
--
|
||||
-- - Assigned Task __TaskName__ Menu: This menu structure allows the player to take actions on the currently engaged task.
|
||||
--
|
||||
--
|
||||
-- In this example, the Group currently seated by the player is not assigned yet to a Task.
|
||||
-- The Player has the option to assign itself to a Planned Task using menu option F5 under the Mission Menu "Alpha".
|
||||
--
|
||||
--
|
||||
-- This would be an example menu structure,
|
||||
-- for CC |Gori|, Mission "Alpha", when a player would have joined Task CAS.001:
|
||||
--
|
||||
--
|
||||
-- Radio MENU Structure (F10. Other)
|
||||
--
|
||||
--
|
||||
-- F1. Command Center [Gori]
|
||||
-- F1. Mission "Alpha"
|
||||
-- F1. Mission Briefing
|
||||
@@ -261,26 +262,25 @@ do -- TASK_A2G_DISPATCHER
|
||||
-- F3. Task Reports
|
||||
-- F4. Mission Reports
|
||||
-- F5. Assigned Task CAS.001
|
||||
--
|
||||
--
|
||||
-- **The F5. Assigned Task __TaskName__ allows the player to control the current Assigned Task and take further actions.**
|
||||
--
|
||||
--
|
||||
--
|
||||
-- ## 1.3. Join Planned Task Menu
|
||||
--
|
||||
--
|
||||
-- The Join Planned Task Menu contains the different Planned A2G Tasks **in a structured Menu Hierarchy**.
|
||||
-- The Menu Hierarchy is structuring the Tasks per **Task Type**, and then by **Task Name (ID)**.
|
||||
--
|
||||
-- For example, for CC [Gori], Mission "Alpha",
|
||||
-- The Menu Hierarchy is structuring the Tasks per **Task Type**, and then by **Task Name (ID)**.
|
||||
--
|
||||
-- For example, for CC [Gori], Mission "Alpha",
|
||||
-- if a Mission "ALpha" contains 5 Planned Tasks, which would be:
|
||||
--
|
||||
-- - 2 CAS Tasks
|
||||
--
|
||||
-- - 2 CAS Tasks
|
||||
-- - 1 BAI Task
|
||||
-- - 2 SEAD Tasks
|
||||
--
|
||||
--
|
||||
-- the Join Planned Task Menu Hierarchy could look like this:
|
||||
--
|
||||
--
|
||||
-- Radio MENU Structure (F10. Other)
|
||||
--
|
||||
--
|
||||
-- F1. Command Center [Gori]
|
||||
-- F1. Mission "Alpha"
|
||||
-- F1. Mission Briefing
|
||||
@@ -296,26 +296,26 @@ do -- TASK_A2G_DISPATCHER
|
||||
-- F1. SEAD.003
|
||||
-- F2. SEAD.004
|
||||
-- F3. SEAD.005
|
||||
--
|
||||
--
|
||||
-- An example from within a running simulation:
|
||||
--
|
||||
--
|
||||
-- 
|
||||
--
|
||||
--
|
||||
-- Each Task Type Menu would have a list of the Task Menus underneath.
|
||||
-- Each Task Menu (eg. `CAS.001`) has a **detailed Task Menu structure to control the specific task**!
|
||||
--
|
||||
-- ### 1.3.1. Planned Task Menu
|
||||
--
|
||||
-- Each Planned Task Menu will allow for the following actions:
|
||||
--
|
||||
--
|
||||
-- - Report Task Details: Provides a detailed report on the Planned Task.
|
||||
-- - Mark Task Location on Map: Mark the approximate location of the Task on the Map, if relevant.
|
||||
-- - Join Task: Join the Task. This is THE menu option to let a Player join the Task, and to engage within the Mission.
|
||||
--
|
||||
-- The Join Planned Task Menu could look like this for for CC |Gori|, Mission "Alpha":
|
||||
--
|
||||
--
|
||||
-- The Join Planned Task Menu could look like this for for CC |Gori|, Mission "Alpha":
|
||||
--
|
||||
-- Radio MENU Structure (F10. Other)
|
||||
--
|
||||
--
|
||||
-- F1. Command Center |Gori|
|
||||
-- F1. Mission "Alpha"
|
||||
-- F1. Mission Briefing
|
||||
@@ -328,22 +328,22 @@ do -- TASK_A2G_DISPATCHER
|
||||
-- F1. Report Task Details
|
||||
-- F2. Mark Task Location on Map
|
||||
-- F3. Join Task
|
||||
--
|
||||
--
|
||||
-- **The Join Task is THE menu option to let a Player join the Task, and to engage within the Mission.**
|
||||
--
|
||||
--
|
||||
--
|
||||
--
|
||||
-- ## 1.4. Assigned Task Menu
|
||||
--
|
||||
--
|
||||
-- The Assigned Task Menu allows to control the **current assigned task** within the Mission.
|
||||
--
|
||||
--
|
||||
-- Depending on the Type of Task, the following menu options will be available:
|
||||
--
|
||||
--
|
||||
-- - **Report Task Details**: Provides a detailed report on the Planned Task.
|
||||
-- - **Mark Task Location on Map**: Mark the approximate location of the Task on the Map, if relevant.
|
||||
-- - **Abort Task: Abort the current assigned Task:** This menu option lets the player abort the Task.
|
||||
--
|
||||
--
|
||||
-- For example, for CC |Gori|, Mission "Alpha", the Assigned Menu could be:
|
||||
--
|
||||
--
|
||||
-- F1. Command Center |Gori|
|
||||
-- F1. Mission "Alpha"
|
||||
-- F1. Mission Briefing
|
||||
@@ -354,90 +354,89 @@ do -- TASK_A2G_DISPATCHER
|
||||
-- F1. Report Task Details
|
||||
-- F2. Mark Task Location on Map
|
||||
-- F3. Abort Task
|
||||
--
|
||||
--
|
||||
-- Task abortion will result in the Task to be Cancelled, and the Task **may** be **Replanned**.
|
||||
-- However, this will depend on the setup of each Mission.
|
||||
--
|
||||
-- However, this will depend on the setup of each Mission.
|
||||
--
|
||||
-- ## 1.5. Messages
|
||||
--
|
||||
--
|
||||
-- During game play, different messages are displayed.
|
||||
-- These messages provide an update of the achievements made, and the state wherein the task is.
|
||||
--
|
||||
--
|
||||
-- The various reports can be used also to retrieve the current status of the mission and its tasks.
|
||||
--
|
||||
--
|
||||
-- 
|
||||
--
|
||||
--
|
||||
-- The @{Settings} menu provides additional options to control the timing of the messages.
|
||||
-- There are:
|
||||
--
|
||||
--
|
||||
-- - Status messages, which are quick status updates. The settings menu allows to switch off these messages.
|
||||
-- - Information messages, which are shown a bit longer, as they contain important information.
|
||||
-- - Summary reports, which are quick reports showing a high level summary.
|
||||
-- - Overview reports, which are providing the essential information. It provides an overview of a greater thing, and may take a bit of time to read.
|
||||
-- - Detailed reports, which provide with very detailed information. It takes a bit longer to read those reports, so the display of those could be a bit longer.
|
||||
--
|
||||
--
|
||||
-- # 2. TASK\_A2G\_DISPATCHER constructor
|
||||
--
|
||||
--
|
||||
-- The @{#TASK_A2G_DISPATCHER.New}() method creates a new TASK\_A2G\_DISPATCHER instance.
|
||||
--
|
||||
-- # 3. Usage
|
||||
--
|
||||
-- To use the TASK\_A2G\_DISPATCHER class, you need:
|
||||
--
|
||||
--
|
||||
-- - A @{CommandCenter} object. The master communication channel.
|
||||
-- - A @{Mission} object. Each task belongs to a Mission.
|
||||
-- - A @{Detection} object. There are several detection grouping methods to choose from.
|
||||
-- - A @{Task_A2G_Dispatcher} object. The master A2G task dispatcher.
|
||||
-- - A @{Set} of @{Wrapper.Group} objects that will detect the emeny, the RecceSet. This is attached to the @{Detection} object.
|
||||
-- - A @{Set} of @{Wrapper.Group} objects that will detect the enemy, the RecceSet. This is attached to the @{Detection} object.
|
||||
-- - A @{Set} ob @{Wrapper.Group} objects that will attack the enemy, the AttackSet. This is attached to the @{Task_A2G_Dispatcher} object.
|
||||
--
|
||||
-- Below an example mission declaration that is defines a Task A2G Dispatcher object.
|
||||
--
|
||||
-- -- Declare the Command Center
|
||||
-- Below an example mission declaration that is defines a Task A2G Dispatcher object.
|
||||
--
|
||||
-- -- Declare the Command Center
|
||||
-- local HQ = GROUP
|
||||
-- :FindByName( "HQ", "Bravo HQ" )
|
||||
--
|
||||
-- local CommandCenter = COMMANDCENTER
|
||||
-- :New( HQ, "Lima" )
|
||||
--
|
||||
--
|
||||
-- -- Declare the Mission for the Command Center.
|
||||
-- local Mission = MISSION
|
||||
-- :New( CommandCenter, "Overlord", "High", "Attack Detect Mission Briefing", coalition.side.RED )
|
||||
--
|
||||
--
|
||||
-- -- Define the RecceSet that will detect the enemy.
|
||||
-- local RecceSet = SET_GROUP
|
||||
-- :New()
|
||||
-- :FilterPrefixes( "FAC" )
|
||||
-- :FilterCoalitions("red")
|
||||
-- :FilterStart()
|
||||
--
|
||||
--
|
||||
-- -- Setup the detection. We use DETECTION_AREAS to detect and group the enemies within areas of 3 km radius.
|
||||
-- local DetectionAreas = DETECTION_AREAS
|
||||
-- :New( RecceSet, 3000 ) -- The RecceSet will detect the enemies.
|
||||
--
|
||||
--
|
||||
-- -- Setup the AttackSet, which is a SET_GROUP.
|
||||
-- -- The SET_GROUP is a dynamic collection of GROUP objects.
|
||||
-- -- The SET_GROUP is a dynamic collection of GROUP objects.
|
||||
-- local AttackSet = SET_GROUP
|
||||
-- :New() -- Create the SET_GROUP object.
|
||||
-- :FilterCoalitions( "red" ) -- Only incorporate the RED coalitions.
|
||||
-- :FilterPrefixes( "Attack" ) -- Only incorporate groups that start with the name Attack.
|
||||
-- :FilterStart() -- Enable the dynamic filtering. From this moment the AttackSet will contain all groups that are red and start with the name Attack.
|
||||
--
|
||||
--
|
||||
-- -- Now we have everything to setup the main A2G TaskDispatcher.
|
||||
-- TaskDispatcher = TASK_A2G_DISPATCHER
|
||||
-- :New( Mission, AttackSet, DetectionAreas ) -- We assign the TaskDispatcher under Mission. The AttackSet will engage the enemy and will recieve the dispatched Tasks. The DetectionAreas will report any detected enemies to the TaskDispatcher.
|
||||
--
|
||||
--
|
||||
-- :New( Mission, AttackSet, DetectionAreas ) -- We assign the TaskDispatcher under Mission. The AttackSet will engage the enemy and will receive the dispatched Tasks. The DetectionAreas will report any detected enemies to the TaskDispatcher.
|
||||
--
|
||||
--
|
||||
--
|
||||
-- @field #TASK_A2G_DISPATCHER
|
||||
TASK_A2G_DISPATCHER = {
|
||||
ClassName = "TASK_A2G_DISPATCHER",
|
||||
Mission = nil,
|
||||
Detection = nil,
|
||||
Tasks = {},
|
||||
Tasks = {}
|
||||
}
|
||||
|
||||
|
||||
|
||||
--- TASK_A2G_DISPATCHER constructor.
|
||||
-- @param #TASK_A2G_DISPATCHER self
|
||||
-- @param Tasking.Mission#MISSION Mission The mission for which the task dispatching is done.
|
||||
@@ -445,17 +444,18 @@ do -- TASK_A2G_DISPATCHER
|
||||
-- @param Functional.Detection#DETECTION_BASE Detection The detection results that are used to dynamically assign new tasks to human players.
|
||||
-- @return #TASK_A2G_DISPATCHER self
|
||||
function TASK_A2G_DISPATCHER:New( Mission, SetGroup, Detection )
|
||||
|
||||
|
||||
-- Inherits from DETECTION_MANAGER
|
||||
local self = BASE:Inherit( self, DETECTION_MANAGER:New( SetGroup, Detection ) ) -- #TASK_A2G_DISPATCHER
|
||||
|
||||
|
||||
self.Detection = Detection
|
||||
self.Mission = Mission
|
||||
|
||||
self.FlashNewTask = true -- set to false to suppress flash messages
|
||||
|
||||
self.Detection:FilterCategories( { Unit.Category.GROUND_UNIT } )
|
||||
|
||||
|
||||
self:AddTransition( "Started", "Assign", "Started" )
|
||||
|
||||
|
||||
--- OnAfter Transition Handler for Event Assign.
|
||||
-- @function [parent=#TASK_A2G_DISPATCHER] OnAfterAssign
|
||||
-- @param #TASK_A2G_DISPATCHER self
|
||||
@@ -465,13 +465,19 @@ do -- TASK_A2G_DISPATCHER
|
||||
-- @param Tasking.Task_A2G#TASK_A2G Task
|
||||
-- @param Wrapper.Unit#UNIT TaskUnit
|
||||
-- @param #string PlayerName
|
||||
|
||||
|
||||
self:__Start( 5 )
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
|
||||
--- Set flashing player messages on or off
|
||||
-- @param #TASK_A2G_DISPATCHER self
|
||||
-- @param #boolean onoff Set messages on (true) or off (false)
|
||||
function TASK_A2G_DISPATCHER:SetSendMessages( onoff )
|
||||
self.FlashNewTask = onoff
|
||||
end
|
||||
|
||||
--- Creates a SEAD task when there are targets for it.
|
||||
-- @param #TASK_A2G_DISPATCHER self
|
||||
-- @param Functional.Detection#DETECTION_AREAS.DetectedItem DetectedItem
|
||||
@@ -479,7 +485,7 @@ do -- TASK_A2G_DISPATCHER
|
||||
-- @return #nil If there are no targets to be set.
|
||||
function TASK_A2G_DISPATCHER:EvaluateSEAD( DetectedItem )
|
||||
self:F( { DetectedItem.ItemID } )
|
||||
|
||||
|
||||
local DetectedSet = DetectedItem.Set
|
||||
local DetectedZone = DetectedItem.Zone
|
||||
|
||||
@@ -493,10 +499,10 @@ do -- TASK_A2G_DISPATCHER
|
||||
TargetSetUnit:SetDatabase( DetectedSet )
|
||||
TargetSetUnit:FilterHasSEAD()
|
||||
TargetSetUnit:FilterOnce() -- Filter but don't do any events!!! Elements are added manually upon each detection.
|
||||
|
||||
|
||||
return TargetSetUnit
|
||||
end
|
||||
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
@@ -507,11 +513,10 @@ do -- TASK_A2G_DISPATCHER
|
||||
-- @return #nil If there are no targets to be set.
|
||||
function TASK_A2G_DISPATCHER:EvaluateCAS( DetectedItem )
|
||||
self:F( { DetectedItem.ItemID } )
|
||||
|
||||
|
||||
local DetectedSet = DetectedItem.Set
|
||||
local DetectedZone = DetectedItem.Zone
|
||||
|
||||
|
||||
-- Determine if the set has ground units.
|
||||
-- There should be ground unit friendlies nearby. Airborne units are valid friendlies types.
|
||||
-- And there shouldn't be any radar.
|
||||
@@ -525,13 +530,13 @@ do -- TASK_A2G_DISPATCHER
|
||||
local TargetSetUnit = SET_UNIT:New()
|
||||
TargetSetUnit:SetDatabase( DetectedSet )
|
||||
TargetSetUnit:FilterOnce() -- Filter but don't do any events!!! Elements are added manually upon each detection.
|
||||
|
||||
|
||||
return TargetSetUnit
|
||||
end
|
||||
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
--- Creates a BAI task when there are targets for it.
|
||||
-- @param #TASK_A2G_DISPATCHER self
|
||||
-- @param Functional.Detection#DETECTION_AREAS.DetectedItem DetectedItem
|
||||
@@ -539,11 +544,10 @@ do -- TASK_A2G_DISPATCHER
|
||||
-- @return #nil If there are no targets to be set.
|
||||
function TASK_A2G_DISPATCHER:EvaluateBAI( DetectedItem, FriendlyCoalition )
|
||||
self:F( { DetectedItem.ItemID } )
|
||||
|
||||
|
||||
local DetectedSet = DetectedItem.Set
|
||||
local DetectedZone = DetectedItem.Zone
|
||||
|
||||
|
||||
-- Determine if the set has ground units.
|
||||
-- There shouldn't be any ground unit friendlies nearby.
|
||||
-- And there shouldn't be any radar.
|
||||
@@ -557,19 +561,18 @@ do -- TASK_A2G_DISPATCHER
|
||||
local TargetSetUnit = SET_UNIT:New()
|
||||
TargetSetUnit:SetDatabase( DetectedSet )
|
||||
TargetSetUnit:FilterOnce() -- Filter but don't do any events!!! Elements are added manually upon each detection.
|
||||
|
||||
|
||||
return TargetSetUnit
|
||||
end
|
||||
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
|
||||
function TASK_A2G_DISPATCHER:RemoveTask( TaskIndex )
|
||||
self.Mission:RemoveTask( self.Tasks[TaskIndex] )
|
||||
self.Tasks[TaskIndex] = nil
|
||||
end
|
||||
|
||||
|
||||
--- Evaluates the removal of the Task from the Mission.
|
||||
-- Can only occur when the DetectedItem is Changed AND the state of the Task is "Planned".
|
||||
-- @param #TASK_A2G_DISPATCHER self
|
||||
@@ -579,17 +582,16 @@ do -- TASK_A2G_DISPATCHER
|
||||
-- @param #boolean DetectedItemChange
|
||||
-- @return Tasking.Task#TASK
|
||||
function TASK_A2G_DISPATCHER:EvaluateRemoveTask( Mission, Task, TaskIndex, DetectedItemChanged )
|
||||
|
||||
|
||||
if Task then
|
||||
if ( Task:IsStatePlanned() and DetectedItemChanged == true ) or Task:IsStateCancelled() then
|
||||
--self:F( "Removing Tasking: " .. Task:GetTaskName() )
|
||||
if (Task:IsStatePlanned() and DetectedItemChanged == true) or Task:IsStateCancelled() then
|
||||
-- self:F( "Removing Tasking: " .. Task:GetTaskName() )
|
||||
self:RemoveTask( TaskIndex )
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
return Task
|
||||
end
|
||||
|
||||
|
||||
--- Assigns tasks in relation to the detected items to the @{Core.Set#SET_GROUP}.
|
||||
-- @param #TASK_A2G_DISPATCHER self
|
||||
@@ -597,15 +599,15 @@ do -- TASK_A2G_DISPATCHER
|
||||
-- @return #boolean Return true if you want the task assigning to continue... false will cancel the loop.
|
||||
function TASK_A2G_DISPATCHER:ProcessDetected( Detection )
|
||||
self:F()
|
||||
|
||||
|
||||
local AreaMsg = {}
|
||||
local TaskMsg = {}
|
||||
local ChangeMsg = {}
|
||||
|
||||
|
||||
local Mission = self.Mission
|
||||
|
||||
|
||||
if Mission:IsIDLE() or Mission:IsENGAGED() then
|
||||
|
||||
|
||||
local TaskReport = REPORT:New()
|
||||
|
||||
-- Checking the task queue for the dispatcher, and removing any obsolete task!
|
||||
@@ -616,7 +618,9 @@ do -- TASK_A2G_DISPATCHER
|
||||
if not DetectedItem then
|
||||
local TaskText = Task:GetName()
|
||||
for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do
|
||||
Mission:GetCommandCenter():MessageToGroup( string.format( "Obsolete A2G task %s for %s removed.", TaskText, Mission:GetShortText() ), TaskGroup )
|
||||
if self.FlashNewTask then
|
||||
Mission:GetCommandCenter():MessageToGroup( string.format( "Obsolete A2G task %s for %s removed.", TaskText, Mission:GetShortText() ), TaskGroup )
|
||||
end
|
||||
end
|
||||
Task = self:RemoveTask( TaskIndex )
|
||||
end
|
||||
@@ -625,21 +629,21 @@ do -- TASK_A2G_DISPATCHER
|
||||
|
||||
--- First we need to the detected targets.
|
||||
for DetectedItemID, DetectedItem in pairs( Detection:GetDetectedItems() ) do
|
||||
|
||||
|
||||
local DetectedItem = DetectedItem -- Functional.Detection#DETECTION_BASE.DetectedItem
|
||||
local DetectedSet = DetectedItem.Set -- Core.Set#SET_UNIT
|
||||
local DetectedZone = DetectedItem.Zone
|
||||
--self:F( { "Targets in DetectedItem", DetectedItem.ItemID, DetectedSet:Count(), tostring( DetectedItem ) } )
|
||||
--DetectedSet:Flush( self )
|
||||
|
||||
-- self:F( { "Targets in DetectedItem", DetectedItem.ItemID, DetectedSet:Count(), tostring( DetectedItem ) } )
|
||||
-- DetectedSet:Flush( self )
|
||||
|
||||
local DetectedItemID = DetectedItem.ID
|
||||
local TaskIndex = DetectedItem.Index
|
||||
local DetectedItemChanged = DetectedItem.Changed
|
||||
|
||||
|
||||
self:F( { DetectedItemChanged = DetectedItemChanged, DetectedItemID = DetectedItemID, TaskIndex = TaskIndex } )
|
||||
|
||||
|
||||
local Task = self.Tasks[TaskIndex] -- Tasking.Task_A2G#TASK_A2G
|
||||
|
||||
|
||||
if Task then
|
||||
-- If there is a Task and the task was assigned, then we check if the task was changed ... If it was, we need to reevaluate the targets.
|
||||
if Task:IsStateAssigned() then
|
||||
@@ -651,7 +655,7 @@ do -- TASK_A2G_DISPATCHER
|
||||
Task:SetTargetSetUnit( TargetSetUnit )
|
||||
Task:SetDetection( Detection, DetectedItem )
|
||||
Task:UpdateTaskInfo( DetectedItem )
|
||||
TargetsReport:Add( Detection:GetChangeText( DetectedItem ) )
|
||||
TargetsReport:Add( Detection:GetChangeText( DetectedItem ) )
|
||||
else
|
||||
Task:Cancel()
|
||||
end
|
||||
@@ -682,18 +686,18 @@ do -- TASK_A2G_DISPATCHER
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- Now we send to each group the changes, if any.
|
||||
for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do
|
||||
local TargetsText = TargetsReport:Text(", ")
|
||||
if ( Mission:IsGroupAssigned(TaskGroup) ) and TargetsText ~= "" then
|
||||
local TargetsText = TargetsReport:Text( ", " )
|
||||
if (Mission:IsGroupAssigned( TaskGroup )) and TargetsText ~= "" and self.FlashNewTask then
|
||||
Mission:GetCommandCenter():MessageToGroup( string.format( "Task %s has change of targets:\n %s", Task:GetName(), TargetsText ), TaskGroup )
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
if Task then
|
||||
if Task:IsStatePlanned() then
|
||||
if DetectedItemChanged == true then -- The detection has changed, thus a new TargetSet is to be evaluated and set
|
||||
@@ -744,6 +748,7 @@ do -- TASK_A2G_DISPATCHER
|
||||
local TargetSetUnit = self:EvaluateSEAD( DetectedItem ) -- Returns a SetUnit if there are targets to be SEADed...
|
||||
if TargetSetUnit then
|
||||
Task = TASK_A2G_SEAD:New( Mission, self.SetGroup, string.format( "SEAD.%03d", DetectedItemID ), TargetSetUnit )
|
||||
DetectedItem.DesignateMenuName = string.format( "SEAD.%03d", DetectedItemID ) -- inject a name for DESIGNATE, if using same DETECTION object
|
||||
Task:SetDetection( Detection, DetectedItem )
|
||||
end
|
||||
|
||||
@@ -752,6 +757,7 @@ do -- TASK_A2G_DISPATCHER
|
||||
local TargetSetUnit = self:EvaluateCAS( DetectedItem ) -- Returns a SetUnit if there are targets to be CASed...
|
||||
if TargetSetUnit then
|
||||
Task = TASK_A2G_CAS:New( Mission, self.SetGroup, string.format( "CAS.%03d", DetectedItemID ), TargetSetUnit )
|
||||
DetectedItem.DesignateMenuName = string.format( "CAS.%03d", DetectedItemID ) -- inject a name for DESIGNATE, if using same DETECTION object
|
||||
Task:SetDetection( Detection, DetectedItem )
|
||||
end
|
||||
|
||||
@@ -760,18 +766,19 @@ do -- TASK_A2G_DISPATCHER
|
||||
local TargetSetUnit = self:EvaluateBAI( DetectedItem, self.Mission:GetCommandCenter():GetPositionable():GetCoalition() ) -- Returns a SetUnit if there are targets to be BAIed...
|
||||
if TargetSetUnit then
|
||||
Task = TASK_A2G_BAI:New( Mission, self.SetGroup, string.format( "BAI.%03d", DetectedItemID ), TargetSetUnit )
|
||||
DetectedItem.DesignateMenuName = string.format( "BAI.%03d", DetectedItemID ) -- inject a name for DESIGNATE, if using same DETECTION object
|
||||
Task:SetDetection( Detection, DetectedItem )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
if Task then
|
||||
self.Tasks[TaskIndex] = Task
|
||||
Task:SetTargetZone( DetectedZone )
|
||||
Task:SetDispatcher( self )
|
||||
Task:UpdateTaskInfo( DetectedItem )
|
||||
Mission:AddTask( Task )
|
||||
|
||||
|
||||
function Task.OnEnterSuccess( Task, From, Event, To )
|
||||
self:Success( Task )
|
||||
end
|
||||
@@ -779,7 +786,7 @@ do -- TASK_A2G_DISPATCHER
|
||||
function Task.OnEnterCancelled( Task, From, Event, To )
|
||||
self:Cancelled( Task )
|
||||
end
|
||||
|
||||
|
||||
function Task.OnEnterFailed( Task, From, Event, To )
|
||||
self:Failed( Task )
|
||||
end
|
||||
@@ -787,32 +794,30 @@ do -- TASK_A2G_DISPATCHER
|
||||
function Task.OnEnterAborted( Task, From, Event, To )
|
||||
self:Aborted( Task )
|
||||
end
|
||||
|
||||
|
||||
|
||||
TaskReport:Add( Task:GetName() )
|
||||
else
|
||||
self:F("This should not happen")
|
||||
self:F( "This should not happen" )
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- OK, so the tasking has been done, now delete the changes reported for the area.
|
||||
Detection:AcceptChanges( DetectedItem )
|
||||
end
|
||||
|
||||
|
||||
-- TODO set menus using the HQ coordinator
|
||||
Mission:GetCommandCenter():SetMenu()
|
||||
|
||||
local TaskText = TaskReport:Text(", ")
|
||||
|
||||
local TaskText = TaskReport:Text( ", " )
|
||||
for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do
|
||||
if ( not Mission:IsGroupAssigned(TaskGroup) ) and TaskText ~= "" then
|
||||
if (not Mission:IsGroupAssigned( TaskGroup )) and TaskText ~= "" and self.FlashNewTask then
|
||||
Mission:GetCommandCenter():MessageToGroup( string.format( "%s has tasks %s. Subscribe to a task using the radio menu.", Mission:GetShortText(), TaskText ), TaskGroup )
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
@@ -83,7 +83,7 @@ do -- TASK_CAPTURE_DISPATCHER
|
||||
-- ## 1.1. Create a command center.
|
||||
--
|
||||
-- First you need to create a command center using the @{Tasking.CommandCenter#COMMANDCENTER.New}() constructor.
|
||||
-- The command assumes that you´ve setup a group in the mission editor with the name HQ.
|
||||
-- The command assumes that you´ve setup a group in the mission editor with the name HQ.
|
||||
-- This group will act as the command center object.
|
||||
-- It is a good practice to mark this group as invisible and invulnerable.
|
||||
--
|
||||
@@ -184,6 +184,7 @@ do -- TASK_CAPTURE_DISPATCHER
|
||||
local self = BASE:Inherit( self, TASK_MANAGER:New( SetGroup ) ) -- #TASK_CAPTURE_DISPATCHER
|
||||
|
||||
self.Mission = Mission
|
||||
self.FlashNewTask = false
|
||||
|
||||
self:AddTransition( "Started", "Assign", "Started" )
|
||||
self:AddTransition( "Started", "ZoneCaptured", "Started" )
|
||||
@@ -383,7 +384,7 @@ do -- TASK_CAPTURE_DISPATCHER
|
||||
local TaskText = TaskReport:Text(", ")
|
||||
|
||||
for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do
|
||||
if ( not Mission:IsGroupAssigned(TaskGroup) ) and TaskText ~= "" then
|
||||
if ( not Mission:IsGroupAssigned(TaskGroup) ) and TaskText ~= "" and ( not self.FlashNewTask ) then
|
||||
Mission:GetCommandCenter():MessageToGroup( string.format( "%s has tasks %s. Subscribe to a task using the radio menu.", Mission:GetShortText(), TaskText ), TaskGroup )
|
||||
end
|
||||
end
|
||||
|
||||
@@ -683,6 +683,7 @@ do -- TASK_CARGO_DISPATCHER
|
||||
-- If no TaskPrefix is given, then "Transport" will be used as the prefix.
|
||||
-- @param Core.SetCargo#SET_CARGO SetCargo The SetCargo to be transported.
|
||||
-- @param #string Briefing The briefing of the task transport to be shown to the player.
|
||||
-- @param #boolean Silent If true don't send a message that a new task is available.
|
||||
-- @return Tasking.Task_Cargo_Transport#TASK_CARGO_TRANSPORT
|
||||
-- @usage
|
||||
--
|
||||
@@ -705,10 +706,12 @@ do -- TASK_CARGO_DISPATCHER
|
||||
-- -- Here we set a TransportDeployZone. We use the WorkplaceTask as the reference, and provide a ZONE object.
|
||||
-- TaskDispatcher:SetTransportDeployZone( WorkplaceTask, ZONE:New( "Workplace" ) )
|
||||
--
|
||||
function TASK_CARGO_DISPATCHER:AddTransportTask( TaskPrefix, SetCargo, Briefing )
|
||||
function TASK_CARGO_DISPATCHER:AddTransportTask( TaskPrefix, SetCargo, Briefing, Silent )
|
||||
|
||||
self.TransportCount = self.TransportCount + 1
|
||||
|
||||
local verbose = Silent or false
|
||||
|
||||
local TaskName = string.format( ( TaskPrefix or "Transport" ) .. ".%03d", self.TransportCount )
|
||||
|
||||
self.Transport[TaskName] = {}
|
||||
@@ -717,7 +720,7 @@ do -- TASK_CARGO_DISPATCHER
|
||||
self.Transport[TaskName].Task = nil
|
||||
self.Transport[TaskName].TaskPrefix = TaskPrefix
|
||||
|
||||
self:ManageTasks()
|
||||
self:ManageTasks(verbose)
|
||||
|
||||
return self.Transport[TaskName] and self.Transport[TaskName].Task
|
||||
end
|
||||
@@ -785,10 +788,11 @@ do -- TASK_CARGO_DISPATCHER
|
||||
|
||||
--- Assigns tasks to the @{Core.Set#SET_GROUP}.
|
||||
-- @param #TASK_CARGO_DISPATCHER self
|
||||
-- @param #boolean Silent Announce new task (nil/false) or not (true).
|
||||
-- @return #boolean Return true if you want the task assigning to continue... false will cancel the loop.
|
||||
function TASK_CARGO_DISPATCHER:ManageTasks()
|
||||
function TASK_CARGO_DISPATCHER:ManageTasks(Silent)
|
||||
self:F()
|
||||
|
||||
local verbose = Silent and true
|
||||
local AreaMsg = {}
|
||||
local TaskMsg = {}
|
||||
local ChangeMsg = {}
|
||||
@@ -897,7 +901,7 @@ do -- TASK_CARGO_DISPATCHER
|
||||
local TaskText = TaskReport:Text(", ")
|
||||
|
||||
for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do
|
||||
if ( not Mission:IsGroupAssigned(TaskGroup) ) and TaskText ~= "" then
|
||||
if ( not Mission:IsGroupAssigned(TaskGroup) ) and TaskText ~= "" and not verbose then
|
||||
Mission:GetCommandCenter():MessageToGroup( string.format( "%s has tasks %s. Subscribe to a task using the radio menu.", Mission:GetShortText(), TaskText ), TaskGroup )
|
||||
end
|
||||
end
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user