mirror of
https://github.com/FlightControl-Master/MOOSE.git
synced 2025-08-15 10:47:21 +00:00
Compare commits
570 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dd0b2ace65 | ||
|
|
dc83af4d02 | ||
|
|
342e901dd1 | ||
|
|
0a38700edb | ||
|
|
659615114a | ||
|
|
bfd26522d6 | ||
|
|
00d14c7c0a | ||
|
|
47c9e1ba1f | ||
|
|
92e680a276 | ||
|
|
4955fe4d92 | ||
|
|
e439bcdd0f | ||
|
|
792aa73832 | ||
|
|
d375e0ce29 | ||
|
|
e3670219ed | ||
|
|
a915452e6e | ||
|
|
6662a1cf97 | ||
|
|
be8405b72b | ||
|
|
9b24695377 | ||
|
|
5ca3e3b2b8 | ||
|
|
8bbef34b74 | ||
|
|
618a8744a2 | ||
|
|
5329da32eb | ||
|
|
df86c3f2a1 | ||
|
|
784fcb7882 | ||
|
|
23aeef7a20 | ||
|
|
0ee24b86dc | ||
|
|
9ac4f136aa | ||
|
|
ca9b2d79cc | ||
|
|
c9a09c2fc9 | ||
|
|
813286f7f3 | ||
|
|
6028c91f81 | ||
|
|
11b6bb2638 | ||
|
|
3c57928f46 | ||
|
|
e091e659a2 | ||
|
|
32f0bb33c3 | ||
|
|
dea64751c3 | ||
|
|
31d0410284 | ||
|
|
9cdf550432 | ||
|
|
09525029ab | ||
|
|
87c436ba34 | ||
|
|
b1002017e5 | ||
|
|
6e9727e265 | ||
|
|
383e953ba1 | ||
|
|
94ee76fe62 | ||
|
|
9b52b640e6 | ||
|
|
ea23162ca9 | ||
|
|
68d0220912 | ||
|
|
683388faee | ||
|
|
56ec3920c5 | ||
|
|
88842d1c67 | ||
|
|
f335ffc4ec | ||
|
|
f76aefa976 | ||
|
|
4976cd86f2 | ||
|
|
2cd66ae1d4 | ||
|
|
c00eff8b23 | ||
|
|
7f0376561b | ||
|
|
3c710613a8 | ||
|
|
3585208547 | ||
|
|
29c0017e80 | ||
|
|
45ebf9a3c7 | ||
|
|
908e505ce3 | ||
|
|
c808e4a4e2 | ||
|
|
e129eb97a7 | ||
|
|
e2612b97d7 | ||
|
|
70e9d91bb5 | ||
|
|
0b8810f8b3 | ||
|
|
3d39ccbdce | ||
|
|
41b867a4ca | ||
|
|
427baa43d7 | ||
|
|
d4e141f3c5 | ||
|
|
1c0a8d9380 | ||
|
|
f05680f23d | ||
|
|
2b0b9d44eb | ||
|
|
2fc7a3b542 | ||
|
|
820ae2759b | ||
|
|
12b596a47f | ||
|
|
a06d099917 | ||
|
|
7552309a28 | ||
|
|
b9ad5b5ba7 | ||
|
|
8ef781a9ac | ||
|
|
5e24e8658b | ||
|
|
43eeaede65 | ||
|
|
749c5f87de | ||
|
|
a520daeb56 | ||
|
|
9bb713db10 | ||
|
|
f9ba96f228 | ||
|
|
a49bd23a2a | ||
|
|
913b2c6d3f | ||
|
|
cea2f18228 | ||
|
|
fd2d8a5119 | ||
|
|
24b3200777 | ||
|
|
fa4e0447dd | ||
|
|
31aa604fc4 | ||
|
|
3518c89791 | ||
|
|
f44db27565 | ||
|
|
acaab0c6a9 | ||
|
|
9b1abab73a | ||
|
|
cad8f15b61 | ||
|
|
aafbd8c297 | ||
|
|
1d08bcf2e0 | ||
|
|
6d38346eb8 | ||
|
|
9487a5ae91 | ||
|
|
d2bb6a4fc0 | ||
|
|
c3ccecdd4a | ||
|
|
5ad54648ab | ||
|
|
96337cc5df | ||
|
|
fa0ce0927b | ||
|
|
693c2a730f | ||
|
|
0d438a9452 | ||
|
|
bc9eee22b7 | ||
|
|
f74d25b31c | ||
|
|
66af360ce6 | ||
|
|
ad9893c3ab | ||
|
|
1156971d94 | ||
|
|
c79b5c37c4 | ||
|
|
b4e6201b68 | ||
|
|
f26d8d6822 | ||
|
|
3d0b7b9267 | ||
|
|
a2b650a9e3 | ||
|
|
905b442e9b | ||
|
|
7cd95377f9 | ||
|
|
18fe3112bd | ||
|
|
84e85dd0b5 | ||
|
|
2744befbab | ||
|
|
e87dc525b9 | ||
|
|
cc057744ba | ||
|
|
db1779e1db | ||
|
|
ed31b87b2f | ||
|
|
d1e31a3652 | ||
|
|
d25a723fc7 | ||
|
|
3fb4cae7b8 | ||
|
|
f0fe1b431d | ||
|
|
8e286edd25 | ||
|
|
74520b1359 | ||
|
|
0ffcdf2f66 | ||
|
|
fe0edeb011 | ||
|
|
d06e44d37b | ||
|
|
35348d9b81 | ||
|
|
d06a618582 | ||
|
|
4b16e94eaf | ||
|
|
d5253f0420 | ||
|
|
d983676330 | ||
|
|
03c30f3cce | ||
|
|
99b93266ad | ||
|
|
4530b74367 | ||
|
|
e307b57e67 | ||
|
|
5ef9bb2acd | ||
|
|
4aacdc1567 | ||
|
|
57552f4300 | ||
|
|
09d53f7d8c | ||
|
|
161bdc0413 | ||
|
|
44ad362dbc | ||
|
|
ea776aeacc | ||
|
|
e30479329a | ||
|
|
d9948d1a19 | ||
|
|
b962096661 | ||
|
|
725b55a505 | ||
|
|
5f7a4f2bbb | ||
|
|
f555399e2f | ||
|
|
1b6945e0b0 | ||
|
|
4fd55b1bd6 | ||
|
|
fb276cf812 | ||
|
|
41dfaab82a | ||
|
|
20b8deb6de | ||
|
|
6af836c118 | ||
|
|
f87b8a2c2a | ||
|
|
daffd7412a | ||
|
|
bfb60b318e | ||
|
|
a55959dfbb | ||
|
|
4d24eb82be | ||
|
|
4656d3e019 | ||
|
|
86b76ba216 | ||
|
|
ff561410ad | ||
|
|
e26caa2f74 | ||
|
|
b32d17c2ab | ||
|
|
29111e1018 | ||
|
|
3433ebd665 | ||
|
|
b75fff60c8 | ||
|
|
a47fa3f9fc | ||
|
|
4ac57fce7a | ||
|
|
6218b94c99 | ||
|
|
74a6c89801 | ||
|
|
25a9a0120a | ||
|
|
deb0747e66 | ||
|
|
00f5fde5a8 | ||
|
|
03cd354f9e | ||
|
|
66a1fa8af5 | ||
|
|
c2096c8dfd | ||
|
|
1b4033cfce | ||
|
|
750edf1144 | ||
|
|
48bc41873a | ||
|
|
2e54f51229 | ||
|
|
e39e414e0d | ||
|
|
068a1ab99c | ||
|
|
e35d9eb07f | ||
|
|
6551383070 | ||
|
|
b03978cc3d | ||
|
|
3a7233b594 | ||
|
|
82f4c5790a | ||
|
|
5957124e9e | ||
|
|
7c91b9847b | ||
|
|
92a05ca74a | ||
|
|
a3c13c8cea | ||
|
|
d02b5db6dd | ||
|
|
bd074728fe | ||
|
|
8d87531464 | ||
|
|
5fbd3d9525 | ||
|
|
61b7b3ead6 | ||
|
|
8f2178a79c | ||
|
|
72bb23ed0d | ||
|
|
7f7999e3e5 | ||
|
|
0a08e3fdac | ||
|
|
b522b38d31 | ||
|
|
646a2aec66 | ||
|
|
7392cb9bf3 | ||
|
|
2e77988473 | ||
|
|
b2dc7bc232 | ||
|
|
1b25220729 | ||
|
|
4953000565 | ||
|
|
01b7575bca | ||
|
|
f6e6dcac9a | ||
|
|
93c307d9dd | ||
|
|
852c18cef8 | ||
|
|
def622a02c | ||
|
|
acfe1a856e | ||
|
|
9f1f6af647 | ||
|
|
30bedb39f1 | ||
|
|
fbcc4ee32b | ||
|
|
27571cc22f | ||
|
|
1182a5eb1e | ||
|
|
5f57c71c62 | ||
|
|
c0d60b1bcf | ||
|
|
63c68d729b | ||
|
|
5fe29e2ba1 | ||
|
|
16ff1ac3e7 | ||
|
|
0e4f805e1c | ||
|
|
5f29bdc7a7 | ||
|
|
04f8f6d512 | ||
|
|
f4768aff07 | ||
|
|
b74d46f762 | ||
|
|
02decc3901 | ||
|
|
dcdea16624 | ||
|
|
2635cf6345 | ||
|
|
578c65196c | ||
|
|
64fb24ce96 | ||
|
|
45dd7117c7 | ||
|
|
e86069d39c | ||
|
|
e9db714937 | ||
|
|
fdcf153b0b | ||
|
|
8523b7e20a | ||
|
|
29b992bf81 | ||
|
|
4c52509d6d | ||
|
|
8d603a0cef | ||
|
|
9bc067f2e8 | ||
|
|
5fbe0d9a70 | ||
|
|
4c1c36ef46 | ||
|
|
067fbcaeaf | ||
|
|
f0aad83d53 | ||
|
|
474f767e56 | ||
|
|
be062061d5 | ||
|
|
ac5dfab82f | ||
|
|
13d5f4ac99 | ||
|
|
f9030be843 | ||
|
|
3cea1cc78c | ||
|
|
37c1423c9f | ||
|
|
538e35d8f0 | ||
|
|
d23f029953 | ||
|
|
aafa37f80d | ||
|
|
203f0c8abc | ||
|
|
1c0e6362a1 | ||
|
|
68165cee75 | ||
|
|
8ef5f0b3f6 | ||
|
|
d0736b0b56 | ||
|
|
2fbcd9d2b9 | ||
|
|
15b1ed028e | ||
|
|
a611a38181 | ||
|
|
008617a35c | ||
|
|
81a403f02d | ||
|
|
6144a61a2e | ||
|
|
df102fba6c | ||
|
|
5d192abd25 | ||
|
|
902f6dae11 | ||
|
|
62337f445a | ||
|
|
b38c1c5827 | ||
|
|
8309768735 | ||
|
|
42e6b57296 | ||
|
|
2835ec812f | ||
|
|
990161729b | ||
|
|
c08cee2317 | ||
|
|
e1ea4322a1 | ||
|
|
0f7759d070 | ||
|
|
6523c4473f | ||
|
|
4a0842bea6 | ||
|
|
3745e6a8d8 | ||
|
|
3b3666c5f7 | ||
|
|
9e1a28f7ca | ||
|
|
82c7e921eb | ||
|
|
67ccf532ea | ||
|
|
45912911ee | ||
|
|
c7fe42d919 | ||
|
|
9916afaa49 | ||
|
|
55c5a23616 | ||
|
|
7b6df08268 | ||
|
|
9300050573 | ||
|
|
6bee1cc88e | ||
|
|
c94767a715 | ||
|
|
7aa3169523 | ||
|
|
cf86eacb16 | ||
|
|
43ab4d5f38 | ||
|
|
aa064a1d0e | ||
|
|
21240a60de | ||
|
|
0427c0d3a7 | ||
|
|
3f632b92e2 | ||
|
|
014750ea7f | ||
|
|
548b80969a | ||
|
|
e4bbfce314 | ||
|
|
278f1db9a9 | ||
|
|
359429b17e | ||
|
|
f151271cb1 | ||
|
|
6001f6abda | ||
|
|
18e5012546 | ||
|
|
fd9b5d8d16 | ||
|
|
da3ee13582 | ||
|
|
b6f184388a | ||
|
|
903c065b74 | ||
|
|
d8471698ab | ||
|
|
6204cecbbd | ||
|
|
ddeca49916 | ||
|
|
d8dcf37886 | ||
|
|
5ae41a208b | ||
|
|
0462d900e6 | ||
|
|
4fb2ad88bc | ||
|
|
ea55e90e62 | ||
|
|
6025c05f33 | ||
|
|
64f854e646 | ||
|
|
0f962461e1 | ||
|
|
62dfb5b5ff | ||
|
|
171af5a3c3 | ||
|
|
874548d1c1 | ||
|
|
aadc03c38d | ||
|
|
fb0aeafaa2 | ||
|
|
683fa13bb2 | ||
|
|
d3bd55a290 | ||
|
|
e4408a964d | ||
|
|
1fbe78b667 | ||
|
|
4902e0f597 | ||
|
|
34e248b1c3 | ||
|
|
5a05917bc2 | ||
|
|
98a613261c | ||
|
|
ee880a893e | ||
|
|
7e8555d6b7 | ||
|
|
0a6bdd6e04 | ||
|
|
f97f33ab59 | ||
|
|
f59102ee09 | ||
|
|
de304d6bb9 | ||
|
|
73181e3f45 | ||
|
|
e42e4e1ddf | ||
|
|
86e899f39b | ||
|
|
a30079c45b | ||
|
|
f644d49e71 | ||
|
|
c0b09b03d9 | ||
|
|
50ffd9aba6 | ||
|
|
4d4138c1e6 | ||
|
|
936fec1f49 | ||
|
|
9625d87dd5 | ||
|
|
c4d0319bb2 | ||
|
|
802139205c | ||
|
|
6022a3905f | ||
|
|
6f724c62fb | ||
|
|
fb918cb2a4 | ||
|
|
1a74f112ef | ||
|
|
abb4de46d7 | ||
|
|
5747c49abf | ||
|
|
b87930738a | ||
|
|
d2c78516f5 | ||
|
|
93cff96794 | ||
|
|
75e80a0091 | ||
|
|
46d2c9c196 | ||
|
|
6f61d160af | ||
|
|
13419171a9 | ||
|
|
147cfea587 | ||
|
|
c27da8e54e | ||
|
|
f3c8b4c1cd | ||
|
|
09e1883488 | ||
|
|
dce9631399 | ||
|
|
fb2ba1dea0 | ||
|
|
19073534dd | ||
|
|
23080e3cc4 | ||
|
|
b50d3fa809 | ||
|
|
1f191cf9bb | ||
|
|
6c773786d2 | ||
|
|
8939963187 | ||
|
|
e3fb693cb7 | ||
|
|
7cc11f55bb | ||
|
|
f9747d1c4c | ||
|
|
60a3d3409e | ||
|
|
cd8cbc54c8 | ||
|
|
b72124c0d9 | ||
|
|
0d6a1644e2 | ||
|
|
d51e761b26 | ||
|
|
1ebe8b82ec | ||
|
|
1445ef61a0 | ||
|
|
f82490f0d5 | ||
|
|
dfe2ed2a98 | ||
|
|
bb5c044a25 | ||
|
|
be87103b53 | ||
|
|
54f7627f9c | ||
|
|
2fb460c4bb | ||
|
|
1b3c94cc57 | ||
|
|
216ea230a8 | ||
|
|
7e727620a4 | ||
|
|
13ec9bcdd4 | ||
|
|
90096163ee | ||
|
|
d2564d4a54 | ||
|
|
cd178d6a8c | ||
|
|
2aa0b5ddfb | ||
|
|
cf6e026392 | ||
|
|
37f819458a | ||
|
|
16fb0c3081 | ||
|
|
168f4301d2 | ||
|
|
9fb03d9d8a | ||
|
|
d89ed535b7 | ||
|
|
08b4b890ce | ||
|
|
d5a406c60f | ||
|
|
cb16210577 | ||
|
|
8c0e0de45f | ||
|
|
3524cba4ef | ||
|
|
df6a37a972 | ||
|
|
13600ff6fd | ||
|
|
a957bb4968 | ||
|
|
5f8d1cf5b0 | ||
|
|
a115267e08 | ||
|
|
37998fbf1d | ||
|
|
846aa823d4 | ||
|
|
0d877853eb | ||
|
|
68298fc585 | ||
|
|
b97a7cf387 | ||
|
|
e9d75f6d94 | ||
|
|
1dce2eb747 | ||
|
|
7c110d90cd | ||
|
|
2ecc02ce4d | ||
|
|
8fa5277417 | ||
|
|
7cce5745af | ||
|
|
231f1f236d | ||
|
|
ece0a46f97 | ||
|
|
2c9b0b8376 | ||
|
|
76577c03e7 | ||
|
|
48d30250a0 | ||
|
|
a798f2d61c | ||
|
|
d0f8b1436b | ||
|
|
ddc5e0f86e | ||
|
|
ae08c87822 | ||
|
|
ae880e9d1c | ||
|
|
101d2e1de5 | ||
|
|
f6b7708567 | ||
|
|
051286acd1 | ||
|
|
d6d9c9d8cf | ||
|
|
7f572a1a9b | ||
|
|
68007306b3 | ||
|
|
0c3f97370c | ||
|
|
d62025dfe0 | ||
|
|
6bc766e95f | ||
|
|
07009630c6 | ||
|
|
c2aeb1ebcd | ||
|
|
865042a843 | ||
|
|
6243137d4c | ||
|
|
f00d0dc871 | ||
|
|
8695953d5a | ||
|
|
6afb68390b | ||
|
|
d6adcdf8bd | ||
|
|
8151700ad7 | ||
|
|
2c192fba30 | ||
|
|
97f11c93bb | ||
|
|
a3102a7381 | ||
|
|
d41b5e8ede | ||
|
|
a45856c925 | ||
|
|
f531fdaa70 | ||
|
|
f4b2f07656 | ||
|
|
81f8e84ca4 | ||
|
|
f7e64d98bc | ||
|
|
d74de11b8b | ||
|
|
4380c1be2e | ||
|
|
30f2097d7a | ||
|
|
db2d4e97a6 | ||
|
|
f903844059 | ||
|
|
b4653be54e | ||
|
|
68b2b452cc | ||
|
|
e2b6efde7a | ||
|
|
668d120d60 | ||
|
|
3eea17bc76 | ||
|
|
2894bcc833 | ||
|
|
7543f31c85 | ||
|
|
ff52197707 | ||
|
|
56d84e7b25 | ||
|
|
b025abaa88 | ||
|
|
ed2d3d856b | ||
|
|
dac637f360 | ||
|
|
1e5c3a3c21 | ||
|
|
861588205a | ||
|
|
37d5b6a0fc | ||
|
|
4d3fb2990e | ||
|
|
c58a954d18 | ||
|
|
cce878e759 | ||
|
|
0d481afa16 | ||
|
|
2dbad7cb1b | ||
|
|
016875d724 | ||
|
|
7a0fa629b5 | ||
|
|
5df0d60135 | ||
|
|
aee7bd8bb6 | ||
|
|
e77d61c4cb | ||
|
|
b1a40407b3 | ||
|
|
504142c585 | ||
|
|
0496571c92 | ||
|
|
62a0adce5a | ||
|
|
d1a1be1a9e | ||
|
|
80011f4e4e | ||
|
|
80df849d18 | ||
|
|
c2933b6bed | ||
|
|
fc4ba9f21d | ||
|
|
e74c79b5d6 | ||
|
|
f738ddfca8 | ||
|
|
5bbd122fa4 | ||
|
|
af45b0d709 | ||
|
|
928c20c867 | ||
|
|
4bd36a4786 | ||
|
|
e746617139 | ||
|
|
3105f7407d | ||
|
|
2f568bca17 | ||
|
|
ad7dba6fac | ||
|
|
9efe0fe243 | ||
|
|
2836d7a40f | ||
|
|
f1bf62e3c1 | ||
|
|
a560d65ce7 | ||
|
|
9de3cb73f7 | ||
|
|
757cc86a68 | ||
|
|
aeb1664134 | ||
|
|
2af1483724 | ||
|
|
b7702ab933 | ||
|
|
cc14f82e85 | ||
|
|
f23dd0a5dd | ||
|
|
5aea17e20e | ||
|
|
8336b744bb | ||
|
|
ad9eaea010 | ||
|
|
9c47e35d54 | ||
|
|
284a770daa | ||
|
|
16030d4fa1 | ||
|
|
37d2dd17d7 | ||
|
|
ccd190a8b1 | ||
|
|
36bf2436b6 | ||
|
|
19f6a8d8f6 | ||
|
|
b8e09afcce | ||
|
|
24264bd885 | ||
|
|
ed690ee6e9 | ||
|
|
872bb3d775 | ||
|
|
c0dcd12ada | ||
|
|
d36cd8e284 | ||
|
|
5d98672de5 | ||
|
|
eab643268f | ||
|
|
e2fa1f992b | ||
|
|
9356794112 | ||
|
|
bb865aef38 | ||
|
|
35c24810f9 | ||
|
|
875b5fb34d | ||
|
|
19187bcb14 | ||
|
|
ebe6e9fb9f | ||
|
|
a54944b021 | ||
|
|
5f7f75b1c9 | ||
|
|
bc9938d08a | ||
|
|
59963e152d | ||
|
|
b77f179acc |
@@ -3895,10 +3895,14 @@ do -- AI_A2G_DISPATCHER
|
||||
|
||||
if Squadron then
|
||||
local FirstUnit = AttackSetUnit:GetRandomSurely()
|
||||
if FirstUnit then
|
||||
local Coordinate = FirstUnit:GetCoordinate() -- Core.Point#COORDINATE
|
||||
if self.SetSendPlayerMessages then
|
||||
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", on route to ground target at " .. Coordinate:ToStringA2G( DefenderGroup ), DefenderGroup )
|
||||
end
|
||||
else
|
||||
return
|
||||
end
|
||||
end
|
||||
self:GetParent(self).onafterEngageRoute( self, DefenderGroup, From, Event, To, AttackSetUnit )
|
||||
end
|
||||
@@ -4784,4 +4788,5 @@ end
|
||||
Squadron.ResourceCount = Squadron.ResourceCount - Amount
|
||||
end
|
||||
self:T({Squadron = Squadron.Name,SquadronResourceCount = Squadron.ResourceCount})
|
||||
end
|
||||
end
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
-- @module AI.AI_Air
|
||||
-- @image MOOSE.JPG
|
||||
|
||||
---
|
||||
-- @type AI_AIR
|
||||
-- @extends Core.Fsm#FSM_CONTROLLABLE
|
||||
|
||||
|
||||
@@ -174,8 +174,7 @@ function AI_BAI_ZONE:New( PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
-- @param #string To The To State string.
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event Engage.
|
||||
|
||||
@@ -162,7 +162,6 @@ function AI_CAS_ZONE:New( PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
-- @param #string To The To State string.
|
||||
|
||||
-- @return #boolean Return false to cancel Transition.
|
||||
|
||||
--- OnAfter Transition Handler for Event Engage.
|
||||
|
||||
@@ -846,7 +846,6 @@ function AI_PATROL_ZONE:onafterStatus()
|
||||
OldAIControllable:SetTask( TimedOrbitTask, 10 )
|
||||
|
||||
RTB = true
|
||||
else
|
||||
end
|
||||
|
||||
-- TODO: Check GROUP damage function.
|
||||
@@ -856,6 +855,16 @@ function AI_PATROL_ZONE:onafterStatus()
|
||||
RTB = true
|
||||
end
|
||||
|
||||
if self:IsInstanceOf("AI_CAS") or self:IsInstanceOf("AI_BAI") then
|
||||
local atotal,shells,rockets,bombs,missiles = self.Controllable:GetAmmunition()
|
||||
local arelevant = rockets+bombs
|
||||
if arelevant == 0 or missiles == 0 then
|
||||
RTB = true
|
||||
self:T({total=atotal,shells=shells,rockets=rockets,bombs=bombs,missiles=missiles})
|
||||
self:T( self.Controllable:GetName() .. " is out of ammo, RTB!" )
|
||||
end
|
||||
end
|
||||
|
||||
if RTB == true then
|
||||
self:RTB()
|
||||
else
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
-- @module Core.Base
|
||||
-- @image Core_Base.JPG
|
||||
|
||||
local _TraceOnOff = true
|
||||
local _TraceOnOff = false -- default to no tracing
|
||||
local _TraceLevel = 1
|
||||
local _TraceAll = false
|
||||
local _TraceClass = {}
|
||||
@@ -34,11 +34,12 @@ local _TraceClassMethod = {}
|
||||
|
||||
local _ClassID = 0
|
||||
|
||||
---
|
||||
--- Base class of everything
|
||||
-- @type BASE
|
||||
-- @field ClassName The name of the class.
|
||||
-- @field ClassID The ID number of the class.
|
||||
-- @field ClassNameAndID The name of the class concatenated with the ID number of the class.
|
||||
-- @field #string ClassName The name of the class.
|
||||
-- @field #number ClassID The ID number of the class.
|
||||
-- @field #string ClassNameAndID The name of the class concatenated with the ID number of the class.
|
||||
-- @field Core.Scheduler#SCHEDULER Scheduler The scheduler object.
|
||||
|
||||
--- BASE class
|
||||
--
|
||||
@@ -200,6 +201,7 @@ BASE = {
|
||||
States = {},
|
||||
Debug = debug,
|
||||
Scheduler = nil,
|
||||
Properties = {},
|
||||
}
|
||||
|
||||
-- @field #BASE.__
|
||||
@@ -210,14 +212,6 @@ BASE._ = {
|
||||
Schedules = {}, --- Contains the Schedulers Active
|
||||
}
|
||||
|
||||
--- The Formation Class
|
||||
-- @type FORMATION
|
||||
-- @field Cone A cone formation.
|
||||
FORMATION = {
|
||||
Cone = "Cone",
|
||||
Vee = "Vee",
|
||||
}
|
||||
|
||||
--- BASE constructor.
|
||||
--
|
||||
-- This is an example how to use the BASE:New() constructor in a new class definition when inheriting from BASE.
|
||||
@@ -741,7 +735,31 @@ do -- Event Handling
|
||||
-- @function [parent=#BASE] OnEventPlayerEnterAircraft
|
||||
-- @param #BASE self
|
||||
-- @param Core.Event#EVENTDATA EventData The EventData structure.
|
||||
|
||||
|
||||
--- Occurs when a player creates a dynamic cargo object from the F8 ground crew menu.
|
||||
-- *** NOTE *** this is a workarounf for DCS not creating these events as of Aug 2024.
|
||||
-- @function [parent=#BASE] OnEventNewDynamicCargo
|
||||
-- @param #BASE self
|
||||
-- @param Core.Event#EVENTDATA EventData The EventData structure.
|
||||
|
||||
--- Occurs when a player loads a dynamic cargo object with the F8 ground crew menu into a helo.
|
||||
-- *** NOTE *** this is a workarounf for DCS not creating these events as of Aug 2024.
|
||||
-- @function [parent=#BASE] OnEventDynamicCargoLoaded
|
||||
-- @param #BASE self
|
||||
-- @param Core.Event#EVENTDATA EventData The EventData structure.
|
||||
|
||||
--- Occurs when a player unloads a dynamic cargo object with the F8 ground crew menu from a helo.
|
||||
-- *** NOTE *** this is a workarounf for DCS not creating these events as of Aug 2024.
|
||||
-- @function [parent=#BASE] OnEventDynamicCargoUnloaded
|
||||
-- @param #BASE self
|
||||
-- @param Core.Event#EVENTDATA EventData The EventData structure.
|
||||
|
||||
--- Occurs when a dynamic cargo crate is removed.
|
||||
-- *** NOTE *** this is a workarounf for DCS not creating these events as of Aug 2024.
|
||||
-- @function [parent=#BASE] OnEventDynamicCargoRemoved
|
||||
-- @param #BASE self
|
||||
-- @param Core.Event#EVENTDATA EventData The EventData structure.
|
||||
|
||||
end
|
||||
|
||||
--- Creation of a Birth Event.
|
||||
@@ -862,6 +880,62 @@ end
|
||||
|
||||
world.onEvent(Event)
|
||||
end
|
||||
|
||||
--- Creation of a S_EVENT_NEW_DYNAMIC_CARGO event.
|
||||
-- @param #BASE self
|
||||
-- @param Wrapper.DynamicCargo#DYNAMICCARGO DynamicCargo the dynamic cargo object
|
||||
function BASE:CreateEventNewDynamicCargo(DynamicCargo)
|
||||
self:F({DynamicCargo})
|
||||
local Event = {
|
||||
id = EVENTS.NewDynamicCargo,
|
||||
time = timer.getTime(),
|
||||
dynamiccargo = DynamicCargo,
|
||||
initiator = DynamicCargo:GetDCSObject(),
|
||||
}
|
||||
world.onEvent( Event )
|
||||
end
|
||||
|
||||
--- Creation of a S_EVENT_DYNAMIC_CARGO_LOADED event.
|
||||
-- @param #BASE self
|
||||
-- @param Wrapper.DynamicCargo#DYNAMICCARGO DynamicCargo the dynamic cargo object
|
||||
function BASE:CreateEventDynamicCargoLoaded(DynamicCargo)
|
||||
self:F({DynamicCargo})
|
||||
local Event = {
|
||||
id = EVENTS.DynamicCargoLoaded,
|
||||
time = timer.getTime(),
|
||||
dynamiccargo = DynamicCargo,
|
||||
initiator = DynamicCargo:GetDCSObject(),
|
||||
}
|
||||
world.onEvent( Event )
|
||||
end
|
||||
|
||||
--- Creation of a S_EVENT_DYNAMIC_CARGO_UNLOADED event.
|
||||
-- @param #BASE self
|
||||
-- @param Wrapper.DynamicCargo#DYNAMICCARGO DynamicCargo the dynamic cargo object
|
||||
function BASE:CreateEventDynamicCargoUnloaded(DynamicCargo)
|
||||
self:F({DynamicCargo})
|
||||
local Event = {
|
||||
id = EVENTS.DynamicCargoUnloaded,
|
||||
time = timer.getTime(),
|
||||
dynamiccargo = DynamicCargo,
|
||||
initiator = DynamicCargo:GetDCSObject(),
|
||||
}
|
||||
world.onEvent( Event )
|
||||
end
|
||||
|
||||
--- Creation of a S_EVENT_DYNAMIC_CARGO_REMOVED event.
|
||||
-- @param #BASE self
|
||||
-- @param Wrapper.DynamicCargo#DYNAMICCARGO DynamicCargo the dynamic cargo object
|
||||
function BASE:CreateEventDynamicCargoRemoved(DynamicCargo)
|
||||
self:F({DynamicCargo})
|
||||
local Event = {
|
||||
id = EVENTS.DynamicCargoRemoved,
|
||||
time = timer.getTime(),
|
||||
dynamiccargo = DynamicCargo,
|
||||
initiator = DynamicCargo:GetDCSObject(),
|
||||
}
|
||||
world.onEvent( Event )
|
||||
end
|
||||
|
||||
--- The main event handling function... This function captures all events generated for the class.
|
||||
-- @param #BASE self
|
||||
@@ -1036,6 +1110,31 @@ function BASE:ClearState( Object, StateName )
|
||||
end
|
||||
end
|
||||
|
||||
--- Set one property of an object.
|
||||
-- @param #BASE self
|
||||
-- @param Key The key that is used as a reference of the value. Note that the key can be a #string, but it can also be any other type!
|
||||
-- @param Value The value that is stored. Note that the value can be a #string, but it can also be any other type!
|
||||
function BASE:SetProperty(Key,Value)
|
||||
self.Properties = self.Properties or {}
|
||||
self.Properties[Key] = Value
|
||||
end
|
||||
|
||||
--- Get one property of an object by the key.
|
||||
-- @param #BASE self
|
||||
-- @param Key The key that is used as a reference of the value. Note that the key can be a #string, but it can also be any other type!
|
||||
-- @return Value The value that is stored. Note that the value can be a #string, but it can also be any other type! Nil if not found.
|
||||
function BASE:GetProperty(Key)
|
||||
self.Properties = self.Properties or {}
|
||||
return self.Properties[Key]
|
||||
end
|
||||
|
||||
--- Get all of the properties of an object in a table.
|
||||
-- @param #BASE self
|
||||
-- @return #table of values, indexed by keys.
|
||||
function BASE:GetProperties()
|
||||
return self.Properties
|
||||
end
|
||||
|
||||
-- Trace section
|
||||
|
||||
-- Log a trace (only shown when trace is on)
|
||||
@@ -1200,7 +1299,7 @@ end
|
||||
-- @param Arguments A #table or any field.
|
||||
function BASE:F( Arguments )
|
||||
|
||||
if BASE.Debug and _TraceOnOff then
|
||||
if BASE.Debug and _TraceOnOff == true then
|
||||
local DebugInfoCurrent = BASE.Debug.getinfo( 2, "nl" )
|
||||
local DebugInfoFrom = BASE.Debug.getinfo( 3, "l" )
|
||||
|
||||
@@ -1215,7 +1314,7 @@ end
|
||||
-- @param Arguments A #table or any field.
|
||||
function BASE:F2( Arguments )
|
||||
|
||||
if BASE.Debug and _TraceOnOff then
|
||||
if BASE.Debug and _TraceOnOff == true and _TraceLevel >= 2 then
|
||||
local DebugInfoCurrent = BASE.Debug.getinfo( 2, "nl" )
|
||||
local DebugInfoFrom = BASE.Debug.getinfo( 3, "l" )
|
||||
|
||||
@@ -1230,7 +1329,7 @@ end
|
||||
-- @param Arguments A #table or any field.
|
||||
function BASE:F3( Arguments )
|
||||
|
||||
if BASE.Debug and _TraceOnOff then
|
||||
if BASE.Debug and _TraceOnOff == true and _TraceLevel >= 3 then
|
||||
local DebugInfoCurrent = BASE.Debug.getinfo( 2, "nl" )
|
||||
local DebugInfoFrom = BASE.Debug.getinfo( 3, "l" )
|
||||
|
||||
@@ -1274,7 +1373,7 @@ end
|
||||
-- @param Arguments A #table or any field.
|
||||
function BASE:T( Arguments )
|
||||
|
||||
if BASE.Debug and _TraceOnOff then
|
||||
if BASE.Debug and _TraceOnOff == true then
|
||||
local DebugInfoCurrent = BASE.Debug.getinfo( 2, "nl" )
|
||||
local DebugInfoFrom = BASE.Debug.getinfo( 3, "l" )
|
||||
|
||||
@@ -1289,7 +1388,7 @@ end
|
||||
-- @param Arguments A #table or any field.
|
||||
function BASE:T2( Arguments )
|
||||
|
||||
if BASE.Debug and _TraceOnOff then
|
||||
if BASE.Debug and _TraceOnOff == true and _TraceLevel >= 2 then
|
||||
local DebugInfoCurrent = BASE.Debug.getinfo( 2, "nl" )
|
||||
local DebugInfoFrom = BASE.Debug.getinfo( 3, "l" )
|
||||
|
||||
@@ -1304,7 +1403,7 @@ end
|
||||
-- @param Arguments A #table or any field.
|
||||
function BASE:T3( Arguments )
|
||||
|
||||
if BASE.Debug and _TraceOnOff then
|
||||
if BASE.Debug and _TraceOnOff == true and _TraceLevel >= 3 then
|
||||
local DebugInfoCurrent = BASE.Debug.getinfo( 2, "nl" )
|
||||
local DebugInfoFrom = BASE.Debug.getinfo( 3, "l" )
|
||||
|
||||
@@ -1336,7 +1435,7 @@ function BASE:E( Arguments )
|
||||
|
||||
env.info( string.format( "%6d(%6d)/%1s:%30s%05d.%s(%s)", LineCurrent, LineFrom, "E", self.ClassName, self.ClassID, Function, UTILS.BasicSerialize( Arguments ) ) )
|
||||
else
|
||||
env.info( string.format( "%1s:%30s%05d(%s)", "E", self.ClassName, self.ClassID, BASE:_Serialize(Arguments) ) )
|
||||
env.info( string.format( "%1s:%30s%05d(%s)", "E", self.ClassName, self.ClassID, UTILS.BasicSerialize(Arguments) ) )
|
||||
end
|
||||
|
||||
end
|
||||
@@ -1363,8 +1462,7 @@ function BASE:I( Arguments )
|
||||
|
||||
env.info( string.format( "%6d(%6d)/%1s:%30s%05d.%s(%s)", LineCurrent, LineFrom, "I", self.ClassName, self.ClassID, Function, UTILS.BasicSerialize( Arguments ) ) )
|
||||
else
|
||||
env.info( string.format( "%1s:%30s%05d(%s)", "I", self.ClassName, self.ClassID, BASE:_Serialize(Arguments)) )
|
||||
env.info( string.format( "%1s:%30s%05d(%s)", "I", self.ClassName, self.ClassID, UTILS.BasicSerialize(Arguments)) )
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
--
|
||||
-- @module Core.ClientMenu
|
||||
-- @image Core_Menu.JPG
|
||||
-- last change: May 2024
|
||||
-- last change: Jan 2025
|
||||
|
||||
-- TODO
|
||||
----------------------------------------------------------------------------------------------------------------
|
||||
@@ -57,9 +57,9 @@
|
||||
---
|
||||
-- @field #CLIENTMENU
|
||||
CLIENTMENU = {
|
||||
ClassName = "CLIENTMENUE",
|
||||
ClassName = "CLIENTMENU",
|
||||
lid = "",
|
||||
version = "0.1.2",
|
||||
version = "0.1.3",
|
||||
name = nil,
|
||||
path = nil,
|
||||
group = nil,
|
||||
@@ -455,7 +455,7 @@ end
|
||||
-- @param #CLIENTMENUMANAGER self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
-- @return #CLIENTMENUMANAGER self
|
||||
function CLIENTMENUMANAGER:_EventHandler(EventData)
|
||||
function CLIENTMENUMANAGER:_EventHandler(EventData,Retry)
|
||||
self:T(self.lid.."_EventHandler: "..EventData.id)
|
||||
--self:I(self.lid.."_EventHandler: "..tostring(EventData.IniPlayerName))
|
||||
if EventData.id == EVENTS.PlayerLeaveUnit or EventData.id == EVENTS.Ejection or EventData.id == EVENTS.Crash or EventData.id == EVENTS.PilotDead then
|
||||
@@ -468,6 +468,10 @@ function CLIENTMENUMANAGER:_EventHandler(EventData)
|
||||
if EventData.IniPlayerName and EventData.IniGroup then
|
||||
if (not self.clientset:IsIncludeObject(_DATABASE:FindClient( EventData.IniUnitName ))) then
|
||||
self:T(self.lid.."Client not in SET: "..EventData.IniPlayerName)
|
||||
if not Retry then
|
||||
-- try again in 2 secs
|
||||
self:ScheduleOnce(2,CLIENTMENUMANAGER._EventHandler,self,EventData,true)
|
||||
end
|
||||
return self
|
||||
end
|
||||
--self:I(self.lid.."Join event for player: "..EventData.IniPlayerName)
|
||||
@@ -524,7 +528,7 @@ function CLIENTMENUMANAGER:InitAutoPropagation()
|
||||
self:HandleEvent(EVENTS.PilotDead, self._EventHandler)
|
||||
self:HandleEvent(EVENTS.PlayerEnterAircraft, self._EventHandler)
|
||||
self:HandleEvent(EVENTS.PlayerEnterUnit, self._EventHandler)
|
||||
self:SetEventPriority(5)
|
||||
self:SetEventPriority(6)
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
-- * Manage database of hits to units and statics.
|
||||
-- * Manage database of destroys of units and statics.
|
||||
-- * Manage database of @{Core.Zone#ZONE_BASE} objects.
|
||||
-- * Manage database of @{Wrapper.DynamicCargo#DYNAMICCARGO} objects alive in the mission.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
@@ -39,6 +40,7 @@
|
||||
-- @field #table STORAGES DCS warehouse storages.
|
||||
-- @field #table STNS Used Link16 octal numbers for F16/15/18/AWACS planes.
|
||||
-- @field #table SADL Used Link16 octal numbers for A10/C-II planes.
|
||||
-- @field #table DYNAMICCARGO Dynamic Cargo objects.
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
--- Contains collections of wrapper objects defined within MOOSE that reflect objects within the simulator.
|
||||
@@ -54,6 +56,7 @@
|
||||
-- * PLAYERS
|
||||
-- * CARGOS
|
||||
-- * STORAGES (DCS warehouses)
|
||||
-- * DYNAMICCARGO
|
||||
--
|
||||
-- On top, for internal MOOSE administration purposes, the DATABASE administers the Unit and Group TEMPLATES as defined within the Mission Editor.
|
||||
--
|
||||
@@ -97,6 +100,7 @@ DATABASE = {
|
||||
STORAGES = {},
|
||||
STNS={},
|
||||
SADL={},
|
||||
DYNAMICCARGO={},
|
||||
}
|
||||
|
||||
local _DATABASECoalition =
|
||||
@@ -143,6 +147,8 @@ function DATABASE:New()
|
||||
self:HandleEvent( EVENTS.DeleteZone )
|
||||
--self:HandleEvent( EVENTS.PlayerEnterUnit, self._EventOnPlayerEnterUnit ) -- This is not working anymore!, handling this through the birth event.
|
||||
self:HandleEvent( EVENTS.PlayerLeaveUnit, self._EventOnPlayerLeaveUnit )
|
||||
-- DCS 2.9.7 Moose own dynamic cargo events
|
||||
self:HandleEvent( EVENTS.DynamicCargoRemoved, self._EventOnDynamicCargoRemoved)
|
||||
|
||||
self:_RegisterTemplates()
|
||||
self:_RegisterGroupsAndUnits()
|
||||
@@ -173,16 +179,20 @@ end
|
||||
-- @param #boolean force
|
||||
-- @return Wrapper.Unit#UNIT The added unit.
|
||||
function DATABASE:AddUnit( DCSUnitName, force )
|
||||
|
||||
if not self.UNITS[DCSUnitName] or force == true then
|
||||
|
||||
local DCSunitName = DCSUnitName
|
||||
|
||||
if type(DCSunitName) == "number" then DCSunitName = string.format("%d",DCSUnitName) end
|
||||
|
||||
if not self.UNITS[DCSunitName] or force == true then
|
||||
-- Debug info.
|
||||
self:T( { "Add UNIT:", DCSUnitName } )
|
||||
self:T( { "Add UNIT:", DCSunitName } )
|
||||
|
||||
-- Register unit
|
||||
self.UNITS[DCSUnitName]=UNIT:Register(DCSUnitName)
|
||||
self.UNITS[DCSunitName]=UNIT:Register(DCSunitName)
|
||||
end
|
||||
|
||||
return self.UNITS[DCSUnitName]
|
||||
return self.UNITS[DCSunitName]
|
||||
end
|
||||
|
||||
|
||||
@@ -201,10 +211,9 @@ function DATABASE:AddStatic( DCSStaticName )
|
||||
|
||||
if not self.STATICS[DCSStaticName] then
|
||||
self.STATICS[DCSStaticName] = STATIC:Register( DCSStaticName )
|
||||
return self.STATICS[DCSStaticName]
|
||||
end
|
||||
|
||||
return nil
|
||||
return self.STATICS[DCSStaticName]
|
||||
end
|
||||
|
||||
|
||||
@@ -214,16 +223,42 @@ function DATABASE:DeleteStatic( DCSStaticName )
|
||||
self.STATICS[DCSStaticName] = nil
|
||||
end
|
||||
|
||||
--- Finds a STATIC based on the StaticName.
|
||||
--- Finds a STATIC based on the Static Name.
|
||||
-- @param #DATABASE self
|
||||
-- @param #string StaticName
|
||||
-- @param #string StaticName Name of the static object.
|
||||
-- @return Wrapper.Static#STATIC The found STATIC.
|
||||
function DATABASE:FindStatic( StaticName )
|
||||
|
||||
local StaticFound = self.STATICS[StaticName]
|
||||
return StaticFound
|
||||
end
|
||||
|
||||
--- Add a DynamicCargo to the database.
|
||||
-- @param #DATABASE self
|
||||
-- @param #string Name Name of the dynamic cargo.
|
||||
-- @return Wrapper.DynamicCargo#DYNAMICCARGO The dynamic cargo object.
|
||||
function DATABASE:AddDynamicCargo( Name )
|
||||
if not self.DYNAMICCARGO[Name] then
|
||||
self.DYNAMICCARGO[Name] = DYNAMICCARGO:Register(Name)
|
||||
end
|
||||
return self.DYNAMICCARGO[Name]
|
||||
end
|
||||
|
||||
--- Finds a DYNAMICCARGO based on the Dynamic Cargo Name.
|
||||
-- @param #DATABASE self
|
||||
-- @param #string DynamicCargoName
|
||||
-- @return Wrapper.DynamicCargo#DYNAMICCARGO The found DYNAMICCARGO.
|
||||
function DATABASE:FindDynamicCargo( DynamicCargoName )
|
||||
local StaticFound = self.DYNAMICCARGO[DynamicCargoName]
|
||||
return StaticFound
|
||||
end
|
||||
|
||||
--- Deletes a DYNAMICCARGO from the DATABASE based on the Dynamic Cargo Name.
|
||||
-- @param #DATABASE self
|
||||
function DATABASE:DeleteDynamicCargo( DynamicCargoName )
|
||||
self.DYNAMICCARGO[DynamicCargoName] = nil
|
||||
return self
|
||||
end
|
||||
|
||||
--- Adds a Airbase based on the Airbase Name in the DATABASE.
|
||||
-- @param #DATABASE self
|
||||
-- @param #string AirbaseName The name of the airbase.
|
||||
@@ -818,12 +853,16 @@ end
|
||||
-- @param #boolean Force (optional) Force registration of client.
|
||||
-- @return Wrapper.Client#CLIENT The client object.
|
||||
function DATABASE:AddClient( ClientName, Force )
|
||||
|
||||
if not self.CLIENTS[ClientName] or Force == true then
|
||||
self.CLIENTS[ClientName] = CLIENT:Register( ClientName )
|
||||
|
||||
local DCSUnitName = ClientName
|
||||
|
||||
if type(DCSUnitName) == "number" then DCSUnitName = string.format("%d",ClientName) end
|
||||
|
||||
if not self.CLIENTS[DCSUnitName] or Force == true then
|
||||
self.CLIENTS[DCSUnitName] = CLIENT:Register( DCSUnitName )
|
||||
end
|
||||
|
||||
return self.CLIENTS[ClientName]
|
||||
return self.CLIENTS[DCSUnitName]
|
||||
end
|
||||
|
||||
|
||||
@@ -863,9 +902,11 @@ end
|
||||
--- Adds a player based on the Player Name in the DATABASE.
|
||||
-- @param #DATABASE self
|
||||
function DATABASE:AddPlayer( UnitName, PlayerName )
|
||||
|
||||
|
||||
if type(UnitName) == "number" then UnitName = string.format("%d",UnitName) end
|
||||
|
||||
if PlayerName then
|
||||
self:T( { "Add player for unit:", UnitName, PlayerName } )
|
||||
self:I( { "Add player for unit:", UnitName, PlayerName } )
|
||||
self.PLAYERS[PlayerName] = UnitName
|
||||
self.PLAYERUNITS[PlayerName] = self:FindUnit( UnitName )
|
||||
self.PLAYERSJOINED[PlayerName] = PlayerName
|
||||
@@ -873,6 +914,21 @@ function DATABASE:AddPlayer( UnitName, PlayerName )
|
||||
|
||||
end
|
||||
|
||||
--- Get a PlayerName by UnitName from PLAYERS in DATABASE.
|
||||
-- @param #DATABASE self
|
||||
-- @return #string PlayerName
|
||||
-- @return Wrapper.Unit#UNIT PlayerUnit
|
||||
function DATABASE:_FindPlayerNameByUnitName(UnitName)
|
||||
if UnitName then
|
||||
for playername,unitname in pairs(self.PLAYERS) do
|
||||
if unitname == UnitName and self.PLAYERUNITS[playername] and self.PLAYERUNITS[playername]:IsAlive() then
|
||||
return playername, self.PLAYERUNITS[playername]
|
||||
end
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
--- Deletes a player from the DATABASE based on the Player Name.
|
||||
-- @param #DATABASE self
|
||||
function DATABASE:DeletePlayer( UnitName, PlayerName )
|
||||
@@ -1244,7 +1300,7 @@ function DATABASE:_GetGenericStaticCargoGroupTemplate(Name,Typename,Mass,Coaliti
|
||||
StaticTemplate.CategoryID = "static"
|
||||
StaticTemplate.CoalitionID = Coalition or coalition.side.BLUE
|
||||
StaticTemplate.CountryID = Country or country.id.GERMANY
|
||||
UTILS.PrintTableToLog(StaticTemplate)
|
||||
--UTILS.PrintTableToLog(StaticTemplate)
|
||||
return StaticTemplate
|
||||
end
|
||||
|
||||
@@ -1324,7 +1380,7 @@ function DATABASE:GetCoalitionFromClientTemplate( ClientName )
|
||||
if self.Templates.ClientsByName[ClientName] then
|
||||
return self.Templates.ClientsByName[ClientName].CoalitionID
|
||||
end
|
||||
self:E("ERROR: Template does not exist for client "..tostring(ClientName))
|
||||
self:E("WARNING: Template does not exist for client "..tostring(ClientName))
|
||||
return nil
|
||||
end
|
||||
|
||||
@@ -1336,7 +1392,7 @@ function DATABASE:GetCategoryFromClientTemplate( ClientName )
|
||||
if self.Templates.ClientsByName[ClientName] then
|
||||
return self.Templates.ClientsByName[ClientName].CategoryID
|
||||
end
|
||||
self:E("ERROR: Template does not exist for client "..tostring(ClientName))
|
||||
self:E("WARNING: Template does not exist for client "..tostring(ClientName))
|
||||
return nil
|
||||
end
|
||||
|
||||
@@ -1348,7 +1404,7 @@ function DATABASE:GetCountryFromClientTemplate( ClientName )
|
||||
if self.Templates.ClientsByName[ClientName] then
|
||||
return self.Templates.ClientsByName[ClientName].CountryID
|
||||
end
|
||||
self:E("ERROR: Template does not exist for client "..tostring(ClientName))
|
||||
self:E("WARNING: Template does not exist for client "..tostring(ClientName))
|
||||
return nil
|
||||
end
|
||||
|
||||
@@ -1417,7 +1473,7 @@ function DATABASE:_RegisterDynamicGroup(Groupname)
|
||||
|
||||
-- Add unit.
|
||||
self:I(string.format("Register Unit: %s", tostring(DCSUnitName)))
|
||||
self:AddUnit( DCSUnitName, true )
|
||||
self:AddUnit( tostring(DCSUnitName), true )
|
||||
|
||||
end
|
||||
else
|
||||
@@ -1523,12 +1579,29 @@ end
|
||||
-- @param DCS#Airbase airbase Airbase.
|
||||
-- @return #DATABASE self
|
||||
function DATABASE:_RegisterAirbase(airbase)
|
||||
|
||||
|
||||
local IsSyria = UTILS.GetDCSMap() == "Syria" and true or false
|
||||
local countHSyria = 0
|
||||
|
||||
if airbase then
|
||||
|
||||
-- Get the airbase name.
|
||||
local DCSAirbaseName = airbase:getName()
|
||||
|
||||
|
||||
-- DCS 2.9.8.1107 added 143 helipads all named H with the same object ID ..
|
||||
if IsSyria and DCSAirbaseName == "H" and countHSyria > 0 then
|
||||
--[[
|
||||
local p = airbase:getPosition().p
|
||||
local mgrs = COORDINATE:New(p.x,p.z,p.y):ToStringMGRS()
|
||||
self:I("Airbase on Syria map named H @ "..mgrs)
|
||||
countHSyria = countHSyria + 1
|
||||
if countHSyria > 1 then return self end
|
||||
--]]
|
||||
return self
|
||||
elseif IsSyria and DCSAirbaseName == "H" and countHSyria == 0 then
|
||||
countHSyria = countHSyria + 1
|
||||
end
|
||||
|
||||
-- This gave the incorrect value to be inserted into the airdromeID for DCS 2.5.6. Is fixed now.
|
||||
local airbaseID=airbase:getID()
|
||||
|
||||
@@ -1568,7 +1641,7 @@ end
|
||||
-- @param #DATABASE self
|
||||
-- @param Core.Event#EVENTDATA Event
|
||||
function DATABASE:_EventOnBirth( Event )
|
||||
self:F( { Event } )
|
||||
self:T( { Event } )
|
||||
|
||||
if Event.IniDCSUnit then
|
||||
|
||||
@@ -1576,7 +1649,17 @@ function DATABASE:_EventOnBirth( Event )
|
||||
|
||||
-- Add static object to DB.
|
||||
self:AddStatic( Event.IniDCSUnitName )
|
||||
|
||||
elseif Event.IniObjectCategory == Object.Category.CARGO and string.match(Event.IniUnitName,".+|%d%d:%d%d|PKG%d+") then
|
||||
|
||||
-- Add dynamic cargo object to DB
|
||||
|
||||
local cargo = self:AddDynamicCargo(Event.IniDCSUnitName)
|
||||
|
||||
self:I(string.format("Adding dynamic cargo %s", tostring(Event.IniDCSUnitName)))
|
||||
|
||||
self:CreateEventNewDynamicCargo( cargo )
|
||||
|
||||
else
|
||||
|
||||
if Event.IniObjectCategory == Object.Category.UNIT then
|
||||
@@ -1762,6 +1845,15 @@ function DATABASE:_EventOnPlayerEnterUnit( Event )
|
||||
end
|
||||
end
|
||||
|
||||
--- Handles the OnDynamicCargoRemoved event to clean the active dynamic cargo table.
|
||||
-- @param #DATABASE self
|
||||
-- @param Core.Event#EVENTDATA Event
|
||||
function DATABASE:_EventOnDynamicCargoRemoved( Event )
|
||||
self:T( { Event } )
|
||||
if Event.IniDynamicCargoName then
|
||||
self:DeleteDynamicCargo(Event.IniDynamicCargoName)
|
||||
end
|
||||
end
|
||||
|
||||
--- Handles the OnPlayerLeaveUnit event to clean the active players table.
|
||||
-- @param #DATABASE self
|
||||
|
||||
@@ -194,6 +194,11 @@ world.event.S_EVENT_NEW_ZONE_GOAL = world.event.S_EVENT_MAX + 1004
|
||||
world.event.S_EVENT_DELETE_ZONE_GOAL = world.event.S_EVENT_MAX + 1005
|
||||
world.event.S_EVENT_REMOVE_UNIT = world.event.S_EVENT_MAX + 1006
|
||||
world.event.S_EVENT_PLAYER_ENTER_AIRCRAFT = world.event.S_EVENT_MAX + 1007
|
||||
-- dynamic cargo
|
||||
world.event.S_EVENT_NEW_DYNAMIC_CARGO = world.event.S_EVENT_MAX + 1008
|
||||
world.event.S_EVENT_DYNAMIC_CARGO_LOADED = world.event.S_EVENT_MAX + 1009
|
||||
world.event.S_EVENT_DYNAMIC_CARGO_UNLOADED = world.event.S_EVENT_MAX + 1010
|
||||
world.event.S_EVENT_DYNAMIC_CARGO_REMOVED = world.event.S_EVENT_MAX + 1011
|
||||
|
||||
|
||||
--- The different types of events supported by MOOSE.
|
||||
@@ -275,7 +280,12 @@ EVENTS = {
|
||||
SimulationFreeze = world.event.S_EVENT_SIMULATION_FREEZE or -1,
|
||||
SimulationUnfreeze = world.event.S_EVENT_SIMULATION_UNFREEZE or -1,
|
||||
HumanAircraftRepairStart = world.event.S_EVENT_HUMAN_AIRCRAFT_REPAIR_START or -1,
|
||||
HumanAircraftRepairFinish = world.event.S_EVENT_HUMAN_AIRCRAFT_REPAIR_FINISH or -1,
|
||||
HumanAircraftRepairFinish = world.event.S_EVENT_HUMAN_AIRCRAFT_REPAIR_FINISH or -1,
|
||||
-- dynamic cargo
|
||||
NewDynamicCargo = world.event.S_EVENT_NEW_DYNAMIC_CARGO or -1,
|
||||
DynamicCargoLoaded = world.event.S_EVENT_DYNAMIC_CARGO_LOADED or -1,
|
||||
DynamicCargoUnloaded = world.event.S_EVENT_DYNAMIC_CARGO_UNLOADED or -1,
|
||||
DynamicCargoRemoved = world.event.S_EVENT_DYNAMIC_CARGO_REMOVED or -1,
|
||||
}
|
||||
|
||||
|
||||
@@ -334,6 +344,9 @@ EVENTS = {
|
||||
--
|
||||
-- @field Core.Zone#ZONE Zone The zone object.
|
||||
-- @field #string ZoneName The name of the zone.
|
||||
--
|
||||
-- @field Wrapper.DynamicCargo#DYNAMICCARGO IniDynamicCargo The dynamic cargo object.
|
||||
-- @field #string IniDynamicCargoName The dynamic cargo unit name.
|
||||
|
||||
|
||||
|
||||
@@ -730,6 +743,31 @@ local _EVENTMETA = {
|
||||
Side = "I",
|
||||
Event = "OnEventHumanAircraftRepairFinish",
|
||||
Text = "S_EVENT_HUMAN_AIRCRAFT_REPAIR_FINISH"
|
||||
},
|
||||
-- dynamic cargo
|
||||
[EVENTS.NewDynamicCargo] = {
|
||||
Order = 1,
|
||||
Side = "I",
|
||||
Event = "OnEventNewDynamicCargo",
|
||||
Text = "S_EVENT_NEW_DYNAMIC_CARGO"
|
||||
},
|
||||
[EVENTS.DynamicCargoLoaded] = {
|
||||
Order = 1,
|
||||
Side = "I",
|
||||
Event = "OnEventDynamicCargoLoaded",
|
||||
Text = "S_EVENT_DYNAMIC_CARGO_LOADED"
|
||||
},
|
||||
[EVENTS.DynamicCargoUnloaded] = {
|
||||
Order = 1,
|
||||
Side = "I",
|
||||
Event = "OnEventDynamicCargoUnloaded",
|
||||
Text = "S_EVENT_DYNAMIC_CARGO_UNLOADED"
|
||||
},
|
||||
[EVENTS.DynamicCargoRemoved] = {
|
||||
Order = 1,
|
||||
Side = "I",
|
||||
Event = "OnEventDynamicCargoRemoved",
|
||||
Text = "S_EVENT_DYNAMIC_CARGO_REMOVED"
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1146,7 +1184,63 @@ do -- Event Creation
|
||||
|
||||
world.onEvent( Event )
|
||||
end
|
||||
|
||||
|
||||
--- Creation of a S_EVENT_NEW_DYNAMIC_CARGO event.
|
||||
-- @param #EVENT self
|
||||
-- @param Wrapper.DynamicCargo#DYNAMICCARGO DynamicCargo the dynamic cargo object
|
||||
function EVENT:CreateEventNewDynamicCargo(DynamicCargo)
|
||||
self:F({DynamicCargo})
|
||||
local Event = {
|
||||
id = EVENTS.NewDynamicCargo,
|
||||
time = timer.getTime(),
|
||||
dynamiccargo = DynamicCargo,
|
||||
initiator = DynamicCargo:GetDCSObject(),
|
||||
}
|
||||
world.onEvent( Event )
|
||||
end
|
||||
|
||||
--- Creation of a S_EVENT_DYNAMIC_CARGO_LOADED event.
|
||||
-- @param #EVENT self
|
||||
-- @param Wrapper.DynamicCargo#DYNAMICCARGO DynamicCargo the dynamic cargo object
|
||||
function EVENT:CreateEventDynamicCargoLoaded(DynamicCargo)
|
||||
self:F({DynamicCargo})
|
||||
local Event = {
|
||||
id = EVENTS.DynamicCargoLoaded,
|
||||
time = timer.getTime(),
|
||||
dynamiccargo = DynamicCargo,
|
||||
initiator = DynamicCargo:GetDCSObject(),
|
||||
}
|
||||
world.onEvent( Event )
|
||||
end
|
||||
|
||||
--- Creation of a S_EVENT_DYNAMIC_CARGO_UNLOADED event.
|
||||
-- @param #EVENT self
|
||||
-- @param Wrapper.DynamicCargo#DYNAMICCARGO DynamicCargo the dynamic cargo object
|
||||
function EVENT:CreateEventDynamicCargoUnloaded(DynamicCargo)
|
||||
self:F({DynamicCargo})
|
||||
local Event = {
|
||||
id = EVENTS.DynamicCargoUnloaded,
|
||||
time = timer.getTime(),
|
||||
dynamiccargo = DynamicCargo,
|
||||
initiator = DynamicCargo:GetDCSObject(),
|
||||
}
|
||||
world.onEvent( Event )
|
||||
end
|
||||
|
||||
--- Creation of a S_EVENT_DYNAMIC_CARGO_REMOVED event.
|
||||
-- @param #EVENT self
|
||||
-- @param Wrapper.DynamicCargo#DYNAMICCARGO DynamicCargo the dynamic cargo object
|
||||
function EVENT:CreateEventDynamicCargoRemoved(DynamicCargo)
|
||||
self:F({DynamicCargo})
|
||||
local Event = {
|
||||
id = EVENTS.DynamicCargoRemoved,
|
||||
time = timer.getTime(),
|
||||
dynamiccargo = DynamicCargo,
|
||||
initiator = DynamicCargo:GetDCSObject(),
|
||||
}
|
||||
world.onEvent( Event )
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Main event function.
|
||||
@@ -1235,6 +1329,7 @@ function EVENT:onEvent( Event )
|
||||
end
|
||||
|
||||
Event.IniDCSGroupName = Event.IniUnit and Event.IniUnit.GroupName or ""
|
||||
Event.IniGroupName=Event.IniDCSGroupName --At least set the group name because group might not exist any more
|
||||
if Event.IniDCSGroup and Event.IniDCSGroup:isExist() then
|
||||
Event.IniDCSGroupName = Event.IniDCSGroup:getName()
|
||||
Event.IniGroup = GROUP:FindByName( Event.IniDCSGroupName )
|
||||
@@ -1261,7 +1356,13 @@ function EVENT:onEvent( Event )
|
||||
Event.IniDCSUnit = Event.initiator
|
||||
Event.IniDCSUnitName = Event.IniDCSUnit:getName()
|
||||
Event.IniUnitName = Event.IniDCSUnitName
|
||||
Event.IniUnit = CARGO:FindByName( Event.IniDCSUnitName )
|
||||
if string.match(Event.IniUnitName,".+|%d%d:%d%d|PKG%d+") then
|
||||
Event.IniDynamicCargo = DYNAMICCARGO:FindByName(Event.IniUnitName)
|
||||
Event.IniDynamicCargoName = Event.IniUnitName
|
||||
Event.IniPlayerName = string.match(Event.IniUnitName,"^(.+)|%d%d:%d%d|PKG%d+")
|
||||
else
|
||||
Event.IniUnit = CARGO:FindByName( Event.IniDCSUnitName )
|
||||
end
|
||||
Event.IniCoalition = Event.IniDCSUnit:getCoalition()
|
||||
Event.IniCategory = Event.IniDCSUnit:getDesc().category
|
||||
Event.IniTypeName = Event.IniDCSUnit:getTypeName()
|
||||
@@ -1271,10 +1372,10 @@ function EVENT:onEvent( Event )
|
||||
-- Scenery
|
||||
---
|
||||
Event.IniDCSUnit = Event.initiator
|
||||
Event.IniDCSUnitName = Event.IniDCSUnit:getName()
|
||||
Event.IniDCSUnitName = Event.IniDCSUnit.getName and Event.IniDCSUnit:getName() or "Scenery no name "..math.random(1,20000)
|
||||
Event.IniUnitName = Event.IniDCSUnitName
|
||||
Event.IniUnit = SCENERY:Register( Event.IniDCSUnitName, Event.initiator )
|
||||
Event.IniCategory = Event.IniDCSUnit:getDesc().category
|
||||
Event.IniCategory = Event.IniDCSUnit.getDesc and Event.IniDCSUnit:getDesc().category
|
||||
Event.IniTypeName = Event.initiator:isExist() and Event.IniDCSUnit:getTypeName() or "SCENERY"
|
||||
|
||||
elseif Event.IniObjectCategory == Object.Category.BASE then
|
||||
@@ -1373,16 +1474,18 @@ function EVENT:onEvent( Event )
|
||||
-- SCENERY
|
||||
---
|
||||
Event.TgtDCSUnit = Event.target
|
||||
Event.TgtDCSUnitName = Event.TgtDCSUnit:getName()
|
||||
Event.TgtUnitName = Event.TgtDCSUnitName
|
||||
Event.TgtUnit = SCENERY:Register( Event.TgtDCSUnitName, Event.target )
|
||||
Event.TgtCategory = Event.TgtDCSUnit:getDesc().category
|
||||
Event.TgtTypeName = Event.TgtDCSUnit:getTypeName()
|
||||
Event.TgtDCSUnitName = Event.TgtDCSUnit.getName and Event.TgtDCSUnit.getName() or nil
|
||||
if Event.TgtDCSUnitName~=nil then
|
||||
Event.TgtUnitName = Event.TgtDCSUnitName
|
||||
Event.TgtUnit = SCENERY:Register( Event.TgtDCSUnitName, Event.target )
|
||||
Event.TgtCategory = Event.TgtDCSUnit:getDesc().category
|
||||
Event.TgtTypeName = Event.TgtDCSUnit:getTypeName()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Weapon.
|
||||
if Event.weapon and type(Event.weapon) == "table" then
|
||||
if Event.weapon and type(Event.weapon) == "table" and Event.weapon.isExist and Event.weapon:isExist() then
|
||||
Event.Weapon = Event.weapon
|
||||
Event.WeaponName = Event.weapon:isExist() and Event.weapon:getTypeName() or "Unknown Weapon"
|
||||
Event.WeaponUNIT = CLIENT:Find( Event.Weapon, '', true ) -- Sometimes, the weapon is a player unit!
|
||||
@@ -1425,6 +1528,15 @@ function EVENT:onEvent( Event )
|
||||
Event.Cargo = Event.cargo
|
||||
Event.CargoName = Event.cargo.Name
|
||||
end
|
||||
|
||||
-- Dynamic cargo Object
|
||||
if Event.dynamiccargo then
|
||||
Event.IniDynamicCargo = Event.dynamiccargo
|
||||
Event.IniDynamicCargoName = Event.IniDynamicCargo.StaticName
|
||||
if Event.IniDynamicCargo.Owner or Event.IniUnitName then
|
||||
Event.IniPlayerName = Event.IniDynamicCargo.Owner or string.match(Event.IniUnitName or "None|00:00|PKG00","^(.+)|%d%d:%d%d|PKG%d+")
|
||||
end
|
||||
end
|
||||
|
||||
-- Zone object.
|
||||
if Event.zone then
|
||||
|
||||
@@ -948,7 +948,8 @@ do -- FSM
|
||||
end
|
||||
|
||||
do -- FSM_CONTROLLABLE
|
||||
|
||||
|
||||
---
|
||||
-- @type FSM_CONTROLLABLE
|
||||
-- @field Wrapper.Controllable#CONTROLLABLE Controllable
|
||||
-- @extends Core.Fsm#FSM
|
||||
@@ -1081,7 +1082,8 @@ do -- FSM_CONTROLLABLE
|
||||
end
|
||||
|
||||
do -- FSM_PROCESS
|
||||
|
||||
|
||||
---
|
||||
-- @type FSM_PROCESS
|
||||
-- @field Tasking.Task#TASK Task
|
||||
-- @extends Core.Fsm#FSM_CONTROLLABLE
|
||||
|
||||
@@ -108,26 +108,30 @@ function MARKEROPS_BASE:New(Tagname,Keywords,Casesensitive)
|
||||
--- 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 #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.
|
||||
-- @param #number MarkerID Id of this marker
|
||||
-- @param #number CoalitionNumber Coalition of the marker creator
|
||||
-- @param #number MarkerID Id of this marker.
|
||||
-- @param #number CoalitionNumber Coalition of the marker creator.
|
||||
-- @param #string PlayerName Name of the player creating/changing the mark. nil if it cannot be obtained.
|
||||
-- @param Core.Event#EVENTDATA EventData the event data table.
|
||||
|
||||
--- 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 #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.
|
||||
-- @param #number MarkerID Id of this marker
|
||||
-- @param #number CoalitionNumber Coalition of the marker creator
|
||||
-- @param #number MarkerID Id of this marker.
|
||||
-- @param #number CoalitionNumber Coalition of the marker creator.
|
||||
-- @param #string PlayerName Name of the player creating/changing the mark. nil if it cannot be obtained.
|
||||
-- @param Core.Event#EVENTDATA EventData the event data table
|
||||
|
||||
--- On after "MarkDeleted" event. Triggered when a Marker is deleted from the F10 map.
|
||||
-- @function [parent=#MARKEROPS_BASE] OnAfterMarkDeleted
|
||||
@@ -167,7 +171,7 @@ function MARKEROPS_BASE:OnEventMark(Event)
|
||||
if Eventtext~=nil then
|
||||
if self:_MatchTag(Eventtext) then
|
||||
local matchtable = self:_MatchKeywords(Eventtext)
|
||||
self:MarkAdded(Eventtext,matchtable,coord,Event.idx,coalition)
|
||||
self:MarkAdded(Eventtext,matchtable,coord,Event.idx,coalition,Event.PlayerName,Event)
|
||||
end
|
||||
end
|
||||
elseif Event.id==world.event.S_EVENT_MARK_CHANGE then
|
||||
@@ -177,7 +181,7 @@ function MARKEROPS_BASE:OnEventMark(Event)
|
||||
if Eventtext~=nil then
|
||||
if self:_MatchTag(Eventtext) then
|
||||
local matchtable = self:_MatchKeywords(Eventtext)
|
||||
self:MarkChanged(Eventtext,matchtable,coord,Event.idx,coalition)
|
||||
self:MarkChanged(Eventtext,matchtable,coord,Event.idx,coalition,Event.PlayerName,Event)
|
||||
end
|
||||
end
|
||||
elseif Event.id==world.event.S_EVENT_MARK_REMOVED then
|
||||
|
||||
@@ -105,6 +105,7 @@ 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 )
|
||||
@@ -118,9 +119,11 @@ 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
|
||||
@@ -128,9 +131,11 @@ 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
|
||||
@@ -138,19 +143,24 @@ end
|
||||
function MENU_INDEX:HasGroupMenu( Group, Path )
|
||||
if Group and Group:IsAlive() then
|
||||
local MenuGroupName = Group:GetName()
|
||||
return self.Group[MenuGroupName].Menus[Path]
|
||||
if self.Group[MenuGroupName] and self.Group[MenuGroupName].Menus and self.Group[MenuGroupName].Menus[Path] then
|
||||
return self.Group[MenuGroupName].Menus[Path]
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
function MENU_INDEX:SetGroupMenu( Group, Path, Menu )
|
||||
local MenuGroupName = Group:GetName()
|
||||
Group:F({MenuGroupName=MenuGroupName,Path=Path})
|
||||
--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()
|
||||
|
||||
@@ -75,35 +75,37 @@ MESSAGE.Type = {
|
||||
|
||||
--- Creates a new MESSAGE object. Note that these MESSAGE objects are not yet displayed on the display panel. You must use the functions @{#MESSAGE.ToClient} or @{#MESSAGE.ToCoalition} or @{#MESSAGE.ToAll} to send these Messages to the respective recipients.
|
||||
-- @param self
|
||||
-- @param #string MessageText is the text of the Message.
|
||||
-- @param #number MessageDuration is a number in seconds of how long the MESSAGE should be shown on the display panel.
|
||||
-- @param #string MessageCategory (optional) is a string expressing the "category" of the Message. The category will be shown as the first text in the message followed by a ": ".
|
||||
-- @param #string Text is the text of the Message.
|
||||
-- @param #number Duration Duration in seconds how long the message text is shown.
|
||||
-- @param #string Category (Optional) String expressing the "category" of the Message. The category will be shown as the first text in the message followed by a ": ".
|
||||
-- @param #boolean ClearScreen (optional) Clear all previous messages if true.
|
||||
-- @return #MESSAGE
|
||||
-- @return #MESSAGE self
|
||||
-- @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".
|
||||
-- -- 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 )
|
||||
function MESSAGE:New( Text, Duration, Category, ClearScreen )
|
||||
|
||||
local self = BASE:Inherit( self, BASE:New() )
|
||||
self:F( { MessageText, MessageDuration, MessageCategory } )
|
||||
|
||||
self:F( { Text, Duration, Category } )
|
||||
|
||||
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
|
||||
self.MessageCategory = MessageCategory .. ": "
|
||||
if Category and Category ~= "" then
|
||||
if Category:sub( -1 ) ~= "\n" then
|
||||
self.MessageCategory = Category .. ": "
|
||||
else
|
||||
self.MessageCategory = MessageCategory:sub( 1, -2 ) .. ":\n"
|
||||
self.MessageCategory = Category:sub( 1, -2 ) .. ":\n"
|
||||
end
|
||||
else
|
||||
self.MessageCategory = ""
|
||||
@@ -114,9 +116,9 @@ function MESSAGE:New( MessageText, MessageDuration, MessageCategory, ClearScreen
|
||||
self.ClearScreen = ClearScreen
|
||||
end
|
||||
|
||||
self.MessageDuration = MessageDuration or 5
|
||||
self.MessageDuration = Duration or 5
|
||||
self.MessageTime = timer.getTime()
|
||||
self.MessageText = MessageText:gsub( "^\n", "", 1 ):gsub( "\n$", "", 1 )
|
||||
self.MessageText = Text:gsub( "^\n", "", 1 ):gsub( "\n$", "", 1 )
|
||||
|
||||
self.MessageSent = false
|
||||
self.MessageGroup = false
|
||||
@@ -462,6 +464,7 @@ _MESSAGESRS = {}
|
||||
-- @param #number Volume (optional) Volume, can be between 0.0 and 1.0 (loudest).
|
||||
-- @param #string Label (optional) Label, defaults to "MESSAGE" or the Message Category set.
|
||||
-- @param Core.Point#COORDINATE Coordinate (optional) Coordinate this messages originates from.
|
||||
-- @param #string Backend (optional) Backend to be used, can be MSRS.Backend.SRSEXE or MSRS.Backend.GRPC
|
||||
-- @usage
|
||||
-- -- Mind the dot here, not using the colon this time around!
|
||||
-- -- Needed once only
|
||||
@@ -469,7 +472,7 @@ _MESSAGESRS = {}
|
||||
-- -- later on in your code
|
||||
-- MESSAGE:New("Test message!",15,"SPAWN"):ToSRS()
|
||||
--
|
||||
function MESSAGE.SetMSRS(PathToSRS,Port,PathToCredentials,Frequency,Modulation,Gender,Culture,Voice,Coalition,Volume,Label,Coordinate)
|
||||
function MESSAGE.SetMSRS(PathToSRS,Port,PathToCredentials,Frequency,Modulation,Gender,Culture,Voice,Coalition,Volume,Label,Coordinate,Backend)
|
||||
|
||||
_MESSAGESRS.PathToSRS = PathToSRS or MSRS.path or "C:\\Program Files\\DCS-SimpleRadio-Standalone"
|
||||
|
||||
@@ -487,6 +490,10 @@ function MESSAGE.SetMSRS(PathToSRS,Port,PathToCredentials,Frequency,Modulation,G
|
||||
_MESSAGESRS.MSRS:SetCoordinate(Coordinate)
|
||||
end
|
||||
|
||||
if Backend then
|
||||
_MESSAGESRS.MSRS:SetBackend(Backend)
|
||||
end
|
||||
|
||||
_MESSAGESRS.Culture = Culture or MSRS.culture or "en-GB"
|
||||
_MESSAGESRS.MSRS:SetCulture(Culture)
|
||||
|
||||
|
||||
@@ -110,7 +110,7 @@ do -- COORDINATE
|
||||
--
|
||||
-- ## 4.4) Get the North correction of the current location.
|
||||
--
|
||||
-- * @{#COORDINATE.GetNorthCorrection}(): Obtains the north correction at the current 3D point.
|
||||
-- * @{#COORDINATE.GetNorthCorrectionRadians}(): Obtains the north correction at the current 3D point.
|
||||
--
|
||||
-- ## 4.5) Point Randomization
|
||||
--
|
||||
@@ -968,8 +968,13 @@ do -- COORDINATE
|
||||
-- @return DCS#Distance Distance The distance in meters.
|
||||
function COORDINATE:Get2DDistance(TargetCoordinate)
|
||||
if not TargetCoordinate then return 1000000 end
|
||||
local a={x=TargetCoordinate.x-self.x, y=0, z=TargetCoordinate.z-self.z}
|
||||
local norm=UTILS.VecNorm(a)
|
||||
--local a={x=TargetCoordinate.x-self.x, y=0, z=TargetCoordinate.z-self.z}
|
||||
local a = self:GetVec2()
|
||||
if not TargetCoordinate.ClassName then
|
||||
TargetCoordinate=COORDINATE:NewFromVec3(TargetCoordinate)
|
||||
end
|
||||
local b = TargetCoordinate:GetVec2()
|
||||
local norm=UTILS.VecDist2D(a,b)
|
||||
return norm
|
||||
end
|
||||
|
||||
@@ -1216,7 +1221,7 @@ do -- COORDINATE
|
||||
local s = string.format( '%03d°', AngleDegrees )
|
||||
|
||||
if MagVar then
|
||||
local variation = UTILS.GetMagneticDeclination() or 0
|
||||
local variation = self:GetMagneticDeclination() or 0
|
||||
local AngleMagnetic = AngleDegrees - variation
|
||||
|
||||
if AngleMagnetic < 0 then AngleMagnetic = 360-AngleMagnetic end
|
||||
@@ -1329,13 +1334,16 @@ do -- COORDINATE
|
||||
-- @param Core.Settings#SETTINGS Settings
|
||||
-- @param #string Language (Optional) Language "en" or "ru"
|
||||
-- @param #boolean MagVar If true, also state angle in magnetic
|
||||
-- @param #number Precision Rounding precision, defaults to 0
|
||||
-- @return #string The BR Text
|
||||
function COORDINATE:GetBRText( AngleRadians, Distance, Settings, Language, MagVar )
|
||||
function COORDINATE:GetBRText( AngleRadians, Distance, Settings, Language, MagVar, Precision )
|
||||
|
||||
local Settings = Settings or _SETTINGS -- Core.Settings#SETTINGS
|
||||
|
||||
|
||||
Precision = Precision or 0
|
||||
|
||||
local BearingText = self:GetBearingText( AngleRadians, 0, Settings, MagVar )
|
||||
local DistanceText = self:GetDistanceText( Distance, Settings, Language, 0 )
|
||||
local DistanceText = self:GetDistanceText( Distance, Settings, Language, Precision )
|
||||
|
||||
local BRText = BearingText .. DistanceText
|
||||
|
||||
@@ -1957,9 +1965,18 @@ do -- COORDINATE
|
||||
--- Smokes the point in a color.
|
||||
-- @param #COORDINATE self
|
||||
-- @param Utilities.Utils#SMOKECOLOR SmokeColor
|
||||
function COORDINATE:Smoke( SmokeColor )
|
||||
-- @param #string name (Optional) Name if you want to stop the smoke early (normal duration: 5mins)
|
||||
function COORDINATE:Smoke( SmokeColor, name )
|
||||
self:F2( { SmokeColor } )
|
||||
trigger.action.smoke( self:GetVec3(), SmokeColor )
|
||||
self.firename = name or "Smoke-"..math.random(1,100000)
|
||||
trigger.action.smoke( self:GetVec3(), SmokeColor, self.firename )
|
||||
end
|
||||
|
||||
--- Stops smoking the point in a color.
|
||||
-- @param #COORDINATE self
|
||||
-- @param #string name (Optional) Name if you want to stop the smoke early (normal duration: 5mins)
|
||||
function COORDINATE:StopSmoke( name )
|
||||
self:StopBigSmokeAndFire( name )
|
||||
end
|
||||
|
||||
--- Smoke the COORDINATE Green.
|
||||
@@ -2410,7 +2427,7 @@ do -- COORDINATE
|
||||
for i,coord in ipairs(Coordinates) do
|
||||
vecs[i+1]=coord:GetVec3()
|
||||
end
|
||||
|
||||
|
||||
if #vecs<3 then
|
||||
self:E("ERROR: A free form polygon needs at least three points!")
|
||||
elseif #vecs==3 then
|
||||
@@ -2669,9 +2686,9 @@ do -- COORDINATE
|
||||
local date=UTILS.GetDCSMissionDate()
|
||||
|
||||
-- Debug output.
|
||||
--self:I(string.format("Sun rise at lat=%.3f long=%.3f on %s (DayOfYear=%d): %s (%d sec of the day) (GMT %d)", Latitude, Longitude, date, DayOfYear, tostring(UTILS.SecondsToClock(sunrise)), sunrise, Tdiff))
|
||||
--self:I(string.format("Sun rise at lat=%.3f long=%.3f on %s (DayOfYear=%d): %s (%s sec of the day) (GMT %d)", Latitude, Longitude, date, DayOfYear, tostring(UTILS.SecondsToClock(sunrise)), tonumber(sunrise) or "0", Tdiff))
|
||||
|
||||
if InSeconds then
|
||||
if InSeconds or type(sunrise) == "string" then
|
||||
return sunrise
|
||||
else
|
||||
return UTILS.SecondsToClock(sunrise, true)
|
||||
@@ -2837,9 +2854,9 @@ do -- COORDINATE
|
||||
local date=UTILS.GetDCSMissionDate()
|
||||
|
||||
-- Debug output.
|
||||
--self:I(string.format("Sun set at lat=%.3f long=%.3f on %s (DayOfYear=%d): %s (%d sec of the day) (GMT %d)", Latitude, Longitude, date, DayOfYear, tostring(UTILS.SecondsToClock(sunrise)), sunrise, Tdiff))
|
||||
--self:I(string.format("Sun set at lat=%.3f long=%.3f on %s (DayOfYear=%d): %s (%s sec of the day) (GMT %d)", Latitude, Longitude, date, DayOfYear, tostring(UTILS.SecondsToClock(sunrise)), tostring(sunrise) or "0", Tdiff))
|
||||
|
||||
if InSeconds then
|
||||
if InSeconds or type(sunrise) == "string" then
|
||||
return sunrise
|
||||
else
|
||||
return UTILS.SecondsToClock(sunrise, true)
|
||||
@@ -2900,12 +2917,13 @@ do -- COORDINATE
|
||||
-- @param #COORDINATE FromCoordinate The coordinate to measure the distance and the bearing from.
|
||||
-- @param Core.Settings#SETTINGS Settings (optional) The settings. Can be nil, and in this case the default settings are used. If you want to specify your own settings, use the _SETTINGS object.
|
||||
-- @param #boolean MagVar If true, also get angle in MagVar for BR/BRA
|
||||
-- @param #number Precision Rounding precision, currently full km as default (=0)
|
||||
-- @return #string The BR text.
|
||||
function COORDINATE:ToStringBR( FromCoordinate, Settings, MagVar )
|
||||
function COORDINATE:ToStringBR( FromCoordinate, Settings, MagVar, Precision )
|
||||
local DirectionVec3 = FromCoordinate:GetDirectionVec3( self )
|
||||
local AngleRadians = self:GetAngleRadians( DirectionVec3 )
|
||||
local Distance = self:Get2DDistance( FromCoordinate )
|
||||
return "BR, " .. self:GetBRText( AngleRadians, Distance, Settings, nil, MagVar )
|
||||
return "BR, " .. self:GetBRText( AngleRadians, Distance, Settings, nil, MagVar, Precision )
|
||||
end
|
||||
|
||||
--- Return a BRA string from a COORDINATE to the COORDINATE.
|
||||
@@ -2941,6 +2959,8 @@ do -- COORDINATE
|
||||
local AngleRadians = self:GetAngleRadians( DirectionVec3 )
|
||||
|
||||
local bearing = UTILS.Round( UTILS.ToDegree( AngleRadians ),0 )
|
||||
local magnetic = self:GetMagneticDeclination() or 0
|
||||
bearing = bearing - magnetic
|
||||
|
||||
local rangeMetres = self:Get2DDistance(currentCoord)
|
||||
local rangeNM = UTILS.Round( UTILS.MetersToNM(rangeMetres), 0)
|
||||
@@ -3313,16 +3333,16 @@ do -- COORDINATE
|
||||
-- @param #COORDINATE self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The controllable to retrieve the settings from, otherwise the default settings will be chosen.
|
||||
-- @param Core.Settings#SETTINGS Settings (optional) The settings. Can be nil, and in this case the default settings are used. If you want to specify your own settings, use the _SETTINGS object.
|
||||
-- @param Tasking.Task#TASK Task The task for which coordinates need to be calculated.
|
||||
-- @return #string The coordinate Text in the configured coordinate system.
|
||||
function COORDINATE:ToString( Controllable, Settings, Task )
|
||||
function COORDINATE:ToString( Controllable, Settings )
|
||||
|
||||
-- self:E( { Controllable = Controllable and Controllable:GetName() } )
|
||||
|
||||
local Settings = Settings or ( Controllable and _DATABASE:GetPlayerSettings( Controllable:GetPlayerName() ) ) or _SETTINGS
|
||||
|
||||
local ModeA2A = nil
|
||||
|
||||
|
||||
--[[
|
||||
if Task then
|
||||
if Task:IsInstanceOf( TASK_A2A ) then
|
||||
ModeA2A = true
|
||||
@@ -3339,7 +3359,7 @@ do -- COORDINATE
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--]]
|
||||
|
||||
if ModeA2A == nil then
|
||||
local IsAir = Controllable and ( Controllable:IsAirPlane() or Controllable:IsHelicopter() ) or false
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -494,7 +494,7 @@ do -- SETTINGS
|
||||
return (self.A2ASystem and self.A2ASystem == "MGRS") or (not self.A2ASystem and _SETTINGS:IsA2A_MGRS())
|
||||
end
|
||||
|
||||
-- @param #SETTINGS self
|
||||
--- @param #SETTINGS self
|
||||
-- @param Wrapper.Group#GROUP MenuGroup Group for which to add menus.
|
||||
-- @param #table RootMenu Root menu table
|
||||
-- @return #SETTINGS
|
||||
@@ -948,49 +948,49 @@ do -- SETTINGS
|
||||
return self
|
||||
end
|
||||
|
||||
-- @param #SETTINGS self
|
||||
--- @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()
|
||||
self:SetSystemMenu( MenuGroup, RootMenu )
|
||||
end
|
||||
|
||||
-- @param #SETTINGS self
|
||||
--- @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()
|
||||
self:SetSystemMenu( MenuGroup, RootMenu )
|
||||
end
|
||||
|
||||
-- @param #SETTINGS self
|
||||
--- @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()
|
||||
self:SetSystemMenu( MenuGroup, RootMenu )
|
||||
end
|
||||
|
||||
-- @param #SETTINGS self
|
||||
--- @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()
|
||||
self:SetSystemMenu( MenuGroup, RootMenu )
|
||||
end
|
||||
|
||||
-- @param #SETTINGS self
|
||||
--- @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()
|
||||
self:SetSystemMenu( MenuGroup, RootMenu )
|
||||
end
|
||||
|
||||
-- @param #SETTINGS self
|
||||
--- @param #SETTINGS self
|
||||
function SETTINGS:MenuMessageTimingsSystem( MenuGroup, RootMenu, MessageType, MessageTime )
|
||||
self:SetMessageTime( MessageType, MessageTime )
|
||||
MESSAGE:New( string.format( "Settings: Default message time set for %s to %d.", MessageType, MessageTime ), 5 ):ToAll()
|
||||
end
|
||||
|
||||
do
|
||||
-- @param #SETTINGS self
|
||||
--- @param #SETTINGS self
|
||||
function SETTINGS:MenuGroupA2GSystem( PlayerUnit, PlayerGroup, PlayerName, A2GSystem )
|
||||
--BASE:E( {PlayerUnit:GetName(), A2GSystem } )
|
||||
self.A2GSystem = A2GSystem
|
||||
@@ -1001,7 +1001,7 @@ do -- SETTINGS
|
||||
end
|
||||
end
|
||||
|
||||
-- @param #SETTINGS self
|
||||
--- @param #SETTINGS self
|
||||
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 )
|
||||
@@ -1011,7 +1011,7 @@ do -- SETTINGS
|
||||
end
|
||||
end
|
||||
|
||||
-- @param #SETTINGS self
|
||||
--- @param #SETTINGS self
|
||||
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 )
|
||||
@@ -1021,7 +1021,7 @@ do -- SETTINGS
|
||||
end
|
||||
end
|
||||
|
||||
-- @param #SETTINGS self
|
||||
--- @param #SETTINGS self
|
||||
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 )
|
||||
@@ -1031,7 +1031,7 @@ do -- SETTINGS
|
||||
end
|
||||
end
|
||||
|
||||
-- @param #SETTINGS self
|
||||
--- @param #SETTINGS self
|
||||
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 )
|
||||
@@ -1041,7 +1041,7 @@ do -- SETTINGS
|
||||
end
|
||||
end
|
||||
|
||||
-- @param #SETTINGS self
|
||||
--- @param #SETTINGS self
|
||||
function SETTINGS:MenuGroupMessageTimingsSystem( PlayerUnit, PlayerGroup, PlayerName, MessageType, MessageTime )
|
||||
self:SetMessageTime( MessageType, MessageTime )
|
||||
MESSAGE:New( string.format( "Settings: Default message time set for %s to %d.", MessageType, MessageTime ), 5 ):ToGroup( PlayerGroup )
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -535,12 +535,6 @@ function SPAWNSTATIC:_SpawnStatic(Template, CountryID)
|
||||
-- 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 --DCS#StaticObject
|
||||
|
||||
@@ -577,12 +571,28 @@ function SPAWNSTATIC:_SpawnStatic(Template, CountryID)
|
||||
self:T("Spawning Static")
|
||||
self:T2({Template=Template})
|
||||
Static=coalition.addStaticObject(CountryID, Template)
|
||||
|
||||
if Static then
|
||||
self:T(string.format("Succesfully spawned static object \"%s\" ID=%d", Static:getName(), Static:getID()))
|
||||
--[[
|
||||
local static=StaticObject.getByName(Static:getName())
|
||||
if static then
|
||||
env.info(string.format("FF got static from StaticObject.getByName"))
|
||||
else
|
||||
env.error(string.format("FF error did NOT get static from StaticObject.getByName"))
|
||||
end ]]
|
||||
else
|
||||
self:E(string.format("ERROR: DCS static object \"%s\" is nil!", tostring(Template.name)))
|
||||
end
|
||||
end
|
||||
|
||||
-- Add and register the new static.
|
||||
local mystatic=_DATABASE:AddStatic(Template.name)
|
||||
|
||||
-- If there is a SpawnFunction hook defined, call it.
|
||||
if self.SpawnFunctionHook then
|
||||
-- delay calling this for .3 seconds so that it hopefully comes after the BIRTH event of the group.
|
||||
self:ScheduleOnce(0.3,self.SpawnFunctionHook,mystatic, unpack(self.SpawnFunctionArguments))
|
||||
self:ScheduleOnce(0.3, self.SpawnFunctionHook, mystatic, unpack(self.SpawnFunctionArguments))
|
||||
end
|
||||
|
||||
return mystatic
|
||||
|
||||
@@ -144,7 +144,7 @@ ZONE_BASE = {
|
||||
-- @return #ZONE_BASE self
|
||||
function ZONE_BASE:New( ZoneName )
|
||||
local self = BASE:Inherit( self, FSM:New() )
|
||||
self:F( ZoneName )
|
||||
--self:F( ZoneName )
|
||||
|
||||
self.ZoneName = ZoneName
|
||||
|
||||
@@ -157,7 +157,7 @@ end
|
||||
-- @param #ZONE_BASE self
|
||||
-- @return #string The name of the zone.
|
||||
function ZONE_BASE:GetName()
|
||||
self:F2()
|
||||
--self:F2()
|
||||
|
||||
return self.ZoneName
|
||||
end
|
||||
@@ -167,7 +167,7 @@ end
|
||||
-- @param #string ZoneName The name of the zone.
|
||||
-- @return #ZONE_BASE
|
||||
function ZONE_BASE:SetName( ZoneName )
|
||||
self:F2()
|
||||
--self:F2()
|
||||
|
||||
self.ZoneName = ZoneName
|
||||
end
|
||||
@@ -177,7 +177,7 @@ end
|
||||
-- @param DCS#Vec2 Vec2 The Vec2 to test.
|
||||
-- @return #boolean true if the Vec2 is within the zone.
|
||||
function ZONE_BASE:IsVec2InZone( Vec2 )
|
||||
self:F2( Vec2 )
|
||||
--self:F2( Vec2 )
|
||||
|
||||
return false
|
||||
end
|
||||
@@ -232,13 +232,13 @@ end
|
||||
-- @param DCS#Distance Height The height to add to the land height where the center of the zone is located.
|
||||
-- @return Core.Point#POINT_VEC2 The PointVec2 of the zone.
|
||||
function ZONE_BASE:GetPointVec2()
|
||||
self:F2( self.ZoneName )
|
||||
--self:F2( self.ZoneName )
|
||||
|
||||
local Vec2 = self:GetVec2()
|
||||
|
||||
local PointVec2 = POINT_VEC2:NewFromVec2( Vec2 )
|
||||
|
||||
self:T2( { PointVec2 } )
|
||||
--self:T2( { PointVec2 } )
|
||||
|
||||
return PointVec2
|
||||
end
|
||||
@@ -248,7 +248,7 @@ end
|
||||
-- @param DCS#Distance Height The height to add to the land height where the center of the zone is located.
|
||||
-- @return DCS#Vec3 The Vec3 of the zone.
|
||||
function ZONE_BASE:GetVec3( Height )
|
||||
self:F2( self.ZoneName )
|
||||
--self:F2( self.ZoneName )
|
||||
|
||||
Height = Height or 0
|
||||
|
||||
@@ -256,7 +256,7 @@ function ZONE_BASE:GetVec3( Height )
|
||||
|
||||
local Vec3 = { x = Vec2.x, y = Height and Height or land.getHeight( self:GetVec2() ), z = Vec2.y }
|
||||
|
||||
self:T2( { Vec3 } )
|
||||
--self:T2( { Vec3 } )
|
||||
|
||||
return Vec3
|
||||
end
|
||||
@@ -266,13 +266,13 @@ end
|
||||
-- @param DCS#Distance Height The height to add to the land height where the center of the zone is located.
|
||||
-- @return Core.Point#POINT_VEC3 The PointVec3 of the zone.
|
||||
function ZONE_BASE:GetPointVec3( Height )
|
||||
self:F2( self.ZoneName )
|
||||
--self:F2( self.ZoneName )
|
||||
|
||||
local Vec3 = self:GetVec3( Height )
|
||||
|
||||
local PointVec3 = POINT_VEC3:NewFromVec3( Vec3 )
|
||||
|
||||
self:T2( { PointVec3 } )
|
||||
--self:T2( { PointVec3 } )
|
||||
|
||||
return PointVec3
|
||||
end
|
||||
@@ -282,7 +282,7 @@ end
|
||||
-- @param DCS#Distance Height The height to add to the land height where the center of the zone is located.
|
||||
-- @return Core.Point#COORDINATE The Coordinate of the zone.
|
||||
function ZONE_BASE:GetCoordinate( Height ) --R2.1
|
||||
self:F2(self.ZoneName)
|
||||
--self:F2(self.ZoneName)
|
||||
|
||||
local Vec3 = self:GetVec3( Height )
|
||||
|
||||
@@ -363,7 +363,7 @@ end
|
||||
--- Bound the zone boundaries with a tires.
|
||||
-- @param #ZONE_BASE self
|
||||
function ZONE_BASE:BoundZone()
|
||||
self:F2()
|
||||
--self:F2()
|
||||
end
|
||||
|
||||
--- Set draw coalition of zone.
|
||||
@@ -510,7 +510,7 @@ end
|
||||
-- @param #ZONE_BASE self
|
||||
-- @param Utilities.Utils#SMOKECOLOR SmokeColor The smoke color.
|
||||
function ZONE_BASE:SmokeZone( SmokeColor )
|
||||
self:F2( SmokeColor )
|
||||
--self:F2( SmokeColor )
|
||||
|
||||
end
|
||||
|
||||
@@ -519,7 +519,7 @@ end
|
||||
-- @param #number ZoneProbability A value between 0 and 1. 0 = 0% and 1 = 100% probability.
|
||||
-- @return #ZONE_BASE self
|
||||
function ZONE_BASE:SetZoneProbability( ZoneProbability )
|
||||
self:F( { self:GetName(), ZoneProbability = ZoneProbability } )
|
||||
--self:F( { self:GetName(), ZoneProbability = ZoneProbability } )
|
||||
|
||||
self.ZoneProbability = ZoneProbability or 1
|
||||
return self
|
||||
@@ -529,7 +529,7 @@ end
|
||||
-- @param #ZONE_BASE self
|
||||
-- @return #number A value between 0 and 1. 0 = 0% and 1 = 100% probability.
|
||||
function ZONE_BASE:GetZoneProbability()
|
||||
self:F2()
|
||||
--self:F2()
|
||||
|
||||
return self.ZoneProbability
|
||||
end
|
||||
@@ -560,7 +560,7 @@ end
|
||||
-- -- The result should be that Zone1 would be more probable selected than Zone2.
|
||||
--
|
||||
function ZONE_BASE:GetZoneMaybe()
|
||||
self:F2()
|
||||
--self:F2()
|
||||
|
||||
local Randomization = math.random()
|
||||
if Randomization <= self.ZoneProbability then
|
||||
@@ -605,10 +605,13 @@ function ZONE_BASE:Trigger(Objects)
|
||||
self:AddTransition("TriggerStopped","TriggerStart","TriggerRunning")
|
||||
self:AddTransition("*","EnteredZone","*")
|
||||
self:AddTransition("*","LeftZone","*")
|
||||
self:AddTransition("*","ZoneEmpty","*")
|
||||
self:AddTransition("*","ObjectDead","*")
|
||||
self:AddTransition("*","TriggerRunCheck","*")
|
||||
self:AddTransition("*","TriggerStop","TriggerStopped")
|
||||
self:TriggerStart()
|
||||
self.checkobjects = Objects
|
||||
self.ObjectsInZone = false
|
||||
if UTILS.IsInstanceOf(Objects,"SET_BASE") then
|
||||
self.objectset = Objects.Set
|
||||
else
|
||||
@@ -646,6 +649,22 @@ function ZONE_BASE:Trigger(Objects)
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The controllable leaving the zone.
|
||||
|
||||
--- On After "ObjectDead" event. An observed object has left the zone.
|
||||
-- @function [parent=#ZONE_BASE] OnAfterObjectDead
|
||||
-- @param #ZONE_BASE self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The controllable which died. Might be nil.
|
||||
|
||||
--- On After "ZoneEmpty" event. All observed objects have left the zone or are dead.
|
||||
-- @function [parent=#ZONE_BASE] OnAfterZoneEmpty
|
||||
-- @param #ZONE_BASE self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
|
||||
end
|
||||
|
||||
--- (Internal) Check the assigned objects for being in/out of the zone
|
||||
@@ -659,9 +678,13 @@ function ZONE_BASE:_TriggerCheck(fromstart)
|
||||
-- just earmark everyone in/out
|
||||
for _,_object in pairs(objectset) do
|
||||
local obj = _object -- Wrapper.Controllable#CONTROLLABLE
|
||||
if not obj.TriggerInZone then obj.TriggerInZone = {} end
|
||||
if not obj.TriggerInZone then
|
||||
obj.TriggerInZone = {}
|
||||
obj.TriggerZoneDeadNotification = false
|
||||
end
|
||||
if obj and obj:IsAlive() and self:IsCoordinateInZone(obj:GetCoordinate()) then
|
||||
obj.TriggerInZone[self.ZoneName] = true
|
||||
self.ObjectsInZone = true
|
||||
else
|
||||
obj.TriggerInZone[self.ZoneName] = false
|
||||
end
|
||||
@@ -669,6 +692,7 @@ function ZONE_BASE:_TriggerCheck(fromstart)
|
||||
end
|
||||
else
|
||||
-- Check for changes
|
||||
local objcount = 0
|
||||
for _,_object in pairs(objectset) do
|
||||
local obj = _object -- Wrapper.Controllable#CONTROLLABLE
|
||||
if obj and obj:IsAlive() then
|
||||
@@ -683,11 +707,20 @@ function ZONE_BASE:_TriggerCheck(fromstart)
|
||||
-- is obj in zone?
|
||||
local inzone = self:IsCoordinateInZone(obj:GetCoordinate())
|
||||
--self:I("Object "..obj:GetName().." is in zone: "..tostring(inzone))
|
||||
if inzone and obj.TriggerInZone[self.ZoneName] then
|
||||
-- just count
|
||||
objcount = objcount + 1
|
||||
self.ObjectsInZone = true
|
||||
obj.TriggerZoneDeadNotification = false
|
||||
end
|
||||
if inzone and not obj.TriggerInZone[self.ZoneName] then
|
||||
-- wasn't in zone before
|
||||
--self:I("Newly entered")
|
||||
self:__EnteredZone(0.5,obj)
|
||||
obj.TriggerInZone[self.ZoneName] = true
|
||||
objcount = objcount + 1
|
||||
self.ObjectsInZone = true
|
||||
obj.TriggerZoneDeadNotification = false
|
||||
elseif (not inzone) and obj.TriggerInZone[self.ZoneName] then
|
||||
-- has left the zone
|
||||
--self:I("Newly left")
|
||||
@@ -696,8 +729,21 @@ function ZONE_BASE:_TriggerCheck(fromstart)
|
||||
else
|
||||
--self:I("Not left or not entered, or something went wrong!")
|
||||
end
|
||||
else
|
||||
-- object dead
|
||||
if not obj.TriggerZoneDeadNotification == true then
|
||||
obj.TriggerInZone = nil
|
||||
self:__ObjectDead(0.5,obj)
|
||||
obj.TriggerZoneDeadNotification = true
|
||||
end
|
||||
end
|
||||
end
|
||||
-- zone empty?
|
||||
if objcount == 0 and self.ObjectsInZone == true then
|
||||
-- zone was not but is now empty
|
||||
self.ObjectsInZone = false
|
||||
self:__ZoneEmpty(0.5)
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
@@ -791,7 +837,7 @@ function ZONE_RADIUS:New( ZoneName, Vec2, Radius, DoNotRegisterZone )
|
||||
|
||||
-- Inherit ZONE_BASE.
|
||||
local self = BASE:Inherit( self, ZONE_BASE:New( ZoneName ) ) -- #ZONE_RADIUS
|
||||
self:F( { ZoneName, Vec2, Radius } )
|
||||
--self:F( { ZoneName, Vec2, Radius } )
|
||||
|
||||
self.Radius = Radius
|
||||
self.Vec2 = Vec2
|
||||
@@ -947,7 +993,7 @@ end
|
||||
-- @param #number AddOffSet (optional) The angle to be added for the smoking start position.
|
||||
-- @return #ZONE_RADIUS self
|
||||
function ZONE_RADIUS:SmokeZone( SmokeColor, Points, AddHeight, AngleOffset )
|
||||
self:F2( SmokeColor )
|
||||
--self:F2( SmokeColor )
|
||||
|
||||
local Point = {}
|
||||
local Vec2 = self:GetVec2()
|
||||
@@ -978,7 +1024,7 @@ end
|
||||
-- @param #number AddHeight (optional) The height to be added for the smoke.
|
||||
-- @return #ZONE_RADIUS self
|
||||
function ZONE_RADIUS:FlareZone( FlareColor, Points, Azimuth, AddHeight )
|
||||
self:F2( { FlareColor, Azimuth } )
|
||||
--self:F2( { FlareColor, Azimuth } )
|
||||
|
||||
local Point = {}
|
||||
local Vec2 = self:GetVec2()
|
||||
@@ -1004,9 +1050,9 @@ end
|
||||
-- @param #ZONE_RADIUS self
|
||||
-- @return DCS#Distance The radius of the zone.
|
||||
function ZONE_RADIUS:GetRadius()
|
||||
self:F2( self.ZoneName )
|
||||
--self:F2( self.ZoneName )
|
||||
|
||||
self:T2( { self.Radius } )
|
||||
--self:T2( { self.Radius } )
|
||||
|
||||
return self.Radius
|
||||
end
|
||||
@@ -1016,10 +1062,10 @@ end
|
||||
-- @param DCS#Distance Radius The radius of the zone.
|
||||
-- @return DCS#Distance The radius of the zone.
|
||||
function ZONE_RADIUS:SetRadius( Radius )
|
||||
self:F2( self.ZoneName )
|
||||
--self:F2( self.ZoneName )
|
||||
|
||||
self.Radius = Radius
|
||||
self:T2( { self.Radius } )
|
||||
--self:T2( { self.Radius } )
|
||||
|
||||
return self.Radius
|
||||
end
|
||||
@@ -1028,9 +1074,9 @@ end
|
||||
-- @param #ZONE_RADIUS self
|
||||
-- @return DCS#Vec2 The location of the zone.
|
||||
function ZONE_RADIUS:GetVec2()
|
||||
self:F2( self.ZoneName )
|
||||
--self:F2( self.ZoneName )
|
||||
|
||||
self:T2( { self.Vec2 } )
|
||||
--self:T2( { self.Vec2 } )
|
||||
|
||||
return self.Vec2
|
||||
end
|
||||
@@ -1040,11 +1086,11 @@ end
|
||||
-- @param DCS#Vec2 Vec2 The new location of the zone.
|
||||
-- @return DCS#Vec2 The new location of the zone.
|
||||
function ZONE_RADIUS:SetVec2( Vec2 )
|
||||
self:F2( self.ZoneName )
|
||||
--self:F2( self.ZoneName )
|
||||
|
||||
self.Vec2 = Vec2
|
||||
|
||||
self:T2( { self.Vec2 } )
|
||||
--self:T2( { self.Vec2 } )
|
||||
|
||||
return self.Vec2
|
||||
end
|
||||
@@ -1054,14 +1100,14 @@ end
|
||||
-- @param DCS#Distance Height The height to add to the land height where the center of the zone is located.
|
||||
-- @return DCS#Vec3 The point of the zone.
|
||||
function ZONE_RADIUS:GetVec3( Height )
|
||||
self:F2( { self.ZoneName, Height } )
|
||||
--self:F2( { self.ZoneName, Height } )
|
||||
|
||||
Height = Height or 0
|
||||
local Vec2 = self:GetVec2()
|
||||
|
||||
local Vec3 = { x = Vec2.x, y = land.getHeight( self:GetVec2() ) + Height, z = Vec2.y }
|
||||
|
||||
self:T2( { Vec3 } )
|
||||
--self:T2( { Vec3 } )
|
||||
|
||||
return Vec3
|
||||
end
|
||||
@@ -1138,7 +1184,7 @@ function ZONE_RADIUS:Scan( ObjectCategories, UnitCategories )
|
||||
|
||||
self.ScanData.Units[ZoneObject] = ZoneObject
|
||||
|
||||
self:F2( { Name = ZoneObject:getName(), Coalition = CoalitionDCSUnit } )
|
||||
--self:F2( { Name = ZoneObject:getName(), Coalition = CoalitionDCSUnit } )
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1149,7 +1195,7 @@ function ZONE_RADIUS:Scan( ObjectCategories, UnitCategories )
|
||||
self.ScanData.Scenery[SceneryType] = self.ScanData.Scenery[SceneryType] or {}
|
||||
self.ScanData.Scenery[SceneryType][SceneryName] = SCENERY:Register( tostring(SceneryName), ZoneObject)
|
||||
table.insert(self.ScanData.SceneryTable,self.ScanData.Scenery[SceneryType][SceneryName] )
|
||||
self:T( { SCENERY = self.ScanData.Scenery[SceneryType][SceneryName] } )
|
||||
--self:T( { SCENERY = self.ScanData.Scenery[SceneryType][SceneryName] } )
|
||||
end
|
||||
|
||||
end
|
||||
@@ -1409,13 +1455,13 @@ function ZONE_RADIUS:SearchZone( EvaluateFunction, ObjectCategories )
|
||||
local ZoneCoord = self:GetCoordinate()
|
||||
local ZoneRadius = self:GetRadius()
|
||||
|
||||
self:F({ZoneCoord = ZoneCoord, ZoneRadius = ZoneRadius, ZoneCoordLL = ZoneCoord:ToStringLLDMS()})
|
||||
--self:F({ZoneCoord = ZoneCoord, ZoneRadius = ZoneRadius, ZoneCoordLL = ZoneCoord:ToStringLLDMS()})
|
||||
|
||||
local SphereSearch = {
|
||||
id = world.VolumeType.SPHERE,
|
||||
params = {
|
||||
point = ZoneCoord:GetVec3(),
|
||||
radius = ZoneRadius / 2,
|
||||
radius = ZoneRadius,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1436,7 +1482,7 @@ end
|
||||
-- @param DCS#Vec2 Vec2 The location to test.
|
||||
-- @return #boolean true if the location is within the zone.
|
||||
function ZONE_RADIUS:IsVec2InZone( Vec2 )
|
||||
self:F2( Vec2 )
|
||||
--self:F2( Vec2 )
|
||||
|
||||
if not Vec2 then return false end
|
||||
|
||||
@@ -1456,7 +1502,7 @@ end
|
||||
-- @param DCS#Vec3 Vec3 The point to test.
|
||||
-- @return #boolean true if the point is within the zone.
|
||||
function ZONE_RADIUS:IsVec3InZone( Vec3 )
|
||||
self:F2( Vec3 )
|
||||
--self:F2( Vec3 )
|
||||
if not Vec3 then return false end
|
||||
local InZone = self:IsVec2InZone( { x = Vec3.x, y = Vec3.z } )
|
||||
|
||||
@@ -1521,11 +1567,11 @@ end
|
||||
-- @param #number outer (optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone.
|
||||
-- @return Core.Point#POINT_VEC2 The @{Core.Point#POINT_VEC2} object reflecting the random 3D location within the zone.
|
||||
function ZONE_RADIUS:GetRandomPointVec2( inner, outer )
|
||||
self:F( self.ZoneName, inner, outer )
|
||||
--self:F( self.ZoneName, inner, outer )
|
||||
|
||||
local PointVec2 = POINT_VEC2:NewFromVec2( self:GetRandomVec2( inner, outer ) )
|
||||
|
||||
self:T3( { PointVec2 } )
|
||||
--self:T3( { PointVec2 } )
|
||||
|
||||
return PointVec2
|
||||
end
|
||||
@@ -1536,11 +1582,11 @@ end
|
||||
-- @param #number outer (optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone.
|
||||
-- @return DCS#Vec3 The random location within the zone.
|
||||
function ZONE_RADIUS:GetRandomVec3( inner, outer )
|
||||
self:F( self.ZoneName, inner, outer )
|
||||
--self:F( self.ZoneName, inner, outer )
|
||||
|
||||
local Vec2 = self:GetRandomVec2( inner, outer )
|
||||
|
||||
self:T3( { x = Vec2.x, y = self.y, z = Vec2.y } )
|
||||
--self:T3( { x = Vec2.x, y = self.y, z = Vec2.y } )
|
||||
|
||||
return { x = Vec2.x, y = self.y, z = Vec2.y }
|
||||
end
|
||||
@@ -1552,11 +1598,11 @@ end
|
||||
-- @param #number outer (optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone.
|
||||
-- @return Core.Point#POINT_VEC3 The @{Core.Point#POINT_VEC3} object reflecting the random 3D location within the zone.
|
||||
function ZONE_RADIUS:GetRandomPointVec3( inner, outer )
|
||||
self:F( self.ZoneName, inner, outer )
|
||||
--self:F( self.ZoneName, inner, outer )
|
||||
|
||||
local PointVec3 = POINT_VEC3:NewFromVec2( self:GetRandomVec2( inner, outer ) )
|
||||
|
||||
self:T3( { PointVec3 } )
|
||||
--self:T3( { PointVec3 } )
|
||||
|
||||
return PointVec3
|
||||
end
|
||||
@@ -1685,7 +1731,7 @@ function ZONE_RADIUS:GetRandomCoordinateWithoutBuildings(inner,outer,distance,ma
|
||||
|
||||
T1=timer.getTime()
|
||||
|
||||
self:T(string.format("Found a coordinate: %s | Iterations: %d | Time: %.3f",tostring(found),iterations,T1-T0))
|
||||
--self:T(string.format("Found a coordinate: %s | Iterations: %d | Time: %.3f",tostring(found),iterations,T1-T0))
|
||||
|
||||
if found then return rcoord else return nil end
|
||||
|
||||
@@ -1754,7 +1800,7 @@ function ZONE:New( ZoneName )
|
||||
|
||||
-- Create a new ZONE_RADIUS.
|
||||
local self=BASE:Inherit( self, ZONE_RADIUS:New(ZoneName, {x=Zone.point.x, y=Zone.point.z}, Zone.radius, true))
|
||||
self:F(ZoneName)
|
||||
--self:F(ZoneName)
|
||||
|
||||
-- Color of zone.
|
||||
self.Color={1, 0, 0, 0.15}
|
||||
@@ -1824,7 +1870,7 @@ function ZONE_UNIT:New( ZoneName, ZoneUNIT, Radius, Offset)
|
||||
self.relative_to_unit = Offset.relative_to_unit or false
|
||||
end
|
||||
|
||||
self:F( { ZoneName, ZoneUNIT:GetVec2(), Radius } )
|
||||
--self:F( { ZoneName, ZoneUNIT:GetVec2(), Radius } )
|
||||
|
||||
self.ZoneUNIT = ZoneUNIT
|
||||
self.LastVec2 = ZoneUNIT:GetVec2()
|
||||
@@ -1840,7 +1886,7 @@ end
|
||||
-- @param #ZONE_UNIT self
|
||||
-- @return DCS#Vec2 The location of the zone based on the @{Wrapper.Unit#UNIT}location and the offset, if any.
|
||||
function ZONE_UNIT:GetVec2()
|
||||
self:F2( self.ZoneName )
|
||||
--self:F2( self.ZoneName )
|
||||
|
||||
local ZoneVec2 = self.ZoneUNIT:GetVec2()
|
||||
if ZoneVec2 then
|
||||
@@ -1873,7 +1919,7 @@ function ZONE_UNIT:GetVec2()
|
||||
return self.LastVec2
|
||||
end
|
||||
|
||||
self:T2( { ZoneVec2 } )
|
||||
--self:T2( { ZoneVec2 } )
|
||||
|
||||
return nil
|
||||
end
|
||||
@@ -1882,7 +1928,7 @@ end
|
||||
-- @param #ZONE_UNIT self
|
||||
-- @return DCS#Vec2 The random location within the zone.
|
||||
function ZONE_UNIT:GetRandomVec2()
|
||||
self:F( self.ZoneName )
|
||||
--self:F( self.ZoneName )
|
||||
|
||||
local RandomVec2 = {}
|
||||
--local Vec2 = self.ZoneUNIT:GetVec2() -- FF: This does not take care of the new offset feature!
|
||||
@@ -1896,7 +1942,7 @@ function ZONE_UNIT:GetRandomVec2()
|
||||
RandomVec2.x = Vec2.x + math.cos( angle ) * math.random() * self:GetRadius();
|
||||
RandomVec2.y = Vec2.y + math.sin( angle ) * math.random() * self:GetRadius();
|
||||
|
||||
self:T( { RandomVec2 } )
|
||||
--self:T( { RandomVec2 } )
|
||||
|
||||
return RandomVec2
|
||||
end
|
||||
@@ -1906,7 +1952,7 @@ end
|
||||
-- @param DCS#Distance Height The height to add to the land height where the center of the zone is located.
|
||||
-- @return DCS#Vec3 The point of the zone.
|
||||
function ZONE_UNIT:GetVec3( Height )
|
||||
self:F2( self.ZoneName )
|
||||
--self:F2( self.ZoneName )
|
||||
|
||||
Height = Height or 0
|
||||
|
||||
@@ -1914,7 +1960,7 @@ function ZONE_UNIT:GetVec3( Height )
|
||||
|
||||
local Vec3 = { x = Vec2.x, y = land.getHeight( self:GetVec2() ) + Height, z = Vec2.y }
|
||||
|
||||
self:T2( { Vec3 } )
|
||||
--self:T2( { Vec3 } )
|
||||
|
||||
return Vec3
|
||||
end
|
||||
@@ -1940,7 +1986,7 @@ ZONE_GROUP = {
|
||||
-- @return #ZONE_GROUP self
|
||||
function ZONE_GROUP:New( ZoneName, ZoneGROUP, Radius )
|
||||
local self = BASE:Inherit( self, ZONE_RADIUS:New( ZoneName, ZoneGROUP:GetVec2(), Radius, true ) )
|
||||
self:F( { ZoneName, ZoneGROUP:GetVec2(), Radius } )
|
||||
--self:F( { ZoneName, ZoneGROUP:GetVec2(), Radius } )
|
||||
|
||||
self._.ZoneGROUP = ZoneGROUP
|
||||
self._.ZoneVec2Cache = self._.ZoneGROUP:GetVec2()
|
||||
@@ -1956,7 +2002,7 @@ end
|
||||
-- @param #ZONE_GROUP self
|
||||
-- @return DCS#Vec2 The location of the zone based on the @{Wrapper.Group} location.
|
||||
function ZONE_GROUP:GetVec2()
|
||||
self:F( self.ZoneName )
|
||||
--self:F( self.ZoneName )
|
||||
|
||||
local ZoneVec2 = nil
|
||||
|
||||
@@ -1967,7 +2013,7 @@ function ZONE_GROUP:GetVec2()
|
||||
ZoneVec2 = self._.ZoneVec2Cache
|
||||
end
|
||||
|
||||
self:T( { ZoneVec2 } )
|
||||
--self:T( { ZoneVec2 } )
|
||||
|
||||
return ZoneVec2
|
||||
end
|
||||
@@ -1976,7 +2022,7 @@ end
|
||||
-- @param #ZONE_GROUP self
|
||||
-- @return DCS#Vec2 The random location of the zone based on the @{Wrapper.Group} location.
|
||||
function ZONE_GROUP:GetRandomVec2()
|
||||
self:F( self.ZoneName )
|
||||
--self:F( self.ZoneName )
|
||||
|
||||
local Point = {}
|
||||
local Vec2 = self._.ZoneGROUP:GetVec2()
|
||||
@@ -1985,7 +2031,7 @@ function ZONE_GROUP:GetRandomVec2()
|
||||
Point.x = Vec2.x + math.cos( angle ) * math.random() * self:GetRadius();
|
||||
Point.y = Vec2.y + math.sin( angle ) * math.random() * self:GetRadius();
|
||||
|
||||
self:T( { Point } )
|
||||
--self:T( { Point } )
|
||||
|
||||
return Point
|
||||
end
|
||||
@@ -1996,11 +2042,11 @@ end
|
||||
-- @param #number outer (optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone.
|
||||
-- @return Core.Point#POINT_VEC2 The @{Core.Point#POINT_VEC2} object reflecting the random 3D location within the zone.
|
||||
function ZONE_GROUP:GetRandomPointVec2( inner, outer )
|
||||
self:F( self.ZoneName, inner, outer )
|
||||
--self:F( self.ZoneName, inner, outer )
|
||||
|
||||
local PointVec2 = POINT_VEC2:NewFromVec2( self:GetRandomVec2() )
|
||||
|
||||
self:T3( { PointVec2 } )
|
||||
--self:T3( { PointVec2 } )
|
||||
|
||||
return PointVec2
|
||||
end
|
||||
@@ -2046,7 +2092,7 @@ function _ZONE_TRIANGLE:New(p1, p2, p3)
|
||||
end
|
||||
|
||||
self.SurfaceArea = math.abs((p2.x - p1.x) * (p3.y - p1.y) - (p3.x - p1.x) * (p2.y - p1.y)) * 0.5
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -2054,7 +2100,7 @@ end
|
||||
-- @param #_ZONE_TRIANGLE self
|
||||
-- @param #table pt The point to check
|
||||
-- @param #table points (optional) The points of the triangle, or 3 other points if you're just using the TRIANGLE class without an object of it
|
||||
-- @return #bool True if the point is contained, false otherwise
|
||||
-- @return #boolean True if the point is contained, false otherwise
|
||||
function _ZONE_TRIANGLE:ContainsPoint(pt, points)
|
||||
points = points or self.Points
|
||||
|
||||
@@ -2183,7 +2229,7 @@ function ZONE_POLYGON_BASE:New( ZoneName, PointsArray )
|
||||
|
||||
-- Inherit ZONE_BASE.
|
||||
local self = BASE:Inherit( self, ZONE_BASE:New( ZoneName ) )
|
||||
self:F( { ZoneName, PointsArray } )
|
||||
--self:F( { ZoneName, PointsArray } )
|
||||
|
||||
if PointsArray then
|
||||
|
||||
@@ -2351,7 +2397,7 @@ end
|
||||
-- @param #ZONE_POLYGON_BASE self
|
||||
-- @return DCS#Vec2 The location of the zone based on the @{Wrapper.Group} location.
|
||||
function ZONE_POLYGON_BASE:GetVec2()
|
||||
self:F( self.ZoneName )
|
||||
--self:F( self.ZoneName )
|
||||
|
||||
local Bounds = self:GetBoundingSquare()
|
||||
|
||||
@@ -2434,9 +2480,9 @@ end
|
||||
-- @param #ZONE_POLYGON_BASE self
|
||||
-- @return #ZONE_POLYGON_BASE self
|
||||
function ZONE_POLYGON_BASE:Flush()
|
||||
self:F2()
|
||||
--self:F2()
|
||||
|
||||
self:F( { Polygon = self.ZoneName, Coordinates = self._.Polygon } )
|
||||
--self:F( { Polygon = self.ZoneName, Coordinates = self._.Polygon } )
|
||||
|
||||
return self
|
||||
end
|
||||
@@ -2455,7 +2501,7 @@ function ZONE_POLYGON_BASE:BoundZone( UnBound )
|
||||
j = #self._.Polygon
|
||||
|
||||
while i <= #self._.Polygon do
|
||||
self:T( { i, j, self._.Polygon[i], self._.Polygon[j] } )
|
||||
--self:T( { i, j, self._.Polygon[i], self._.Polygon[j] } )
|
||||
|
||||
local DeltaX = self._.Polygon[j].x - self._.Polygon[i].x
|
||||
local DeltaY = self._.Polygon[j].y - self._.Polygon[i].y
|
||||
@@ -2563,7 +2609,7 @@ function ZONE_POLYGON_BASE:ReFill(Color,Alpha)
|
||||
self.FillTriangles = {}
|
||||
end
|
||||
-- refill
|
||||
for _, triangle in pairs(self._Triangles) do
|
||||
for _,triangle in pairs(self._Triangles) do
|
||||
local draw_ids = triangle:Fill(coalition,color,alpha,nil)
|
||||
self.FillTriangles = draw_ids
|
||||
table.combine(self.DrawID, draw_ids)
|
||||
@@ -2707,7 +2753,7 @@ end
|
||||
-- @param #number Segments (Optional) Number of segments within boundary line. Default 10.
|
||||
-- @return #ZONE_POLYGON_BASE self
|
||||
function ZONE_POLYGON_BASE:SmokeZone( SmokeColor, Segments )
|
||||
self:F2( SmokeColor )
|
||||
--self:F2( SmokeColor )
|
||||
|
||||
Segments=Segments or 10
|
||||
|
||||
@@ -2715,7 +2761,7 @@ function ZONE_POLYGON_BASE:SmokeZone( SmokeColor, Segments )
|
||||
local j=#self._.Polygon
|
||||
|
||||
while i <= #self._.Polygon do
|
||||
self:T( { i, j, self._.Polygon[i], self._.Polygon[j] } )
|
||||
--self:T( { i, j, self._.Polygon[i], self._.Polygon[j] } )
|
||||
|
||||
local DeltaX = self._.Polygon[j].x - self._.Polygon[i].x
|
||||
local DeltaY = self._.Polygon[j].y - self._.Polygon[i].y
|
||||
@@ -2740,7 +2786,7 @@ end
|
||||
-- @param #number AddHeight (optional) The height to be added for the smoke.
|
||||
-- @return #ZONE_POLYGON_BASE self
|
||||
function ZONE_POLYGON_BASE:FlareZone( FlareColor, Segments, Azimuth, AddHeight )
|
||||
self:F2(FlareColor)
|
||||
--self:F2(FlareColor)
|
||||
|
||||
Segments=Segments or 10
|
||||
|
||||
@@ -2750,7 +2796,7 @@ function ZONE_POLYGON_BASE:FlareZone( FlareColor, Segments, Azimuth, AddHeight )
|
||||
local j=#self._.Polygon
|
||||
|
||||
while i <= #self._.Polygon do
|
||||
self:T( { i, j, self._.Polygon[i], self._.Polygon[j] } )
|
||||
--self:T( { i, j, self._.Polygon[i], self._.Polygon[j] } )
|
||||
|
||||
local DeltaX = self._.Polygon[j].x - self._.Polygon[i].x
|
||||
local DeltaY = self._.Polygon[j].y - self._.Polygon[i].y
|
||||
@@ -2773,7 +2819,7 @@ end
|
||||
-- @param DCS#Vec2 Vec2 The location to test.
|
||||
-- @return #boolean true if the location is within the zone.
|
||||
function ZONE_POLYGON_BASE:IsVec2InZone( Vec2 )
|
||||
self:F2( Vec2 )
|
||||
--self:F2( Vec2 )
|
||||
if not Vec2 then return false end
|
||||
local Next
|
||||
local Prev
|
||||
@@ -2783,18 +2829,18 @@ function ZONE_POLYGON_BASE:IsVec2InZone( Vec2 )
|
||||
Prev = #self._.Polygon
|
||||
|
||||
while Next <= #self._.Polygon do
|
||||
self:T( { Next, Prev, self._.Polygon[Next], self._.Polygon[Prev] } )
|
||||
--self:T( { Next, Prev, self._.Polygon[Next], self._.Polygon[Prev] } )
|
||||
if ( ( ( self._.Polygon[Next].y > Vec2.y ) ~= ( self._.Polygon[Prev].y > Vec2.y ) ) and
|
||||
( Vec2.x < ( self._.Polygon[Prev].x - self._.Polygon[Next].x ) * ( Vec2.y - self._.Polygon[Next].y ) / ( self._.Polygon[Prev].y - self._.Polygon[Next].y ) + self._.Polygon[Next].x )
|
||||
) then
|
||||
InPolygon = not InPolygon
|
||||
end
|
||||
self:T2( { InPolygon = InPolygon } )
|
||||
--self:T2( { InPolygon = InPolygon } )
|
||||
Prev = Next
|
||||
Next = Next + 1
|
||||
end
|
||||
|
||||
self:T( { InPolygon = InPolygon } )
|
||||
--self:T( { InPolygon = InPolygon } )
|
||||
return InPolygon
|
||||
end
|
||||
|
||||
@@ -2803,7 +2849,7 @@ end
|
||||
-- @param DCS#Vec3 Vec3 The point to test.
|
||||
-- @return #boolean true if the point is within the zone.
|
||||
function ZONE_POLYGON_BASE:IsVec3InZone( Vec3 )
|
||||
self:F2( Vec3 )
|
||||
--self:F2( Vec3 )
|
||||
|
||||
if not Vec3 then return false end
|
||||
|
||||
@@ -2838,11 +2884,11 @@ end
|
||||
-- @param #ZONE_POLYGON_BASE self
|
||||
-- @return @{Core.Point#POINT_VEC2}
|
||||
function ZONE_POLYGON_BASE:GetRandomPointVec2()
|
||||
self:F2()
|
||||
--self:F2()
|
||||
|
||||
local PointVec2 = POINT_VEC2:NewFromVec2( self:GetRandomVec2() )
|
||||
|
||||
self:T2( PointVec2 )
|
||||
--self:T2( PointVec2 )
|
||||
|
||||
return PointVec2
|
||||
end
|
||||
@@ -2851,11 +2897,11 @@ end
|
||||
-- @param #ZONE_POLYGON_BASE self
|
||||
-- @return @{Core.Point#POINT_VEC3}
|
||||
function ZONE_POLYGON_BASE:GetRandomPointVec3()
|
||||
self:F2()
|
||||
--self:F2()
|
||||
|
||||
local PointVec3 = POINT_VEC3:NewFromVec2( self:GetRandomVec2() )
|
||||
|
||||
self:T2( PointVec3 )
|
||||
--self:T2( PointVec3 )
|
||||
|
||||
return PointVec3
|
||||
end
|
||||
@@ -2865,11 +2911,11 @@ end
|
||||
-- @param #ZONE_POLYGON_BASE self
|
||||
-- @return Core.Point#COORDINATE
|
||||
function ZONE_POLYGON_BASE:GetRandomCoordinate()
|
||||
self:F2()
|
||||
--self:F2()
|
||||
|
||||
local Coordinate = COORDINATE:NewFromVec2( self:GetRandomVec2() )
|
||||
|
||||
self:T2( Coordinate )
|
||||
--self:T2( Coordinate )
|
||||
|
||||
return Coordinate
|
||||
end
|
||||
@@ -2886,7 +2932,7 @@ function ZONE_POLYGON_BASE:GetBoundingSquare()
|
||||
local y2 = self._.Polygon[1].y
|
||||
|
||||
for i = 2, #self._.Polygon do
|
||||
self:T2( { self._.Polygon[i], x1, y1, x2, y2 } )
|
||||
--self:T2( { self._.Polygon[i], x1, y1, x2, y2 } )
|
||||
x1 = ( x1 > self._.Polygon[i].x ) and self._.Polygon[i].x or x1
|
||||
x2 = ( x2 < self._.Polygon[i].x ) and self._.Polygon[i].x or x2
|
||||
y1 = ( y1 > self._.Polygon[i].y ) and self._.Polygon[i].y or y1
|
||||
@@ -2909,7 +2955,7 @@ function ZONE_POLYGON_BASE:GetBoundingVec2()
|
||||
local y2 = self._.Polygon[1].y
|
||||
|
||||
for i = 2, #self._.Polygon do
|
||||
self:T2( { self._.Polygon[i], x1, y1, x2, y2 } )
|
||||
--self:T2( { self._.Polygon[i], x1, y1, x2, y2 } )
|
||||
x1 = ( x1 > self._.Polygon[i].x ) and self._.Polygon[i].x or x1
|
||||
x2 = ( x2 < self._.Polygon[i].x ) and self._.Polygon[i].x or x2
|
||||
y1 = ( y1 > self._.Polygon[i].y ) and self._.Polygon[i].y or y1
|
||||
@@ -2948,7 +2994,7 @@ function ZONE_POLYGON_BASE:Boundary(Coalition, Color, Radius, Alpha, Segments, C
|
||||
Limit = #self._.Polygon
|
||||
end
|
||||
while i <= #self._.Polygon do
|
||||
self:T( { i, j, self._.Polygon[i], self._.Polygon[j] } )
|
||||
--self:T( { i, j, self._.Polygon[i], self._.Polygon[j] } )
|
||||
if j ~= Limit then
|
||||
local DeltaX = self._.Polygon[j].x - self._.Polygon[i].x
|
||||
local DeltaY = self._.Polygon[j].y - self._.Polygon[i].y
|
||||
@@ -3019,7 +3065,7 @@ function ZONE_POLYGON:New( ZoneName, ZoneGroup )
|
||||
local GroupPoints = ZoneGroup:GetTaskRoute()
|
||||
|
||||
local self = BASE:Inherit( self, ZONE_POLYGON_BASE:New( ZoneName, GroupPoints ) )
|
||||
self:F( { ZoneName, ZoneGroup, self._.Polygon } )
|
||||
--self:F( { ZoneName, ZoneGroup, self._.Polygon } )
|
||||
|
||||
-- Zone objects are added to the _DATABASE and SET_ZONE objects.
|
||||
_EVENTDISPATCHER:CreateEventNewZone( self )
|
||||
@@ -3035,7 +3081,7 @@ end
|
||||
function ZONE_POLYGON:NewFromPointsArray( ZoneName, PointsArray )
|
||||
|
||||
local self = BASE:Inherit( self, ZONE_POLYGON_BASE:New( ZoneName, PointsArray ) )
|
||||
self:F( { ZoneName, self._.Polygon } )
|
||||
--self:F( { ZoneName, self._.Polygon } )
|
||||
|
||||
-- Zone objects are added to the _DATABASE and SET_ZONE objects.
|
||||
_EVENTDISPATCHER:CreateEventNewZone( self )
|
||||
@@ -3055,7 +3101,7 @@ function ZONE_POLYGON:NewFromGroupName( GroupName )
|
||||
local GroupPoints = ZoneGroup:GetTaskRoute()
|
||||
|
||||
local self = BASE:Inherit( self, ZONE_POLYGON_BASE:New( GroupName, GroupPoints ) )
|
||||
self:F( { GroupName, ZoneGroup, self._.Polygon } )
|
||||
--self:F( { GroupName, ZoneGroup, self._.Polygon } )
|
||||
|
||||
-- Zone objects are added to the _DATABASE and SET_ZONE objects.
|
||||
_EVENTDISPATCHER:CreateEventNewZone( self )
|
||||
@@ -3221,7 +3267,7 @@ function ZONE_POLYGON:Scan( ObjectCategories, UnitCategories )
|
||||
|
||||
self.ScanData.Units[ZoneObject] = ZoneObject
|
||||
|
||||
self:F2( { Name = ZoneObject:getName(), Coalition = CoalitionDCSUnit } )
|
||||
--self:F2( { Name = ZoneObject:getName(), Coalition = CoalitionDCSUnit } )
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3232,7 +3278,7 @@ function ZONE_POLYGON:Scan( ObjectCategories, UnitCategories )
|
||||
self.ScanData.Scenery[SceneryType] = self.ScanData.Scenery[SceneryType] or {}
|
||||
self.ScanData.Scenery[SceneryType][SceneryName] = SCENERY:Register( SceneryName, ZoneObject )
|
||||
table.insert(self.ScanData.SceneryTable,self.ScanData.Scenery[SceneryType][SceneryName])
|
||||
self:T( { SCENERY = self.ScanData.Scenery[SceneryType][SceneryName] } )
|
||||
--self:T( { SCENERY = self.ScanData.Scenery[SceneryType][SceneryName] } )
|
||||
end
|
||||
|
||||
end
|
||||
@@ -3536,7 +3582,37 @@ do -- ZONE_ELASTIC
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Remove a vertex (point) from the polygon.
|
||||
-- @param #ZONE_ELASTIC self
|
||||
-- @param DCS#Vec2 Vec2 Point in 2D (with x and y coordinates).
|
||||
-- @return #ZONE_ELASTIC self
|
||||
function ZONE_ELASTIC:RemoveVertex2D(Vec2)
|
||||
|
||||
local found = false
|
||||
local findex = 0
|
||||
for _id,_vec2 in pairs(self.points) do
|
||||
if _vec2.x == Vec2.x and _vec2.y == Vec2.y then
|
||||
found = true
|
||||
findex = _id
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if found == true and findex > 0 then
|
||||
table.remove(self.points,findex)
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Remove a vertex (point) from the polygon.
|
||||
-- @param #ZONE_ELASTIC self
|
||||
-- @param DCS#Vec3 Vec3 Point in 3D (with x, y and z coordinates). Only the x and z coordinates are used.
|
||||
-- @return #ZONE_ELASTIC self
|
||||
function ZONE_ELASTIC:RemoveVertex3D(Vec3)
|
||||
return self:RemoveVertex2D({x=Vec3.x, y=Vec3.z})
|
||||
end
|
||||
|
||||
--- Add a vertex (point) to the polygon.
|
||||
-- @param #ZONE_ELASTIC self
|
||||
@@ -3573,8 +3649,8 @@ do -- ZONE_ELASTIC
|
||||
function ZONE_ELASTIC:Update(Delay, Draw)
|
||||
|
||||
-- Debug info.
|
||||
self:T(string.format("Updating ZONE_ELASTIC %s", tostring(self.ZoneName)))
|
||||
|
||||
--self:T(string.format("Updating ZONE_ELASTIC %s", tostring(self.ZoneName)))
|
||||
|
||||
-- Copy all points.
|
||||
local points=UTILS.DeepCopy(self.points or {})
|
||||
|
||||
@@ -3592,6 +3668,9 @@ do -- ZONE_ELASTIC
|
||||
|
||||
-- Update polygon verticies from points.
|
||||
self._.Polygon=self:_ConvexHull(points)
|
||||
|
||||
self._Triangles = self:_Triangulate()
|
||||
self.SurfaceArea = self:_CalculateSurfaceArea()
|
||||
|
||||
if Draw~=false then
|
||||
if self.DrawID or Draw==true then
|
||||
@@ -3790,7 +3869,7 @@ end
|
||||
--- Checks if a point is contained within the oval.
|
||||
-- @param #ZONE_OVAL self
|
||||
-- @param #table point The point to check
|
||||
-- @return #bool True if the point is contained, false otherwise
|
||||
-- @return #boolean True if the point is contained, false otherwise
|
||||
function ZONE_OVAL:IsVec2InZone(vec2)
|
||||
local cos, sin = math.cos, math.sin
|
||||
local dx = vec2.x - self.CenterVec2.x
|
||||
@@ -3987,7 +4066,7 @@ do -- ZONE_AIRBASE
|
||||
-- @param #ZONE_AIRBASE self
|
||||
-- @return DCS#Vec2 The location of the zone based on the AIRBASE location.
|
||||
function ZONE_AIRBASE:GetVec2()
|
||||
self:F( self.ZoneName )
|
||||
--self:F( self.ZoneName )
|
||||
|
||||
local ZoneVec2 = nil
|
||||
|
||||
@@ -3998,7 +4077,7 @@ do -- ZONE_AIRBASE
|
||||
ZoneVec2 = self._.ZoneVec2Cache
|
||||
end
|
||||
|
||||
self:T( { ZoneVec2 } )
|
||||
--self:T( { ZoneVec2 } )
|
||||
|
||||
return ZoneVec2
|
||||
end
|
||||
@@ -4009,11 +4088,11 @@ do -- ZONE_AIRBASE
|
||||
-- @param #number outer (optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone.
|
||||
-- @return Core.Point#POINT_VEC2 The @{Core.Point#POINT_VEC2} object reflecting the random 3D location within the zone.
|
||||
function ZONE_AIRBASE:GetRandomPointVec2( inner, outer )
|
||||
self:F( self.ZoneName, inner, outer )
|
||||
--self:F( self.ZoneName, inner, outer )
|
||||
|
||||
local PointVec2 = POINT_VEC2:NewFromVec2( self:GetRandomVec2() )
|
||||
|
||||
self:T3( { PointVec2 } )
|
||||
--self:T3( { PointVec2 } )
|
||||
|
||||
return PointVec2
|
||||
end
|
||||
|
||||
@@ -14,6 +14,7 @@ do -- world
|
||||
-- @field #world.event event [https://wiki.hoggitworld.com/view/DCS_enum_world](https://wiki.hoggitworld.com/view/DCS_enum_world)
|
||||
-- @field #world.BirthPlace BirthPlace The birthplace enumerator is used to define where an aircraft or helicopter has spawned in association with birth events.
|
||||
-- @field #world.VolumeType VolumeType The volumeType enumerator defines the types of 3d geometery used within the [world.searchObjects](https://wiki.hoggitworld.com/view/DCS_func_searchObjects) function.
|
||||
-- @field #world.weather weather Weather functions for fog etc.
|
||||
|
||||
--- The world singleton contains functions centered around two different but extremely useful functions.
|
||||
-- * Events and event handlers are all governed within world.
|
||||
@@ -132,6 +133,36 @@ do -- world
|
||||
-- @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.
|
||||
|
||||
|
||||
--- Weather functions.
|
||||
-- @type world.weather
|
||||
|
||||
--- Fog animation data structure.
|
||||
-- @type world.FogAnimation
|
||||
-- @field #number time
|
||||
-- @field #number visibility
|
||||
-- @field #number thickness
|
||||
|
||||
--- Returns the current fog thickness.
|
||||
-- @function [parent=#world.weather] getFogThickness Returns the fog thickness.
|
||||
-- @return #number Fog thickness in meters. If there is no fog, zero is returned.
|
||||
|
||||
--- Sets the fog thickness instantly. Any current fog animation is discarded.
|
||||
-- @function [parent=#world.weather] setFogThickness
|
||||
-- @param #number thickness Fog thickness in meters. Set to zero to disable fog.
|
||||
|
||||
--- Returns the current fog visibility distance.
|
||||
-- @function [parent=#world.weather] getFogVisibilityDistance Returns the current maximum visibility distance in meters. Returns zero if fog is not present.
|
||||
|
||||
--- Instantly sets the maximum visibility distance of fog at sea level when looking at the horizon. Any current fog animation is discarded. Set zero to disable the fog.
|
||||
-- @function [parent=#world.weather] setFogVisibilityDistance
|
||||
-- @param #number visibility Max fog visibility in meters. Set to zero to disable fog.
|
||||
|
||||
--- Sets fog animation keys. Time is set in seconds and relative to the current simulation time, where time=0 is the current moment.
|
||||
-- Time must be increasing. Previous animation is always discarded despite the data being correct.
|
||||
-- @function [parent=#world.weather] setFogAnimation
|
||||
-- @param #world.FogAnimation animation List of fog animations
|
||||
|
||||
end -- world
|
||||
|
||||
@@ -407,7 +438,7 @@ do -- coalition
|
||||
-- @param #table groupData Group data table.
|
||||
-- @return DCS#Group The spawned Group object.
|
||||
|
||||
--- Dynamically spawns a static object. See [hoggit](https://wiki.hoggitworld.com/view/DCS_func_addGroup)
|
||||
--- Dynamically spawns a static object. See [hoggit](https://wiki.hoggitworld.com/view/DCS_func_addStaticObject)
|
||||
-- @function [parent=#coalition] addStaticObject
|
||||
-- @param #number countryId Id of the country.
|
||||
-- @param #table groupData Group data table.
|
||||
@@ -420,6 +451,7 @@ end -- coalition
|
||||
|
||||
do -- Types
|
||||
|
||||
--- Descriptors.
|
||||
-- @type Desc
|
||||
-- @field #number speedMax0 Max speed in meters/second at zero altitude.
|
||||
-- @field #number massEmpty Empty mass in kg.
|
||||
@@ -598,9 +630,13 @@ do -- Object
|
||||
--- @function [parent=#Object] destroy
|
||||
-- @param #Object self
|
||||
|
||||
--- @function [parent=#Object] getCategory
|
||||
--- Returns an enumerator of the category for the specific object.
|
||||
-- The enumerator returned is dependent on the category of the object and how the function is called.
|
||||
-- As of DCS 2.9.2 when this function is called on an Object, Unit, Weapon, or Airbase a 2nd value will be returned which details the object sub-category value.
|
||||
-- @function [parent=#Object] getCategory
|
||||
-- @param #Object self
|
||||
-- @return #Object.Category
|
||||
-- @return #Object.Category The object category (1=UNIT, 2=WEAPON, 3=STATIC, 4=BASE, 5=SCENERY, 6=Cargo)
|
||||
-- @return #number The subcategory of the passed object, e.g. Unit.Category if a unit object was passed.
|
||||
|
||||
--- Returns type name of the Object.
|
||||
-- @function [parent=#Object] getTypeName
|
||||
@@ -1013,14 +1049,16 @@ do -- Spot
|
||||
end -- Spot
|
||||
|
||||
do -- Controller
|
||||
|
||||
--- Controller is an object that performs A.I.-tasks. 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.
|
||||
--
|
||||
-- This class has 2 types of functions:
|
||||
--
|
||||
-- * Tasks
|
||||
-- * Commands: Commands are instant actions those required zero time to perform. Commands may be used both for control unit/group behavior and control game mechanics.
|
||||
-- * Commands: Commands are instant actions those required zero time to perform. Commands may be used both for control unit/group behavior and control game mechanics.
|
||||
--
|
||||
-- @type Controller
|
||||
-- @field #Controller.Detection Detection Enum contains identifiers of surface types.
|
||||
-- @field #Controller.Detection Detection Enum contains identifiers of surface types.
|
||||
|
||||
--- Enables and disables the controller.
|
||||
-- Note: Now it works only for ground / naval groups!
|
||||
@@ -1079,18 +1117,18 @@ do -- Controller
|
||||
|
||||
-- Detection
|
||||
|
||||
--- Enum contains identifiers of surface types.
|
||||
--- Enum containing detection types.
|
||||
-- @type Controller.Detection
|
||||
-- @field VISUAL
|
||||
-- @field OPTIC
|
||||
-- @field RADAR
|
||||
-- @field IRST
|
||||
-- @field RWR
|
||||
-- @field DLINK
|
||||
-- @field #number VISUAL Visual detection. Numeric value 1.
|
||||
-- @field #number OPTIC Optical detection. Numeric value 2.
|
||||
-- @field #number RADAR Radar detection. Numeric value 4.
|
||||
-- @field #number IRST Infra-red search and track detection. Numeric value 8.
|
||||
-- @field #number RWR Radar Warning Receiver detection. Numeric value 16.
|
||||
-- @field #number DLINK Data link detection. Numeric value 32.
|
||||
|
||||
--- Detected target.
|
||||
-- @type DetectedTarget
|
||||
-- @field Wrapper.Object#Object object The target
|
||||
-- @type Controller.DetectedTarget
|
||||
-- @field DCS#Object object The target
|
||||
-- @field #boolean visible The target is visible
|
||||
-- @field #boolean type The target type is known
|
||||
-- @field #boolean distance Distance to the target is known
|
||||
@@ -1103,9 +1141,9 @@ do -- Controller
|
||||
-- @param #Controller.Detection detection Controller.Detection detection1, Controller.Detection detection2, ... Controller.Detection detectionN
|
||||
-- @return #boolean detected True if the target is detected.
|
||||
-- @return #boolean visible Has effect only if detected is true. True if the target is visible now.
|
||||
-- @return #boolean type Has effect only if detected is true. True if the target type is known.
|
||||
-- @return #boolean distance Has effect only if detected is true. True if the distance to the target is known.
|
||||
-- @return #ModelTime lastTime Has effect only if visible is false. Last time when target was seen.
|
||||
-- @return #boolean type Has effect only if detected is true. True if the target type is known.
|
||||
-- @return #boolean distance Has effect only if detected is true. True if the distance to the target is known.
|
||||
-- @return #Vec3 lastPos Has effect only if visible is false. Last position of the target when it was seen.
|
||||
-- @return #Vec3 lastVel Has effect only if visible is false. Last velocity of the target when it was seen.
|
||||
|
||||
@@ -1131,6 +1169,7 @@ end -- Controller
|
||||
|
||||
do -- Unit
|
||||
|
||||
--- Unit.
|
||||
-- @type Unit
|
||||
-- @extends #CoalitionObject
|
||||
-- @field ID Identifier of an unit. It assigned to an unit by the Mission Editor automatically.
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
-- ### Author: FlightControl - Framework Design & Programming
|
||||
-- ### Refactoring to use the Runway auto-detection: Applevangelist
|
||||
-- @date August 2022
|
||||
-- Last Update Nov 2023
|
||||
-- Last Update Feb 2025
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
@@ -416,7 +416,7 @@ end
|
||||
-- @field #ATC_GROUND_UNIVERSAL
|
||||
ATC_GROUND_UNIVERSAL = {
|
||||
ClassName = "ATC_GROUND_UNIVERSAL",
|
||||
Version = "0.0.1",
|
||||
Version = "0.0.2",
|
||||
SetClient = nil,
|
||||
Airbases = nil,
|
||||
AirbaseList = nil,
|
||||
@@ -441,17 +441,25 @@ function ATC_GROUND_UNIVERSAL:New(AirbaseList)
|
||||
self:T( { self.ClassName } )
|
||||
|
||||
self.Airbases = {}
|
||||
|
||||
for _name,_ in pairs(_DATABASE.AIRBASES) do
|
||||
self.Airbases[_name]={}
|
||||
end
|
||||
|
||||
self.AirbaseList = AirbaseList
|
||||
|
||||
if not self.AirbaseList then
|
||||
self.AirbaseList = {}
|
||||
for _name,_ in pairs(_DATABASE.AIRBASES) do
|
||||
self.AirbaseList[_name]=_name
|
||||
for _name,_base in pairs(_DATABASE.AIRBASES) do
|
||||
-- DONE exclude FARPS and Ships
|
||||
if _base and _base.isAirdrome == true then
|
||||
self.AirbaseList[_name]=_name
|
||||
self.Airbases[_name]={}
|
||||
end
|
||||
end
|
||||
else
|
||||
for _,_name in pairs(AirbaseList) do
|
||||
-- DONE exclude FARPS and Ships
|
||||
local airbase = _DATABASE:FindAirbase(_name)
|
||||
if airbase and (airbase.isAirdrome == true) then
|
||||
self.Airbases[_name]={}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -721,14 +729,18 @@ function ATC_GROUND_UNIVERSAL:_AirbaseMonitor()
|
||||
|
||||
if NotInRunwayZone then
|
||||
|
||||
local Taxi = Client:GetState( self, "Taxi" )
|
||||
|
||||
if IsOnGround then
|
||||
local Taxi = Client:GetState( self, "Taxi" )
|
||||
|
||||
self:T( Taxi )
|
||||
if Taxi == false then
|
||||
local Velocity = VELOCITY:New( AirbaseMeta.KickSpeed or self.KickSpeed )
|
||||
Client:Message( "Welcome to " .. AirbaseID .. ". The maximum taxiing speed is " ..
|
||||
Velocity:ToString() , 20, "ATC" )
|
||||
Client:SetState( self, "Taxi", true )
|
||||
Client:SetState( self, "Speeding", false )
|
||||
Client:SetState( self, "Warnings", 0 )
|
||||
end
|
||||
|
||||
-- TODO: GetVelocityKMH function usage
|
||||
@@ -737,7 +749,7 @@ function ATC_GROUND_UNIVERSAL:_AirbaseMonitor()
|
||||
local IsAboveRunway = Client:IsAboveRunway()
|
||||
self:T( {IsAboveRunway, IsOnGround, Velocity:Get() })
|
||||
|
||||
if IsOnGround then
|
||||
if IsOnGround and not Taxi then
|
||||
local Speeding = false
|
||||
if AirbaseMeta.MaximumKickSpeed then
|
||||
if Velocity:Get() > AirbaseMeta.MaximumKickSpeed then
|
||||
@@ -749,15 +761,17 @@ function ATC_GROUND_UNIVERSAL:_AirbaseMonitor()
|
||||
end
|
||||
end
|
||||
if Speeding == true then
|
||||
MESSAGE:New( "Penalty! Player " .. Client:GetPlayerName() ..
|
||||
" has been kicked, due to a severe airbase traffic rule violation ...", 10, "ATC" ):ToAll()
|
||||
Client:Destroy()
|
||||
Client:SetState( self, "Speeding", false )
|
||||
Client:SetState( self, "Warnings", 0 )
|
||||
--MESSAGE:New( "Penalty! Player " .. Client:GetPlayerName() ..
|
||||
-- " has been kicked, due to a severe airbase traffic rule violation ...", 10, "ATC" ):ToAll()
|
||||
--Client:Destroy()
|
||||
Client:SetState( self, "Speeding", true )
|
||||
local SpeedingWarnings = Client:GetState( self, "Warnings" )
|
||||
Client:SetState( self, "Warnings", SpeedingWarnings + 1 )
|
||||
Client:Message( "Warning " .. SpeedingWarnings .. "/3! Airbase traffic rule violation! Slow down now! Your speed is " ..
|
||||
Velocity:ToString(), 5, "ATC" )
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
if IsOnGround then
|
||||
|
||||
local Speeding = false
|
||||
@@ -1035,23 +1049,23 @@ end
|
||||
-- The following airbases are monitored at the Nevada region.
|
||||
-- Use the @{Wrapper.Airbase#AIRBASE.Nevada} enumeration to select the airbases to be monitored.
|
||||
--
|
||||
-- * `AIRBASE.Nevada.Beatty_Airport`
|
||||
-- * `AIRBASE.Nevada.Boulder_City_Airport`
|
||||
-- * `AIRBASE.Nevada.Creech_AFB`
|
||||
-- * `AIRBASE.Nevada.Beatty`
|
||||
-- * `AIRBASE.Nevada.Boulder_City`
|
||||
-- * `AIRBASE.Nevada.Creech`
|
||||
-- * `AIRBASE.Nevada.Echo_Bay`
|
||||
-- * `AIRBASE.Nevada.Groom_Lake_AFB`
|
||||
-- * `AIRBASE.Nevada.Henderson_Executive_Airport`
|
||||
-- * `AIRBASE.Nevada.Jean_Airport`
|
||||
-- * `AIRBASE.Nevada.Laughlin_Airport`
|
||||
-- * `AIRBASE.Nevada.Groom_Lake`
|
||||
-- * `AIRBASE.Nevada.Henderson_Executive`
|
||||
-- * `AIRBASE.Nevada.Jean`
|
||||
-- * `AIRBASE.Nevada.Laughlin`
|
||||
-- * `AIRBASE.Nevada.Lincoln_County`
|
||||
-- * `AIRBASE.Nevada.McCarran_International_Airport`
|
||||
-- * `AIRBASE.Nevada.McCarran_International`
|
||||
-- * `AIRBASE.Nevada.Mesquite`
|
||||
-- * `AIRBASE.Nevada.Mina_Airport`
|
||||
-- * `AIRBASE.Nevada.Nellis_AFB`
|
||||
-- * `AIRBASE.Nevada.Mina`
|
||||
-- * `AIRBASE.Nevada.Nellis`
|
||||
-- * `AIRBASE.Nevada.North_Las_Vegas`
|
||||
-- * `AIRBASE.Nevada.Pahute_Mesa_Airstrip`
|
||||
-- * `AIRBASE.Nevada.Tonopah_Airport`
|
||||
-- * `AIRBASE.Nevada.Tonopah_Test_Range_Airfield`
|
||||
-- * `AIRBASE.Nevada.Pahute_Mesa`
|
||||
-- * `AIRBASE.Nevada.Tonopah`
|
||||
-- * `AIRBASE.Nevada.Tonopah_Test_Range`
|
||||
--
|
||||
-- # Installation
|
||||
--
|
||||
@@ -1088,10 +1102,10 @@ end
|
||||
--
|
||||
-- -- Monitor specific airbases.
|
||||
-- ATC_Ground = ATC_GROUND_NEVADA:New(
|
||||
-- { AIRBASE.Nevada.Laughlin_Airport,
|
||||
-- { AIRBASE.Nevada.Laughlin,
|
||||
-- AIRBASE.Nevada.Lincoln_County,
|
||||
-- AIRBASE.Nevada.North_Las_Vegas,
|
||||
-- AIRBASE.Nevada.McCarran_International_Airport
|
||||
-- AIRBASE.Nevada.McCarran_International
|
||||
-- }
|
||||
-- )
|
||||
--
|
||||
@@ -1330,33 +1344,33 @@ end
|
||||
-- The following airbases are monitored at the PersianGulf region.
|
||||
-- Use the @{Wrapper.Airbase#AIRBASE.PersianGulf} enumeration to select the airbases to be monitored.
|
||||
--
|
||||
-- * `AIRBASE.PersianGulf.Abu_Musa_Island_Airport`
|
||||
-- * `AIRBASE.PersianGulf.Al_Dhafra_AB`
|
||||
-- * `AIRBASE.PersianGulf.Abu_Musa_Island`
|
||||
-- * `AIRBASE.PersianGulf.Al_Dhafra_AFB`
|
||||
-- * `AIRBASE.PersianGulf.Al_Maktoum_Intl`
|
||||
-- * `AIRBASE.PersianGulf.Al_Minhad_AB`
|
||||
-- * `AIRBASE.PersianGulf.Al_Minhad_AFB`
|
||||
-- * `AIRBASE.PersianGulf.Bandar_Abbas_Intl`
|
||||
-- * `AIRBASE.PersianGulf.Bandar_Lengeh`
|
||||
-- * `AIRBASE.PersianGulf.Dubai_Intl`
|
||||
-- * `AIRBASE.PersianGulf.Fujairah_Intl`
|
||||
-- * `AIRBASE.PersianGulf.Havadarya`
|
||||
-- * `AIRBASE.PersianGulf.Kerman_Airport`
|
||||
-- * `AIRBASE.PersianGulf.Kerman`
|
||||
-- * `AIRBASE.PersianGulf.Khasab`
|
||||
-- * `AIRBASE.PersianGulf.Lar_Airbase`
|
||||
-- * `AIRBASE.PersianGulf.Lar`
|
||||
-- * `AIRBASE.PersianGulf.Qeshm_Island`
|
||||
-- * `AIRBASE.PersianGulf.Sharjah_Intl`
|
||||
-- * `AIRBASE.PersianGulf.Shiraz_International_Airport`
|
||||
-- * `AIRBASE.PersianGulf.Shiraz_Intl`
|
||||
-- * `AIRBASE.PersianGulf.Sir_Abu_Nuayr`
|
||||
-- * `AIRBASE.PersianGulf.Sirri_Island`
|
||||
-- * `AIRBASE.PersianGulf.Tunb_Island_AFB`
|
||||
-- * `AIRBASE.PersianGulf.Tunb_Kochak`
|
||||
-- * `AIRBASE.PersianGulf.Sas_Al_Nakheel_Airport`
|
||||
-- * `AIRBASE.PersianGulf.Bandar_e_Jask_airfield`
|
||||
-- * `AIRBASE.PersianGulf.Abu_Dhabi_International_Airport`
|
||||
-- * `AIRBASE.PersianGulf.Al_Bateen_Airport`
|
||||
-- * `AIRBASE.PersianGulf.Kish_International_Airport`
|
||||
-- * `AIRBASE.PersianGulf.Al_Ain_International_Airport`
|
||||
-- * `AIRBASE.PersianGulf.Lavan_Island_Airport`
|
||||
-- * `AIRBASE.PersianGulf.Jiroft_Airport`
|
||||
-- * `AIRBASE.PersianGulf.Sas_Al_Nakheel`
|
||||
-- * `AIRBASE.PersianGulf.Bandar_e_Jask`
|
||||
-- * `AIRBASE.PersianGulf.Abu_Dhabi_Intl`
|
||||
-- * `AIRBASE.PersianGulf.Al_Bateen`
|
||||
-- * `AIRBASE.PersianGulf.Kish_Intl`
|
||||
-- * `AIRBASE.PersianGulf.Al_Ain_Intl`
|
||||
-- * `AIRBASE.PersianGulf.Lavan_Island`
|
||||
-- * `AIRBASE.PersianGulf.Jiroft`
|
||||
--
|
||||
-- # Installation
|
||||
--
|
||||
@@ -1391,8 +1405,8 @@ end
|
||||
-- AirbasePoliceCaucasus = ATC_GROUND_PERSIANGULF:New()
|
||||
--
|
||||
-- ATC_Ground = ATC_GROUND_PERSIANGULF:New(
|
||||
-- { AIRBASE.PersianGulf.Kerman_Airport,
|
||||
-- AIRBASE.PersianGulf.Al_Minhad_AB
|
||||
-- { AIRBASE.PersianGulf.Kerman,
|
||||
-- AIRBASE.PersianGulf.Al_Minhad_AFB
|
||||
-- }
|
||||
-- )
|
||||
--
|
||||
@@ -1441,11 +1455,10 @@ function ATC_GROUND_PERSIANGULF:Start( RepeatScanSeconds )
|
||||
self.AirbaseMonitor = SCHEDULER:New( self, self._AirbaseMonitor, { self }, 0, RepeatScanSeconds )
|
||||
end
|
||||
|
||||
|
||||
-- @type ATC_GROUND_MARIANAISLANDS
|
||||
---
|
||||
-- @type ATC_GROUND_MARIANAISLANDS
|
||||
-- @extends #ATC_GROUND
|
||||
|
||||
|
||||
|
||||
--- # ATC\_GROUND\_MARIANA, extends @{#ATC_GROUND}
|
||||
--
|
||||
|
||||
@@ -619,63 +619,148 @@ ARTY.WeaponType={
|
||||
}
|
||||
|
||||
--- Database of common artillery unit properties.
|
||||
-- @type ARTY.dbitem
|
||||
-- @field #string displayname Name displayed in ME.
|
||||
-- @field #number minrange Minimum firing range in meters.
|
||||
-- @field #number maxrange Maximum firing range in meters.
|
||||
-- @field #number reloadtime Reload time in seconds.
|
||||
|
||||
--- Database of common artillery unit properties.
|
||||
-- Table key is the "type name" and table value is and `ARTY.dbitem`.
|
||||
-- @type ARTY.db
|
||||
ARTY.db={
|
||||
["2B11 mortar"] = { -- type "2B11 mortar"
|
||||
minrange = 500, -- correct?
|
||||
maxrange = 7000, -- 7 km
|
||||
reloadtime = 30, -- 30 sec
|
||||
["LeFH_18-40-105"] = {
|
||||
displayname = "FH LeFH-18 105mm", -- name displayed in the ME
|
||||
minrange = 500, -- min range (green circle) in meters
|
||||
maxrange = 10500, -- max range (red circle) in meters
|
||||
reloadtime = nil, -- reload time in seconds
|
||||
},
|
||||
["SPH 2S1 Gvozdika"] = { -- type "SAU Gvozdika"
|
||||
minrange = 300, -- correct?
|
||||
maxrange = 15000, -- 15 km
|
||||
reloadtime = nil, -- unknown
|
||||
["M2A1-105"] = {
|
||||
displayname = "FH M2A1 105mm",
|
||||
minrange = 500,
|
||||
maxrange = 11500,
|
||||
reloadtime = nil,
|
||||
},
|
||||
["SPH 2S19 Msta"] = { --type "SAU Msta", alias "2S19 Msta"
|
||||
minrange = 300, -- correct?
|
||||
maxrange = 23500, -- 23.5 km
|
||||
reloadtime = nil, -- unknown
|
||||
["Pak40"] = {
|
||||
displayname = "FH Pak 40 75mm",
|
||||
minrange = 500,
|
||||
maxrange = 3000,
|
||||
reloadtime = nil,
|
||||
},
|
||||
["L118_Unit"] = {
|
||||
displayname = "L118 Light Artillery Gun",
|
||||
minrange = 500,
|
||||
maxrange = 17500,
|
||||
reloadtime = nil,
|
||||
},
|
||||
["SPH 2S3 Akatsia"] = { -- type "SAU Akatsia", alias "2S3 Akatsia"
|
||||
minrange = 300, -- correct?
|
||||
maxrange = 17000, -- 17 km
|
||||
reloadtime = nil, -- unknown
|
||||
["Smerch"] = {
|
||||
displayname = "MLRS 9A52 Smerch CM 300mm",
|
||||
minrange = 20000,
|
||||
maxrange = 70000,
|
||||
reloadtime = 2160,
|
||||
},
|
||||
["SPH 2S9 Nona"] = { --type "SAU 2-C9"
|
||||
minrange = 500, -- correct?
|
||||
maxrange = 7000, -- 7 km
|
||||
reloadtime = nil, -- unknown
|
||||
["Smerch_HE"] = {
|
||||
displayname = "MLRS 9A52 Smerch HE 300mm",
|
||||
minrange = 20000,
|
||||
maxrange = 70000,
|
||||
reloadtime = 2160,
|
||||
},
|
||||
["SPH M109 Paladin"] = { -- type "M-109", alias "M109"
|
||||
minrange = 300, -- correct?
|
||||
maxrange = 22000, -- 22 km
|
||||
reloadtime = nil, -- unknown
|
||||
["Uragan_BM-27"] = {
|
||||
displayname = "MLRS 9K57 Uragan BM-27 220mm",
|
||||
minrange = 11500,
|
||||
maxrange = 35800,
|
||||
reloadtime = 840,
|
||||
},
|
||||
["SpGH Dana"] = { -- type "SpGH_Dana"
|
||||
minrange = 300, -- correct?
|
||||
maxrange = 18700, -- 18.7 km
|
||||
reloadtime = nil, -- unknown
|
||||
["Grad-URAL"] = {
|
||||
displayname = "MLRS BM-21 Grad 122mm",
|
||||
minrange = 5000,
|
||||
maxrange = 19000,
|
||||
reloadtime = 420,
|
||||
},
|
||||
["MLRS BM-21 Grad"] = { --type "Grad-URAL", alias "MLRS BM-21 Grad"
|
||||
minrange = 5000, -- 5 km
|
||||
maxrange = 19000, -- 19 km
|
||||
reloadtime = 420, -- 7 min
|
||||
["HL_B8M1"] = {
|
||||
displayname = "MLRS HL with B8M1 80mm",
|
||||
minrange = 500,
|
||||
maxrange = 5000,
|
||||
reloadtime = nil,
|
||||
},
|
||||
["MLRS 9K57 Uragan BM-27"] = { -- type "Uragan_BM-27"
|
||||
minrange = 11500, -- 11.5 km
|
||||
maxrange = 35800, -- 35.8 km
|
||||
reloadtime = 840, -- 14 min
|
||||
["tt_B8M1"] = {
|
||||
displayname = "MLRS LC with B8M1 80mm",
|
||||
minrange = 500,
|
||||
maxrange = 5000,
|
||||
reloadtime = nil,
|
||||
},
|
||||
["MLRS 9A52 Smerch"] = { -- type "Smerch"
|
||||
minrange = 20000, -- 20 km
|
||||
maxrange = 70000, -- 70 km
|
||||
reloadtime = 2160, -- 36 min
|
||||
["MLRS"] = {
|
||||
displayname = "MLRS M270 227mm",
|
||||
minrange = 10000,
|
||||
maxrange = 32000,
|
||||
reloadtime = 540,
|
||||
},
|
||||
["MLRS M270"] = { --type "MRLS", alias "M270 MRLS"
|
||||
minrange = 10000, -- 10 km
|
||||
maxrange = 32000, -- 32 km
|
||||
reloadtime = 540, -- 9 min
|
||||
["2B11 mortar"] = {
|
||||
displayname = "Mortar 2B11 120mm",
|
||||
minrange = 500,
|
||||
maxrange = 7000,
|
||||
reloadtime = 30,
|
||||
},
|
||||
["PLZ05"] = {
|
||||
displayname = "PLZ-05",
|
||||
minrange = 500,
|
||||
maxrange = 23500,
|
||||
reloadtime = nil,
|
||||
},
|
||||
["SAU Gvozdika"] = {
|
||||
displayname = "SPH 2S1 Gvozdika 122mm",
|
||||
minrange = 300,
|
||||
maxrange = 15000,
|
||||
reloadtime = nil,
|
||||
},
|
||||
["SAU Msta"] = {
|
||||
displayname = "SPH 2S19 Msta 152mm",
|
||||
minrange = 300,
|
||||
maxrange = 23500,
|
||||
reloadtime = nil,
|
||||
},
|
||||
["SAU Akatsia"] = {
|
||||
displayname = "SPH 2S3 Akatsia 152mm",
|
||||
minrange = 300,
|
||||
maxrange = 17000,
|
||||
reloadtime = nil,
|
||||
},
|
||||
["SpGH_Dana"] = {
|
||||
displayname = "SPH Dana vz77 152mm",
|
||||
minrange = 300,
|
||||
maxrange = 18700,
|
||||
reloadtime = nil,
|
||||
},
|
||||
["M-109"] = {
|
||||
displayname = "SPH M109 Paladin 155mm",
|
||||
minrange = 300,
|
||||
maxrange = 22000,
|
||||
reloadtime = nil,
|
||||
},
|
||||
["M12_GMC"] = {
|
||||
displayname = "SPH M12 GMC 155mm",
|
||||
minrange = 300,
|
||||
maxrange = 18200,
|
||||
reloadtime = nil,
|
||||
},
|
||||
["Wespe124"] = {
|
||||
displayname = "SPH Sd.Kfz.124 Wespe 105mm",
|
||||
minrange = 300,
|
||||
maxrange = 7000,
|
||||
reloadtime = nil,
|
||||
},
|
||||
["T155_Firtina"] = {
|
||||
displayname = "SPH T155 Firtina 155mm",
|
||||
minrange = 300,
|
||||
maxrange = 41000,
|
||||
reloadtime = nil,
|
||||
},
|
||||
["SAU 2-C9"] = {
|
||||
displayname = "SPM 2S9 Nona 120mm M",
|
||||
minrange = 500,
|
||||
maxrange = 7000,
|
||||
reloadtime = nil,
|
||||
},
|
||||
}
|
||||
|
||||
--- Target.
|
||||
@@ -695,7 +780,7 @@ ARTY.db={
|
||||
|
||||
--- Arty script version.
|
||||
-- @field #string version
|
||||
ARTY.version="1.3.1"
|
||||
ARTY.version="1.3.3"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
@@ -797,8 +882,8 @@ function ARTY:New(group, alias)
|
||||
-- Maximum speed in km/h.
|
||||
self.SpeedMax=group:GetSpeedMax()
|
||||
|
||||
-- Group is mobile or not (e.g. mortars).
|
||||
if self.SpeedMax>1 then
|
||||
-- Group is mobile or not (e.g. mortars). Some immobile units have a speed of 1 m/s = 3.6 km/h. So we check this number.
|
||||
if self.SpeedMax>3.6 then
|
||||
self.ismobile=true
|
||||
else
|
||||
self.ismobile=false
|
||||
@@ -1923,7 +2008,7 @@ function ARTY:onafterStart(Controllable, From, Event, To)
|
||||
end
|
||||
|
||||
-- Check if we have and arty type that is in the DB.
|
||||
local _dbproperties=self:_CheckDB(self.DisplayName)
|
||||
local _dbproperties=self:_CheckDB(self.Type)
|
||||
self:T({dbproperties=_dbproperties})
|
||||
if _dbproperties~=nil then
|
||||
for property,value in pairs(_dbproperties) do
|
||||
@@ -1969,8 +2054,8 @@ function ARTY:onafterStart(Controllable, From, Event, To)
|
||||
text=text..string.format("Type = %s\n", self.Type)
|
||||
text=text..string.format("Display Name = %s\n", self.DisplayName)
|
||||
text=text..string.format("Number of units = %d\n", self.IniGroupStrength)
|
||||
text=text..string.format("Speed max = %d km/h\n", self.SpeedMax)
|
||||
text=text..string.format("Speed default = %d km/h\n", self.Speed)
|
||||
text=text..string.format("Speed max = %.1f km/h\n", self.SpeedMax)
|
||||
text=text..string.format("Speed default = %.1f km/h\n", self.Speed)
|
||||
text=text..string.format("Is mobile = %s\n", tostring(self.ismobile))
|
||||
text=text..string.format("Is cargo = %s\n", tostring(self.iscargo))
|
||||
text=text..string.format("Min range = %.1f km\n", self.minrange/1000)
|
||||
@@ -3049,7 +3134,7 @@ function ARTY:onafterOpenFire(Controllable, From, Event, To, target)
|
||||
local nfire=Narty
|
||||
local _type="shots"
|
||||
if target.weapontype==ARTY.WeaponType.Auto then
|
||||
nfire=Narty
|
||||
nfire=Nammo -- We take everything that is available
|
||||
_type="shots"
|
||||
elseif target.weapontype==ARTY.WeaponType.Cannon then
|
||||
nfire=Narty
|
||||
@@ -3070,6 +3155,8 @@ function ARTY:onafterOpenFire(Controllable, From, Event, To, target)
|
||||
nfire=Nmissiles
|
||||
_type="cruise missiles"
|
||||
end
|
||||
|
||||
--env.info(string.format("FF type=%s, Nrockets=%d, Nfire=%d target.nshells=%d", _type, Nrockets, nfire, target.nshells))
|
||||
|
||||
-- Adjust if less than requested ammo is left.
|
||||
target.nshells=math.min(target.nshells, nfire)
|
||||
|
||||
@@ -74,7 +74,7 @@
|
||||
-- @image Designation.JPG
|
||||
--
|
||||
-- Date: 24 Oct 2021
|
||||
-- Last Update: May 2024
|
||||
-- Last Update: Mar 2025
|
||||
--
|
||||
--- Class AUTOLASE
|
||||
-- @type AUTOLASE
|
||||
@@ -89,6 +89,10 @@
|
||||
-- @field #table playermenus
|
||||
-- @field #boolean smokemenu
|
||||
-- @field #boolean threatmenu
|
||||
-- @field #number RoundingPrecision
|
||||
-- @field #table smokeoffset
|
||||
-- @field #boolean increasegroundawareness
|
||||
-- @field #number MonitorFrequency
|
||||
-- @extends Ops.Intel#INTEL
|
||||
|
||||
---
|
||||
@@ -100,6 +104,9 @@ AUTOLASE = {
|
||||
alias = "",
|
||||
debug = false,
|
||||
smokemenu = true,
|
||||
RoundingPrecision = 0,
|
||||
increasegroundawareness = true,
|
||||
MonitorFrequency = 30,
|
||||
}
|
||||
|
||||
--- Laser spot info
|
||||
@@ -118,7 +125,7 @@ AUTOLASE = {
|
||||
|
||||
--- AUTOLASE class version.
|
||||
-- @field #string version
|
||||
AUTOLASE.version = "0.1.25"
|
||||
AUTOLASE.version = "0.1.31"
|
||||
|
||||
-------------------------------------------------------------------
|
||||
-- Begin Functional.Autolase.lua
|
||||
@@ -191,6 +198,7 @@ function AUTOLASE:New(RecceSet, Coalition, Alias, PilotSet)
|
||||
self.reporttimelong = 30
|
||||
self.smoketargets = false
|
||||
self.smokecolor = SMOKECOLOR.Red
|
||||
self.smokeoffset = nil
|
||||
self.notifypilots = true
|
||||
self.targetsperrecce = {}
|
||||
self.RecceUnits = {}
|
||||
@@ -207,6 +215,11 @@ function AUTOLASE:New(RecceSet, Coalition, Alias, PilotSet)
|
||||
self.playermenus = {}
|
||||
self.smokemenu = true
|
||||
self.threatmenu = true
|
||||
self.RoundingPrecision = 0
|
||||
self.increasegroundawareness = true
|
||||
self.MonitorFrequency = 30
|
||||
|
||||
self:EnableSmokeMenu({Angle=math.random(0,359),Distance=math.random(10,20)})
|
||||
|
||||
-- Set some string id for output to DCS.log file.
|
||||
self.lid=string.format("AUTOLASE %s (%s) | ", self.alias, self.coalition and UTILS.GetCoalitionName(self.coalition) or "unknown")
|
||||
@@ -309,16 +322,41 @@ end
|
||||
-- Helper Functions
|
||||
-------------------------------------------------------------------
|
||||
|
||||
--- [User] When using Monitor, set the frequency here in which the report will appear
|
||||
-- @param #AUTOLASE self
|
||||
-- @param #number Seconds Run the report loop every number of seconds defined here.
|
||||
-- @return #AUTOLASE self
|
||||
function AUTOLASE:SetMonitorFrequency(Seconds)
|
||||
self.MonitorFrequency = Seconds or 30
|
||||
return self
|
||||
end
|
||||
|
||||
--- [User] Set a table of possible laser codes.
|
||||
-- Each new RECCE can select a code from this table, default is { 1688, 1130, 4785, 6547, 1465, 4578 } .
|
||||
-- Each new RECCE can select a code from this table, default is { 1688, 1130, 4785, 6547, 1465, 4578 }.
|
||||
-- @param #AUTOLASE self
|
||||
-- @param #list<#number> LaserCodes
|
||||
-- @return #AUTOLASE
|
||||
-- @return #AUTOLASE self
|
||||
function AUTOLASE:SetLaserCodes( LaserCodes )
|
||||
self.LaserCodes = ( type( LaserCodes ) == "table" ) and LaserCodes or { LaserCodes }
|
||||
return self
|
||||
end
|
||||
|
||||
--- [User] Improve ground unit detection by using a zone scan and LOS check.
|
||||
-- @param #AUTOLASE self
|
||||
-- @return #AUTOLASE self
|
||||
function AUTOLASE:EnableImproveGroundUnitsDetection()
|
||||
self.increasegroundawareness = true
|
||||
return self
|
||||
end
|
||||
|
||||
--- [User] Do not improve ground unit detection by using a zone scan and LOS check.
|
||||
-- @param #AUTOLASE self
|
||||
-- @return #AUTOLASE self
|
||||
function AUTOLASE:DisableImproveGroundUnitsDetection()
|
||||
self.increasegroundawareness = false
|
||||
return self
|
||||
end
|
||||
|
||||
--- (Internal) Function to set pilot menu.
|
||||
-- @param #AUTOLASE self
|
||||
-- @return #AUTOLASE self
|
||||
@@ -600,11 +638,26 @@ function AUTOLASE:SetSmokeTargets(OnOff,Color)
|
||||
return self
|
||||
end
|
||||
|
||||
--- (User) Function to set rounding precision for BR distance output.
|
||||
-- @param #AUTOLASE self
|
||||
-- @param #number IDP Rounding precision before/after the decimal sign. Defaults to zero. Positive values round right of the decimal sign, negative ones left of the decimal sign.
|
||||
-- @return #AUTOLASE self
|
||||
function AUTOLASE:SetRoundingPrecsion(IDP)
|
||||
self.RoundingPrecision = IDP or 0
|
||||
return self
|
||||
end
|
||||
|
||||
--- (User) Show the "Switch smoke target..." menu entry for pilots. On by default.
|
||||
-- @param #AUTOLASE self
|
||||
-- @param #table Offset (Optional) Define an offset for the smoke, i.e. not directly on the unit itself, angle is degrees and distance is meters. E.g. `autolase:EnableSmokeMenu({Angle=30,Distance=20})`
|
||||
-- @return #AUTOLASE self
|
||||
function AUTOLASE:EnableSmokeMenu()
|
||||
function AUTOLASE:EnableSmokeMenu(Offset)
|
||||
self.smokemenu = true
|
||||
if Offset then
|
||||
self.smokeoffset = {}
|
||||
self.smokeoffset.Distance = Offset.Distance or math.random(10,20)
|
||||
self.smokeoffset.Angle = Offset.Angle or math.random(0,359)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -613,6 +666,7 @@ end
|
||||
-- @return #AUTOLASE self
|
||||
function AUTOLASE:DisableSmokeMenu()
|
||||
self.smokemenu = false
|
||||
self.smokeoffset = nil
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -671,7 +725,8 @@ function AUTOLASE:CleanCurrentLasing()
|
||||
local unit = recce:GetUnit(1)
|
||||
local name = unit:GetName()
|
||||
if not self.RecceUnits[name] then
|
||||
self.RecceUnits[name] = { name=name, unit=unit, cooldown = false, timestamp = timer.getAbsTime() }
|
||||
local isground = (unit and unit.IsGround) and unit:IsGround() or false
|
||||
self.RecceUnits[name] = { name=name, unit=unit, cooldown = false, timestamp = timer.getAbsTime(), isground=isground }
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -757,9 +812,11 @@ function AUTOLASE:ShowStatus(Group,Unit)
|
||||
end
|
||||
local code = self:GetLaserCode(unit:GetName())
|
||||
report:Add(string.format("Recce %s has code %d",name,code))
|
||||
report:Add("---------------")
|
||||
end
|
||||
end
|
||||
report:Add(string.format("Lasing min threat level %d",self.minthreatlevel))
|
||||
report:Add("---------------")
|
||||
local lines = 0
|
||||
for _ind,_entry in pairs(self.CurrentLasing) do
|
||||
local entry = _entry -- #AUTOLASE.LaserSpot
|
||||
@@ -779,22 +836,28 @@ function AUTOLASE:ShowStatus(Group,Unit)
|
||||
if playername then
|
||||
local settings = _DATABASE:GetPlayerSettings(playername)
|
||||
if settings then
|
||||
self:I("Get Settings ok!")
|
||||
self:T("Get Settings ok!")
|
||||
if settings:IsA2G_MGRS() then
|
||||
locationstring = entry.coordinate:ToStringMGRS(settings)
|
||||
elseif settings:IsA2G_LL_DMS() then
|
||||
locationstring = entry.coordinate:ToStringLLDMS(settings)
|
||||
elseif settings:IsA2G_LL_DDM() then
|
||||
locationstring = entry.coordinate:ToStringLLDDM(settings)
|
||||
elseif settings:IsA2G_BR() then
|
||||
locationstring = entry.coordinate:ToStringBR(Group:GetCoordinate() or Unit:GetCoordinate(),settings)
|
||||
-- attention this is the distance from the ASKING unit to target, not from RECCE to target!
|
||||
local startcoordinate = Unit:GetCoordinate() or Group:GetCoordinate()
|
||||
locationstring = entry.coordinate:ToStringBR(startcoordinate,settings,false,self.RoundingPrecision)
|
||||
end
|
||||
end
|
||||
end
|
||||
local text = string.format("%s lasing %s code %d\nat %s",reccename,typename,code,locationstring)
|
||||
local text = string.format("+ %s lasing %s code %d\nat %s",reccename,typename,code,locationstring)
|
||||
report:Add(text)
|
||||
report:Add("---------------")
|
||||
lines = lines + 1
|
||||
end
|
||||
if lines == 0 then
|
||||
report:Add("No targets!")
|
||||
report:Add("---------------")
|
||||
end
|
||||
local reporttime = self.reporttimelong
|
||||
if lines == 0 then reporttime = self.reporttimeshort end
|
||||
@@ -913,6 +976,65 @@ function AUTOLASE:CanLase(Recce,Unit)
|
||||
return canlase
|
||||
end
|
||||
|
||||
--- (Internal) Function to do a zone check per ground Recce and make found units and statics "known".
|
||||
-- @param #AUTOLASE self
|
||||
-- @return #AUTOLASE self
|
||||
function AUTOLASE:_Prescient()
|
||||
-- self.RecceUnits[name] = { name=name, unit=unit, cooldown = false, timestamp = timer.getAbsTime(), isground=isground }
|
||||
for _,_data in pairs(self.RecceUnits) do
|
||||
-- ground units only
|
||||
if _data.isground and _data.unit and _data.unit:IsAlive() then
|
||||
local unit = _data.unit -- Wrapper.Unit#UNIT
|
||||
local position = unit:GetCoordinate() -- Core.Point#COORDINATE
|
||||
local needsinit = false
|
||||
if position then
|
||||
local lastposition = unit:GetProperty("lastposition")
|
||||
-- property initiated?
|
||||
if not lastposition then
|
||||
unit:SetProperty("lastposition",position)
|
||||
lastposition = position
|
||||
needsinit = true
|
||||
end
|
||||
-- has moved?
|
||||
local dist = position:Get2DDistance(lastposition)
|
||||
-- refresh?
|
||||
local TNow = timer.getAbsTime()
|
||||
-- check
|
||||
if dist > 10 or needsinit==true or TNow - _data.timestamp > 29 then
|
||||
-- init scan objects
|
||||
local hasunits,hasstatics,_,Units,Statics = position:ScanObjects(self.LaseDistance,true,true,false)
|
||||
-- loop found units
|
||||
if hasunits then
|
||||
self:T(self.lid.."Checking possibly visible UNITs for Recce "..unit:GetName())
|
||||
for _,_target in pairs(Units) do -- Wrapper.Unit#UNIT object here
|
||||
local target = _target -- Wrapper.Unit#UNIT
|
||||
if target and target:GetCoalition() ~= self.coalition then
|
||||
if unit:IsLOS(target) and (not target:IsUnitDetected(unit))then
|
||||
unit:KnowUnit(target,true,true)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
-- loop found statics
|
||||
if hasstatics then
|
||||
self:T(self.lid.."Checking possibly visible STATICs for Recce "..unit:GetName())
|
||||
for _,_static in pairs(Statics) do -- DCS static object here
|
||||
local static = STATIC:Find(_static)
|
||||
if static and static:GetCoalition() ~= self.coalition then
|
||||
local IsLOS = position:IsLOS(static:GetCoordinate())
|
||||
if IsLOS then
|
||||
unit:KnowUnit(static,true,true)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------
|
||||
-- FSM Functions
|
||||
-------------------------------------------------------------------
|
||||
@@ -925,6 +1047,9 @@ end
|
||||
-- @return #AUTOLASE self
|
||||
function AUTOLASE:onbeforeMonitor(From, Event, To)
|
||||
self:T({From, Event, To})
|
||||
if self.increasegroundawareness then
|
||||
self:_Prescient()
|
||||
end
|
||||
-- Check if group has detected any units.
|
||||
self:UpdateIntel()
|
||||
return self
|
||||
@@ -953,7 +1078,7 @@ function AUTOLASE:onafterMonitor(From, Event, To)
|
||||
local grp = contact.group
|
||||
local coord = contact.position
|
||||
local reccename = contact.recce or "none"
|
||||
local threat = contact.threatlevel or 0
|
||||
local threat = contact.threatlevel or 0
|
||||
local reccegrp = UNIT:FindByName(reccename)
|
||||
if reccegrp then
|
||||
local reccecoord = reccegrp:GetCoordinate()
|
||||
@@ -1077,6 +1202,9 @@ function AUTOLASE:onafterMonitor(From, Event, To)
|
||||
}
|
||||
if self.smoketargets then
|
||||
local coord = unit:GetCoordinate()
|
||||
if self.smokeoffset then
|
||||
coord:Translate(self.smokeoffset.Distance,self.smokeoffset.Angle,true,true)
|
||||
end
|
||||
local color = self:GetSmokeColor(reccename)
|
||||
coord:Smoke(color)
|
||||
end
|
||||
@@ -1087,7 +1215,8 @@ function AUTOLASE:onafterMonitor(From, Event, To)
|
||||
end
|
||||
end
|
||||
|
||||
self:__Monitor(-30)
|
||||
local nextloop = -self.MonitorFrequency or -30
|
||||
self:__Monitor(nextloop)
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
@@ -357,7 +357,7 @@ function CLEANUP_AIRBASE.__:EventAddForCleanUp( Event )
|
||||
self:F({Event})
|
||||
|
||||
|
||||
if Event.IniDCSUnit and Event.IniCategory == Object.Category.UNIT then
|
||||
if Event.IniDCSUnit and Event.IniUnit and Event.IniCategory == Object.Category.UNIT then
|
||||
if self.CleanUpList[Event.IniDCSUnitName] == nil then
|
||||
if self:IsInAirbase( Event.IniUnit:GetVec2() ) then
|
||||
self:AddForCleanUp( Event.IniUnit, Event.IniDCSUnitName )
|
||||
@@ -365,7 +365,7 @@ function CLEANUP_AIRBASE.__:EventAddForCleanUp( Event )
|
||||
end
|
||||
end
|
||||
|
||||
if Event.TgtDCSUnit and Event.TgtCategory == Object.Category.UNIT then
|
||||
if Event.TgtDCSUnit and Event.TgtUnit and Event.TgtCategory == Object.Category.UNIT then
|
||||
if self.CleanUpList[Event.TgtDCSUnitName] == nil then
|
||||
if self:IsInAirbase( Event.TgtUnit:GetVec2() ) then
|
||||
self:AddForCleanUp( Event.TgtUnit, Event.TgtDCSUnitName )
|
||||
@@ -387,7 +387,7 @@ function CLEANUP_AIRBASE.__:CleanUpSchedule()
|
||||
local CleanUpUnit = CleanUpListData.CleanUpUnit -- Wrapper.Unit#UNIT
|
||||
local CleanUpGroupName = CleanUpListData.CleanUpGroupName
|
||||
|
||||
if CleanUpUnit:IsAlive() ~= nil then
|
||||
if CleanUpUnit and CleanUpUnit:IsAlive() ~= nil then
|
||||
|
||||
if self:IsInAirbase( CleanUpUnit:GetVec2() ) then
|
||||
|
||||
@@ -414,7 +414,7 @@ function CLEANUP_AIRBASE.__:CleanUpSchedule()
|
||||
end
|
||||
end
|
||||
-- Clean Units which are waiting for a very long time in the CleanUpZone.
|
||||
if CleanUpUnit and not CleanUpUnit:GetPlayerName() then
|
||||
if CleanUpUnit and (CleanUpUnit.GetPlayerName == nil or not CleanUpUnit:GetPlayerName()) then
|
||||
local CleanUpUnitVelocity = CleanUpUnit:GetVelocityKMH()
|
||||
if CleanUpUnitVelocity < 1 then
|
||||
if CleanUpListData.CleanUpMoved then
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
--
|
||||
-- ====
|
||||
-- @module Functional.ClientWatch
|
||||
-- @image ClientWatch.JPG
|
||||
-- @image clientwatch.jpg
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
--- CLIENTWATCH class
|
||||
@@ -28,6 +28,8 @@
|
||||
-- @field #string ClassName Name of the class.
|
||||
-- @field #boolean Debug Write Debug messages to DCS log file and send Debug messages to all players.
|
||||
-- @field #string lid String for DCS log file.
|
||||
-- @field #number FilterCoalition If not nil, will only activate for aircraft of the given coalition value.
|
||||
-- @field #number FilterCategory If not nil, will only activate for aircraft of the given category value.
|
||||
-- @extends Core.Fsm#FSM_CONTROLLABLE
|
||||
|
||||
--- Manage and track client slots easily to add your own client-based menus and modules to.
|
||||
@@ -66,7 +68,7 @@
|
||||
--
|
||||
-- -- Create an instance with a client unit prefix and send them a message when they spawn
|
||||
-- local clientInstance = CLIENTWATCH:New("Rotary")
|
||||
-- function clientInstance:OnAfterSpawn(From,Event,To,ClientObject)
|
||||
-- function clientInstance:OnAfterSpawn(From,Event,To,ClientObject,EventData)
|
||||
-- MESSAGE:New("Welcome to your aircraft!",10):ToUnit(ClientObject.Unit)
|
||||
-- end
|
||||
--
|
||||
@@ -108,7 +110,7 @@
|
||||
--
|
||||
-- -- Show a message to player when they take damage from a weapon
|
||||
-- local clientInstance = CLIENTWATCH:New("Rotary")
|
||||
-- function clientInstance:OnAfterSpawn(From,Event,To,ClientObject)
|
||||
-- function clientInstance:OnAfterSpawn(From,Event,To,ClientObject,EventData)
|
||||
-- function ClientObject:OnAfterHit(From,Event,To,EventData)
|
||||
-- local typeShooter = EventData.IniTypeName
|
||||
-- local nameWeapon = EventData.weapon_name
|
||||
@@ -120,6 +122,7 @@
|
||||
CLIENTWATCH = {}
|
||||
CLIENTWATCH.ClassName = "CLIENTWATCH"
|
||||
CLIENTWATCH.Debug = false
|
||||
CLIENTWATCH.DebugEventData = false
|
||||
CLIENTWATCH.lid = nil
|
||||
|
||||
-- @type CLIENTWATCHTools
|
||||
@@ -139,7 +142,10 @@ CLIENTWATCH.version="1.0.1"
|
||||
|
||||
--- Creates a new instance of CLIENTWATCH to add scripts to. Can be used multiple times with the same client/prefixes if you need it for multiple scripts.
|
||||
-- @param #CLIENTWATCH self
|
||||
-- @param #string, #table, or Wrapper.Client#CLIENT client Takes multiple inputs. If provided a #string, it will watch for clients whos UNIT NAME or GROUP NAME matches part of the #string as a prefix. You can also provide it with a #table containing multiple #string prefixes. Lastly, you can provide it with a Wrapper.Client#CLIENT of the specific client you want to apply this to.
|
||||
-- @param #string Will watch for clients whos UNIT NAME or GROUP NAME matches part of the #string as a prefix.
|
||||
-- @param #table Put strings in a table to use multiple prefixes for the above method.
|
||||
-- @param Wrapper.Client#CLIENT Provide a Moose CLIENT object to apply to that specific aircraft slot (static slots only!)
|
||||
-- @param #nil Leave blank to activate for ALL CLIENTS
|
||||
-- @return #CLIENTWATCH self
|
||||
function CLIENTWATCH:New(client)
|
||||
--Init FSM
|
||||
@@ -147,6 +153,9 @@ function CLIENTWATCH:New(client)
|
||||
self:SetStartState( "Idle" )
|
||||
self:AddTransition( "*", "Spawn", "*" )
|
||||
|
||||
self.FilterCoalition = nil
|
||||
self.FilterCategory = nil
|
||||
|
||||
--- User function for OnAfter "Spawn" event.
|
||||
-- @function [parent=#CLIENTWATCH] OnAfterSpawn
|
||||
-- @param #CLIENTWATCH self
|
||||
@@ -155,27 +164,50 @@ function CLIENTWATCH:New(client)
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param #table clientObject Custom object that handles events and stores Moose object data. See top documentation for more details.
|
||||
-- @param #table eventdata Data from EVENTS.Birth.
|
||||
|
||||
--Set up spawn tracking
|
||||
if type(client) == "table" or type(client) == "string" then
|
||||
if not client then
|
||||
if self.Debug then self:I({"New client instance created. ClientType = All clients"}) end
|
||||
self:HandleEvent(EVENTS.Birth)
|
||||
function self:OnEventBirth(eventdata)
|
||||
if (eventdata.IniCategory == 0 or eventdata.IniCategory == 1) and eventdata.IniPlayerName
|
||||
and (not self.FilterCoalition or self.FilterCoalition == eventdata.IniCoalition)
|
||||
and (not self.FilterCategory or self.FilterCategory == eventdata.IniCategory) then
|
||||
if self.Debug then
|
||||
self:I({"Client spawned in.",IniCategory = eventdata.IniCategory})
|
||||
end
|
||||
local clientWatchDebug = self.Debug
|
||||
local clientObject = CLIENTWATCHTools:_newClient(clientWatchDebug,eventdata)
|
||||
self:Spawn(clientObject,eventdata)
|
||||
end
|
||||
end
|
||||
elseif type(client) == "table" or type(client) == "string" then
|
||||
if type(client) == "table" then
|
||||
|
||||
--CLIENT TABLE
|
||||
if client.ClassName == "CLIENT" then
|
||||
if self.Debug then self:I({"New client instance created. ClientType = Wrapper.CLIENT",client}) end
|
||||
self.ClientName = client:GetName()
|
||||
self:HandleEvent(EVENTS.Birth)
|
||||
function self:OnEventBirth(eventdata)
|
||||
if self.Debug then UTILS.PrintTableToLog(eventdata) end
|
||||
if eventdata.IniCategory and eventdata.IniCategory <= 1 then
|
||||
if (eventdata.IniCategory == 0 or eventdata.IniCategory == 1) and eventdata.IniPlayerName
|
||||
and (not self.FilterCoalition or self.FilterCoalition == eventdata.IniCoalition)
|
||||
and (not self.FilterCategory or self.FilterCategory == eventdata.IniCategory) then
|
||||
if self.ClientName == eventdata.IniUnitName then
|
||||
local clientObject = CLIENTWATCHTools:_newClient(eventdata)
|
||||
self:Spawn(clientObject)
|
||||
if self.Debug then
|
||||
self:I({"Client spawned in.",IniCategory = eventdata.IniCategory})
|
||||
end
|
||||
local clientWatchDebug = self.Debug
|
||||
local clientObject = CLIENTWATCHTools:_newClient(clientWatchDebug,eventdata)
|
||||
self:Spawn(clientObject,eventdata)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--STRING TABLE
|
||||
else
|
||||
if self.Debug then self:I({"New client instance created. ClientType = Multiple Prefixes",client}) end
|
||||
local tableValid = true
|
||||
for _,entry in pairs(client) do
|
||||
if type(entry) ~= "string" then
|
||||
@@ -187,12 +219,17 @@ function CLIENTWATCH:New(client)
|
||||
if tableValid then
|
||||
self:HandleEvent(EVENTS.Birth)
|
||||
function self:OnEventBirth(eventdata)
|
||||
if self.Debug then UTILS.PrintTableToLog(eventdata) end
|
||||
for _,entry in pairs(client) do
|
||||
if eventdata.IniCategory and eventdata.IniCategory <= 1 then
|
||||
if (eventdata.IniCategory == 0 or eventdata.IniCategory == 1) and eventdata.IniPlayerName
|
||||
and (not self.FilterCoalition or self.FilterCoalition == eventdata.IniCoalition)
|
||||
and (not self.FilterCategory or self.FilterCategory == eventdata.IniCategory) then
|
||||
if string.match(eventdata.IniUnitName,entry) or string.match(eventdata.IniGroupName,entry) then
|
||||
local clientObject = CLIENTWATCHTools:_newClient(eventdata)
|
||||
self:Spawn(clientObject)
|
||||
if self.Debug then
|
||||
self:I({"Client spawned in.",IniCategory = eventdata.IniCategory})
|
||||
end
|
||||
local clientWatchDebug = self.Debug
|
||||
local clientObject = CLIENTWATCHTools:_newClient(clientWatchDebug,eventdata)
|
||||
self:Spawn(clientObject,eventdata)
|
||||
break
|
||||
end
|
||||
end
|
||||
@@ -201,15 +238,21 @@ function CLIENTWATCH:New(client)
|
||||
end
|
||||
end
|
||||
else
|
||||
if self.Debug then self:I({"New client instance created. ClientType = Single Prefix",client}) end
|
||||
|
||||
--SOLO STRING
|
||||
self:HandleEvent(EVENTS.Birth)
|
||||
function self:OnEventBirth(eventdata)
|
||||
if self.Debug then UTILS.PrintTableToLog(eventdata) end
|
||||
if eventdata.IniCategory and eventdata.IniCategory <= 1 then
|
||||
if (eventdata.IniCategory == 0 or eventdata.IniCategory == 1) and eventdata.IniPlayerName
|
||||
and (not self.FilterCoalition or self.FilterCoalition == eventdata.IniCoalition)
|
||||
and (not self.FilterCategory or self.FilterCategory == eventdata.IniCategory) then
|
||||
if string.match(eventdata.IniUnitName,client) or string.match(eventdata.IniGroupName,client) then
|
||||
local clientObject = CLIENTWATCHTools:_newClient(eventdata)
|
||||
self:Spawn(clientObject)
|
||||
if self.Debug then
|
||||
self:I({"Client spawned in.",IniCategory = eventdata.IniCategory})
|
||||
end
|
||||
local clientWatchDebug = self.Debug
|
||||
local clientObject = CLIENTWATCHTools:_newClient(clientWatchDebug,eventdata)
|
||||
self:Spawn(clientObject,eventdata)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -222,13 +265,41 @@ function CLIENTWATCH:New(client)
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Filter out all clients not belonging to the provided coalition
|
||||
-- @param #CLIENTWATCH self
|
||||
-- @param #number Coalition number (1 = red, 2 = blue)
|
||||
-- @param #string Coalition string ('red' or 'blue')
|
||||
function CLIENTWATCH:FilterByCoalition(value)
|
||||
if value == 1 or value == "red" then
|
||||
self.FilterCoalition = 1
|
||||
else
|
||||
self.FilterCoalition = 2
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Filter out all clients that are not of the given category
|
||||
-- @param #CLIENTWATCH self
|
||||
-- @param #number Category number (0 = airplane, 1 = helicopter)
|
||||
-- @param #string Category string ('airplane' or 'helicopter')
|
||||
function CLIENTWATCH:FilterByCategory(value)
|
||||
if value == 1 or value == "helicopter" then
|
||||
self.FilterCategory = 1
|
||||
else
|
||||
self.FilterCategory = 0
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Internal function for creating a new client on birth. Do not use!!!.
|
||||
-- @param #CLIENTWATCHTools self
|
||||
-- @param #EVENTS.Birth EventData
|
||||
-- @return #CLIENTWATCHTools self
|
||||
function CLIENTWATCHTools:_newClient(eventdata)
|
||||
function CLIENTWATCHTools:_newClient(clientWatchDebug,eventdata)
|
||||
--Init FSM
|
||||
local self=BASE:Inherit(self, FSM:New())
|
||||
self:SetStartState( "Alive" )
|
||||
@@ -299,78 +370,130 @@ function CLIENTWATCHTools:_newClient(eventdata)
|
||||
|
||||
function self:OnEventHit(EventData)
|
||||
if EventData.TgtUnitName == self.UnitName then
|
||||
if clientWatchDebug then
|
||||
self:I({"Client triggered hit event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName})
|
||||
|
||||
end
|
||||
self:Hit(EventData)
|
||||
end
|
||||
end
|
||||
|
||||
function self:OnEventKill(EventData)
|
||||
if EventData.IniUnitName == self.UnitName then
|
||||
if clientWatchDebug then
|
||||
self:I({"Client triggered kill event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName})
|
||||
|
||||
end
|
||||
self:Kill(EventData)
|
||||
end
|
||||
end
|
||||
|
||||
function self:OnEventScore(EventData)
|
||||
if EventData.IniUnitName == self.UnitName then
|
||||
if clientWatchDebug then
|
||||
self:I({"Client triggered score event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName})
|
||||
|
||||
end
|
||||
self:Score(EventData)
|
||||
end
|
||||
end
|
||||
|
||||
function self:OnEventShot(EventData)
|
||||
if EventData.IniUnitName == self.UnitName then
|
||||
if clientWatchDebug then
|
||||
self:I({"Client triggered shot event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName})
|
||||
|
||||
end
|
||||
self:Shot(EventData)
|
||||
end
|
||||
end
|
||||
|
||||
function self:OnEventShootingStart(EventData)
|
||||
if EventData.IniUnitName == self.UnitName then
|
||||
if clientWatchDebug then
|
||||
self:I({"Client triggered shooting start event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName})
|
||||
|
||||
end
|
||||
self:ShootingStart(EventData)
|
||||
end
|
||||
end
|
||||
|
||||
function self:OnEventShootingEnd(EventData)
|
||||
if EventData.IniUnitName == self.UnitName then
|
||||
if clientWatchDebug then
|
||||
self:I({"Client triggered shooting end event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName})
|
||||
|
||||
end
|
||||
self:ShootingEnd(EventData)
|
||||
end
|
||||
end
|
||||
|
||||
function self:OnEventLand(EventData)
|
||||
if EventData.IniUnitName == self.UnitName then
|
||||
if clientWatchDebug then
|
||||
self:I({"Client triggered land event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName})
|
||||
|
||||
end
|
||||
self:Land(EventData)
|
||||
end
|
||||
end
|
||||
|
||||
function self:OnEventTakeoff(EventData)
|
||||
if EventData.IniUnitName == self.UnitName then
|
||||
if clientWatchDebug then
|
||||
self:I({"Client triggered takeoff event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName})
|
||||
|
||||
end
|
||||
self:Takeoff(EventData)
|
||||
end
|
||||
end
|
||||
|
||||
function self:OnEventRunwayTakeoff(EventData)
|
||||
if EventData.IniUnitName == self.UnitName then
|
||||
if clientWatchDebug then
|
||||
self:I({"Client triggered runway takeoff event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName})
|
||||
|
||||
end
|
||||
self:RunwayTakeoff(EventData)
|
||||
end
|
||||
end
|
||||
|
||||
function self:OnEventRunwayTouch(EventData)
|
||||
if EventData.IniUnitName == self.UnitName then
|
||||
if clientWatchDebug then
|
||||
self:I({"Client triggered runway touch event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName})
|
||||
|
||||
end
|
||||
self:RunwayTouch(EventData)
|
||||
end
|
||||
end
|
||||
|
||||
function self:OnEventRefueling(EventData)
|
||||
if EventData.IniUnitName == self.UnitName then
|
||||
if clientWatchDebug then
|
||||
self:I({"Client triggered refueling event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName})
|
||||
|
||||
end
|
||||
self:Refueling(EventData)
|
||||
end
|
||||
end
|
||||
|
||||
function self:OnEventRefuelingStop(EventData)
|
||||
if EventData.IniUnitName == self.UnitName then
|
||||
if clientWatchDebug then
|
||||
self:I({"Client triggered refueling event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName})
|
||||
|
||||
end
|
||||
self:RefuelingStop(EventData)
|
||||
end
|
||||
end
|
||||
|
||||
function self:OnEventPlayerLeaveUnit(EventData)
|
||||
if EventData.IniUnitName == self.UnitName then
|
||||
if clientWatchDebug then
|
||||
self:I({"Client triggered leave unit event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName})
|
||||
|
||||
end
|
||||
self:PlayerLeaveUnit(EventData)
|
||||
self._deadRoutine()
|
||||
end
|
||||
@@ -378,6 +501,10 @@ function CLIENTWATCHTools:_newClient(eventdata)
|
||||
|
||||
function self:OnEventCrash(EventData)
|
||||
if EventData.IniUnitName == self.UnitName then
|
||||
if clientWatchDebug then
|
||||
self:I({"Client triggered crash event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName})
|
||||
|
||||
end
|
||||
self:Crash(EventData)
|
||||
self._deadRoutine()
|
||||
end
|
||||
@@ -385,6 +512,10 @@ function CLIENTWATCHTools:_newClient(eventdata)
|
||||
|
||||
function self:OnEventDead(EventData)
|
||||
if EventData.IniUnitName == self.UnitName then
|
||||
if clientWatchDebug then
|
||||
self:I({"Client triggered dead event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName})
|
||||
|
||||
end
|
||||
self:Dead(EventData)
|
||||
self._deadRoutine()
|
||||
end
|
||||
@@ -392,6 +523,10 @@ function CLIENTWATCHTools:_newClient(eventdata)
|
||||
|
||||
function self:OnEventPilotDead(EventData)
|
||||
if EventData.IniUnitName == self.UnitName then
|
||||
if clientWatchDebug then
|
||||
self:I({"Client triggered pilot dead event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName})
|
||||
|
||||
end
|
||||
self:PilotDead(EventData)
|
||||
self._deadRoutine()
|
||||
end
|
||||
@@ -399,6 +534,10 @@ function CLIENTWATCHTools:_newClient(eventdata)
|
||||
|
||||
function self:OnEventUnitLost(EventData)
|
||||
if EventData.IniUnitName == self.UnitName then
|
||||
if clientWatchDebug then
|
||||
self:I({"Client triggered unit lost event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName})
|
||||
|
||||
end
|
||||
self:UnitLost(EventData)
|
||||
self._deadRoutine()
|
||||
end
|
||||
@@ -406,6 +545,10 @@ function CLIENTWATCHTools:_newClient(eventdata)
|
||||
|
||||
function self:OnEventEjection(EventData)
|
||||
if EventData.IniUnitName == self.UnitName then
|
||||
if clientWatchDebug then
|
||||
self:I({"Client triggered ejection event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName})
|
||||
|
||||
end
|
||||
self:Ejection(EventData)
|
||||
self._deadRoutine()
|
||||
end
|
||||
@@ -413,6 +556,10 @@ function CLIENTWATCHTools:_newClient(eventdata)
|
||||
|
||||
function self:OnEventHumanFailure(EventData)
|
||||
if EventData.IniUnitName == self.UnitName then
|
||||
if clientWatchDebug then
|
||||
self:I({"Client triggered human failure event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName})
|
||||
|
||||
end
|
||||
self:HumanFailure(EventData)
|
||||
if not self.Unit:IsAlive() then
|
||||
self._deadRoutine()
|
||||
@@ -422,42 +569,70 @@ function CLIENTWATCHTools:_newClient(eventdata)
|
||||
|
||||
function self:OnEventHumanAircraftRepairFinish(EventData)
|
||||
if EventData.IniUnitName == self.UnitName then
|
||||
if clientWatchDebug then
|
||||
self:I({"Client triggered repair finished event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName})
|
||||
|
||||
end
|
||||
self:HumanAircraftRepairFinish(EventData)
|
||||
end
|
||||
end
|
||||
|
||||
function self:OnEventHumanAircraftRepairStart(EventData)
|
||||
if EventData.IniUnitName == self.UnitName then
|
||||
if clientWatchDebug then
|
||||
self:I({"Client triggered repair start event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName})
|
||||
|
||||
end
|
||||
self:HumanAircraftRepairStart(EventData)
|
||||
end
|
||||
end
|
||||
|
||||
function self:OnEventEngineShutdown(EventData)
|
||||
if EventData.IniUnitName == self.UnitName then
|
||||
if clientWatchDebug then
|
||||
self:I({"Client triggered engine shutdown event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName})
|
||||
|
||||
end
|
||||
self:EngineShutdown(EventData)
|
||||
end
|
||||
end
|
||||
|
||||
function self:OnEventEngineStartup(EventData)
|
||||
if EventData.IniUnitName == self.UnitName then
|
||||
if clientWatchDebug then
|
||||
self:I({"Client triggered engine startup event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName})
|
||||
|
||||
end
|
||||
self:EngineStartup(EventData)
|
||||
end
|
||||
end
|
||||
|
||||
function self:OnEventWeaponAdd(EventData)
|
||||
if EventData.IniUnitName == self.UnitName then
|
||||
if clientWatchDebug then
|
||||
self:I({"Client triggered weapon add event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName})
|
||||
|
||||
end
|
||||
self:WeaponAdd(EventData)
|
||||
end
|
||||
end
|
||||
|
||||
function self:OnEventWeaponDrop(EventData)
|
||||
if EventData.IniUnitName == self.UnitName then
|
||||
if clientWatchDebug then
|
||||
self:I({"Client triggered weapon drop event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName})
|
||||
|
||||
end
|
||||
self:WeaponDrop(EventData)
|
||||
end
|
||||
end
|
||||
|
||||
function self:OnEventWeaponRearm(EventData)
|
||||
if EventData.IniUnitName == self.UnitName then
|
||||
if clientWatchDebug then
|
||||
self:I({"Client triggered weapon rearm event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName})
|
||||
|
||||
end
|
||||
self:WeaponRearm(EventData)
|
||||
end
|
||||
end
|
||||
@@ -466,6 +641,9 @@ function CLIENTWATCHTools:_newClient(eventdata)
|
||||
--Fallback timer
|
||||
self.FallbackTimer = TIMER:New(function()
|
||||
if not self.Unit:IsAlive() then
|
||||
if clientWatchDebug then
|
||||
self:I({"Client is registered as dead without an event trigger. Running fallback dead routine.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName})
|
||||
end
|
||||
self._deadRoutine()
|
||||
end
|
||||
end)
|
||||
@@ -473,6 +651,7 @@ function CLIENTWATCHTools:_newClient(eventdata)
|
||||
|
||||
--Stop event handlers and trigger Despawn
|
||||
function self._deadRoutine()
|
||||
if clientWatchDebug then self:I({"Client dead routine triggered. Shutting down tracking...",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName}) end
|
||||
self:UnHandleEvent( EVENTS.Hit )
|
||||
self:UnHandleEvent( EVENTS.Kill )
|
||||
self:UnHandleEvent( EVENTS.Score )
|
||||
@@ -503,6 +682,6 @@ function CLIENTWATCHTools:_newClient(eventdata)
|
||||
self:Despawn()
|
||||
end
|
||||
|
||||
self:I({"CLIENT SPAWN EVENT", PlayerName = self.PlayerName, UnitName = self.UnitName, GroupName = self.GroupName})
|
||||
self:I({"Detected client spawn and applied internal functions and events.", PlayerName = self.PlayerName, UnitName = self.UnitName, GroupName = self.GroupName})
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -595,7 +595,8 @@ do -- DETECTION_BASE
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
---
|
||||
-- @param #DETECTION_BASE self
|
||||
-- @param #string From The From State string.
|
||||
-- @param #string Event The Event string.
|
||||
@@ -615,7 +616,7 @@ do -- DETECTION_BASE
|
||||
self:T( { "DetectionGroup is Alive", Detection:GetName() } )
|
||||
|
||||
local DetectionGroupName = Detection:GetName()
|
||||
local DetectionUnit = Detection:GetUnit( 1 )
|
||||
local DetectionUnit = Detection:GetFirstUnitAlive()
|
||||
|
||||
local DetectedUnits = {}
|
||||
|
||||
@@ -632,26 +633,26 @@ do -- DETECTION_BASE
|
||||
--self:T(UTILS.PrintTableToLog(DetectedTargets))
|
||||
|
||||
|
||||
for DetectionObjectID, Detection in pairs( DetectedTargets ) do
|
||||
for DetectionObjectID, Detection in pairs( DetectedTargets or {}) do
|
||||
local DetectedObject = Detection.object -- DCS#Object
|
||||
|
||||
if DetectedObject and DetectedObject:isExist() and DetectedObject.id_ < 50000000 then -- and ( DetectedObject:getCategory() == Object.Category.UNIT or DetectedObject:getCategory() == Object.Category.STATIC ) then
|
||||
local DetectedObjectName = DetectedObject:getName()
|
||||
if not self.DetectedObjects[DetectedObjectName] then
|
||||
self.DetectedObjects[DetectedObjectName] = self.DetectedObjects[DetectedObjectName] or {}
|
||||
self.DetectedObjects[DetectedObjectName].Name = DetectedObjectName
|
||||
self.DetectedObjects[DetectedObjectName].Name = DetectedObjectName
|
||||
self.DetectedObjects[DetectedObjectName].Object = DetectedObject
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for DetectionObjectName, DetectedObjectData in pairs( self.DetectedObjects ) do
|
||||
for DetectionObjectName, DetectedObjectData in pairs( self.DetectedObjects or {}) do
|
||||
|
||||
local DetectedObject = DetectedObjectData.Object
|
||||
|
||||
if DetectedObject:isExist() then
|
||||
|
||||
local TargetIsDetected, TargetIsVisible, TargetLastTime, TargetKnowType, TargetKnowDistance, TargetLastPos, TargetLastVelocity = DetectionUnit:IsTargetDetected(
|
||||
local TargetIsDetected, TargetIsVisible, TargetKnowType, TargetKnowDistance, TargetLastTime, TargetLastPos, TargetLastVelocity = DetectionUnit:IsTargetDetected(
|
||||
DetectedObject,
|
||||
self.DetectVisual,
|
||||
self.DetectOptical,
|
||||
|
||||
@@ -1154,8 +1154,6 @@ function ESCORT:_ReportTargetsScheduler()
|
||||
|
||||
if self.EscortGroup:IsAlive() and self.EscortClient:IsAlive() then
|
||||
|
||||
if true then
|
||||
|
||||
local EscortGroupName = self.EscortGroup:GetName()
|
||||
|
||||
self.EscortMenuAttackNearbyTargets:RemoveSubMenus()
|
||||
@@ -1226,177 +1224,6 @@ function ESCORT:_ReportTargetsScheduler()
|
||||
end
|
||||
|
||||
return true
|
||||
else
|
||||
-- local EscortGroupName = self.EscortGroup:GetName()
|
||||
-- local EscortTargets = self.EscortGroup:GetDetectedTargets()
|
||||
--
|
||||
-- local ClientEscortTargets = self.EscortClient._EscortGroups[EscortGroupName].Targets
|
||||
--
|
||||
-- local EscortTargetMessages = ""
|
||||
-- for EscortTargetID, EscortTarget in pairs( EscortTargets ) do
|
||||
-- local EscortObject = EscortTarget.object
|
||||
-- self:T( EscortObject )
|
||||
-- if EscortObject and EscortObject:isExist() and EscortObject.id_ < 50000000 then
|
||||
--
|
||||
-- local EscortTargetUnit = UNIT:Find( EscortObject )
|
||||
-- local EscortTargetUnitName = EscortTargetUnit:GetName()
|
||||
--
|
||||
--
|
||||
--
|
||||
-- -- local EscortTargetIsDetected,
|
||||
-- -- EscortTargetIsVisible,
|
||||
-- -- EscortTargetLastTime,
|
||||
-- -- EscortTargetKnowType,
|
||||
-- -- EscortTargetKnowDistance,
|
||||
-- -- EscortTargetLastPos,
|
||||
-- -- EscortTargetLastVelocity
|
||||
-- -- = self.EscortGroup:IsTargetDetected( EscortObject )
|
||||
-- --
|
||||
-- -- self:T( { EscortTargetIsDetected,
|
||||
-- -- EscortTargetIsVisible,
|
||||
-- -- EscortTargetLastTime,
|
||||
-- -- EscortTargetKnowType,
|
||||
-- -- EscortTargetKnowDistance,
|
||||
-- -- EscortTargetLastPos,
|
||||
-- -- EscortTargetLastVelocity } )
|
||||
--
|
||||
--
|
||||
-- local EscortTargetUnitVec3 = EscortTargetUnit:GetVec3()
|
||||
-- local EscortVec3 = self.EscortGroup:GetVec3()
|
||||
-- local Distance = ( ( EscortTargetUnitVec3.x - EscortVec3.x )^2 +
|
||||
-- ( EscortTargetUnitVec3.y - EscortVec3.y )^2 +
|
||||
-- ( EscortTargetUnitVec3.z - EscortVec3.z )^2
|
||||
-- ) ^ 0.5 / 1000
|
||||
--
|
||||
-- self:T( { self.EscortGroup:GetName(), EscortTargetUnit:GetName(), Distance, EscortTarget } )
|
||||
--
|
||||
-- if Distance <= 15 then
|
||||
--
|
||||
-- if not ClientEscortTargets[EscortTargetUnitName] then
|
||||
-- ClientEscortTargets[EscortTargetUnitName] = {}
|
||||
-- end
|
||||
-- ClientEscortTargets[EscortTargetUnitName].AttackUnit = EscortTargetUnit
|
||||
-- ClientEscortTargets[EscortTargetUnitName].visible = EscortTarget.visible
|
||||
-- ClientEscortTargets[EscortTargetUnitName].type = EscortTarget.type
|
||||
-- ClientEscortTargets[EscortTargetUnitName].distance = EscortTarget.distance
|
||||
-- else
|
||||
-- if ClientEscortTargets[EscortTargetUnitName] then
|
||||
-- ClientEscortTargets[EscortTargetUnitName] = nil
|
||||
-- end
|
||||
-- end
|
||||
-- end
|
||||
-- end
|
||||
--
|
||||
-- self:T( { "Sorting Targets Table:", ClientEscortTargets } )
|
||||
-- table.sort( ClientEscortTargets, function( a, b ) return a.Distance < b.Distance end )
|
||||
-- self:T( { "Sorted Targets Table:", ClientEscortTargets } )
|
||||
--
|
||||
-- -- Remove the sub menus of the Attack menu of the Escort for the EscortGroup.
|
||||
-- self.EscortMenuAttackNearbyTargets:RemoveSubMenus()
|
||||
--
|
||||
-- if self.EscortMenuTargetAssistance then
|
||||
-- self.EscortMenuTargetAssistance:RemoveSubMenus()
|
||||
-- end
|
||||
--
|
||||
-- --for MenuIndex = 1, #self.EscortMenuAttackTargets do
|
||||
-- -- self:T( { "Remove Menu:", self.EscortMenuAttackTargets[MenuIndex] } )
|
||||
-- -- self.EscortMenuAttackTargets[MenuIndex] = self.EscortMenuAttackTargets[MenuIndex]:Remove()
|
||||
-- --end
|
||||
--
|
||||
--
|
||||
-- if ClientEscortTargets then
|
||||
-- for ClientEscortTargetUnitName, ClientEscortTargetData in pairs( ClientEscortTargets ) do
|
||||
--
|
||||
-- for ClientEscortGroupName, EscortGroupData in pairs( self.EscortClient._EscortGroups ) do
|
||||
--
|
||||
-- if ClientEscortTargetData and ClientEscortTargetData.AttackUnit:IsAlive() then
|
||||
--
|
||||
-- local EscortTargetMessage = ""
|
||||
-- local EscortTargetCategoryName = ClientEscortTargetData.AttackUnit:GetCategoryName()
|
||||
-- local EscortTargetCategoryType = ClientEscortTargetData.AttackUnit:GetTypeName()
|
||||
-- if ClientEscortTargetData.type then
|
||||
-- EscortTargetMessage = EscortTargetMessage .. EscortTargetCategoryName .. " (" .. EscortTargetCategoryType .. ") at "
|
||||
-- else
|
||||
-- EscortTargetMessage = EscortTargetMessage .. "Unknown target at "
|
||||
-- end
|
||||
--
|
||||
-- local EscortTargetUnitVec3 = ClientEscortTargetData.AttackUnit:GetVec3()
|
||||
-- local EscortVec3 = self.EscortGroup:GetVec3()
|
||||
-- local Distance = ( ( EscortTargetUnitVec3.x - EscortVec3.x )^2 +
|
||||
-- ( EscortTargetUnitVec3.y - EscortVec3.y )^2 +
|
||||
-- ( EscortTargetUnitVec3.z - EscortVec3.z )^2
|
||||
-- ) ^ 0.5 / 1000
|
||||
--
|
||||
-- self:T( { self.EscortGroup:GetName(), ClientEscortTargetData.AttackUnit:GetName(), Distance, ClientEscortTargetData.AttackUnit } )
|
||||
-- if ClientEscortTargetData.visible == false then
|
||||
-- EscortTargetMessage = EscortTargetMessage .. string.format( "%.2f", Distance ) .. " estimated km"
|
||||
-- else
|
||||
-- EscortTargetMessage = EscortTargetMessage .. string.format( "%.2f", Distance ) .. " km"
|
||||
-- end
|
||||
--
|
||||
-- if ClientEscortTargetData.visible then
|
||||
-- EscortTargetMessage = EscortTargetMessage .. ", visual"
|
||||
-- end
|
||||
--
|
||||
-- if ClientEscortGroupName == EscortGroupName then
|
||||
--
|
||||
-- MENU_GROUP_COMMAND:New( self.EscortClient,
|
||||
-- EscortTargetMessage,
|
||||
-- self.EscortMenuAttackNearbyTargets,
|
||||
-- ESCORT._AttackTarget,
|
||||
-- { ParamSelf = self,
|
||||
-- ParamUnit = ClientEscortTargetData.AttackUnit
|
||||
-- }
|
||||
-- )
|
||||
-- EscortTargetMessages = EscortTargetMessages .. "\n - " .. EscortTargetMessage
|
||||
-- else
|
||||
-- if self.EscortMenuTargetAssistance then
|
||||
-- local MenuTargetAssistance = MENU_GROUP:New( self.EscortClient, EscortGroupData.EscortName, self.EscortMenuTargetAssistance )
|
||||
-- MENU_GROUP_COMMAND:New( self.EscortClient,
|
||||
-- EscortTargetMessage,
|
||||
-- MenuTargetAssistance,
|
||||
-- ESCORT._AssistTarget,
|
||||
-- self,
|
||||
-- EscortGroupData.EscortGroup,
|
||||
-- ClientEscortTargetData.AttackUnit
|
||||
-- )
|
||||
-- end
|
||||
-- end
|
||||
-- else
|
||||
-- ClientEscortTargetData = nil
|
||||
-- end
|
||||
-- end
|
||||
-- end
|
||||
--
|
||||
-- if EscortTargetMessages ~= "" and self.ReportTargets == true then
|
||||
-- self.EscortGroup:MessageToClient( "Detected targets within 15 km range:" .. EscortTargetMessages:gsub("\n$",""), 20, self.EscortClient )
|
||||
-- else
|
||||
-- self.EscortGroup:MessageToClient( "No targets detected!", 20, self.EscortClient )
|
||||
-- end
|
||||
-- end
|
||||
--
|
||||
-- if self.EscortMenuResumeMission then
|
||||
-- self.EscortMenuResumeMission:RemoveSubMenus()
|
||||
--
|
||||
-- -- if self.EscortMenuResumeWayPoints then
|
||||
-- -- for MenuIndex = 1, #self.EscortMenuResumeWayPoints do
|
||||
-- -- self:T( { "Remove Menu:", self.EscortMenuResumeWayPoints[MenuIndex] } )
|
||||
-- -- self.EscortMenuResumeWayPoints[MenuIndex] = self.EscortMenuResumeWayPoints[MenuIndex]:Remove()
|
||||
-- -- end
|
||||
-- -- end
|
||||
--
|
||||
-- local TaskPoints = self:RegisterRoute()
|
||||
-- for WayPointID, WayPoint in pairs( TaskPoints ) do
|
||||
-- local EscortVec3 = self.EscortGroup:GetVec3()
|
||||
-- local Distance = ( ( WayPoint.x - EscortVec3.x )^2 +
|
||||
-- ( WayPoint.y - EscortVec3.z )^2
|
||||
-- ) ^ 0.5 / 1000
|
||||
-- MENU_GROUP_COMMAND:New( self.EscortClient, "Waypoint " .. WayPointID .. " at " .. string.format( "%.2f", Distance ).. "km", self.EscortMenuResumeMission, ESCORT._ResumeMission, { ParamSelf = self, ParamWayPoint = WayPointID } )
|
||||
-- end
|
||||
-- end
|
||||
--
|
||||
-- return true
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
|
||||
@@ -1060,7 +1060,7 @@ function FOX:onafterMissileLaunch(From, Event, To, missile)
|
||||
|
||||
-- Tracking info and init of last bomb position.
|
||||
local text=string.format("FOX: Tracking missile %s(%s) - target %s - shooter %s", missile.missileType, missile.missileName, tostring(missile.targetName), missile.shooterName)
|
||||
self:I(FOX.lid..text)
|
||||
self:T(FOX.lid..text)
|
||||
MESSAGE:New(text, 10):ToAllIf(self.Debug)
|
||||
|
||||
-- Loop over players.
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
-- @module Functional.Mantis
|
||||
-- @image Functional.Mantis.jpg
|
||||
--
|
||||
-- Last Update: July 2024
|
||||
-- Last Update: Mar 2025
|
||||
|
||||
-------------------------------------------------------------------------
|
||||
--- **MANTIS** class, extends Core.Base#BASE
|
||||
@@ -60,6 +60,9 @@
|
||||
-- @field #number ShoradActDistance Distance of an attacker in meters from a Mantis SAM site, on which Shorad will be switched on. Useful to not give away Shorad sites too early. Default 15km. Should be smaller than checkradius.
|
||||
-- @field #boolean checkforfriendlies If true, do not activate a SAM installation if a friendly aircraft is in firing range.
|
||||
-- @field #table FilterZones Table of Core.Zone#ZONE Zones Consider SAM groups in this zone(s) only for this MANTIS instance, must be handed as #table of Zone objects.
|
||||
-- @field #boolean SmokeDecoy If true, smoke short range SAM units as decoy if a plane is in firing range.
|
||||
-- @field #number SmokeDecoyColor Color to use, defaults to SMOKECOLOR.White
|
||||
-- @field #number checkcounter Counter for SAM Table refreshes
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
|
||||
@@ -71,7 +74,7 @@
|
||||
--
|
||||
-- * Moose derived Modular, Automatic and Network capable Targeting and Interception System.
|
||||
-- * Controls a network of SAM sites. Uses detection to switch on the SAM site closest to the enemy.
|
||||
-- * **Automatic mode** (default since 0.8) can set-up your SAM site network automatically for you
|
||||
-- * **Automatic mode** (default since 0.8) will set-up your SAM site network automatically for you
|
||||
-- * **Classic mode** behaves like before
|
||||
-- * Leverage evasiveness from SEAD, leverage attack range setting
|
||||
-- * Automatic setup of SHORAD based on groups of the class "short-range"
|
||||
@@ -86,6 +89,7 @@
|
||||
-- * SAM sites, e.g. each **group name** begins with "Red SAM"
|
||||
-- * EWR network and AWACS, e.g. each **group name** begins with "Red EWR" and *not* e.g. "Red SAM EWR" (overlap with "Red SAM"), "Red EWR Awacs" will be found by "Red EWR"
|
||||
-- * SHORAD, e.g. each **group name** begins with "Red SHORAD" and *not" e.g. just "SHORAD" because you might also have "Blue SHORAD"
|
||||
-- * Point Defense, e.g. each **group name** begins with "Red AAA" and *not" e.g. just "AAA" because you might also have "Blue AAA"
|
||||
--
|
||||
-- It's important to get this right because of the nature of the filter-system in @{Core.Set#SET_GROUP}. Filters are "greedy", that is they
|
||||
-- will match *any* string that contains the search string - hence we need to avoid that SAMs, EWR and SHORAD step on each other\'s toes.
|
||||
@@ -144,6 +148,7 @@
|
||||
-- **Location** is of highest importance here. Whilst AWACS in DCS has almost the "all seeing eye", EWR don't have that. Choose your location wisely, against a mountain backdrop or inside a valley even the best EWR system
|
||||
-- doesn't work well. Prefer higher-up locations with a good view; use F7 in-game to check where you actually placed your EWR and have a look around. Apart from the obvious choice, do also consider other radar units
|
||||
-- for this role, most have "SR" (search radar) or "STR" (search and track radar) in their names, use the encyclopedia to see what they actually do.
|
||||
-- **HINT** Set at least one EWR on invisible and immortal so MANTIS doesn't stop working.
|
||||
--
|
||||
-- ## 1.2 SAM sites
|
||||
--
|
||||
@@ -192,26 +197,24 @@
|
||||
-- mybluemantis:AddZones(AcceptZones,RejectZones,ConflictZones)
|
||||
--
|
||||
--
|
||||
-- ### 2.1.2 Change the number of long-, mid- and short-range systems going live on a detected target:
|
||||
-- ### 2.1.2 Change the number of long-, mid- and short-range, point defense systems going live on a detected target:
|
||||
--
|
||||
-- -- parameters are numbers. Defaults are 1,2,2,6 respectively
|
||||
-- mybluemantis:SetMaxActiveSAMs(Short,Mid,Long,Classic)
|
||||
-- -- parameters are numbers. Defaults are 1,2,2,6,6 respectively
|
||||
-- mybluemantis:SetMaxActiveSAMs(Short,Mid,Long,Classic,Point)
|
||||
--
|
||||
-- ### 2.1.3 SHORAD will automatically be added from SAM sites of type "short-range"
|
||||
-- ### 2.1.3 SHORAD/Point defense will automatically be added from SAM sites of type "point" or if the range is less than 5km or if the type is AAA.
|
||||
--
|
||||
-- ### 2.1.4 Advanced features
|
||||
--
|
||||
-- -- switch off auto mode **before** you start MANTIS.
|
||||
-- -- Option to switch off auto mode **before** you start MANTIS (not recommended)
|
||||
-- mybluemantis.automode = false
|
||||
--
|
||||
-- -- switch off auto shorad **before** you start MANTIS.
|
||||
-- mybluemantis.autoshorad = false
|
||||
--
|
||||
-- -- scale of the activation range, i.e. don't activate at the fringes of max range, defaults below.
|
||||
-- -- Option to set the scale of the activation range, i.e. don't activate at the fringes of max range, defaults below.
|
||||
-- -- also see engagerange below.
|
||||
-- self.radiusscale[MANTIS.SamType.LONG] = 1.1
|
||||
-- self.radiusscale[MANTIS.SamType.MEDIUM] = 1.2
|
||||
-- self.radiusscale[MANTIS.SamType.SHORT] = 1.3
|
||||
-- self.radiusscale[MANTIS.SamType.POINT] = 1.4
|
||||
--
|
||||
-- ### 2.1.5 Friendlies check in firing range
|
||||
--
|
||||
@@ -240,9 +243,9 @@
|
||||
--
|
||||
-- Use this option if you want to make use of or allow advanced SEAD tactics.
|
||||
--
|
||||
-- # 5. Integrate SHORAD [classic mode, not necessary in automode]
|
||||
-- # 5. Integrate SHORAD [classic mode, not necessary in automode, not recommended for manual setup]
|
||||
--
|
||||
-- You can also choose to integrate Mantis with @{Functional.Shorad#SHORAD} for protection against HARMs and AGMs. When SHORAD detects a missile fired at one of MANTIS' SAM sites, it will activate SHORAD systems in
|
||||
-- You can also choose to integrate Mantis with @{Functional.Shorad#SHORAD} for protection against HARMs and AGMs manually. When SHORAD detects a missile fired at one of MANTIS' SAM sites, it will activate SHORAD systems in
|
||||
-- the given defense checkradius around that SAM site. Create a SHORAD object first, then integrate with MANTIS like so:
|
||||
--
|
||||
-- local SamSet = SET_GROUP:New():FilterPrefixes("Blue SAM"):FilterCoalitions("blue"):FilterStart()
|
||||
@@ -296,6 +299,7 @@ MANTIS = {
|
||||
SAM_Table_Long = {},
|
||||
SAM_Table_Medium = {},
|
||||
SAM_Table_Short = {},
|
||||
SAM_Table_PointDef = {},
|
||||
lid = "",
|
||||
Detection = nil,
|
||||
AWACS_Detection = nil,
|
||||
@@ -329,6 +333,9 @@ MANTIS = {
|
||||
autoshorad = true,
|
||||
ShoradGroupSet = nil,
|
||||
checkforfriendlies = false,
|
||||
SmokeDecoy = false,
|
||||
SmokeDecoyColor = SMOKECOLOR.White,
|
||||
checkcounter = 1,
|
||||
}
|
||||
|
||||
--- Advanced state enumerator
|
||||
@@ -345,8 +352,17 @@ MANTIS.SamType = {
|
||||
SHORT = "Short",
|
||||
MEDIUM = "Medium",
|
||||
LONG = "Long",
|
||||
POINT = "Point",
|
||||
}
|
||||
|
||||
--- SAM Radiusscale
|
||||
-- @type MANTIS.radiusscale
|
||||
MANTIS.radiusscale = {}
|
||||
MANTIS.radiusscale[MANTIS.SamType.LONG] = 1.1
|
||||
MANTIS.radiusscale[MANTIS.SamType.MEDIUM] = 1.2
|
||||
MANTIS.radiusscale[MANTIS.SamType.SHORT] = 1.75
|
||||
MANTIS.radiusscale[MANTIS.SamType.POINT] = 3
|
||||
|
||||
--- SAM data
|
||||
-- @type MANTIS.SamData
|
||||
-- @field #number Range Max firing range in km
|
||||
@@ -354,6 +370,7 @@ MANTIS.SamType = {
|
||||
-- @field #number Height Max firing height in km
|
||||
-- @field #string Type #MANTIS.SamType of SAM, i.e. SHORT, MEDIUM or LONG (range)
|
||||
-- @field #string Radar Radar typename on unit level (used as key)
|
||||
-- @field #string Point Point defense capable
|
||||
MANTIS.SamData = {
|
||||
["Hawk"] = { Range=35, Blindspot=0, Height=12, Type="Medium", Radar="Hawk" }, -- measures in km
|
||||
["NASAMS"] = { Range=14, Blindspot=0, Height=7, Type="Short", Radar="NSAMS" }, -- AIM 120B
|
||||
@@ -365,16 +382,16 @@ MANTIS.SamData = {
|
||||
["SA-6"] = { Range=25, Blindspot=0, Height=8, Type="Medium", Radar="1S91" },
|
||||
["SA-10"] = { Range=119, Blindspot=0, Height=18, Type="Long" , Radar="S-300PS 4"},
|
||||
["SA-11"] = { Range=35, Blindspot=0, Height=20, Type="Medium", Radar="SA-11" },
|
||||
["Roland"] = { Range=5, Blindspot=0, Height=5, Type="Short", Radar="Roland" },
|
||||
["Roland"] = { Range=5, Blindspot=0, Height=5, Type="Point", Radar="Roland" },
|
||||
["HQ-7"] = { Range=12, Blindspot=0, Height=3, Type="Short", Radar="HQ-7" },
|
||||
["SA-9"] = { Range=4, Blindspot=0, Height=3, Type="Short", Radar="Strela" },
|
||||
["SA-9"] = { Range=4, Blindspot=0, Height=3, Type="Point", Radar="Strela", Point="true" },
|
||||
["SA-8"] = { Range=10, Blindspot=0, Height=5, Type="Short", Radar="Osa 9A33" },
|
||||
["SA-19"] = { Range=8, Blindspot=0, Height=3, Type="Short", Radar="Tunguska" },
|
||||
["SA-15"] = { Range=11, Blindspot=0, Height=6, Type="Short", Radar="Tor 9A331" },
|
||||
["SA-13"] = { Range=5, Blindspot=0, Height=3, Type="Short", Radar="Strela" },
|
||||
["SA-15"] = { Range=11, Blindspot=0, Height=6, Type="Point", Radar="Tor 9A331", Point="true" },
|
||||
["SA-13"] = { Range=5, Blindspot=0, Height=3, Type="Point", Radar="Strela", Point="true" },
|
||||
["Avenger"] = { Range=4, Blindspot=0, Height=3, Type="Short", Radar="Avenger" },
|
||||
["Chaparral"] = { Range=8, Blindspot=0, Height=3, Type="Short", Radar="Chaparral" },
|
||||
["Linebacker"] = { Range=4, Blindspot=0, Height=3, Type="Short", Radar="Linebacker" },
|
||||
["Linebacker"] = { Range=4, Blindspot=0, Height=3, Type="Point", Radar="Linebacker", Point="true" },
|
||||
["Silkworm"] = { Range=90, Blindspot=1, Height=0.2, Type="Long", Radar="Silkworm" },
|
||||
-- units from HDS Mod, multi launcher options is tricky
|
||||
["SA-10B"] = { Range=75, Blindspot=0, Height=18, Type="Medium" , Radar="SA-10B"},
|
||||
@@ -382,7 +399,6 @@ MANTIS.SamData = {
|
||||
["SA-20A"] = { Range=150, Blindspot=5, Height=27, Type="Long" , Radar="S-300PMU1"},
|
||||
["SA-20B"] = { Range=200, Blindspot=4, Height=27, Type="Long" , Radar="S-300PMU2"},
|
||||
["HQ-2"] = { Range=50, Blindspot=6, Height=35, Type="Medium", Radar="HQ_2_Guideline_LN" },
|
||||
["SHORAD"] = { Range=3, Blindspot=0, Height=3, Type="Short", Radar="Igla" },
|
||||
["TAMIR IDFA"] = { Range=20, Blindspot=0.6, Height=12.3, Type="Short", Radar="IRON_DOME_LN" },
|
||||
["STUNNER IDFA"] = { Range=250, Blindspot=1, Height=45, Type="Long", Radar="DAVID_SLING_LN" },
|
||||
}
|
||||
@@ -394,6 +410,7 @@ MANTIS.SamData = {
|
||||
-- @field #number Height Max firing height in km
|
||||
-- @field #string Type #MANTIS.SamType of SAM, i.e. SHORT, MEDIUM or LONG (range)
|
||||
-- @field #string Radar Radar typename on unit level (used as key)
|
||||
-- @field #string Point Point defense capable
|
||||
MANTIS.SamDataHDS = {
|
||||
-- units from HDS Mod, multi launcher options is tricky
|
||||
-- group name MUST contain HDS to ID launcher type correctly!
|
||||
@@ -415,6 +432,7 @@ MANTIS.SamDataHDS = {
|
||||
-- @field #number Height Max firing height in km
|
||||
-- @field #string Type #MANTIS.SamType of SAM, i.e. SHORT, MEDIUM or LONG (range)
|
||||
-- @field #string Radar Radar typename on unit level (used as key)
|
||||
-- @field #string Point Point defense capable
|
||||
MANTIS.SamDataSMA = {
|
||||
-- units from SMA Mod (Sweedish Military Assets)
|
||||
-- https://forum.dcs.world/topic/295202-swedish-military-assets-for-dcs-by-currenthill/
|
||||
@@ -428,7 +446,7 @@ MANTIS.SamDataSMA = {
|
||||
["RBS103B SMA"] = { Range=35, Blindspot=0, Height=36, Type="Medium", Radar="LvS-103_Lavett103_Rb103B" },
|
||||
["RBS103AM SMA"] = { Range=150, Blindspot=3, Height=24.5, Type="Long", Radar="LvS-103_Lavett103_HX_Rb103A" },
|
||||
["RBS103BM SMA"] = { Range=35, Blindspot=0, Height=36, Type="Medium", Radar="LvS-103_Lavett103_HX_Rb103B" },
|
||||
["Lvkv9040M SMA"] = { Range=4, Blindspot=0, Height=2.5, Type="Short", Radar="LvKv9040" },
|
||||
["Lvkv9040M SMA"] = { Range=4, Blindspot=0, Height=2.5, Type="Point", Radar="LvKv9040",Point="true" },
|
||||
}
|
||||
|
||||
--- SAM data CH
|
||||
@@ -438,28 +456,52 @@ MANTIS.SamDataSMA = {
|
||||
-- @field #number Height Max firing height in km
|
||||
-- @field #string Type #MANTIS.SamType of SAM, i.e. SHORT, MEDIUM or LONG (range)
|
||||
-- @field #string Radar Radar typename on unit level (used as key)
|
||||
-- @field #string Point Point defense capable
|
||||
MANTIS.SamDataCH = {
|
||||
-- units from CH (Military Assets by Currenthill)
|
||||
-- https://www.currenthill.com/
|
||||
-- group name MUST contain CHM to ID launcher type correctly!
|
||||
["2S38 CH"] = { Range=8, Blindspot=0.5, Height=6, Type="Short", Radar="2S38" },
|
||||
["PantsirS1 CH"] = { Range=20, Blindspot=1.2, Height=15, Type="Short", Radar="PantsirS1" },
|
||||
["PantsirS2 CH"] = { Range=30, Blindspot=1.2, Height=18, Type="Medium", Radar="PantsirS2" },
|
||||
["PGL-625 CH"] = { Range=10, Blindspot=0.5, Height=5, Type="Short", Radar="PGL_625" },
|
||||
["HQ-17A CH"] = { Range=20, Blindspot=1.5, Height=10, Type="Short", Radar="HQ17A" },
|
||||
["M903PAC2 CH"] = { Range=160, Blindspot=3, Height=24.5, Type="Long", Radar="MIM104_M903_PAC2" },
|
||||
["M903PAC3 CH"] = { Range=120, Blindspot=1, Height=40, Type="Long", Radar="MIM104_M903_PAC3" },
|
||||
["TorM2 CH"] = { Range=12, Blindspot=1, Height=10, Type="Short", Radar="TorM2" },
|
||||
["TorM2K CH"] = { Range=12, Blindspot=1, Height=10, Type="Short", Radar="TorM2K" },
|
||||
["TorM2M CH"] = { Range=16, Blindspot=1, Height=10, Type="Short", Radar="TorM2M" },
|
||||
["NASAMS3-AMRAAMER CH"] = { Range=50, Blindspot=2, Height=35.7, Type="Medium", Radar="CH_NASAMS3_LN_AMRAAM_ER" },
|
||||
["NASAMS3-AIM9X2 CH"] = { Range=20, Blindspot=0.2, Height=18, Type="Short", Radar="CH_NASAMS3_LN_AIM9X2" },
|
||||
["C-RAM CH"] = { Range=2, Blindspot=0, Height=2, Type="Short", Radar="CH_Centurion_C_RAM" },
|
||||
["PGZ-09 CH"] = { Range=4, Blindspot=0, Height=3, Type="Short", Radar="CH_PGZ09" },
|
||||
["S350-9M100 CH"] = { Range=15, Blindspot=1.5, Height=8, Type="Short", Radar="CH_S350_50P6_9M100" },
|
||||
["S350-9M96D CH"] = { Range=150, Blindspot=2.5, Height=30, Type="Long", Radar="CH_S350_50P6_9M96D" },
|
||||
["LAV-AD CH"] = { Range=8, Blindspot=0.2, Height=4.8, Type="Short", Radar="CH_LAVAD" },
|
||||
["HQ-22 CH"] = { Range=170, Blindspot=5, Height=27, Type="Long", Radar="CH_HQ22_LN" },
|
||||
-- units from CH (Military Assets by Currenthill)
|
||||
-- https://www.currenthill.com/
|
||||
-- group name MUST contain CHM to ID launcher type correctly!
|
||||
["2S38 CHM"] = { Range=8, Blindspot=0.5, Height=6, Type="Short", Radar="2S38" },
|
||||
["PantsirS1 CHM"] = { Range=20, Blindspot=1.2, Height=15, Type="Short", Radar="PantsirS1" },
|
||||
["PantsirS2 CHM"] = { Range=30, Blindspot=1.2, Height=18, Type="Medium", Radar="PantsirS2" },
|
||||
["PGL-625 CHM"] = { Range=10, Blindspot=0.5, Height=5, Type="Short", Radar="PGL_625" },
|
||||
["HQ-17A CHM"] = { Range=20, Blindspot=1.5, Height=10, Type="Short", Radar="HQ17A" },
|
||||
["M903PAC2 CHM"] = { Range=160, Blindspot=3, Height=24.5, Type="Long", Radar="MIM104_M903_PAC2" },
|
||||
["M903PAC3 CHM"] = { Range=120, Blindspot=1, Height=40, Type="Long", Radar="MIM104_M903_PAC3" },
|
||||
["TorM2 CHM"] = { Range=12, Blindspot=1, Height=10, Type="Short", Radar="TorM2" },
|
||||
["TorM2K CHM"] = { Range=12, Blindspot=1, Height=10, Type="Short", Radar="TorM2K" },
|
||||
["TorM2M CHM"] = { Range=16, Blindspot=1, Height=10, Type="Short", Radar="TorM2M" },
|
||||
["NASAMS3-AMRAAMER CHM"] = { Range=50, Blindspot=2, Height=35.7, Type="Medium", Radar="CH_NASAMS3_LN_AMRAAM_ER" },
|
||||
["NASAMS3-AIM9X2 CHM"] = { Range=20, Blindspot=0.2, Height=18, Type="Short", Radar="CH_NASAMS3_LN_AIM9X2" },
|
||||
["C-RAM CHM"] = { Range=2, Blindspot=0, Height=2, Type="Point", Radar="CH_Centurion_C_RAM", Point="true" },
|
||||
["PGZ-09 CHM"] = { Range=4, Blindspot=0, Height=3, Type="Point", Radar="CH_PGZ09", Point="true" },
|
||||
["S350-9M100 CHM"] = { Range=15, Blindspot=1.5, Height=8, Type="Short", Radar="CH_S350_50P6_9M100" },
|
||||
["S350-9M96D CHM"] = { Range=150, Blindspot=2.5, Height=30, Type="Long", Radar="CH_S350_50P6_9M96D" },
|
||||
["LAV-AD CHM"] = { Range=8, Blindspot=0.2, Height=4.8, Type="Short", Radar="CH_LAVAD" },
|
||||
["HQ-22 CHM"] = { Range=170, Blindspot=5, Height=27, Type="Long", Radar="CH_HQ22_LN" },
|
||||
["PGZ-95 CHM"] = { Range=2, Blindspot=0, Height=2, Type="Point", Radar="CH_PGZ95",Point="true" },
|
||||
["LD-3000 CHM"] = { Range=3, Blindspot=0, Height=3, Type="Point", Radar="CH_LD3000_stationary", Point="true" },
|
||||
["LD-3000M CHM"] = { Range=3, Blindspot=0, Height=3, Type="Point", Radar="CH_LD3000", Point="true" },
|
||||
["FlaRakRad CHM"] = { Range=8, Blindspot=1.5, Height=6, Type="Short", Radar="HQ17A" },
|
||||
["IRIS-T SLM CHM"] = { Range=40, Blindspot=0.5, Height=20, Type="Medium", Radar="CH_IRIST_SLM" },
|
||||
["M903PAC2KAT1 CHM"] = { Range=160, Blindspot=3, Height=24.5, Type="Long", Radar="CH_MIM104_M903_PAC2_KAT1" },
|
||||
["Skynex CHM"] = { Range=3.5, Blindspot=0, Height=3.5, Type="Point", Radar="CH_SkynexHX", Point="true" },
|
||||
["Skyshield CHM"] = { Range=3.5, Blindspot=0, Height=3.5, Type="Point", Radar="CH_Skyshield_Gun", Point="true" },
|
||||
["WieselOzelot CHM"] = { Range=8, Blindspot=0.2, Height=4.8, Type="Short", Radar="CH_Wiesel2Ozelot" },
|
||||
["BukM3-9M317M CHM"] = { Range=70, Blindspot=0.25, Height=35, Type="Medium", Radar="CH_BukM3_9A317M" },
|
||||
["BukM3-9M317MA CHM"] = { Range=70, Blindspot=0.25, Height=35, Type="Medium", Radar="CH_BukM3_9A317MA" },
|
||||
["SkySabre CHM"] = { Range=30, Blindspot=0.5, Height=10, Type="Medium", Radar="CH_SkySabreLN" },
|
||||
["Stormer CHM"] = { Range=7.5, Blindspot=0.3, Height=7, Type="Short", Radar="CH_StormerHVM" },
|
||||
["THAAD CHM"] = { Range=200, Blindspot=40, Height=150, Type="Long", Radar="CH_THAAD_M1120" },
|
||||
["USInfantryFIM92K CHM"] = { Range=8, Blindspot=0.2, Height=4.8, Type="Short", Radar="CH_USInfantry_FIM92" },
|
||||
["RBS98M CHM"] = { Range=20, Blindspot=0, Height=8, Type="Short", Radar="RBS-98" },
|
||||
["RBS70 CHM"] = { Range=8, Blindspot=0, Height=5.5, Type="Short", Radar="RBS-70" },
|
||||
["RBS90 CHM"] = { Range=8, Blindspot=0, Height=5.5, Type="Short", Radar="RBS-90" },
|
||||
["RBS103A CHM"] = { Range=150, Blindspot=3, Height=24.5, Type="Long", Radar="LvS-103_Lavett103_Rb103A" },
|
||||
["RBS103B CHM"] = { Range=35, Blindspot=0, Height=36, Type="Medium", Radar="LvS-103_Lavett103_Rb103B" },
|
||||
["RBS103AM CHM"] = { Range=150, Blindspot=3, Height=24.5, Type="Long", Radar="LvS-103_Lavett103_HX_Rb103A" },
|
||||
["RBS103BM CHM"] = { Range=35, Blindspot=0, Height=36, Type="Medium", Radar="LvS-103_Lavett103_HX_Rb103B" },
|
||||
["Lvkv9040M CHM"] = { Range=4, Blindspot=0, Height=2.5, Type="Point", Radar="LvKv9040", Point="true" },
|
||||
}
|
||||
|
||||
-----------------------------------------------------------------------
|
||||
@@ -520,6 +562,7 @@ do
|
||||
self.SAM_Table_Long = {}
|
||||
self.SAM_Table_Medium = {}
|
||||
self.SAM_Table_Short = {}
|
||||
self.SAM_Table_PointDef = {}
|
||||
self.dynamic = dynamic or false
|
||||
self.checkradius = 25000
|
||||
self.grouping = 5000
|
||||
@@ -548,10 +591,6 @@ do
|
||||
self.SuppressedGroups = {}
|
||||
-- 0.8 additions
|
||||
self.automode = true
|
||||
self.radiusscale = {}
|
||||
self.radiusscale[MANTIS.SamType.LONG] = 1.1
|
||||
self.radiusscale[MANTIS.SamType.MEDIUM] = 1.2
|
||||
self.radiusscale[MANTIS.SamType.SHORT] = 1.3
|
||||
--self.SAMCheckRanges = {}
|
||||
self.usezones = false
|
||||
self.AcceptZones = {}
|
||||
@@ -560,6 +599,7 @@ do
|
||||
self.maxlongrange = 1
|
||||
self.maxmidrange = 2
|
||||
self.maxshortrange = 2
|
||||
self.maxpointdefrange = 6
|
||||
self.maxclassic = 6
|
||||
self.autoshorad = true
|
||||
self.ShoradGroupSet = SET_GROUP:New() -- Core.Set#SET_GROUP
|
||||
@@ -567,7 +607,10 @@ do
|
||||
|
||||
self.SkateZones = nil
|
||||
self.SkateNumber = 3
|
||||
self.shootandscoot = false
|
||||
self.shootandscoot = false
|
||||
|
||||
self.SmokeDecoy = false
|
||||
self.SmokeDecoyColor = SMOKECOLOR.White
|
||||
|
||||
self.UseEmOnOff = true
|
||||
if EmOnOff == false then
|
||||
@@ -579,6 +622,7 @@ do
|
||||
else
|
||||
self.advAwacs = false
|
||||
end
|
||||
|
||||
|
||||
-- Set the string id for output to DCS.log file.
|
||||
self.lid=string.format("MANTIS %s | ", self.name)
|
||||
@@ -638,9 +682,12 @@ do
|
||||
self.HQ_CC = GROUP:FindByName(self.HQ_Template_CC)
|
||||
end
|
||||
|
||||
-- counter for SAM table updates
|
||||
self.checkcounter = 1
|
||||
|
||||
-- TODO Version
|
||||
-- @field #string version
|
||||
self.version="0.8.18"
|
||||
self.version="0.9.27"
|
||||
self:I(string.format("***** Starting MANTIS Version %s *****", self.version))
|
||||
|
||||
--- FSM Functions ---
|
||||
@@ -837,7 +884,7 @@ do
|
||||
self.AcceptZones = AcceptZones or {}
|
||||
self.RejectZones = RejectZones or {}
|
||||
self.ConflictZones = ConflictZones or {}
|
||||
if #AcceptZones > 0 or #RejectZones > 0 or #ConflictZones > 0 then
|
||||
if #self.AcceptZones > 0 or #self.RejectZones > 0 or #self.ConflictZones > 0 then
|
||||
self.usezones = true
|
||||
end
|
||||
return self
|
||||
@@ -876,19 +923,31 @@ do
|
||||
return self
|
||||
end
|
||||
|
||||
--- Function to set Short Range SAMs to spit out smoke as decoy, if an enemy plane is in range.
|
||||
-- @param #MANTIS self
|
||||
-- @param #boolean Onoff Set to true for on and nil/false for off.
|
||||
-- @param #number Color (Optional) Color to use, defaults to `SMOKECOLOR.White`
|
||||
function MANTIS:SetSmokeDecoy(Onoff,Color)
|
||||
self.SmokeDecoy = Onoff
|
||||
self.SmokeDecoyColor = Color or SMOKECOLOR.White
|
||||
return self
|
||||
end
|
||||
|
||||
--- Function to set number of SAMs going active on a valid, detected thread
|
||||
-- @param #MANTIS self
|
||||
-- @param #number Short Number of short-range systems activated, defaults to 1.
|
||||
-- @param #number Mid Number of mid-range systems activated, defaults to 2.
|
||||
-- @param #number Long Number of long-range systems activated, defaults to 2.
|
||||
-- @param #number Classic (non-automode) Number of overall systems activated, defaults to 6.
|
||||
-- @param #number Point Number of point defense and AAA systems activated, defaults to 6.
|
||||
-- @return #MANTIS self
|
||||
function MANTIS:SetMaxActiveSAMs(Short,Mid,Long,Classic)
|
||||
function MANTIS:SetMaxActiveSAMs(Short,Mid,Long,Classic,Point)
|
||||
self:T(self.lid .. "SetMaxActiveSAMs")
|
||||
self.maxclassic = Classic or 6
|
||||
self.maxlongrange = Long or 1
|
||||
self.maxmidrange = Mid or 2
|
||||
self.maxshortrange = Short or 2
|
||||
self.maxpointdefrange= Point or 6
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -1090,6 +1149,24 @@ do
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- [Internal] Check if any EWR or AWACS is still alive
|
||||
-- @param #MANTIS self
|
||||
-- @return #boolean outcome
|
||||
function MANTIS:_CheckAnyEWRAlive()
|
||||
self:T(self.lid .. "_CheckAnyEWRAlive")
|
||||
local alive = false
|
||||
if self.EWR_Group:CountAlive() > 0 then
|
||||
alive = true
|
||||
end
|
||||
if not alive and self.AWACS_Prefix then
|
||||
local awacs = GROUP:FindByName(self.AWACS_Prefix)
|
||||
if awacs and awacs:IsAlive() then
|
||||
alive = true
|
||||
end
|
||||
end
|
||||
return alive
|
||||
end
|
||||
|
||||
--- [Internal] Function to determine state of the advanced mode
|
||||
-- @param #MANTIS self
|
||||
@@ -1264,9 +1341,9 @@ do
|
||||
-- DEBUG
|
||||
set = self:_PreFilterHeight(height)
|
||||
end
|
||||
local friendlyset -- Core.Set#SET_GROUP
|
||||
if self.checkforfriendlies == true then
|
||||
friendlyset = SET_GROUP:New():FilterCoalitions(self.Coalition):FilterCategories({"plane","helicopter"}):FilterFunction(function(grp) if grp and grp:InAir() then return true else return false end end):FilterOnce()
|
||||
--self.friendlyset -- Core.Set#SET_GROUP
|
||||
if self.checkforfriendlies == true and self.friendlyset == nil then
|
||||
self.friendlyset = SET_GROUP:New():FilterCoalitions(self.Coalition):FilterCategories({"plane","helicopter"}):FilterFunction(function(grp) if grp and grp:InAir() then return true else return false end end):FilterStart()
|
||||
end
|
||||
for _,_coord in pairs (set) do
|
||||
local coord = _coord -- get current coord to check
|
||||
@@ -1282,20 +1359,21 @@ do
|
||||
zonecheck = self:_CheckCoordinateInZones(coord)
|
||||
end
|
||||
if self.verbose and self.debug then
|
||||
local dectstring = coord:ToStringLLDMS()
|
||||
local samstring = samcoordinate:ToStringLLDMS()
|
||||
--local dectstring = coord:ToStringLLDMS()
|
||||
local samstring = samcoordinate:ToStringMGRS({MGRS_Accuracy=0})
|
||||
samstring = string.gsub(samstring,"%s","")
|
||||
local inrange = "false"
|
||||
if targetdistance <= rad then
|
||||
inrange = "true"
|
||||
end
|
||||
local text = string.format("Checking SAM at %s | Targetdist %d | Rad %d | Inrange %s", samstring, targetdistance, rad, inrange)
|
||||
local text = string.format("Checking SAM at %s | Tgtdist %.1fkm | Rad %.1fkm | Inrange %s", samstring, targetdistance/1000, rad/1000, inrange)
|
||||
local m = MESSAGE:New(text,10,"Check"):ToAllIf(self.debug)
|
||||
self:T(self.lid..text)
|
||||
end
|
||||
-- friendlies around?
|
||||
local nofriendlies = true
|
||||
if self.checkforfriendlies == true then
|
||||
local closestfriend, distance = friendlyset:GetClosestGroup(samcoordinate)
|
||||
local closestfriend, distance = self.friendlyset:GetClosestGroup(samcoordinate)
|
||||
if closestfriend and distance and distance < rad then
|
||||
nofriendlies = false
|
||||
end
|
||||
@@ -1396,7 +1474,7 @@ do
|
||||
-- @return #string type Long, medium or short range
|
||||
-- @return #number blind "blind" spot
|
||||
function MANTIS:_GetSAMDataFromUnits(grpname,mod,sma,chm)
|
||||
self:T(self.lid.."_GetSAMRangeFromUnits")
|
||||
self:T(self.lid.."_GetSAMDataFromUnits")
|
||||
local found = false
|
||||
local range = self.checkradius
|
||||
local height = 3000
|
||||
@@ -1435,6 +1513,17 @@ do
|
||||
end
|
||||
if found then break end
|
||||
end
|
||||
--- AAA or Point Defense
|
||||
if not found then
|
||||
local grp = GROUP:FindByName(grpname)
|
||||
if (grp and grp:IsAlive() and grp:IsAAA()) or string.find(grpname,"AAA",1,true) then
|
||||
range = 2000
|
||||
height = 2000
|
||||
blind = 50
|
||||
type = MANTIS.SamType.POINT
|
||||
found = true
|
||||
end
|
||||
end
|
||||
if not found then
|
||||
self:E(self.lid .. string.format("*****Could not match radar data for %s! Will default to midrange values!",grpname))
|
||||
end
|
||||
@@ -1449,7 +1538,7 @@ do
|
||||
-- @return #string type Long, medium or short range
|
||||
-- @return #number blind "blind" spot
|
||||
function MANTIS:_GetSAMRange(grpname)
|
||||
self:T(self.lid.."_GetSAMRange")
|
||||
self:T(self.lid.."_GetSAMRange for "..tostring(grpname))
|
||||
local range = self.checkradius
|
||||
local height = 3000
|
||||
local type = MANTIS.SamType.MEDIUM
|
||||
@@ -1466,9 +1555,9 @@ do
|
||||
elseif string.find(grpname,"CHM",1,true) then
|
||||
CHMod = true
|
||||
end
|
||||
if self.automode then
|
||||
--if self.automode then
|
||||
for idx,entry in pairs(self.SamData) do
|
||||
--self:I("ID = " .. idx)
|
||||
self:T2("ID = " .. idx)
|
||||
if string.find(grpname,idx,1,true) then
|
||||
local _entry = entry -- #MANTIS.SamData
|
||||
type = _entry.Type
|
||||
@@ -1476,18 +1565,32 @@ do
|
||||
range = _entry.Range * 1000 * radiusscale -- max firing range
|
||||
height = _entry.Height * 1000 -- max firing height
|
||||
blind = _entry.Blindspot
|
||||
--self:I("Matching Groupname = " .. grpname .. " Range= " .. range)
|
||||
self:T("Matching Groupname = " .. grpname .. " Range= " .. range)
|
||||
found = true
|
||||
break
|
||||
end
|
||||
end
|
||||
--end
|
||||
--- Secondary - AAA or Point Defense
|
||||
if not found then
|
||||
local grp = GROUP:FindByName(grpname)
|
||||
if (grp and grp:IsAlive() and grp:IsAAA()) or string.find(grpname,"AAA",1,true) then
|
||||
range = 2000
|
||||
height = 2000
|
||||
blind = 50
|
||||
type = MANTIS.SamType.POINT
|
||||
found = true
|
||||
end
|
||||
end
|
||||
-- secondary filter if not found
|
||||
if (not found and self.automode) or HDSmod or SMAMod or CHMod then
|
||||
--- Tertiary filter if not found
|
||||
if (not found) or HDSmod or SMAMod or CHMod then
|
||||
range, height, type = self:_GetSAMDataFromUnits(grpname,HDSmod,SMAMod,CHMod)
|
||||
elseif not found then
|
||||
self:E(self.lid .. string.format("*****Could not match radar data for %s! Will default to midrange values!",grpname))
|
||||
end
|
||||
if found and string.find(grpname,"SHORAD",1,true) then
|
||||
type = MANTIS.SamType.POINT -- force short on match
|
||||
end
|
||||
return range, height, type, blind
|
||||
end
|
||||
|
||||
@@ -1505,6 +1608,7 @@ do
|
||||
local SAM_Tbl_lg = {} -- table of long range SAM defense zones
|
||||
local SAM_Tbl_md = {} -- table of mid range SAM defense zones
|
||||
local SAM_Tbl_sh = {} -- table of short range SAM defense zones
|
||||
local SAM_Tbl_pt = {} -- table of point defense/AAA
|
||||
local SEAD_Grps = {} -- table of SAM names to make evasive
|
||||
local engagerange = self.engagerange -- firing range in % of max
|
||||
--cycle through groups and set alarm state etc
|
||||
@@ -1523,23 +1627,27 @@ do
|
||||
local grpname = group:GetName()
|
||||
local grpcoord = group:GetCoordinate()
|
||||
local grprange,grpheight,type,blind = self:_GetSAMRange(grpname)
|
||||
table.insert( SAM_Tbl, {grpname, grpcoord, grprange, grpheight, blind})
|
||||
table.insert( SAM_Tbl, {grpname, grpcoord, grprange, grpheight, blind, type})
|
||||
--table.insert( SEAD_Grps, grpname )
|
||||
if type == MANTIS.SamType.LONG then
|
||||
table.insert( SAM_Tbl_lg, {grpname, grpcoord, grprange, grpheight, blind})
|
||||
table.insert( SAM_Tbl_lg, {grpname, grpcoord, grprange, grpheight, blind, type})
|
||||
table.insert( SEAD_Grps, grpname )
|
||||
--self:T("SAM "..grpname.." is type LONG")
|
||||
self:T("SAM "..grpname.." is type LONG")
|
||||
elseif type == MANTIS.SamType.MEDIUM then
|
||||
table.insert( SAM_Tbl_md, {grpname, grpcoord, grprange, grpheight, blind})
|
||||
table.insert( SAM_Tbl_md, {grpname, grpcoord, grprange, grpheight, blind, type})
|
||||
table.insert( SEAD_Grps, grpname )
|
||||
--self:T("SAM "..grpname.." is type MEDIUM")
|
||||
self:T("SAM "..grpname.." is type MEDIUM")
|
||||
elseif type == MANTIS.SamType.SHORT then
|
||||
table.insert( SAM_Tbl_sh, {grpname, grpcoord, grprange, grpheight, blind})
|
||||
--self:T("SAM "..grpname.." is type SHORT")
|
||||
table.insert( SAM_Tbl_sh, {grpname, grpcoord, grprange, grpheight, blind, type})
|
||||
table.insert( SEAD_Grps, grpname )
|
||||
self:T("SAM "..grpname.." is type SHORT")
|
||||
elseif type == MANTIS.SamType.POINT then
|
||||
table.insert( SAM_Tbl_pt, {grpname, grpcoord, grprange, grpheight, blind, type})
|
||||
self:T("SAM "..grpname.." is type POINT")
|
||||
self.ShoradGroupSet:Add(grpname,group)
|
||||
if not self.autoshorad then
|
||||
table.insert( SEAD_Grps, grpname )
|
||||
end
|
||||
end
|
||||
end
|
||||
self.SamStateTracker[grpname] = "GREEN"
|
||||
end
|
||||
@@ -1548,6 +1656,7 @@ do
|
||||
self.SAM_Table_Long = SAM_Tbl_lg
|
||||
self.SAM_Table_Medium = SAM_Tbl_md
|
||||
self.SAM_Table_Short = SAM_Tbl_sh
|
||||
self.SAM_Table_PointDef = SAM_Tbl_pt
|
||||
-- make SAMs evasive
|
||||
local mysead = SEAD:New( SEAD_Grps, self.Padding ) -- Functional.Sead#SEAD
|
||||
mysead:SetEngagementRange(engagerange)
|
||||
@@ -1571,7 +1680,8 @@ do
|
||||
local SAM_Tbl = {} -- table of SAM defense zones
|
||||
local SAM_Tbl_lg = {} -- table of long range SAM defense zones
|
||||
local SAM_Tbl_md = {} -- table of mid range SAM defense zones
|
||||
local SAM_Tbl_sh = {} -- table of short range SAM defense zon
|
||||
local SAM_Tbl_sh = {} -- table of short range SAM defense zones
|
||||
local SAM_Tbl_pt = {} -- table of point defense/AAA
|
||||
local SEAD_Grps = {} -- table of SAM names to make evasive
|
||||
local engagerange = self.engagerange -- firing range in % of max
|
||||
--cycle through groups and set alarm state etc
|
||||
@@ -1582,17 +1692,21 @@ do
|
||||
local grpname = group:GetName()
|
||||
local grpcoord = group:GetCoordinate()
|
||||
local grprange, grpheight,type,blind = self:_GetSAMRange(grpname)
|
||||
table.insert( SAM_Tbl, {grpname, grpcoord, grprange, grpheight, blind}) -- make the table lighter, as I don't really use the zone here
|
||||
local radaralive = group:IsSAM()
|
||||
table.insert( SAM_Tbl, {grpname, grpcoord, grprange, grpheight, blind, type}) -- make the table lighter, as I don't really use the zone here
|
||||
table.insert( SEAD_Grps, grpname )
|
||||
if type == MANTIS.SamType.LONG then
|
||||
table.insert( SAM_Tbl_lg, {grpname, grpcoord, grprange, grpheight, blind})
|
||||
--self:I({grpname,grprange, grpheight})
|
||||
elseif type == MANTIS.SamType.MEDIUM then
|
||||
table.insert( SAM_Tbl_md, {grpname, grpcoord, grprange, grpheight, blind})
|
||||
--self:I({grpname,grprange, grpheight})
|
||||
elseif type == MANTIS.SamType.SHORT then
|
||||
table.insert( SAM_Tbl_sh, {grpname, grpcoord, grprange, grpheight, blind})
|
||||
-- self:I({grpname,grprange, grpheight})
|
||||
if type == MANTIS.SamType.LONG and radaralive then
|
||||
table.insert( SAM_Tbl_lg, {grpname, grpcoord, grprange, grpheight, blind, type})
|
||||
self:T({grpname,grprange, grpheight})
|
||||
elseif type == MANTIS.SamType.MEDIUM and radaralive then
|
||||
table.insert( SAM_Tbl_md, {grpname, grpcoord, grprange, grpheight, blind, type})
|
||||
self:T({grpname,grprange, grpheight})
|
||||
elseif type == MANTIS.SamType.SHORT and radaralive then
|
||||
table.insert( SAM_Tbl_sh, {grpname, grpcoord, grprange, grpheight, blind, type})
|
||||
self:T({grpname,grprange, grpheight})
|
||||
elseif type == MANTIS.SamType.POINT or (not radaralive) then
|
||||
table.insert( SAM_Tbl_pt, {grpname, grpcoord, grprange, grpheight, blind, type})
|
||||
self:T({grpname,grprange, grpheight})
|
||||
self.ShoradGroupSet:Add(grpname,group)
|
||||
if self.autoshorad then
|
||||
self.Shorad.Groupset = self.ShoradGroupSet
|
||||
@@ -1604,6 +1718,7 @@ do
|
||||
self.SAM_Table_Long = SAM_Tbl_lg
|
||||
self.SAM_Table_Medium = SAM_Tbl_md
|
||||
self.SAM_Table_Short = SAM_Tbl_sh
|
||||
self.SAM_Table_PointDef = SAM_Tbl_pt
|
||||
-- make SAMs evasive
|
||||
if self.mysead ~= nil then
|
||||
local mysead = self.mysead
|
||||
@@ -1647,20 +1762,33 @@ do
|
||||
-- @param #table detset Table of COORDINATES
|
||||
-- @param #boolean dlink Using DLINK
|
||||
-- @param #number limit of SAM sites to go active on a contact
|
||||
-- @return #MANTIS self
|
||||
-- @return #number instatusred
|
||||
-- @return #number instatusgreen
|
||||
-- @return #number activeshorads
|
||||
function MANTIS:_CheckLoop(samset,detset,dlink,limit)
|
||||
self:T(self.lid .. "CheckLoop " .. #detset .. " Coordinates")
|
||||
local switchedon = 0
|
||||
local instatusred = 0
|
||||
local instatusgreen = 0
|
||||
local activeshorads = 0
|
||||
local SEADactive = 0
|
||||
for _,_data in pairs (samset) do
|
||||
local samcoordinate = _data[2]
|
||||
local name = _data[1]
|
||||
local radius = _data[3]
|
||||
local height = _data[4]
|
||||
local blind = _data[5] * 1.25 + 1
|
||||
local shortsam = (_data[6] == MANTIS.SamType.SHORT) and true or false
|
||||
if not shortsam then
|
||||
shortsam = (_data[6] == MANTIS.SamType.POINT) and true or false
|
||||
end
|
||||
local samgroup = GROUP:FindByName(name)
|
||||
local IsInZone, Distance = self:_CheckObjectInZone(detset, samcoordinate, radius, height, dlink)
|
||||
local suppressed = self.SuppressedGroups[name] or false
|
||||
local activeshorad = self.Shorad.ActiveGroups[name] or false
|
||||
local activeshorad = false
|
||||
if self.Shorad and self.Shorad.ActiveGroups and self.Shorad.ActiveGroups[name] then
|
||||
activeshorad = true
|
||||
end
|
||||
if IsInZone and not suppressed and not activeshorad then --check any target in zone and not currently managed by SEAD
|
||||
if samgroup:IsAlive() then
|
||||
-- switch on SAM
|
||||
@@ -1673,12 +1801,23 @@ do
|
||||
elseif (not self.UseEmOnOff) and switchedon < limit then
|
||||
samgroup:OptionAlarmStateRed()
|
||||
switchedon = switchedon + 1
|
||||
switch = true
|
||||
switch = true
|
||||
end
|
||||
if self.SamStateTracker[name] ~= "RED" and switch then
|
||||
self:__RedState(1,samgroup)
|
||||
self.SamStateTracker[name] = "RED"
|
||||
end
|
||||
-- TODO doesn't work
|
||||
if shortsam == true and self.SmokeDecoy == true then
|
||||
self:T("Smoking")
|
||||
local units = samgroup:GetUnits() or {}
|
||||
local smoke = self.SmokeDecoyColor or SMOKECOLOR.White
|
||||
for _,unit in pairs(units) do
|
||||
if unit and unit:IsAlive() then
|
||||
unit:GetCoordinate():Smoke(smoke)
|
||||
end
|
||||
end
|
||||
end
|
||||
-- link in to SHORAD if available
|
||||
-- DONE: Test integration fully
|
||||
if self.ShoradLink and (Distance < self.ShoradActDistance or Distance < blind ) then -- don't give SHORAD position away too early
|
||||
@@ -1691,7 +1830,7 @@ do
|
||||
-- debug output
|
||||
if (self.debug or self.verbose) and switch then
|
||||
local text = string.format("SAM %s in alarm state RED!", name)
|
||||
local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug)
|
||||
--local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug
|
||||
if self.verbose then self:I(self.lid..text) end
|
||||
end
|
||||
end --end alive
|
||||
@@ -1709,13 +1848,27 @@ do
|
||||
end
|
||||
if self.debug or self.verbose then
|
||||
local text = string.format("SAM %s in alarm state GREEN!", name)
|
||||
local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug)
|
||||
--local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug)
|
||||
if self.verbose then self:I(self.lid..text) end
|
||||
end
|
||||
end --end alive
|
||||
end --end check
|
||||
end --for for loop
|
||||
return self
|
||||
end --end check
|
||||
end --for loop
|
||||
if self.debug or self.verbose then
|
||||
for _,_status in pairs(self.SamStateTracker) do
|
||||
if _status == "GREEN" then
|
||||
instatusgreen=instatusgreen+1
|
||||
elseif _status == "RED" then
|
||||
instatusred=instatusred+1
|
||||
end
|
||||
end
|
||||
if self.Shorad then
|
||||
for _,_name in pairs(self.Shorad.ActiveGroups or {}) do
|
||||
activeshorads=activeshorads+1
|
||||
end
|
||||
end
|
||||
end
|
||||
return instatusred, instatusgreen, activeshorads
|
||||
end
|
||||
|
||||
--- [Internal] Check detection function
|
||||
@@ -1728,22 +1881,38 @@ do
|
||||
--get detected set
|
||||
local detset = detection:GetDetectedItemCoordinates()
|
||||
--self:T("Check:", {detset})
|
||||
-- randomly update SAM Table
|
||||
local rand = math.random(1,100)
|
||||
if rand > 65 then -- 1/3 of cases
|
||||
-- update SAM Table evey 3 runs
|
||||
if self.checkcounter%3 == 0 then
|
||||
self:_RefreshSAMTable()
|
||||
end
|
||||
self.checkcounter = self.checkcounter + 1
|
||||
local instatusred = 0
|
||||
local instatusgreen = 0
|
||||
local activeshorads = 0
|
||||
-- switch SAMs on/off if (n)one of the detected groups is inside their reach
|
||||
if self.automode then
|
||||
local samset = self.SAM_Table_Long -- table of i.1=names, i.2=coordinates, i.3=firing range, i.4=firing height
|
||||
self:_CheckLoop(samset,detset,dlink,self.maxlongrange)
|
||||
local instatusredl, instatusgreenl, activeshoradsl = self:_CheckLoop(samset,detset,dlink,self.maxlongrange)
|
||||
local samset = self.SAM_Table_Medium -- table of i.1=names, i.2=coordinates, i.3=firing range, i.4=firing height
|
||||
self:_CheckLoop(samset,detset,dlink,self.maxmidrange)
|
||||
local instatusredm, instatusgreenm, activeshoradsm = self:_CheckLoop(samset,detset,dlink,self.maxmidrange)
|
||||
local samset = self.SAM_Table_Short -- table of i.1=names, i.2=coordinates, i.3=firing range, i.4=firing height
|
||||
self:_CheckLoop(samset,detset,dlink,self.maxshortrange)
|
||||
local instatusreds, instatusgreens, activeshoradss = self:_CheckLoop(samset,detset,dlink,self.maxshortrange)
|
||||
local samset = self.SAM_Table_PointDef -- table of i.1=names, i.2=coordinates, i.3=firing range, i.4=firing height
|
||||
instatusred, instatusgreen, activeshorads = self:_CheckLoop(samset,detset,dlink,self.maxpointdefrange)
|
||||
else
|
||||
local samset = self:_GetSAMTable() -- table of i.1=names, i.2=coordinates, i.3=firing range, i.4=firing height
|
||||
self:_CheckLoop(samset,detset,dlink,self.maxclassic)
|
||||
instatusred, instatusgreen, activeshorads = self:_CheckLoop(samset,detset,dlink,self.maxclassic)
|
||||
end
|
||||
if self.debug or self.verbose then
|
||||
local statusreport = REPORT:New("\nMANTIS Status "..self.name)
|
||||
statusreport:Add("+-----------------------------+")
|
||||
statusreport:Add(string.format("+ SAM in RED State: %2d",instatusred))
|
||||
statusreport:Add(string.format("+ SAM in GREEN State: %2d",instatusgreen))
|
||||
if self.Shorad then
|
||||
statusreport:Add(string.format("+ SHORAD active: %2d",activeshorads))
|
||||
end
|
||||
statusreport:Add("+-----------------------------+")
|
||||
MESSAGE:New(statusreport:Text(),10):ToAll():ToLog()
|
||||
end
|
||||
return self
|
||||
end
|
||||
@@ -1828,7 +1997,7 @@ do
|
||||
end
|
||||
--]]
|
||||
if self.autoshorad then
|
||||
self.Shorad = SHORAD:New(self.name.."-SHORAD",self.name.."-SHORAD",self.SAM_Group,self.ShoradActDistance,self.ShoradTime,self.coalition,self.UseEmOnOff)
|
||||
self.Shorad = SHORAD:New(self.name.."-SHORAD","SHORAD",self.SAM_Group,self.ShoradActDistance,self.ShoradTime,self.coalition,self.UseEmOnOff)
|
||||
self.Shorad:SetDefenseLimits(80,95)
|
||||
self.ShoradLink = true
|
||||
self.Shorad.Groupset=self.ShoradGroupSet
|
||||
@@ -1853,12 +2022,34 @@ do
|
||||
if not self.state2flag then
|
||||
self:_Check(self.Detection,self.DLink)
|
||||
end
|
||||
|
||||
--[[ check Awacs
|
||||
if self.advAwacs and not self.state2flag then
|
||||
self:_Check(self.AWACS_Detection,false)
|
||||
|
||||
local EWRAlive = self:_CheckAnyEWRAlive()
|
||||
|
||||
local function FindSAMSRTR()
|
||||
for i=1,1000 do
|
||||
local randomsam = self.SAM_Group:GetRandom()
|
||||
if randomsam and randomsam:IsAlive() then
|
||||
if randomsam:IsSAM() then return randomsam end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Switch on a random SR/TR if no EWR left over
|
||||
if not EWRAlive then
|
||||
local randomsam = FindSAMSRTR() -- Wrapper.Group#GROUP
|
||||
if randomsam and randomsam:IsAlive() then
|
||||
if self.UseEmOnOff then
|
||||
randomsam:EnableEmission(true)
|
||||
else
|
||||
randomsam:OptionAlarmStateRed()
|
||||
end
|
||||
local name = randomsam:GetName()
|
||||
if self.SamStateTracker[name] ~= "RED" then
|
||||
self:__RedState(1,randomsam)
|
||||
self.SamStateTracker[name] = "RED"
|
||||
end
|
||||
end
|
||||
end
|
||||
--]]
|
||||
|
||||
-- relocate HQ and EWR
|
||||
if self.autorelocate then
|
||||
@@ -1868,8 +2059,6 @@ do
|
||||
|
||||
local halfintv = math.floor(timepassed / relointerval)
|
||||
|
||||
--self:T({timepassed=timepassed, halfintv=halfintv})
|
||||
|
||||
if halfintv >= 1 then
|
||||
self.TimeStamp = timer.getAbsTime()
|
||||
self:_Relocate()
|
||||
@@ -1998,7 +2187,7 @@ do
|
||||
local Shorad = self.Shorad
|
||||
local radius = self.checkradius
|
||||
local ontime = self.ShoradTime
|
||||
Shorad:WakeUpShorad(Name, radius, ontime)
|
||||
Shorad:WakeUpShorad(Name, radius, ontime, nil, true)
|
||||
self:__ShoradActivated(1,Name, radius, ontime)
|
||||
end
|
||||
return self
|
||||
|
||||
@@ -3814,15 +3814,20 @@ function RAT:Status(message, forID)
|
||||
local N0units=group:GetInitialSize()
|
||||
|
||||
-- Monitor travelled distance since last check.
|
||||
local Pnow=coords
|
||||
local Dtravel=Pnow:Get2DDistance(ratcraft.Pnow)
|
||||
ratcraft.Pnow=Pnow
|
||||
local Dtravel=0
|
||||
if coords and ratcraft.Pnow then
|
||||
local Dtravel=coords:Get2DDistance(ratcraft.Pnow)
|
||||
ratcraft.Pnow=coords
|
||||
end
|
||||
|
||||
-- Add up the travelled distance.
|
||||
ratcraft.Distance=ratcraft.Distance+Dtravel
|
||||
|
||||
-- Distance remaining to destination.
|
||||
local Ddestination=Pnow:Get2DDistance(ratcraft.destination:GetCoordinate())
|
||||
local Ddestination=-1
|
||||
if ratcraft.Pnow then
|
||||
Ddestination=ratcraft.Pnow:Get2DDistance(ratcraft.destination:GetCoordinate())
|
||||
end
|
||||
|
||||
-- Status report.
|
||||
if (forID and spawnindex==forID) or (not forID) then
|
||||
|
||||
@@ -107,6 +107,9 @@
|
||||
-- @field Sound.SRS#MSRSQUEUE instructsrsQ SRS queue for range instructor.
|
||||
-- @field #number Coalition Coalition side for the menu, if any.
|
||||
-- @field Core.Menu#MENU_MISSION menuF10root Specific user defined root F10 menu.
|
||||
-- @field #number ceilingaltitude Range ceiling altitude in ft MSL. Aircraft above this altitude are not considered to be in the range. Default is 20000 ft.
|
||||
-- @field #boolean ceilingenabled Range has a ceiling and is not unlimited. Default is false.
|
||||
|
||||
-- @extends Core.Fsm#FSM
|
||||
|
||||
--- *Don't only practice your art, but force your way into its secrets; art deserves that, for it and knowledge can raise man to the Divine.* - Ludwig van Beethoven
|
||||
@@ -273,6 +276,10 @@
|
||||
-- -- Create a range object.
|
||||
-- GoldwaterRange=RANGE:New("Goldwater Range")
|
||||
--
|
||||
-- -- Set and enable the range ceiling altitude in feet MSL. If aircraft are above this altitude they are not considered to be in the range.
|
||||
-- GoldwaterRange:SetRangeCeiling(20000)
|
||||
-- GoldwaterRange:EnableRangeCeiling(true)
|
||||
--
|
||||
-- -- Distance between strafe target and foul line. You have to specify the names of the unit or static objects.
|
||||
-- -- Note that this could also be done manually by simply measuring the distance between the target and the foul line in the ME.
|
||||
-- GoldwaterRange:GetFoullineDistance("GWR Strafe Pit Left 1", "GWR Foul Line Left")
|
||||
@@ -358,6 +365,8 @@ RANGE = {
|
||||
targetpath = nil,
|
||||
targetprefix = nil,
|
||||
Coalition = nil,
|
||||
ceilingaltitude = 20000,
|
||||
ceilingenabled = false,
|
||||
}
|
||||
|
||||
--- Default range parameters.
|
||||
@@ -1085,6 +1094,37 @@ function RANGE:SetRangeZone( zone )
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set range ceiling altitude in feet MSL.
|
||||
-- @param #RANGE self
|
||||
-- @param #number altitude (optional) Ceiling altitude of the range in ft MSL. Default 20000ft MSL
|
||||
-- @return #RANGE self
|
||||
function RANGE:SetRangeCeiling( altitude )
|
||||
self:T(self.lid.."SetRangeCeiling")
|
||||
if altitude and type(altitude) == "number" then
|
||||
self.ceilingaltitude=altitude
|
||||
else
|
||||
self:E(self.lid.."Altitude either not provided or is not a number, using default setting (20000).")
|
||||
self.ceilingaltitude=20000
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Enable range ceiling. Aircraft must be below the ceiling altitude to be considered in the range zone.
|
||||
-- @param #RANGE self
|
||||
-- @param #boolean enabled True if you would like to enable the ceiling check. If no value give, will Default to false.
|
||||
-- @return #RANGE self
|
||||
function RANGE:EnableRangeCeiling( enabled )
|
||||
self:T(self.lid.."EnableRangeCeiling")
|
||||
if enabled and type(enabled) == "boolean" then
|
||||
self.ceilingenabled=enabled
|
||||
else
|
||||
self:E(self.lid.."Enabled either not provide or is not a boolean, using default setting (false).")
|
||||
self.ceilingenabled=false
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set smoke color for marking bomb targets. By default bomb targets are marked by red smoke.
|
||||
-- @param #RANGE self
|
||||
-- @param Utilities.Utils#SMOKECOLOR colorid Color id. Default `SMOKECOLOR.Red`.
|
||||
@@ -1340,7 +1380,7 @@ end
|
||||
-- @return #RANGE self
|
||||
function RANGE:SetSoundfilesPath( path )
|
||||
self.soundpath = tostring( path or "Range Soundfiles/" )
|
||||
self:I( self.lid .. string.format( "Setting sound files path to %s", self.soundpath ) )
|
||||
self:T2( self.lid .. string.format( "Setting sound files path to %s", self.soundpath ) )
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -1636,9 +1676,9 @@ function RANGE:AddBombingTargetUnit( unit, goodhitrange, randommove )
|
||||
|
||||
-- Debug or error output.
|
||||
if _isstatic == true then
|
||||
self:I( self.lid .. string.format( "Adding STATIC bombing target %s with good hit range %d. Random move = %s.", name, goodhitrange, tostring( randommove ) ) )
|
||||
self:T( self.lid .. string.format( "Adding STATIC bombing target %s with good hit range %d. Random move = %s.", name, goodhitrange, tostring( randommove ) ) )
|
||||
elseif _isstatic == false then
|
||||
self:I( self.lid .. string.format( "Adding UNIT bombing target %s with good hit range %d. Random move = %s.", name, goodhitrange, tostring( randommove ) ) )
|
||||
self:T( self.lid .. string.format( "Adding UNIT bombing target %s with good hit range %d. Random move = %s.", name, goodhitrange, tostring( randommove ) ) )
|
||||
else
|
||||
self:E( self.lid .. string.format( "ERROR! No bombing target with name %s could be found. Carefully check all UNIT and STATIC names defined in the mission editor!", name ) )
|
||||
end
|
||||
@@ -1706,7 +1746,7 @@ function RANGE:AddBombingTargetScenery( scenery, goodhitrange)
|
||||
|
||||
-- Debug or error output.
|
||||
if name then
|
||||
self:I( self.lid .. string.format( "Adding SCENERY bombing target %s with good hit range %d", name, goodhitrange) )
|
||||
self:T( self.lid .. string.format( "Adding SCENERY bombing target %s with good hit range %d", name, goodhitrange) )
|
||||
else
|
||||
self:E( self.lid .. string.format( "ERROR! No bombing target with name %s could be found!", name ) )
|
||||
end
|
||||
@@ -1811,7 +1851,7 @@ function RANGE:OnEventBirth( EventData )
|
||||
if not EventData.IniPlayerName then return end
|
||||
|
||||
local _unitName = EventData.IniUnitName
|
||||
local _unit, _playername = self:_GetPlayerUnitAndName( _unitName )
|
||||
local _unit, _playername = self:_GetPlayerUnitAndName( _unitName, EventData.IniPlayerName )
|
||||
|
||||
self:T3( self.lid .. "BIRTH: unit = " .. tostring( EventData.IniUnitName ) )
|
||||
self:T3( self.lid .. "BIRTH: group = " .. tostring( EventData.IniGroupName ) )
|
||||
@@ -1893,7 +1933,7 @@ function RANGE:OnEventHit( EventData )
|
||||
local _currentTarget = self.strafeStatus[_unitID] --#RANGE.StrafeStatus
|
||||
|
||||
-- Player has rolled in on a strafing target.
|
||||
if _currentTarget and target:IsAlive() then
|
||||
if _currentTarget and target and target:IsAlive() then
|
||||
|
||||
local playerPos = _unit:GetCoordinate()
|
||||
local targetPos = target:GetCoordinate()
|
||||
@@ -1967,7 +2007,9 @@ end
|
||||
-- @param #number attackAlt Attack altitude.
|
||||
-- @param #number attackVel Attack velocity.
|
||||
function RANGE._OnImpact(weapon, self, playerData, attackHdg, attackAlt, attackVel)
|
||||
|
||||
|
||||
if not playerData then return end
|
||||
|
||||
-- Get closet target to last position.
|
||||
local _closetTarget = nil -- #RANGE.BombTarget
|
||||
local _distance = nil
|
||||
@@ -1984,13 +2026,13 @@ function RANGE._OnImpact(weapon, self, playerData, attackHdg, attackAlt, attackV
|
||||
-- Coordinate of impact point.
|
||||
local impactcoord = weapon:GetImpactCoordinate()
|
||||
|
||||
-- Check if impact happened in range zone.
|
||||
-- Check if impact happened in range zone.+
|
||||
local insidezone = self.rangezone:IsCoordinateInZone( impactcoord )
|
||||
|
||||
|
||||
-- Smoke impact point of bomb.
|
||||
if playerData.smokebombimpact and insidezone then
|
||||
if playerData.delaysmoke then
|
||||
if playerData and playerData.smokebombimpact and insidezone then
|
||||
if playerData and playerData.delaysmoke then
|
||||
timer.scheduleFunction( self._DelayedSmoke, { coord = impactcoord, color = playerData.smokecolor }, timer.getTime() + self.TdelaySmoke )
|
||||
else
|
||||
impactcoord:Smoke( playerData.smokecolor )
|
||||
@@ -2115,7 +2157,7 @@ function RANGE:OnEventShot( EventData )
|
||||
local _unitName = EventData.IniUnitName
|
||||
|
||||
-- Get player unit and name.
|
||||
local _unit, _playername = self:_GetPlayerUnitAndName( _unitName )
|
||||
local _unit, _playername = self:_GetPlayerUnitAndName( _unitName, EventData.IniPlayerName )
|
||||
|
||||
-- Distance Player-to-Range. Set this to larger value than the threshold.
|
||||
local dPR = self.BombtrackThreshold * 2
|
||||
@@ -2127,11 +2169,13 @@ function RANGE:OnEventShot( EventData )
|
||||
end
|
||||
|
||||
-- Only track if distance player to range is < 25 km. Also check that a player shot. No need to track AI weapons.
|
||||
if _track and dPR <= self.BombtrackThreshold and _unit and _playername then
|
||||
if _track and dPR <= self.BombtrackThreshold and _unit and _playername and self.PlayerSettings[_playername] then
|
||||
|
||||
-- Player data.
|
||||
local playerData = self.PlayerSettings[_playername] -- #RANGE.PlayerData
|
||||
|
||||
|
||||
if not playerData then return end
|
||||
|
||||
-- Attack parameters.
|
||||
local attackHdg=_unit:GetHeading()
|
||||
local attackAlt=_unit:GetHeight()
|
||||
@@ -2192,7 +2236,7 @@ function RANGE:onafterStatus( From, Event, To )
|
||||
end
|
||||
|
||||
-- Check range status.
|
||||
self:I( self.lid .. text )
|
||||
self:T( self.lid .. text )
|
||||
|
||||
end
|
||||
|
||||
@@ -2393,7 +2437,7 @@ function RANGE:onafterSave( From, Event, To )
|
||||
if f then
|
||||
f:write( data )
|
||||
f:close()
|
||||
self:I( self.lid .. string.format( "Saving player results to file %s", tostring( filename ) ) )
|
||||
self:T( self.lid .. string.format( "Saving player results to file %s", tostring( filename ) ) )
|
||||
else
|
||||
self:E( self.lid .. string.format( "ERROR: Could not save results to file %s", tostring( filename ) ) )
|
||||
end
|
||||
@@ -2472,7 +2516,7 @@ function RANGE:onafterLoad( From, Event, To )
|
||||
|
||||
-- Info message.
|
||||
local text = string.format( "Loading player bomb results from file %s", filename )
|
||||
self:I( self.lid .. text )
|
||||
self:T( self.lid .. text )
|
||||
|
||||
-- Load asset data from file.
|
||||
local data = _loadfile( filename )
|
||||
@@ -2845,7 +2889,7 @@ function RANGE:_DisplayRangeInfo( _unitname )
|
||||
|
||||
-- Check if we have a player.
|
||||
if unit and playername then
|
||||
self:I(playername)
|
||||
--self:I(playername)
|
||||
-- Message text.
|
||||
local text = ""
|
||||
|
||||
@@ -2983,7 +3027,7 @@ function RANGE:_DisplayBombTargets( _unitname )
|
||||
end
|
||||
end
|
||||
|
||||
self:_DisplayMessageToGroup( _unit, _text, 120, true, true, _multiplayer )
|
||||
self:_DisplayMessageToGroup( _unit, _text, 150, true, true, _multiplayer )
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3108,7 +3152,10 @@ function RANGE:_CheckPlayers()
|
||||
|
||||
if unit and unit:IsAlive() then
|
||||
|
||||
if unit:IsInZone( self.rangezone ) then
|
||||
local unitalt = unit:GetAltitude(false)
|
||||
local unitaltinfeet = UTILS.MetersToFeet(unitalt)
|
||||
|
||||
if unit:IsInZone(self.rangezone) and (not self.ceilingenabled or unitaltinfeet < self.ceilingaltitude) then
|
||||
|
||||
------------------------------
|
||||
-- Player INSIDE Range Zone --
|
||||
@@ -3449,10 +3496,10 @@ function RANGE:_AddF10Commands( _unitName )
|
||||
-- Range menu
|
||||
local _rangePath = MENU_GROUP:New( group, self.rangename, _rootMenu )
|
||||
|
||||
local _statsPath = MENU_GROUP:New( group, "Statistics", _rangePath )
|
||||
local _markPath = MENU_GROUP:New( group, "Mark Targets", _rangePath )
|
||||
local _settingsPath = MENU_GROUP:New( group, "My Settings", _rangePath )
|
||||
local _infoPath = MENU_GROUP:New( group, "Range Info", _rangePath )
|
||||
local _markPath = MENU_GROUP:New( group, "Mark Targets", _rangePath )
|
||||
local _statsPath = MENU_GROUP:New( group, "Statistics", _rangePath )
|
||||
local _settingsPath = MENU_GROUP:New( group, "My Settings", _rangePath )
|
||||
|
||||
-- F10/On the Range/<Range Name>/My Settings/
|
||||
local _mysmokePath = MENU_GROUP:New( group, "Smoke Color", _settingsPath )
|
||||
@@ -4099,8 +4146,8 @@ end
|
||||
-- @return Wrapper.Unit#UNIT Unit of player.
|
||||
-- @return #string Name of the player.
|
||||
-- @return #boolean If true, group has > 1 player in it
|
||||
function RANGE:_GetPlayerUnitAndName( _unitName )
|
||||
self:F2( _unitName )
|
||||
function RANGE:_GetPlayerUnitAndName( _unitName, PlayerName )
|
||||
--self:I( _unitName )
|
||||
|
||||
if _unitName ~= nil then
|
||||
|
||||
@@ -4109,9 +4156,9 @@ function RANGE:_GetPlayerUnitAndName( _unitName )
|
||||
-- Get DCS unit from its name.
|
||||
local DCSunit = Unit.getByName( _unitName )
|
||||
|
||||
if DCSunit then
|
||||
if DCSunit and DCSunit.getPlayerName then
|
||||
|
||||
local playername = DCSunit:getPlayerName()
|
||||
local playername = DCSunit:getPlayerName() or PlayerName or "None"
|
||||
local unit = UNIT:Find( DCSunit )
|
||||
|
||||
self:T2( { DCSunit = DCSunit, unit = unit, playername = playername } )
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
--
|
||||
-- ### Authors: **applevangelist**, **FlightControl**
|
||||
--
|
||||
-- Last Update: Dec 2023
|
||||
-- Last Update: Dec 2024
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
@@ -28,6 +28,16 @@
|
||||
|
||||
---
|
||||
-- @type SEAD
|
||||
-- @field #string ClassName The Class Name.
|
||||
-- @field #table TargetSkill Table of target skills.
|
||||
-- @field #table SEADGroupPrefixes Table of SEAD prefixes.
|
||||
-- @field #table SuppressedGroups Table of currently suppressed groups.
|
||||
-- @field #number EngagementRange Engagement Range.
|
||||
-- @field #number Padding Padding in seconds.
|
||||
-- @field #function CallBack Callback function for suppression plans.
|
||||
-- @field #boolean UseCallBack Switch for callback function to be used.
|
||||
-- @field #boolean debug Debug switch.
|
||||
-- @field #boolen WeaponTrack Track switch, if true track weapon speed for 30 secs.
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
--- Make SAM sites execute evasive and defensive behaviour when being fired upon.
|
||||
@@ -56,10 +66,11 @@ SEAD = {
|
||||
SEADGroupPrefixes = {},
|
||||
SuppressedGroups = {},
|
||||
EngagementRange = 75, -- default 75% engagement range Feature Request #1355
|
||||
Padding = 10,
|
||||
Padding = 15,
|
||||
CallBack = nil,
|
||||
UseCallBack = false,
|
||||
debug = false,
|
||||
WeaponTrack = false,
|
||||
}
|
||||
|
||||
--- Missile enumerators
|
||||
@@ -69,6 +80,7 @@ SEAD = {
|
||||
["AGM_122"] = "AGM_122",
|
||||
["AGM_84"] = "AGM_84",
|
||||
["AGM_45"] = "AGM_45",
|
||||
["AGM_65"] = "AGM_65",
|
||||
["ALARM"] = "ALARM",
|
||||
["LD-10"] = "LD-10",
|
||||
["X_58"] = "X_58",
|
||||
@@ -88,6 +100,7 @@ SEAD = {
|
||||
-- km and mach
|
||||
["AGM_88"] = { 150, 3},
|
||||
["AGM_45"] = { 12, 2},
|
||||
["AGM_65"] = { 16, 0.9},
|
||||
["AGM_122"] = { 16.5, 2.3},
|
||||
["AGM_84"] = { 280, 0.8},
|
||||
["ALARM"] = { 45, 2},
|
||||
@@ -144,7 +157,7 @@ function SEAD:New( SEADGroupPrefixes, Padding )
|
||||
self:AddTransition("*", "ManageEvasion", "*")
|
||||
self:AddTransition("*", "CalculateHitZone", "*")
|
||||
|
||||
self:I("*** SEAD - Started Version 0.4.6")
|
||||
self:I("*** SEAD - Started Version 0.4.9")
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -371,7 +384,7 @@ function SEAD:onafterManageEvasion(From,Event,To,_targetskill,_targetgroup,SEADP
|
||||
reach = wpndata[1] * 1.1
|
||||
local mach = wpndata[2]
|
||||
wpnspeed = math.floor(mach * 340.29)
|
||||
if Weapon then
|
||||
if Weapon and Weapon:GetSpeed() > 0 then
|
||||
wpnspeed = Weapon:GetSpeed()
|
||||
self:T(string.format("*** SEAD - Weapon Speed from WEAPON: %f m/s",wpnspeed))
|
||||
end
|
||||
@@ -452,29 +465,38 @@ end
|
||||
-- @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 WeaponWrapper = WEAPON:New(EventData.Weapon)
|
||||
--local SEADWeaponSpeed = WeaponWrapper:GetSpeed() -- mps
|
||||
|
||||
self:T( "*** SEAD - Missile Launched = " .. SEADWeaponName)
|
||||
--self:T({ SEADWeapon })
|
||||
|
||||
local SEADWeapon = EventData.Weapon -- Identify the weapon fired
|
||||
local SEADWeaponName = EventData.WeaponName or "None" -- return weapon type
|
||||
|
||||
if self:_CheckHarms(SEADWeaponName) then
|
||||
--UTILS.PrintTableToLog(EventData)
|
||||
local SEADPlane = EventData.IniUnit -- Wrapper.Unit#UNIT
|
||||
|
||||
if not SEADPlane then return self end -- case IniUnit is empty
|
||||
|
||||
local SEADGroup = EventData.IniGroup -- Wrapper.Group#GROUP
|
||||
local SEADPlanePos = SEADPlane:GetCoordinate() -- Core.Point#COORDINATE
|
||||
local SEADUnit = EventData.IniDCSUnit
|
||||
local SEADUnitName = EventData.IniDCSUnitName
|
||||
|
||||
local WeaponWrapper = WEAPON:New(EventData.Weapon) -- Wrapper.Weapon#WEAPON
|
||||
|
||||
self:T( "*** SEAD - Missile Launched = " .. SEADWeaponName)
|
||||
|
||||
self:T( '*** SEAD - Weapon Match' )
|
||||
if self.WeaponTrack == true then
|
||||
WeaponWrapper:SetFuncTrack(function(weapon) env.info(string.format("*** Weapon Speed: %d m/s",weapon:GetSpeed() or -1)) end)
|
||||
WeaponWrapper:StartTrack(0.1)
|
||||
WeaponWrapper:StopTrack(30)
|
||||
end
|
||||
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.")
|
||||
self:T("**** Tracking AGM-88/154 with no target data.")
|
||||
local pos0 = SEADPlane:GetCoordinate()
|
||||
local fheight = SEADPlane:GetHeight()
|
||||
self:__CalculateHitZone(20,SEADWeapon,pos0,fheight,SEADGroup,SEADWeaponName)
|
||||
@@ -520,7 +542,7 @@ function SEAD:HandleEventShot( EventData )
|
||||
end
|
||||
if SEADGroupFound == true then -- yes we are being attacked
|
||||
if string.find(SEADWeaponName,"ADM_141",1,true) then
|
||||
self:__ManageEvasion(2,_targetskill,_targetgroup,SEADPlanePos,SEADWeaponName,SEADGroup,0,WeaponWrapper)
|
||||
self:__ManageEvasion(2,_targetskill,_targetgroup,SEADPlanePos,SEADWeaponName,SEADGroup,2,WeaponWrapper)
|
||||
else
|
||||
self:ManageEvasion(_targetskill,_targetgroup,SEADPlanePos,SEADWeaponName,SEADGroup,0,WeaponWrapper)
|
||||
end
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
-- @image Functional.Shorad.jpg
|
||||
--
|
||||
-- Date: Nov 2021
|
||||
-- Last Update: Nov 2023
|
||||
-- Last Update: Jan 2025
|
||||
|
||||
-------------------------------------------------------------------------
|
||||
--- **SHORAD** class, extends Core.Base#BASE
|
||||
@@ -113,7 +113,7 @@ SHORAD = {
|
||||
SkateNumber = 3,
|
||||
SkateZones = nil,
|
||||
minscootdist = 100,
|
||||
minscootdist = 3000,
|
||||
maxscootdist = 3000,
|
||||
scootrandomcoord = false,
|
||||
}
|
||||
|
||||
@@ -443,7 +443,9 @@ do
|
||||
for _,_groups in pairs (shoradset) do
|
||||
local groupname = _groups:GetName()
|
||||
if string.find(groupname, tgtgrp, 1, true) then
|
||||
returnname = true
|
||||
if _groups:IsSAM() then
|
||||
returnname = true
|
||||
end
|
||||
end
|
||||
end
|
||||
return returnname
|
||||
@@ -470,6 +472,7 @@ do
|
||||
-- @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
|
||||
-- @param #boolean ShotAt If true, function is called after a shot
|
||||
-- @return #SHORAD self
|
||||
-- @usage Use this function to integrate with other systems, example
|
||||
--
|
||||
@@ -479,7 +482,7 @@ do
|
||||
-- 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)
|
||||
function SHORAD:onafterWakeUpShorad(From, Event, To, TargetGroup, Radius, ActiveTimer, TargetCat, ShotAt)
|
||||
self:T(self.lid .. " WakeUpShorad")
|
||||
self:T({TargetGroup, Radius, ActiveTimer, TargetCat})
|
||||
local targetcat = TargetCat or Object.Category.UNIT
|
||||
@@ -521,7 +524,27 @@ do
|
||||
-- go through set and find the one(s) to activate
|
||||
local TDiff = 4
|
||||
for _,_group in pairs (shoradset) do
|
||||
if _group:IsAnyInZone(targetzone) then
|
||||
|
||||
local groupname = _group:GetName()
|
||||
|
||||
if groupname == TargetGroup and ShotAt==true then
|
||||
-- Shot at a SHORAD group
|
||||
if self.UseEmOnOff then
|
||||
_group:EnableEmission(false)
|
||||
end
|
||||
_group:OptionAlarmStateGreen()
|
||||
self.ActiveGroups[groupname] = nil
|
||||
local text = string.format("Shot at SHORAD %s! Evading!", _group:GetName())
|
||||
self:T(text)
|
||||
local m = MESSAGE:New(text,10,"SHORAD"):ToAllIf(self.debug)
|
||||
|
||||
--Shoot and Scoot
|
||||
if self.shootandscoot then
|
||||
self:__ShootAndScoot(1,_group)
|
||||
end
|
||||
|
||||
elseif _group:IsAnyInZone(targetzone) or groupname == TargetGroup then
|
||||
-- shot at a group we protect
|
||||
local text = string.format("Waking up SHORAD %s", _group:GetName())
|
||||
self:T(text)
|
||||
local m = MESSAGE:New(text,10,"SHORAD"):ToAllIf(self.debug)
|
||||
@@ -529,7 +552,6 @@ do
|
||||
_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
|
||||
@@ -607,7 +629,7 @@ do
|
||||
_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)
|
||||
self:WakeUpShorad(_targetgroupname, self.Radius, self.ActiveTimer, Object.Category.UNIT,true)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -736,7 +758,7 @@ do
|
||||
-- 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)
|
||||
self:WakeUpShorad(targetgroupname, self.Radius, self.ActiveTimer, targetcat, true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -288,7 +288,7 @@ function TIRESIAS:_InitGroups()
|
||||
}
|
||||
end
|
||||
if grp.Tiresias and (not grp.Tiresias.exception == true) then
|
||||
if grp.Tiresias.invisible and grp.Tiresias.invisible == false then
|
||||
if grp.Tiresias.invisible == false then
|
||||
grp:SetCommandInvisible(true)
|
||||
grp.Tiresias.invisible = true
|
||||
if SwitchAAA then
|
||||
@@ -315,10 +315,11 @@ function TIRESIAS:_InitGroups()
|
||||
}
|
||||
end
|
||||
if grp.Tiresias and (not grp.Tiresias.exception == true) then
|
||||
if grp.Tiresias and grp.Tiresias.invisible and grp.Tiresias.invisible == false then
|
||||
if grp.Tiresias and grp.Tiresias.invisible == false then
|
||||
grp:SetCommandInvisible(true)
|
||||
grp:SetAIOff()
|
||||
grp.Tiresias.invisible = true
|
||||
grp.Tiresias.AIOff = true
|
||||
end
|
||||
end
|
||||
--BASE:I(string.format("Init/Switch off Vehicle %s (Exception %s)",grp:GetName(),tostring(grp.Tiresias.exception)))
|
||||
@@ -336,7 +337,7 @@ function TIRESIAS:_InitGroups()
|
||||
}
|
||||
end
|
||||
if grp.Tiresias and (not grp.Tiresias.exception == true) then
|
||||
if grp.Tiresias and grp.Tiresias.invisible and grp.Tiresias.invisible == false then
|
||||
if grp.Tiresias and grp.Tiresias.invisible == false then
|
||||
grp:SetCommandInvisible(true)
|
||||
grp.Tiresias.invisible = true
|
||||
end
|
||||
@@ -392,7 +393,8 @@ function TIRESIAS:_SwitchOnGroups(group,radius)
|
||||
ground:ForEachGroupAlive(
|
||||
function(grp)
|
||||
local name = grp:GetName()
|
||||
if grp.Tiresias and grp.Tiresias.type and (not grp.Tiresias.exception == true ) then
|
||||
if grp:GetCoalition() ~= group:GetCoalition()
|
||||
and grp.Tiresias and grp.Tiresias.type and (not grp.Tiresias.exception == true ) then
|
||||
if grp.Tiresias.invisible == true then
|
||||
grp:SetCommandInvisible(false)
|
||||
grp.Tiresias.invisible = false
|
||||
|
||||
@@ -6047,7 +6047,7 @@ function WAREHOUSE:_SpawnAssetAircraft(alias, asset, request, parking, uncontrol
|
||||
|
||||
else
|
||||
|
||||
if #parking<#template.units and not airstart then
|
||||
if parking and #parking<#template.units and not airstart then
|
||||
local text=string.format("ERROR: Not enough parking! Free parking = %d < %d aircraft to be spawned.", #parking, #template.units)
|
||||
self:_DebugMessage(text)
|
||||
return nil
|
||||
@@ -6089,7 +6089,7 @@ function WAREHOUSE:_SpawnAssetAircraft(alias, asset, request, parking, uncontrol
|
||||
terminal=parking[i].TerminalID
|
||||
end
|
||||
|
||||
if self.Debug then
|
||||
if self.Debug and terminal then
|
||||
local text=string.format("Spawnplace unit %s terminal %d.", unit.name, terminal)
|
||||
coord:MarkToAll(text)
|
||||
env.info(text)
|
||||
@@ -6732,7 +6732,7 @@ end
|
||||
-- @param Wrapper.Group#GROUP deadgroup Group of unit that died.
|
||||
-- @param #WAREHOUSE.Pendingitem request Request that needs to be updated.
|
||||
function WAREHOUSE:_UnitDead(deadunit, deadgroup, request)
|
||||
self:F(self.lid.."FF unit dead "..deadunit:GetName())
|
||||
--self:F(self.lid.."FF unit dead "..deadunit:GetName())
|
||||
|
||||
-- Find opsgroup.
|
||||
local opsgroup=_DATABASE:FindOpsGroup(deadgroup)
|
||||
@@ -8122,9 +8122,11 @@ function WAREHOUSE:_FindParkingForAssets(airbase, assets)
|
||||
-- Debug output for occupied spots.
|
||||
if self.Debug then
|
||||
local coord=problem.coord --Core.Point#COORDINATE
|
||||
local text=string.format("Obstacle %s [type=%s] blocking spot=%d! Size=%.1f m and distance=%.1f m.", problem.name, problem.type, _termid, problem.size, problem.dist)
|
||||
self:I(self.lid..text)
|
||||
coord:MarkToAll(string.format(text))
|
||||
if coord then
|
||||
local text=string.format("Obstacle %s [type=%s] blocking spot=%d! Size=%.1f m and distance=%.1f m.", problem.name, problem.type, _termid, problem.size, problem.dist)
|
||||
self:I(self.lid..text)
|
||||
coord:MarkToAll(text)
|
||||
end
|
||||
else
|
||||
self:T(self.lid..string.format("Parking spot %d is occupied or not big enough!", _termid))
|
||||
end
|
||||
@@ -8433,12 +8435,14 @@ function WAREHOUSE:_GetAttribute(group)
|
||||
local attribute=WAREHOUSE.Attribute.OTHER_UNKNOWN --#WAREHOUSE.Attribute
|
||||
|
||||
if group then
|
||||
|
||||
local groupCat=group:GetCategory()
|
||||
|
||||
-----------
|
||||
--- Air ---
|
||||
-----------
|
||||
-- Planes
|
||||
local transportplane=group:HasAttribute("Transports") and group:HasAttribute("Planes")
|
||||
local transportplane=group:HasAttribute("Transports") and group:HasAttribute("Planes") and groupCat==Group.Category.AIRPLANE
|
||||
local awacs=group:HasAttribute("AWACS")
|
||||
local fighter=group:HasAttribute("Fighters") or group:HasAttribute("Interceptors") or group:HasAttribute("Multirole fighters") or (group:HasAttribute("Bombers") and not group:HasAttribute("Strategic bombers"))
|
||||
local bomber=group:HasAttribute("Strategic bombers")
|
||||
@@ -8593,7 +8597,6 @@ end
|
||||
-- @param #WAREHOUSE.Queueitem qitem Item of queue to be removed.
|
||||
-- @param #table queue The queue from which the item should be deleted.
|
||||
function WAREHOUSE:_DeleteQueueItem(qitem, queue)
|
||||
self:F({qitem=qitem, queue=queue})
|
||||
|
||||
for i=1,#queue do
|
||||
local _item=queue[i] --#WAREHOUSE.Queueitem
|
||||
|
||||
@@ -10,7 +10,7 @@ _SCHEDULEDISPATCHER = SCHEDULEDISPATCHER:New() -- Core.ScheduleDispatcher#SCHEDU
|
||||
_DATABASE = DATABASE:New() -- Core.Database#DATABASE
|
||||
|
||||
--- Settings
|
||||
_SETTINGS = SETTINGS:Set()
|
||||
_SETTINGS = SETTINGS:Set() -- Core.Settings#SETTINGS
|
||||
_SETTINGS:SetPlayerMenuOn()
|
||||
|
||||
--- Register cargos.
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Utilities/Enums.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Utilities/Utils.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Utilities/Profiler.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Utilities/Templates.lua' )
|
||||
--__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Utilities/STTS.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Utilities/FiFo.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Utilities/Socket.lua' )
|
||||
|
||||
@@ -49,6 +47,7 @@ __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Wrapper/Marker.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Wrapper/Weapon.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Wrapper/Net.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Wrapper/Storage.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Wrapper/DynamicCargo.lua' )
|
||||
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Cargo/Cargo.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Cargo/CargoUnit.lua' )
|
||||
|
||||
@@ -47,6 +47,7 @@ __Moose.Include( 'Wrapper\\Marker.lua' )
|
||||
__Moose.Include( 'Wrapper\\Net.lua' )
|
||||
__Moose.Include( 'Wrapper\\Weapon.lua' )
|
||||
__Moose.Include( 'Wrapper\\Storage.lua' )
|
||||
__Moose.Include( 'Wrapper\\DynamicCargo.lua' )
|
||||
|
||||
__Moose.Include( 'Cargo\\Cargo.lua' )
|
||||
__Moose.Include( 'Cargo\\CargoUnit.lua' )
|
||||
@@ -81,6 +82,7 @@ __Moose.Include( 'Functional\\AICSAR.lua' )
|
||||
__Moose.Include( 'Functional\\AmmoTruck.lua' )
|
||||
__Moose.Include( 'Functional\\Tiresias.lua' )
|
||||
__Moose.Include( 'Functional\\Stratego.lua' )
|
||||
__Moose.Include( 'Functional\\ClientWatch.lua' )
|
||||
|
||||
__Moose.Include( 'Ops\\Airboss.lua' )
|
||||
__Moose.Include( 'Ops\\RecoveryTanker.lua' )
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
-- * Option to present information in imperial or metric units
|
||||
-- * Runway length and airfield elevation (optional)
|
||||
-- * Frequencies/channels of nav aids (ILS, VOR, NDB, TACAN, PRMG, RSBN) (optional)
|
||||
-- * SRS Simple-Text-To-Speech (STTS) integration (no sound files necessary)
|
||||
-- * SRS Simple-Text-To-Speech (MSRS) integration (no sound files necessary)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
@@ -291,7 +291,7 @@
|
||||
-- ## Nevada: Nellis AFB
|
||||
--
|
||||
-- -- ATIS Nellis AFB on 270.10 MHz AM.
|
||||
-- atisNellis=ATIS:New(AIRBASE.Nevada.Nellis_AFB, 270.1)
|
||||
-- atisNellis=ATIS:New(AIRBASE.Nevada.Nellis, 270.1)
|
||||
-- atisNellis:SetRadioRelayUnitName("Radio Relay Nellis")
|
||||
-- atisNellis:SetActiveRunway("21L")
|
||||
-- atisNellis:SetTowerFrequencies({327.000, 132.550})
|
||||
@@ -302,7 +302,7 @@
|
||||
-- ## Persian Gulf: Abu Dhabi International Airport
|
||||
--
|
||||
-- -- ATIS Abu Dhabi International on 125.1 MHz AM.
|
||||
-- atisAbuDhabi=ATIS:New(AIRBASE.PersianGulf.Abu_Dhabi_International_Airport, 125.1)
|
||||
-- atisAbuDhabi=ATIS:New(AIRBASE.PersianGulf.Abu_Dhabi_Intl, 125.1)
|
||||
-- atisAbuDhabi:SetRadioRelayUnitName("Radio Relay Abu Dhabi International Airport")
|
||||
-- atisAbuDhabi:SetMetricUnits()
|
||||
-- atisAbuDhabi:SetActiveRunway("L")
|
||||
@@ -498,6 +498,9 @@ ATIS.Alphabet = {
|
||||
-- @field #number Syria +5° (East).
|
||||
-- @field #number MarianaIslands +2° (East).
|
||||
-- @field #number SinaiMap +5° (East).
|
||||
-- @field #number Kola +15° (East).
|
||||
-- @field #number Afghanistan +3° (East).
|
||||
-- @field #number Iraq +4.4° (East).
|
||||
ATIS.RunwayM2T = {
|
||||
Caucasus = 0,
|
||||
Nevada = 12,
|
||||
@@ -508,6 +511,9 @@ ATIS.RunwayM2T = {
|
||||
MarianaIslands = 2,
|
||||
Falklands = 12,
|
||||
SinaiMap = 5,
|
||||
Kola = 15,
|
||||
Afghanistan = 3,
|
||||
Iraq=4.4
|
||||
}
|
||||
|
||||
--- Whether ICAO phraseology is used for ATIS broadcasts.
|
||||
@@ -521,6 +527,9 @@ ATIS.RunwayM2T = {
|
||||
-- @field #boolean MarianaIslands true.
|
||||
-- @field #boolean Falklands true.
|
||||
-- @field #boolean SinaiMap true.
|
||||
-- @field #boolean Kola true.
|
||||
-- @field #boolean Afghanistan true.
|
||||
-- @field #boolean Iraq true.
|
||||
ATIS.ICAOPhraseology = {
|
||||
Caucasus = true,
|
||||
Nevada = false,
|
||||
@@ -531,6 +540,9 @@ ATIS.ICAOPhraseology = {
|
||||
MarianaIslands = true,
|
||||
Falklands = true,
|
||||
SinaiMap = true,
|
||||
Kola = true,
|
||||
Afghanistan = true,
|
||||
Iraq = true,
|
||||
}
|
||||
|
||||
--- Nav point data.
|
||||
@@ -619,83 +631,83 @@ ATIS.ICAOPhraseology = {
|
||||
-- @field #ATIS.Soundfile TACANChannel
|
||||
-- @field #ATIS.Soundfile VORFrequency
|
||||
ATIS.Sound = {
|
||||
ActiveRunway = { filename = "ActiveRunway.ogg", duration = 0.99 },
|
||||
ActiveRunwayDeparture = { filename = "ActiveRunwayDeparture.ogg", duration = 0.99 },
|
||||
ActiveRunwayArrival = { filename = "ActiveRunwayArrival.ogg", duration = 0.99 },
|
||||
AdviceOnInitial = { filename = "AdviceOnInitial.ogg", duration = 3.00 },
|
||||
Airport = { filename = "Airport.ogg", duration = 0.66 },
|
||||
Altimeter = { filename = "Altimeter.ogg", duration = 0.68 },
|
||||
At = { filename = "At.ogg", duration = 0.41 },
|
||||
CloudBase = { filename = "CloudBase.ogg", duration = 0.82 },
|
||||
CloudCeiling = { filename = "CloudCeiling.ogg", duration = 0.61 },
|
||||
CloudsBroken = { filename = "CloudsBroken.ogg", duration = 1.07 },
|
||||
CloudsFew = { filename = "CloudsFew.ogg", duration = 0.99 },
|
||||
CloudsNo = { filename = "CloudsNo.ogg", duration = 1.01 },
|
||||
CloudsNotAvailable = { filename = "CloudsNotAvailable.ogg", duration = 2.35 },
|
||||
CloudsOvercast = { filename = "CloudsOvercast.ogg", duration = 0.83 },
|
||||
CloudsScattered = { filename = "CloudsScattered.ogg", duration = 1.18 },
|
||||
Decimal = { filename = "Decimal.ogg", duration = 0.54 },
|
||||
DegreesCelsius = { filename = "DegreesCelsius.ogg", duration = 1.27 },
|
||||
DegreesFahrenheit = { filename = "DegreesFahrenheit.ogg", duration = 1.23 },
|
||||
DewPoint = { filename = "DewPoint.ogg", duration = 0.65 },
|
||||
Dust = { filename = "Dust.ogg", duration = 0.54 },
|
||||
Elevation = { filename = "Elevation.ogg", duration = 0.78 },
|
||||
EndOfInformation = { filename = "EndOfInformation.ogg", duration = 1.15 },
|
||||
Feet = { filename = "Feet.ogg", duration = 0.45 },
|
||||
Fog = { filename = "Fog.ogg", duration = 0.47 },
|
||||
Gusting = { filename = "Gusting.ogg", duration = 0.55 },
|
||||
HectoPascal = { filename = "HectoPascal.ogg", duration = 1.15 },
|
||||
Hundred = { filename = "Hundred.ogg", duration = 0.47 },
|
||||
InchesOfMercury = { filename = "InchesOfMercury.ogg", duration = 1.16 },
|
||||
Information = { filename = "Information.ogg", duration = 0.85 },
|
||||
Kilometers = { filename = "Kilometers.ogg", duration = 0.78 },
|
||||
Knots = { filename = "Knots.ogg", duration = 0.59 },
|
||||
Left = { filename = "Left.ogg", duration = 0.54 },
|
||||
MegaHertz = { filename = "MegaHertz.ogg", duration = 0.87 },
|
||||
Meters = { filename = "Meters.ogg", duration = 0.59 },
|
||||
MetersPerSecond = { filename = "MetersPerSecond.ogg", duration = 1.14 },
|
||||
Miles = { filename = "Miles.ogg", duration = 0.60 },
|
||||
MillimetersOfMercury = { filename = "MillimetersOfMercury.ogg", duration = 1.53 },
|
||||
Minus = { filename = "Minus.ogg", duration = 0.64 },
|
||||
N0 = { filename = "N-0.ogg", duration = 0.55 },
|
||||
N1 = { filename = "N-1.ogg", duration = 0.41 },
|
||||
N2 = { filename = "N-2.ogg", duration = 0.37 },
|
||||
N3 = { filename = "N-3.ogg", duration = 0.41 },
|
||||
N4 = { filename = "N-4.ogg", duration = 0.37 },
|
||||
N5 = { filename = "N-5.ogg", duration = 0.43 },
|
||||
N6 = { filename = "N-6.ogg", duration = 0.55 },
|
||||
N7 = { filename = "N-7.ogg", duration = 0.43 },
|
||||
N8 = { filename = "N-8.ogg", duration = 0.38 },
|
||||
N9 = { filename = "N-9.ogg", duration = 0.55 },
|
||||
NauticalMiles = { filename = "NauticalMiles.ogg", duration = 1.04 },
|
||||
None = { filename = "None.ogg", duration = 0.43 },
|
||||
QFE = { filename = "QFE.ogg", duration = 0.63 },
|
||||
QNH = { filename = "QNH.ogg", duration = 0.71 },
|
||||
Rain = { filename = "Rain.ogg", duration = 0.41 },
|
||||
Right = { filename = "Right.ogg", duration = 0.44 },
|
||||
Snow = { filename = "Snow.ogg", duration = 0.48 },
|
||||
SnowStorm = { filename = "SnowStorm.ogg", duration = 0.82 },
|
||||
StatuteMiles = { filename = "StatuteMiles.ogg", duration = 1.15 },
|
||||
SunriseAt = { filename = "SunriseAt.ogg", duration = 0.92 },
|
||||
SunsetAt = { filename = "SunsetAt.ogg", duration = 0.95 },
|
||||
Temperature = { filename = "Temperature.ogg", duration = 0.64 },
|
||||
Thousand = { filename = "Thousand.ogg", duration = 0.55 },
|
||||
ThunderStorm = { filename = "ThunderStorm.ogg", duration = 0.81 },
|
||||
TimeLocal = { filename = "TimeLocal.ogg", duration = 0.90 },
|
||||
TimeZulu = { filename = "TimeZulu.ogg", duration = 0.86 },
|
||||
TowerFrequency = { filename = "TowerFrequency.ogg", duration = 1.19 },
|
||||
Visibilty = { filename = "Visibility.ogg", duration = 0.79 },
|
||||
WeatherPhenomena = { filename = "WeatherPhenomena.ogg", duration = 1.07 },
|
||||
WindFrom = { filename = "WindFrom.ogg", duration = 0.60 },
|
||||
ActiveRunway = { filename = "ActiveRunway.ogg", duration = 0.85 },
|
||||
ActiveRunwayDeparture = { filename = "ActiveRunwayDeparture.ogg", duration = 1.50 },
|
||||
ActiveRunwayArrival = { filename = "ActiveRunwayArrival.ogg", duration = 1.38 },
|
||||
AdviceOnInitial = { filename = "AdviceOnInitial.ogg", duration = 2.98 },
|
||||
Airport = { filename = "Airport.ogg", duration = 0.55 },
|
||||
Altimeter = { filename = "Altimeter.ogg", duration = 0.91 },
|
||||
At = { filename = "At.ogg", duration = 0.32 },
|
||||
CloudBase = { filename = "CloudBase.ogg", duration = 0.69 },
|
||||
CloudCeiling = { filename = "CloudCeiling.ogg", duration = 0.53 },
|
||||
CloudsBroken = { filename = "CloudsBroken.ogg", duration = 0.81 },
|
||||
CloudsFew = { filename = "CloudsFew.ogg", duration = 0.74 },
|
||||
CloudsNo = { filename = "CloudsNo.ogg", duration = 0.69},
|
||||
CloudsNotAvailable = { filename = "CloudsNotAvailable.ogg", duration = 2.64 },
|
||||
CloudsOvercast = { filename = "CloudsOvercast.ogg", duration = 0.82 },
|
||||
CloudsScattered = { filename = "CloudsScattered.ogg", duration = 0.89 },
|
||||
Decimal = { filename = "Decimal.ogg", duration = 0.71 },
|
||||
DegreesCelsius = { filename = "DegreesCelsius.ogg", duration = 1.08 },
|
||||
DegreesFahrenheit = { filename = "DegreesFahrenheit.ogg", duration = 1.07 },
|
||||
DewPoint = { filename = "DewPoint.ogg", duration = 0.59 },
|
||||
Dust = { filename = "Dust.ogg", duration = 0.37 },
|
||||
Elevation = { filename = "Elevation.ogg", duration = 0.92 },
|
||||
EndOfInformation = { filename = "EndOfInformation.ogg", duration = 1.24 },
|
||||
Feet = { filename = "Feet.ogg", duration = 0.34 },
|
||||
Fog = { filename = "Fog.ogg", duration = 0.41 },
|
||||
Gusting = { filename = "Gusting.ogg", duration = 0.58 },
|
||||
HectoPascal = { filename = "HectoPascal.ogg", duration = 0.92 },
|
||||
Hundred = { filename = "Hundred.ogg", duration = 0.53 },
|
||||
ILSFrequency = { filename = "ILSFrequency.ogg", duration = 1.30 },
|
||||
InnerNDBFrequency = { filename = "InnerNDBFrequency.ogg", duration = 1.56 },
|
||||
OuterNDBFrequency = { filename = "OuterNDBFrequency.ogg", duration = 1.59 },
|
||||
RunwayLength = { filename = "RunwayLength.ogg", duration = 0.91 },
|
||||
VORFrequency = { filename = "VORFrequency.ogg", duration = 1.38 },
|
||||
TACANChannel = { filename = "TACANChannel.ogg", duration = 0.88 },
|
||||
PRMGChannel = { filename = "PRMGChannel.ogg", duration = 1.18 },
|
||||
RSBNChannel = { filename = "RSBNChannel.ogg", duration = 1.14 },
|
||||
Zulu = { filename = "Zulu.ogg", duration = 0.62 },
|
||||
InchesOfMercury = { filename = "InchesOfMercury.ogg", duration = 1.26 },
|
||||
Information = { filename = "Information.ogg", duration = 0.99 },
|
||||
InnerNDBFrequency = { filename = "InnerNDBFrequency.ogg", duration = 1.69 },
|
||||
Kilometers = { filename = "Kilometers.ogg", duration = 0.93 },
|
||||
Knots = { filename = "Knots.ogg", duration = 0.46 },
|
||||
Left = { filename = "Left.ogg", duration = 0.41 },
|
||||
MegaHertz = { filename = "MegaHertz.ogg", duration = 0.83 },
|
||||
Meters = { filename = "Meters.ogg", duration = 0.55 },
|
||||
MetersPerSecond = { filename = "MetersPerSecond.ogg", duration = 1.03 },
|
||||
Miles = { filename = "Miles.ogg", duration = 0.44 },
|
||||
MillimetersOfMercury = { filename = "MillimetersOfMercury.ogg", duration = 1.59 },
|
||||
Minus = { filename = "Minus.ogg", duration = 0.55 },
|
||||
N0 = { filename = "N-0.ogg", duration = 0.52 },
|
||||
N1 = { filename = "N-1.ogg", duration = 0.35 },
|
||||
N2 = { filename = "N-2.ogg", duration = 0.41 },
|
||||
N3 = { filename = "N-3.ogg", duration = 0.34 },
|
||||
N4 = { filename = "N-4.ogg", duration = 0.37 },
|
||||
N5 = { filename = "N-5.ogg", duration = 0.40 },
|
||||
N6 = { filename = "N-6.ogg", duration = 0.46 },
|
||||
N7 = { filename = "N-7.ogg", duration = 0.52 },
|
||||
N8 = { filename = "N-8.ogg", duration = 0.36 },
|
||||
N9 = { filename = "N-9.ogg", duration = 0.51 },
|
||||
NauticalMiles = { filename = "NauticalMiles.ogg", duration = 0.93 },
|
||||
None = { filename = "None.ogg", duration = 0.33 },
|
||||
OuterNDBFrequency = { filename = "OuterNDBFrequency.ogg", duration = 1.70 },
|
||||
PRMGChannel = { filename = "PRMGChannel.ogg", duration = 1.27 },
|
||||
QFE = { filename = "QFE.ogg", duration = 0.90 },
|
||||
QNH = { filename = "QNH.ogg", duration = 0.94 },
|
||||
Rain = { filename = "Rain.ogg", duration = 0.35 },
|
||||
Right = { filename = "Right.ogg", duration = 0.31 },
|
||||
RSBNChannel = { filename = "RSBNChannel.ogg", duration = 1.26 },
|
||||
RunwayLength = { filename = "RunwayLength.ogg", duration = 0.81 },
|
||||
Snow = { filename = "Snow.ogg", duration = 0.40 },
|
||||
SnowStorm = { filename = "SnowStorm.ogg", duration = 0.73 },
|
||||
StatuteMiles = { filename = "StatuteMiles.ogg", duration = 0.90 },
|
||||
SunriseAt = { filename = "SunriseAt.ogg", duration = 0.82 },
|
||||
SunsetAt = { filename = "SunsetAt.ogg", duration = 0.87 },
|
||||
TACANChannel = { filename = "TACANChannel.ogg", duration = 0.81 },
|
||||
Temperature = { filename = "Temperature.ogg", duration = 0.70 },
|
||||
Thousand = { filename = "Thousand.ogg", duration = 0.58 },
|
||||
ThunderStorm = { filename = "ThunderStorm.ogg", duration = 0.79 },
|
||||
TimeLocal = { filename = "TimeLocal.ogg", duration = 0.83 },
|
||||
TimeZulu = { filename = "TimeZulu.ogg", duration = 0.83 },
|
||||
TowerFrequency = { filename = "TowerFrequency.ogg", duration = 1.05 },
|
||||
Visibilty = { filename = "Visibility.ogg", duration = 1.16 },
|
||||
VORFrequency = { filename = "VORFrequency.ogg", duration = 1.28 },
|
||||
WeatherPhenomena = { filename = "WeatherPhenomena.ogg", duration = 1.09 },
|
||||
WindFrom = { filename = "WindFrom.ogg", duration = 0.63 },
|
||||
Zulu = { filename = "Zulu.ogg", duration = 0.51 },
|
||||
}
|
||||
|
||||
---
|
||||
@@ -882,6 +894,66 @@ ATIS.Messages = {
|
||||
FARP = "Farp",
|
||||
DELIMITER = "Punto", -- decimal delimiter
|
||||
},
|
||||
-- French messages thanks to @Wojtech and Bing
|
||||
FR = {
|
||||
HOURS = "Heures",
|
||||
TIME = "Temps",
|
||||
NOCLOUDINFO = "Informations sur la couverture nuageuse non disponibles",
|
||||
OVERCAST = "Ciel couvert",
|
||||
BROKEN = "Nuages fragmentés",
|
||||
SCATTERED = "Nuages épars",
|
||||
FEWCLOUDS = "Nuages rares",
|
||||
NOCLOUDS = "Clair",
|
||||
AIRPORT = "Aéroport",
|
||||
INFORMATION ="Information",
|
||||
SUNRISEAT = "Levé du soleil à %s heure locale",
|
||||
SUNSETAT = "Couché du soleil à %s heure locale",
|
||||
WINDFROMMS = "Vent du %s pour %s mètres par seconde",
|
||||
WINDFROMKNOTS = "Vent du %s pour %s noeuds",
|
||||
GUSTING = "Rafale de vent",
|
||||
VISIKM = "Visibilité %s kilomètres",
|
||||
VISISM = "Visibilité %s Miles",
|
||||
RAIN = "Pluie",
|
||||
TSTORM = "Orage",
|
||||
SNOW = "Neige",
|
||||
SSTROM = "Tempête de neige",
|
||||
FOG = "Brouillard",
|
||||
DUST = "Poussière",
|
||||
PHENOMENA = "Phénomène météorologique",
|
||||
CLOUDBASEM = "Couverture nuageuse de %s à %s mètres",
|
||||
CLOUDBASEFT = "Couverture nuageuse de %s à %s pieds",
|
||||
TEMPERATURE = "Température",
|
||||
DEWPOINT = "Point de rosée",
|
||||
ALTIMETER = "Altimètre",
|
||||
ACTIVERUN = "Décollages piste",
|
||||
ACTIVELANDING = "Atterrissages piste",
|
||||
LEFT = "Gauche",
|
||||
RIGHT = "Droite",
|
||||
RWYLENGTH = "Longueur de piste",
|
||||
METERS = "Mètre",
|
||||
FEET = "Pieds",
|
||||
ELEVATION = "Hauteur",
|
||||
TOWERFREQ = "Fréquences de la tour",
|
||||
ILSFREQ = "Fréquences ILS",
|
||||
OUTERNDB = "Fréquences Outer NDB",
|
||||
INNERNDB = "Fréquences Inner NDB",
|
||||
VORFREQ = "Fréquences VOR",
|
||||
VORFREQTTS = "Fréquences V O R",
|
||||
TACANCH = "Canal TACAN %d",
|
||||
RSBNCH = "Canal RSBN",
|
||||
PRMGCH = "Canal PRMG",
|
||||
ADVISE = "Informez le contrôle que vous avez copié l'information",
|
||||
STATUTE = "Statute Miles",
|
||||
DEGREES = "Degré celcius",
|
||||
FAHRENHEIT = "Degré Fahrenheit",
|
||||
INCHHG = "Pouces de mercure",
|
||||
MMHG = "Millimètres de mercure",
|
||||
HECTO = "Hectopascals",
|
||||
METERSPER = "Mètres par seconde",
|
||||
TACAN = "TAKAN",
|
||||
FARP = "FARPE",
|
||||
DELIMITER = "Décimal", -- decimal delimiter
|
||||
}
|
||||
}
|
||||
|
||||
---
|
||||
@@ -894,7 +966,7 @@ _ATIS = {}
|
||||
|
||||
--- ATIS class version.
|
||||
-- @field #string version
|
||||
ATIS.version = "1.0.0"
|
||||
ATIS.version = "1.0.1"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- TODO list
|
||||
@@ -1061,7 +1133,7 @@ end
|
||||
-- @return #ATIS self
|
||||
function ATIS:_InitLocalization()
|
||||
self:T(self.lid.."_InitLocalization")
|
||||
self.gettext = TEXTANDSOUND:New("AWACS","en") -- Core.TextAndSound#TEXTANDSOUND
|
||||
self.gettext = TEXTANDSOUND:New("ATIS","en") -- Core.TextAndSound#TEXTANDSOUND
|
||||
self.locale = "en"
|
||||
for locale,table in pairs(self.Messages) do
|
||||
local Locale = string.lower(tostring(locale))
|
||||
@@ -1975,17 +2047,28 @@ function ATIS:onafterBroadcast( From, Event, To )
|
||||
|
||||
local hours = self.gettext:GetEntry("HOURS",self.locale)
|
||||
local sunrise = coord:GetSunrise()
|
||||
sunrise = UTILS.Split( sunrise, ":" )
|
||||
local SUNRISE = string.format( "%s%s", sunrise[1], sunrise[2] )
|
||||
if self.useSRS then
|
||||
SUNRISE = string.format( "%s %s %s", sunrise[1], sunrise[2], hours )
|
||||
--self:I(sunrise)
|
||||
local SUNRISE = "no time"
|
||||
local NorthPolar = true
|
||||
if tostring(sunrise) ~= "N/S" and tostring(sunrise) ~= "N/R" then
|
||||
sunrise = UTILS.Split( sunrise, ":" )
|
||||
SUNRISE = string.format( "%s%s", sunrise[1], sunrise[2] )
|
||||
if self.useSRS then
|
||||
SUNRISE = string.format( "%s %s %s", sunrise[1], sunrise[2], hours )
|
||||
end
|
||||
NorthPolar = false
|
||||
end
|
||||
|
||||
|
||||
local sunset = coord:GetSunset()
|
||||
sunset = UTILS.Split( sunset, ":" )
|
||||
local SUNSET = string.format( "%s%s", sunset[1], sunset[2] )
|
||||
if self.useSRS then
|
||||
SUNSET = string.format( "%s %s %s", sunset[1], sunset[2], hours )
|
||||
--self:I(sunset)
|
||||
local SUNSET = "no time"
|
||||
if tostring(sunset) ~= "N/S" and tostring(sunset) ~= "N/R" then
|
||||
sunset = UTILS.Split( sunset, ":" )
|
||||
SUNSET = string.format( "%s%s", sunset[1], sunset[2] )
|
||||
if self.useSRS then
|
||||
SUNSET = string.format( "%s %s %s", sunset[1], sunset[2], hours )
|
||||
end
|
||||
NorthPolar = false
|
||||
end
|
||||
|
||||
---------------------------------
|
||||
@@ -2012,34 +2095,32 @@ function ATIS:onafterBroadcast( From, Event, To )
|
||||
---------------
|
||||
|
||||
-- Get mission weather info. Most of this is static.
|
||||
local clouds, visibility, turbulence, fog, dust, static = self:GetMissionWeather()
|
||||
|
||||
-- Check that fog is actually "thick" enough to reach the airport. If an airport is in the mountains, fog might not affect it as it is measured from sea level.
|
||||
if fog and fog.thickness < height + 25 then
|
||||
fog = nil
|
||||
end
|
||||
|
||||
-- Dust only up to 1500 ft = 457 m ASL.
|
||||
if dust and height + 25 > UTILS.FeetToMeters( 1500 ) then
|
||||
dust = nil
|
||||
end
|
||||
local clouds, visibility, turbulence, dustdens, static = self:GetMissionWeather()
|
||||
|
||||
local dust=false
|
||||
local fog=false
|
||||
|
||||
------------------
|
||||
--- Visibility ---
|
||||
------------------
|
||||
|
||||
-- Get min visibility.
|
||||
local visibilitymin = visibility
|
||||
|
||||
if fog then
|
||||
if fog.visibility < visibilitymin then
|
||||
visibilitymin = fog.visibility
|
||||
if dustdens then
|
||||
|
||||
-- Dust only up to 1500 ft = 457 m ASL.
|
||||
if UTILS.FeetToMeters( 1500 )> height+25 then
|
||||
dust=true
|
||||
visibility=math.min(visibility, dustdens)
|
||||
end
|
||||
end
|
||||
|
||||
if dust then
|
||||
if dust < visibilitymin then
|
||||
visibilitymin = dust
|
||||
|
||||
else -- As of DCS 2.9.10.3948 (December 2024), fog and dust are mutually exclusive!
|
||||
|
||||
-- Get current fog visibility and thickness
|
||||
local fvis=world.weather.getFogVisibilityDistance()
|
||||
local fheight=world.weather.getFogThickness()
|
||||
|
||||
if fvis>0 and fheight>height+25 then
|
||||
fog=true
|
||||
visibility=math.min(visibility, fvis)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2047,7 +2128,7 @@ function ATIS:onafterBroadcast( From, Event, To )
|
||||
|
||||
if self.metric then
|
||||
-- Visibility in km.
|
||||
local reportedviz = UTILS.Round( visibilitymin / 1000 )
|
||||
local reportedviz = UTILS.Round( visibility / 1000 )
|
||||
-- max reported visibility 9999 m
|
||||
if reportedviz > 10 then
|
||||
reportedviz = 10
|
||||
@@ -2055,7 +2136,7 @@ function ATIS:onafterBroadcast( From, Event, To )
|
||||
VISIBILITY = string.format( "%d", reportedviz )
|
||||
else
|
||||
-- max reported visibility 10 NM
|
||||
local reportedviz = UTILS.Round( UTILS.MetersToSM( visibilitymin ) )
|
||||
local reportedviz = UTILS.Round( UTILS.MetersToSM( visibility ) )
|
||||
if reportedviz > 10 then
|
||||
reportedviz = 10
|
||||
end
|
||||
@@ -2069,7 +2150,7 @@ function ATIS:onafterBroadcast( From, Event, To )
|
||||
local cloudbase = clouds.base
|
||||
local cloudceil = clouds.base + clouds.thickness
|
||||
local clouddens = clouds.density
|
||||
|
||||
|
||||
-- Cloud preset (DCS 2.7)
|
||||
local cloudspreset = clouds.preset or "Nothing"
|
||||
|
||||
@@ -2100,6 +2181,39 @@ function ATIS:onafterBroadcast( From, Event, To )
|
||||
else
|
||||
precepitation = 3 -- snow
|
||||
end
|
||||
elseif cloudspreset:find( "RainyPreset4" ) then
|
||||
-- Overcast + Rain
|
||||
clouddens = 5
|
||||
if temperature > 5 then
|
||||
precepitation = 1 -- rain
|
||||
else
|
||||
precepitation = 3 -- snow
|
||||
end
|
||||
elseif cloudspreset:find( "RainyPreset5" ) then
|
||||
-- Overcast + Rain
|
||||
clouddens = 5
|
||||
if temperature > 5 then
|
||||
precepitation = 1 -- rain
|
||||
else
|
||||
precepitation = 3 -- snow
|
||||
end
|
||||
elseif cloudspreset:find( "RainyPreset6" ) then
|
||||
-- Overcast + Rain
|
||||
clouddens = 5
|
||||
if temperature > 5 then
|
||||
precepitation = 1 -- rain
|
||||
else
|
||||
precepitation = 3 -- snow
|
||||
end
|
||||
-- NEWRAINPRESET4
|
||||
elseif cloudspreset:find( "NEWRAINPRESET4" ) then
|
||||
-- Overcast + Rain
|
||||
clouddens = 5
|
||||
if temperature > 5 then
|
||||
precepitation = 1 -- rain
|
||||
else
|
||||
precepitation = 3 -- snow
|
||||
end
|
||||
elseif cloudspreset:find( "RainyPreset" ) then
|
||||
-- Overcast + Rain
|
||||
clouddens = 9
|
||||
@@ -2294,7 +2408,7 @@ function ATIS:onafterBroadcast( From, Event, To )
|
||||
local sunrise = self.gettext:GetEntry("SUNRISEAT",self.locale)
|
||||
--subtitle = string.format( "Sunrise at %s local time", SUNRISE )
|
||||
subtitle = string.format( sunrise, SUNRISE )
|
||||
if not self.useSRS then
|
||||
if not self.useSRS and NorthPolar == false then
|
||||
self:Transmission( self.Sound.SunriseAt, 0.5, subtitle )
|
||||
self.radioqueue:Number2Transmission( SUNRISE, nil, 0.2 )
|
||||
self:Transmission( self.Sound.TimeLocal, 0.2 )
|
||||
@@ -2305,7 +2419,7 @@ function ATIS:onafterBroadcast( From, Event, To )
|
||||
local sunset = self.gettext:GetEntry("SUNSETAT",self.locale)
|
||||
--subtitle = string.format( "Sunset at %s local time", SUNSET )
|
||||
subtitle = string.format( sunset, SUNSET )
|
||||
if not self.useSRS then
|
||||
if not self.useSRS and NorthPolar == false then
|
||||
self:Transmission( self.Sound.SunsetAt, 0.5, subtitle )
|
||||
self.radioqueue:Number2Transmission( SUNSET, nil, 0.5 )
|
||||
self:Transmission( self.Sound.TimeLocal, 0.2 )
|
||||
@@ -3249,28 +3363,13 @@ function ATIS:GetMissionWeather()
|
||||
dust = weather.dust_density
|
||||
end
|
||||
|
||||
-- Fog
|
||||
--[[
|
||||
["enable_fog"] = false,
|
||||
["fog"] =
|
||||
{
|
||||
["thickness"] = 0,
|
||||
["visibility"] = 25,
|
||||
}, -- end of ["fog"]
|
||||
]]
|
||||
local fog = nil
|
||||
if weather.enable_fog == true then
|
||||
fog = weather.fog
|
||||
end
|
||||
|
||||
self:T( "FF weather:" )
|
||||
self:T( { clouds = clouds } )
|
||||
self:T( { visibility = visibility } )
|
||||
self:T( { turbulence = turbulence } )
|
||||
self:T( { fog = fog } )
|
||||
self:T( { dust = dust } )
|
||||
self:T( { static = static } )
|
||||
return clouds, visibility, turbulence, fog, dust, static
|
||||
return clouds, visibility, turbulence, dust, static
|
||||
end
|
||||
|
||||
--- Get thousands of a number.
|
||||
|
||||
@@ -187,7 +187,7 @@ AIRWING = {
|
||||
|
||||
--- AIRWING class version.
|
||||
-- @field #string version
|
||||
AIRWING.version="0.9.5"
|
||||
AIRWING.version="0.9.6"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- ToDo list
|
||||
@@ -1365,16 +1365,20 @@ function AIRWING:CheckRescuhelo()
|
||||
|
||||
local N=self:CountMissionsInQueue({AUFTRAG.Type.RESCUEHELO})
|
||||
|
||||
local name=self.airbase:GetName()
|
||||
|
||||
local carrier=UNIT:FindByName(name)
|
||||
|
||||
for i=1,self.nflightsRescueHelo-N do
|
||||
|
||||
local mission=AUFTRAG:NewRESCUEHELO(carrier)
|
||||
|
||||
self:AddMission(mission)
|
||||
|
||||
if self.airbase then
|
||||
|
||||
local name=self.airbase:GetName()
|
||||
|
||||
local carrier=UNIT:FindByName(name)
|
||||
|
||||
for i=1,self.nflightsRescueHelo-N do
|
||||
|
||||
local mission=AUFTRAG:NewRESCUEHELO(carrier)
|
||||
|
||||
self:AddMission(mission)
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
return self
|
||||
|
||||
@@ -3615,7 +3615,7 @@ function AIRBOSS:onafterStart( From, Event, To )
|
||||
|
||||
-- Handle events.
|
||||
self:HandleEvent( EVENTS.Birth )
|
||||
self:HandleEvent( EVENTS.Land )
|
||||
self:HandleEvent( EVENTS.RunwayTouch )
|
||||
self:HandleEvent( EVENTS.EngineShutdown )
|
||||
self:HandleEvent( EVENTS.Takeoff )
|
||||
self:HandleEvent( EVENTS.Crash )
|
||||
@@ -4381,7 +4381,7 @@ function AIRBOSS:onafterStop( From, Event, To )
|
||||
|
||||
-- Unhandle events.
|
||||
self:UnHandleEvent( EVENTS.Birth )
|
||||
self:UnHandleEvent( EVENTS.Land )
|
||||
self:UnHandleEvent( EVENTS.RunwayTouch )
|
||||
self:UnHandleEvent( EVENTS.EngineShutdown )
|
||||
self:UnHandleEvent( EVENTS.Takeoff )
|
||||
self:UnHandleEvent( EVENTS.Crash )
|
||||
@@ -8291,7 +8291,7 @@ end
|
||||
--- Airboss event handler for event land.
|
||||
-- @param #AIRBOSS self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function AIRBOSS:OnEventLand( EventData )
|
||||
function AIRBOSS:OnEventRunwayTouch( EventData )
|
||||
self:F3( { eventland = EventData } )
|
||||
|
||||
-- Nil checks.
|
||||
@@ -14680,7 +14680,7 @@ function AIRBOSS:_GetPlayerUnitAndName( _unitName )
|
||||
-- Get DCS unit from its name.
|
||||
local DCSunit = Unit.getByName( _unitName )
|
||||
|
||||
if DCSunit then
|
||||
if DCSunit and DCSunit.getPlayerName then
|
||||
|
||||
-- Get player name if any.
|
||||
local playername = DCSunit:getPlayerName()
|
||||
@@ -15651,7 +15651,7 @@ function AIRBOSS:_Number2Sound( playerData, sender, number, delay )
|
||||
end
|
||||
|
||||
-- Split string into characters.
|
||||
local numbers = _split( number )
|
||||
local numbers = _split( tostring(number) )
|
||||
|
||||
local wait = 0
|
||||
for i = 1, #numbers do
|
||||
@@ -15719,7 +15719,7 @@ function AIRBOSS:_Number2Radio( radio, number, delay, interval, pilotcall )
|
||||
end
|
||||
|
||||
-- Split string into characters.
|
||||
local numbers = _split( number )
|
||||
local numbers = _split( tostring(number) )
|
||||
|
||||
local wait = 0
|
||||
for i = 1, #numbers do
|
||||
@@ -18087,7 +18087,7 @@ function AIRBOSS:_MarkCaseZones( _unitName, flare )
|
||||
self:_GetZoneArcIn( case ):FlareZone( FLARECOLOR.White, 45 )
|
||||
text = text .. "\n* arc turn in with WHITE flares"
|
||||
self:_GetZoneArcOut( case ):FlareZone( FLARECOLOR.White, 45 )
|
||||
text = text .. "\n* arc trun out with WHITE flares"
|
||||
text = text .. "\n* arc turn out with WHITE flares"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -18139,7 +18139,7 @@ function AIRBOSS:_MarkCaseZones( _unitName, flare )
|
||||
self:_GetZoneArcIn( case ):SmokeZone( SMOKECOLOR.Blue, 45 )
|
||||
text = text .. "\n* arc turn in with BLUE smoke"
|
||||
self:_GetZoneArcOut( case ):SmokeZone( SMOKECOLOR.Blue, 45 )
|
||||
text = text .. "\n* arc trun out with BLUE smoke"
|
||||
text = text .. "\n* arc turn out with BLUE smoke"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -68,7 +68,7 @@ ARMYGROUP = {
|
||||
|
||||
--- Army Group version.
|
||||
-- @field #string version
|
||||
ARMYGROUP.version="1.0.1"
|
||||
ARMYGROUP.version="1.0.3"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- TODO list
|
||||
@@ -2049,114 +2049,70 @@ end
|
||||
--- Initialize group parameters. Also initializes waypoints if self.waypoints is nil.
|
||||
-- @param #ARMYGROUP self
|
||||
-- @param #table Template Template used to init the group. Default is `self.template`.
|
||||
-- @param #number Delay Delay in seconds before group is initialized. Default `nil`, *i.e.* instantaneous.
|
||||
-- @return #ARMYGROUP self
|
||||
function ARMYGROUP:_InitGroup(Template, Delay)
|
||||
|
||||
if Delay and Delay>0 then
|
||||
self:ScheduleOnce(Delay, ARMYGROUP._InitGroup, self, Template, 0)
|
||||
else
|
||||
-- First check if group was already initialized.
|
||||
if self.groupinitialized then
|
||||
self:T(self.lid.."WARNING: Group was already initialized! Will NOT do it again!")
|
||||
return
|
||||
end
|
||||
|
||||
-- Get template of group.
|
||||
local template=Template or self:_GetTemplate()
|
||||
|
||||
-- Ground are always AI.
|
||||
self.isAI=true
|
||||
|
||||
-- Is (template) group late activated.
|
||||
self.isLateActivated=template.lateActivation
|
||||
|
||||
-- Ground groups cannot be uncontrolled.
|
||||
self.isUncontrolled=false
|
||||
|
||||
-- Max speed in km/h.
|
||||
self.speedMax=self.group:GetSpeedMax()
|
||||
|
||||
-- Is group mobile?
|
||||
if self.speedMax and self.speedMax>3.6 then
|
||||
self.isMobile=true
|
||||
else
|
||||
self.isMobile=false
|
||||
self.speedMax = 0
|
||||
end
|
||||
|
||||
-- Cruise speed in km/h
|
||||
self.speedCruise=self.speedMax*0.7
|
||||
|
||||
-- Group ammo.
|
||||
self.ammo=self:GetAmmoTot()
|
||||
|
||||
-- Radio parameters from template.
|
||||
self.radio.On=false -- Radio is always OFF for ground.
|
||||
self.radio.Freq=133
|
||||
self.radio.Modu=radio.modulation.AM
|
||||
|
||||
-- Set default radio.
|
||||
self:SetDefaultRadio(self.radio.Freq, self.radio.Modu, self.radio.On)
|
||||
|
||||
-- Get current formation from first waypoint.
|
||||
self.option.Formation=template.route.points[1].action
|
||||
|
||||
-- Set default formation to "on road".
|
||||
self.optionDefault.Formation=ENUMS.Formation.Vehicle.OnRoad
|
||||
|
||||
|
||||
-- First check if group was already initialized.
|
||||
if self.groupinitialized then
|
||||
self:T(self.lid.."WARNING: Group was already initialized! Will NOT do it again!")
|
||||
return
|
||||
end
|
||||
|
||||
self:T(self.lid.."FF Initializing Group")
|
||||
|
||||
|
||||
-- Get template of group.
|
||||
local template=Template or self:_GetTemplate()
|
||||
|
||||
|
||||
-- Ground are always AI.
|
||||
self.isAI=true
|
||||
|
||||
|
||||
-- Is (template) group late activated.
|
||||
self.isLateActivated=template.lateActivation
|
||||
|
||||
|
||||
-- Ground groups cannot be uncontrolled.
|
||||
self.isUncontrolled=false
|
||||
|
||||
|
||||
-- Max speed in km/h.
|
||||
self.speedMax=self.group:GetSpeedMax()
|
||||
|
||||
|
||||
-- Is group mobile?
|
||||
if self.speedMax>3.6 then
|
||||
if self.speedMax and self.speedMax>3.6 then
|
||||
self.isMobile=true
|
||||
else
|
||||
self.isMobile=false
|
||||
self.speedMax = 0
|
||||
end
|
||||
|
||||
|
||||
-- Cruise speed in km/h
|
||||
self.speedCruise=self.speedMax*0.7
|
||||
|
||||
|
||||
-- Group ammo.
|
||||
self.ammo=self:GetAmmoTot()
|
||||
|
||||
|
||||
-- Radio parameters from template.
|
||||
self.radio.On=false -- Radio is always OFF for ground.
|
||||
self.radio.Freq=133
|
||||
self.radio.Modu=radio.modulation.AM
|
||||
|
||||
|
||||
-- Set default radio.
|
||||
self:SetDefaultRadio(self.radio.Freq, self.radio.Modu, self.radio.On)
|
||||
|
||||
|
||||
-- Get current formation from first waypoint.
|
||||
self.option.Formation=template.route.points[1].action
|
||||
|
||||
|
||||
-- Set default formation to "on road".
|
||||
self.optionDefault.Formation=ENUMS.Formation.Vehicle.OnRoad
|
||||
|
||||
-- Default TACAN off.
|
||||
self:SetDefaultTACAN(nil, nil, nil, nil, true)
|
||||
self.tacan=UTILS.DeepCopy(self.tacanDefault)
|
||||
if not self.tacanDefault then
|
||||
self:SetDefaultTACAN(nil, nil, nil, nil, true)
|
||||
end
|
||||
if not self.tacan then
|
||||
self.tacan=UTILS.DeepCopy(self.tacanDefault)
|
||||
end
|
||||
|
||||
-- Units of the group.
|
||||
local units=self.group:GetUnits()
|
||||
@@ -2177,7 +2133,6 @@ function ARMYGROUP:_InitGroup(Template, Delay)
|
||||
self:_AddElementByName(unitname)
|
||||
end
|
||||
|
||||
|
||||
-- Init done.
|
||||
self.groupinitialized=true
|
||||
end
|
||||
|
||||
@@ -162,6 +162,7 @@
|
||||
-- @field #number missionRange Mission range in meters. Used by LEGION classes (AIRWING, BRIGADE, ...).
|
||||
-- @field Core.Point#COORDINATE missionWaypointCoord Mission waypoint coordinate.
|
||||
-- @field Core.Point#COORDINATE missionEgressCoord Mission egress waypoint coordinate.
|
||||
-- @field Core.Point#COORDINATE missionIngressCoord Mission Ingress waypoint coordinate.
|
||||
-- @field #number missionWaypointRadius Random radius in meters.
|
||||
-- @field #boolean legionReturn If `true`, assets return to their legion (default). If `false`, they will stay alive.
|
||||
--
|
||||
@@ -1718,15 +1719,16 @@ end
|
||||
-- @param #AUFTRAG self
|
||||
-- @param Core.Point#COORDINATE Target The target coordinate. Can also be given as a GROUP, UNIT, STATIC or TARGET object.
|
||||
-- @param #number Altitude Engage altitude in feet. Default 2000 ft.
|
||||
-- @param #number EngageWeaponType Which weapon to use. Defaults to auto, ie ENUMS.WeaponFlag.Auto. See ENUMS.WeaponFlag for options.
|
||||
-- @return #AUFTRAG self
|
||||
function AUFTRAG:NewSTRIKE(Target, Altitude)
|
||||
function AUFTRAG:NewSTRIKE(Target, Altitude, EngageWeaponType)
|
||||
|
||||
local mission=AUFTRAG:New(AUFTRAG.Type.STRIKE)
|
||||
|
||||
mission:_TargetFromObject(Target)
|
||||
|
||||
-- DCS Task options:
|
||||
mission.engageWeaponType=ENUMS.WeaponFlag.Auto
|
||||
mission.engageWeaponType=EngageWeaponType or ENUMS.WeaponFlag.Auto
|
||||
mission.engageWeaponExpend=AI.Task.WeaponExpend.ALL
|
||||
mission.engageAltitude=UTILS.FeetToMeters(Altitude or 2000)
|
||||
|
||||
@@ -1749,15 +1751,16 @@ end
|
||||
-- @param #AUFTRAG self
|
||||
-- @param Core.Point#COORDINATE Target Target coordinate. Can also be specified as a GROUP, UNIT, STATIC or TARGET object.
|
||||
-- @param #number Altitude Engage altitude in feet. Default 25000 ft.
|
||||
-- @param #number EngageWeaponType Which weapon to use. Defaults to auto, ie ENUMS.WeaponFlag.Auto. See ENUMS.WeaponFlag for options.
|
||||
-- @return #AUFTRAG self
|
||||
function AUFTRAG:NewBOMBING(Target, Altitude)
|
||||
function AUFTRAG:NewBOMBING(Target, Altitude, EngageWeaponType)
|
||||
|
||||
local mission=AUFTRAG:New(AUFTRAG.Type.BOMBING)
|
||||
|
||||
mission:_TargetFromObject(Target)
|
||||
|
||||
-- DCS task options:
|
||||
mission.engageWeaponType=ENUMS.WeaponFlag.Auto
|
||||
mission.engageWeaponType=EngageWeaponType or ENUMS.WeaponFlag.Auto
|
||||
mission.engageWeaponExpend=AI.Task.WeaponExpend.ALL
|
||||
mission.engageAltitude=UTILS.FeetToMeters(Altitude or 25000)
|
||||
|
||||
@@ -1819,7 +1822,7 @@ end
|
||||
|
||||
--- **[AIR]** Create a BOMBRUNWAY mission.
|
||||
-- @param #AUFTRAG self
|
||||
-- @param Wrapper.Airbase#AIRBASE Airdrome The airbase to bomb. This must be an airdrome (not a FARP or ship) as these to not have a runway.
|
||||
-- @param Wrapper.Airbase#AIRBASE Airdrome The airbase to bomb. This must be an airdrome (not a FARP or ship) as these do not have a runway.
|
||||
-- @param #number Altitude Engage altitude in feet. Default 25000 ft.
|
||||
-- @return #AUFTRAG self
|
||||
function AUFTRAG:NewBOMBRUNWAY(Airdrome, Altitude)
|
||||
@@ -2155,7 +2158,11 @@ end
|
||||
|
||||
]]
|
||||
|
||||
--- **[GROUND, NAVAL]** Create an ARTY mission.
|
||||
--- **[GROUND, NAVAL]** Create an ARTY mission ("Fire at point" task).
|
||||
--
|
||||
-- If the group has more than one weapon type supporting the "Fire at point" task, the employed weapon type can be set via the `AUFTRAG:SetWeaponType()` function.
|
||||
--
|
||||
-- **Note** that it is recommended to set the weapon range via the `OPSGROUP:AddWeaponRange()` function as this cannot be retrieved from the DCS API.
|
||||
-- @param #AUFTRAG self
|
||||
-- @param Core.Point#COORDINATE Target Center of the firing solution.
|
||||
-- @param #number Nshots Number of shots to be fired. Default `#nil`.
|
||||
@@ -2178,6 +2185,7 @@ function AUFTRAG:NewARTY(Target, Nshots, Radius, Altitude)
|
||||
mission.optionAlarm=0
|
||||
|
||||
mission.missionFraction=0.0
|
||||
mission.missionWaypointRadius=0.0
|
||||
|
||||
-- Evaluate after 8 min.
|
||||
mission.dTevaluate=8*60
|
||||
@@ -2302,7 +2310,7 @@ function AUFTRAG:NewCAPTUREZONE(OpsZone, Coalition, Speed, Altitude, Formation)
|
||||
params.formation=Formation or "Off Road"
|
||||
params.zone=mission:GetObjective()
|
||||
params.altitude=mission.missionAltitude
|
||||
params.speed=mission.missionSpeed
|
||||
params.speed=mission.missionSpeed and UTILS.KmphToMps(mission.missionSpeed) or nil
|
||||
|
||||
mission.DCStask.params=params
|
||||
|
||||
@@ -2352,7 +2360,7 @@ function AUFTRAG:NewGROUNDATTACK(Target, Speed, Formation)
|
||||
|
||||
mission.DCStask=mission:GetDCSMissionTask()
|
||||
|
||||
mission.DCStask.params.speed=Speed
|
||||
mission.DCStask.params.speed=mission.missionSpeed and UTILS.KmphToMps(mission.missionSpeed) or nil
|
||||
mission.DCStask.params.formation=Formation or ENUMS.Formation.Vehicle.Vee
|
||||
|
||||
return mission
|
||||
@@ -3481,7 +3489,7 @@ end
|
||||
|
||||
--- Set Rules of Engagement (ROE) for this mission.
|
||||
-- @param #AUFTRAG self
|
||||
-- @param #string roe Mission ROE.
|
||||
-- @param #number roe Mission ROE, e.g. `ENUMS.ROE.ReturnFire` (whiche equals 3)
|
||||
-- @return #AUFTRAG self
|
||||
function AUFTRAG:SetROE(roe)
|
||||
|
||||
@@ -3493,7 +3501,7 @@ end
|
||||
|
||||
--- Set Reaction on Threat (ROT) for this mission.
|
||||
-- @param #AUFTRAG self
|
||||
-- @param #string rot Mission ROT.
|
||||
-- @param #number rot Mission ROT, e.g. `ENUMS.ROT.NoReaction` (whiche equals 0)
|
||||
-- @return #AUFTRAG self
|
||||
function AUFTRAG:SetROT(rot)
|
||||
|
||||
@@ -4657,6 +4665,16 @@ function AUFTRAG:SetGroupWaypointCoordinate(opsgroup, coordinate)
|
||||
return self
|
||||
end
|
||||
|
||||
--- [Air] Set mission (ingress) waypoint coordinate for FLIGHT group.
|
||||
-- @param #AUFTRAG self
|
||||
-- @param Core.Point#COORDINATE coordinate Waypoint Coordinate.
|
||||
-- @return #AUFTRAG self
|
||||
function AUFTRAG:SetIngressCoordinate(coordinate)
|
||||
self.missionIngressCoord = coordinate
|
||||
self.missionIngressCoordAlt = UTILS.MetersToFeet(coordinate.y) or 10000
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get mission (ingress) waypoint coordinate of OPS group
|
||||
-- @param #AUFTRAG self
|
||||
-- @param Ops.OpsGroup#OPSGROUP opsgroup The OPS group.
|
||||
@@ -5712,7 +5730,7 @@ function AUFTRAG:GetMissionTypesText(MissionTypes)
|
||||
return text
|
||||
end
|
||||
|
||||
--- Set the mission waypoint coordinate where the mission is executed. Note that altitude is set via `:SetMissionAltitude`.
|
||||
--- [NON-AIR] Set the mission waypoint coordinate from where the mission is executed. Note that altitude is set via `:SetMissionAltitude`.
|
||||
-- @param #AUFTRAG self
|
||||
-- @param Core.Point#COORDINATE Coordinate Coordinate where the mission is executed.
|
||||
-- @return #AUFTRAG self
|
||||
@@ -5740,8 +5758,9 @@ end
|
||||
-- @param #AUFTRAG self
|
||||
-- @param Core.Point#COORDINATE Coordinate Egrees coordinate.
|
||||
-- @param #number Altitude (Optional) Altitude in feet. Default is y component of coordinate.
|
||||
-- @param #number Speed (Optional) Speed in knots to reach this waypoint. Defaults to mission speed.
|
||||
-- @return #AUFTRAG self
|
||||
function AUFTRAG:SetMissionEgressCoord(Coordinate, Altitude)
|
||||
function AUFTRAG:SetMissionEgressCoord(Coordinate, Altitude, Speed)
|
||||
|
||||
-- Obviously a zone was passed. We get the coordinate.
|
||||
if Coordinate:IsInstanceOf("ZONE_BASE") then
|
||||
@@ -5752,7 +5771,64 @@ function AUFTRAG:SetMissionEgressCoord(Coordinate, Altitude)
|
||||
|
||||
if Altitude then
|
||||
self.missionEgressCoord.y=UTILS.FeetToMeters(Altitude)
|
||||
self.missionEgressCoordAlt = UTILS.FeetToMeters(Altitude)
|
||||
end
|
||||
|
||||
self.missionEgressCoordSpeed=Speed and Speed or nil
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- [Air] Set the mission ingress coordinate. This is the coordinate where the assigned group will fly before the actual mission coordinate.
|
||||
-- @param #AUFTRAG self
|
||||
-- @param Core.Point#COORDINATE Coordinate Ingrees coordinate.
|
||||
-- @param #number Altitude (Optional) Altitude in feet. Default is y component of coordinate.
|
||||
-- @param #number Speed (Optional) Speed in knots to reach this waypoint. Defaults to mission speed.
|
||||
-- @return #AUFTRAG self
|
||||
function AUFTRAG:SetMissionIngressCoord(Coordinate, Altitude, Speed)
|
||||
|
||||
-- Obviously a zone was passed. We get the coordinate.
|
||||
if Coordinate:IsInstanceOf("ZONE_BASE") then
|
||||
Coordinate=Coordinate:GetCoordinate()
|
||||
end
|
||||
|
||||
self.missionIngressCoord=Coordinate
|
||||
|
||||
if Altitude then
|
||||
self.missionIngressCoord.y=UTILS.FeetToMeters(Altitude)
|
||||
self.missionIngressCoordAlt = UTILS.FeetToMeters(Altitude or 10000)
|
||||
end
|
||||
|
||||
self.missionIngressCoordSpeed=Speed and Speed or nil
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- [Air] Set the mission holding coordinate. This is the coordinate where the assigned group will fly before the actual mission execution starts. Do not forget to add a push condition, too!
|
||||
-- @param #AUFTRAG self
|
||||
-- @param Core.Point#COORDINATE Coordinate Holding coordinate.
|
||||
-- @param #number Altitude (Optional) Altitude in feet. Default is y component of coordinate.
|
||||
-- @param #number Speed (Optional) Speed in knots to reach this waypoint and hold there. Defaults to mission speed.
|
||||
-- @param #number Duration (Optional) Duration in seconds on how long to hold, defaults to 15 minutes. Mission continues if either a push condition is met or the time is up.
|
||||
-- @return #AUFTRAG self
|
||||
function AUFTRAG:SetMissionHoldingCoord(Coordinate, Altitude, Speed, Duration)
|
||||
|
||||
-- Obviously a zone was passed. We get the coordinate.
|
||||
if Coordinate:IsInstanceOf("ZONE_BASE") then
|
||||
Coordinate=Coordinate:GetCoordinate()
|
||||
end
|
||||
|
||||
self.missionHoldingCoord=Coordinate
|
||||
self.missionHoldingDuration=Duration or 900
|
||||
|
||||
if Altitude then
|
||||
self.missionHoldingCoord.y=UTILS.FeetToMeters(Altitude)
|
||||
self.missionHoldingCoordAlt = UTILS.FeetToMeters(Altitude or 10000)
|
||||
end
|
||||
|
||||
self.missionHoldingCoordSpeed=Speed and Speed or nil
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get the mission egress coordinate if this was defined.
|
||||
@@ -5762,6 +5838,20 @@ function AUFTRAG:GetMissionEgressCoord()
|
||||
return self.missionEgressCoord
|
||||
end
|
||||
|
||||
--- Get the mission ingress coordinate if this was defined.
|
||||
-- @param #AUFTRAG self
|
||||
-- @return Core.Point#COORDINATE Coordinate Coordinate or nil.
|
||||
function AUFTRAG:GetMissionIngressCoord()
|
||||
return self.missionIngressCoord
|
||||
end
|
||||
|
||||
--- Get the mission holding coordinate if this was defined.
|
||||
-- @param #AUFTRAG self
|
||||
-- @return Core.Point#COORDINATE Coordinate Coordinate or nil.
|
||||
function AUFTRAG:GetMissionHoldingCoord()
|
||||
return self.missionHoldingCoord
|
||||
end
|
||||
|
||||
--- Get coordinate which was set as mission waypoint coordinate.
|
||||
-- @param #AUFTRAG self
|
||||
-- @return Core.Point#COORDINATE Coordinate where the mission is executed or `#nil`.
|
||||
@@ -5796,10 +5886,27 @@ function AUFTRAG:GetMissionWaypointCoord(group, randomradius, surfacetypes)
|
||||
end
|
||||
return coord
|
||||
end
|
||||
|
||||
local coord=group:GetCoordinate()
|
||||
|
||||
-- Check if an ingress or holding coord has been explicitly set.
|
||||
if self.missionHoldingCoord then
|
||||
coord=self.missionHoldingCoord
|
||||
if self.missionHoldingCoorddAlt then
|
||||
coord:SetAltitude(self.missionHoldingCoordAlt, true)
|
||||
end
|
||||
end
|
||||
|
||||
if self.missionIngressCoord then
|
||||
coord=self.missionIngressCoord
|
||||
if self.missionIngressCoordAlt then
|
||||
coord:SetAltitude(self.missionIngressCoordAlt, true)
|
||||
end
|
||||
end
|
||||
|
||||
-- Create waypoint coordinate half way between us and the target.
|
||||
local waypointcoord=COORDINATE:New(0,0,0)
|
||||
local coord=group:GetCoordinate()
|
||||
|
||||
if coord then
|
||||
waypointcoord=coord:GetIntermediateCoordinate(self:GetTargetCoordinate(), self.missionFraction)
|
||||
else
|
||||
@@ -6099,7 +6206,7 @@ function AUFTRAG:GetDCSMissionTask()
|
||||
local param={}
|
||||
param.zone=self:GetObjective()
|
||||
param.altitude=self.missionAltitude
|
||||
param.speed=self.missionSpeed
|
||||
param.speed=self.missionSpeed and UTILS.KmphToMps(self.missionSpeed) or nil
|
||||
|
||||
DCStask.params=param
|
||||
|
||||
@@ -6179,7 +6286,7 @@ function AUFTRAG:GetDCSMissionTask()
|
||||
local param={}
|
||||
param.target=self.engageTarget
|
||||
param.altitude=self.missionAltitude
|
||||
param.speed=self.missionSpeed
|
||||
param.speed=self.missionSpeed and UTILS.KmphToMps(self.missionSpeed) or nil
|
||||
param.lastindex=nil
|
||||
|
||||
DCStask.params=param
|
||||
@@ -6352,7 +6459,7 @@ function AUFTRAG:GetDCSMissionTask()
|
||||
local param={}
|
||||
param.zone=self:GetObjective()
|
||||
param.altitude=self.missionAltitude
|
||||
param.speed=self.missionSpeed
|
||||
param.speed=self.missionSpeed and UTILS.KmphToMps(self.missionSpeed) or nil
|
||||
|
||||
DCStask.params=param
|
||||
|
||||
@@ -6388,7 +6495,7 @@ function AUFTRAG:GetDCSMissionTask()
|
||||
local param={}
|
||||
param.zone=self:GetObjective()
|
||||
param.altitude=self.missionAltitude
|
||||
param.speed=self.missionSpeed
|
||||
param.speed=self.missionSpeed and UTILS.KmphToMps(self.missionSpeed) or nil
|
||||
|
||||
DCStask.params=param
|
||||
|
||||
@@ -6408,7 +6515,7 @@ function AUFTRAG:GetDCSMissionTask()
|
||||
local param={}
|
||||
param.target=self:GetTargetData()
|
||||
param.action="Wedge"
|
||||
param.speed=self.missionSpeed
|
||||
param.speed=self.missionSpeed and UTILS.KmphToMps(self.missionSpeed) or nil
|
||||
|
||||
DCStask.params=param
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **applevangelist**
|
||||
-- @date Last Update July 2024
|
||||
-- @date Last Update Jan 2025
|
||||
-- @module Ops.AWACS
|
||||
-- @image OPS_AWACS.jpg
|
||||
|
||||
@@ -122,6 +122,7 @@ do
|
||||
-- @field #number TacticalModulation
|
||||
-- @field #number TacticalInterval
|
||||
-- @field Core.Set#SET_GROUP DetectionSet
|
||||
-- @field #number MaxMissionRange
|
||||
-- @extends Core.Fsm#FSM
|
||||
|
||||
|
||||
@@ -183,7 +184,7 @@ do
|
||||
--
|
||||
-- Add Escorts Squad (recommended, optional)
|
||||
--
|
||||
-- local Squad_Two = SQUADRON:New("Escorts",4,"Escorts North")
|
||||
-- local Squad_Two = SQUADRON:New("Escorts",4,"Escorts North") -- taking a template with 2 planes here, will result in a group of 2 escorts which can fly in formation escorting the AWACS.
|
||||
-- Squad_Two:AddMissionCapability({AUFTRAG.Type.ESCORT})
|
||||
-- Squad_Two:SetFuelLowRefuel(true)
|
||||
-- Squad_Two:SetFuelLowThreshold(0.3)
|
||||
@@ -231,8 +232,8 @@ do
|
||||
-- -- set up in the mission editor with a late activated helo named "Rock#ZONE_POLYGON". Note this also sets the BullsEye to be referenced as "Rock".
|
||||
-- -- The CAP station zone is called "Fremont". We will be on 255 AM.
|
||||
-- local testawacs = AWACS:New("AWACS North",AwacsAW,"blue",AIRBASE.Caucasus.Kutaisi,"Awacs Orbit",ZONE:FindByName("Rock"),"Fremont",255,radio.modulation.AM )
|
||||
-- -- set two escorts
|
||||
-- testawacs:SetEscort(2)
|
||||
-- -- set one escort group; this example has two units in the template group, so they can fly a nice formation.
|
||||
-- testawacs:SetEscort(1,ENUMS.Formation.FixedWing.FingerFour.Group,{x=-500,y=50,z=500},45)
|
||||
-- -- Callsign will be "Focus". We'll be a Angels 30, doing 300 knots, orbit leg to 88deg with a length of 25nm.
|
||||
-- testawacs:SetAwacsDetails(CALLSIGN.AWACS.Focus,1,30,300,88,25)
|
||||
-- -- Set up SRS on port 5010 - change the below to your path and port
|
||||
@@ -508,7 +509,7 @@ do
|
||||
-- @field #AWACS
|
||||
AWACS = {
|
||||
ClassName = "AWACS", -- #string
|
||||
version = "0.2.65", -- #string
|
||||
version = "0.2.71", -- #string
|
||||
lid = "", -- #string
|
||||
coalition = coalition.side.BLUE, -- #number
|
||||
coalitiontxt = "blue", -- #string
|
||||
@@ -605,6 +606,7 @@ AWACS = {
|
||||
TacticalModulation = radio.modulation.AM,
|
||||
TacticalInterval = 120,
|
||||
DetectionSet = nil,
|
||||
MaxMissionRange = 125,
|
||||
}
|
||||
|
||||
---
|
||||
@@ -1572,6 +1574,15 @@ function AWACS:SetLocale(Locale)
|
||||
return self
|
||||
end
|
||||
|
||||
--- [User] Set the max mission range flights can be away from their home base.
|
||||
-- @param #AWACS self
|
||||
-- @param #number NM Distance in nautical miles
|
||||
-- @return #AWACS self
|
||||
function AWACS:SetMaxMissionRange(NM)
|
||||
self.MaxMissionRange = NM or 125
|
||||
return self
|
||||
end
|
||||
|
||||
--- [User] Add additional frequency and modulation for AWACS SRS output.
|
||||
-- @param #AWACS self
|
||||
-- @param #number Frequency The frequency to add, e.g. 132.5
|
||||
@@ -1762,7 +1773,7 @@ function AWACS:_EventHandler(EventData)
|
||||
end
|
||||
end
|
||||
|
||||
if Event.id == EVENTS.PlayerLeaveUnit then --player left unit
|
||||
if Event.id == EVENTS.PlayerLeaveUnit and Event.IniGroupName then --player left unit
|
||||
-- check known player?
|
||||
self:T("Player group left unit: " .. Event.IniGroupName)
|
||||
self:T("Player name left: " .. Event.IniPlayerName)
|
||||
@@ -2158,9 +2169,12 @@ end
|
||||
|
||||
--- [User] Set AWACS Escorts Template
|
||||
-- @param #AWACS self
|
||||
-- @param #number EscortNumber Number of fighther planes to accompany this AWACS. 0 or nil means no escorts.
|
||||
-- @param #number EscortNumber Number of fighther plane GROUPs to accompany this AWACS. 0 or nil means no escorts. If you want >1 plane in an escort group, you can either set the respective squadron grouping to the desired number, or use a template for escorts with >1 unit.
|
||||
-- @param #number Formation Formation the escort should take (if more than one plane), e.g. `ENUMS.Formation.FixedWing.FingerFour.Group`. Formation is used on GROUP level, multiple groups of one unit will NOT conform to this formation.
|
||||
-- @param #table OffsetVector Offset the escorts should fly behind the AWACS, given as table, distance in meters, e.g. `{x=-500,y=0,z=500}` - 500m behind (negative value) and to the right (negative for left), no vertical separation (positive over, negative under the AWACS flight). For multiple groups, the vectors will be slightly changed to avoid collisions.
|
||||
-- @param #number EscortEngageMaxDistance Escorts engage air targets max this NM away, defaults to 45NM.
|
||||
-- @return #AWACS self
|
||||
function AWACS:SetEscort(EscortNumber)
|
||||
function AWACS:SetEscort(EscortNumber,Formation,OffsetVector,EscortEngageMaxDistance)
|
||||
self:T(self.lid.."SetEscort")
|
||||
if EscortNumber and EscortNumber > 0 then
|
||||
self.HasEscorts = true
|
||||
@@ -2169,6 +2183,9 @@ function AWACS:SetEscort(EscortNumber)
|
||||
self.HasEscorts = false
|
||||
self.EscortNumber = 0
|
||||
end
|
||||
self.EscortFormation = Formation
|
||||
self.OffsetVec = OffsetVector or {x=500,y=100,z=500}
|
||||
self.EscortEngageMaxDistance = EscortEngageMaxDistance or 45
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -2223,11 +2240,26 @@ function AWACS:_StartEscorts(Shiftchange)
|
||||
local group = AwacsFG:GetGroup()
|
||||
|
||||
local timeonstation = (self.EscortsTimeOnStation + self.ShiftChangeTime) * 3600 -- hours to seconds
|
||||
local OffsetX = 500
|
||||
local OffsetY = 500
|
||||
local OffsetZ = 500
|
||||
if self.OffsetVec then
|
||||
OffsetX = self.OffsetVec.x or 500
|
||||
OffsetY = self.OffsetVec.y or 500
|
||||
OffsetZ = self.OffsetVec.z or 500
|
||||
end
|
||||
|
||||
for i=1,self.EscortNumber do
|
||||
-- every
|
||||
local escort = AUFTRAG:NewESCORT(group, {x= -100*((i + (i%2))/2), y=0, z=(100 + 100*((i + (i%2))/2))*(-1)^i},45,{"Air"})
|
||||
escort:SetRequiredAssets(1)
|
||||
-- every
|
||||
local escort = AUFTRAG:NewESCORT(group, {x= OffsetX*((i + (i%2))/2), y=OffsetY*((i + (i%2))/2), z=(OffsetZ + OffsetZ*((i + (i%2))/2))*(-1)^i},self.EscortEngageMaxDistance,{"Air"})
|
||||
--local escort = AUFTRAG:NewESCORT(group,self.OffsetVec,self.EscortEngageMaxDistance,{"Air"})
|
||||
--escort:SetRequiredAssets(self.EscortNumber)
|
||||
escort:SetTime(nil,timeonstation)
|
||||
if self.Escortformation then
|
||||
escort:SetFormation(self.Escortformation)
|
||||
end
|
||||
escort:SetMissionRange(self.MaxMissionRange)
|
||||
|
||||
self.AirWing:AddMission(escort)
|
||||
self.CatchAllMissions[#self.CatchAllMissions+1] = escort
|
||||
|
||||
@@ -2434,7 +2466,7 @@ function AWACS:_GetCallSign(Group,GID, IsPlayer)
|
||||
|
||||
local callsign = "Ghost 1"
|
||||
if Group and Group:IsAlive() then
|
||||
callsign = Group:GetCustomCallSign(self.callsignshort,self.keepnumber,self.callsignTranslations)
|
||||
callsign = Group:GetCustomCallSign(self.callsignshort,self.keepnumber,self.callsignTranslations,self.callsignCustomFunc,self.callsignCustomArgs)
|
||||
end
|
||||
return callsign
|
||||
end
|
||||
@@ -2443,10 +2475,12 @@ end
|
||||
-- @param #AWACS self
|
||||
-- @param #boolean ShortCallsign If true, only call out the major flight number
|
||||
-- @param #boolean Keepnumber If true, keep the **customized callsign** in the #GROUP name as-is, no amendments or numbers.
|
||||
-- @param #table CallsignTranslations (optional) Table to translate between DCS standard callsigns and bespoke ones. Does not apply if using customized
|
||||
-- @param #table CallsignTranslations (Optional) Table to translate between DCS standard callsigns and bespoke ones. Does not apply if using customized.
|
||||
-- callsigns from playername or group name.
|
||||
-- @param #func CallsignCustomFunc (Optional) For player names only(!). If given, this function will return the callsign. Needs to take the groupname and the playername as first two arguments.
|
||||
-- @param #arg ... (Optional) Comma separated arguments to add to the custom function call after groupname and playername.
|
||||
-- @return #AWACS self
|
||||
function AWACS:SetCallSignOptions(ShortCallsign,Keepnumber,CallsignTranslations)
|
||||
function AWACS:SetCallSignOptions(ShortCallsign,Keepnumber,CallsignTranslations,CallsignCustomFunc,...)
|
||||
if not ShortCallsign or ShortCallsign == false then
|
||||
self.callsignshort = false
|
||||
else
|
||||
@@ -2454,6 +2488,8 @@ function AWACS:SetCallSignOptions(ShortCallsign,Keepnumber,CallsignTranslations)
|
||||
end
|
||||
self.keepnumber = Keepnumber or false
|
||||
self.callsignTranslations = CallsignTranslations
|
||||
self.callsignCustomFunc = CallsignCustomFunc
|
||||
self.callsignCustomArgs = arg or {}
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -3626,7 +3662,7 @@ function AWACS:_CheckIn(Group)
|
||||
managedgroup.LastTasking = timer.getTime()
|
||||
|
||||
GID = managedgroup.GID
|
||||
self.ManagedGrps[self.ManagedGrpID]=managedgroup
|
||||
self.ManagedGrps[self.ManagedGrpID]=managedgroup
|
||||
|
||||
local alphacheckbulls = self:_ToStringBULLS(Group:GetCoordinate())
|
||||
local alphacheckbullstts = self:_ToStringBULLS(Group:GetCoordinate(),false,true)
|
||||
@@ -3893,6 +3929,12 @@ function AWACS:_SetClientMenus()
|
||||
checkin = checkin,
|
||||
}
|
||||
self.clientmenus:Push(menus,cgrpname)
|
||||
-- catch errors - when this entry is built we should NOT have a managed entry
|
||||
local GID,hasentry = self:_GetManagedGrpID(cgrp)
|
||||
if hasentry then
|
||||
-- this user is checked in but has the check in entry ... not good.
|
||||
self:_CheckOut(cgrp,GID,true)
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
@@ -5739,7 +5781,7 @@ function AWACS:_AssignPilotToTarget(Pilots,Targets)
|
||||
local intercept = AUFTRAG:NewINTERCEPT(Target.Target)
|
||||
intercept:SetWeaponExpend(AI.Task.WeaponExpend.ALL)
|
||||
intercept:SetWeaponType(ENUMS.WeaponFlag.Auto)
|
||||
|
||||
intercept:SetMissionRange(self.MaxMissionRange)
|
||||
-- TODO
|
||||
-- now this is going to be interesting...
|
||||
-- Check if the target left the "hot" area or is dead already
|
||||
@@ -5787,6 +5829,7 @@ function AWACS:_AssignPilotToTarget(Pilots,Targets)
|
||||
AnchorSpeed = UTILS.KnotsToAltKIAS(AnchorSpeed,Angels)
|
||||
local Anchor = self.AnchorStacks:ReadByPointer(Pilot.AnchorStackNo) -- #AWACS.AnchorData
|
||||
local capauftrag = AUFTRAG:NewCAP(Anchor.StationZone,Angels,AnchorSpeed,Anchor.StationZoneCoordinate,0,15,{})
|
||||
capauftrag:SetMissionRange(self.MaxMissionRange)
|
||||
capauftrag:SetTime(nil,((self.CAPTimeOnStation*3600)+(15*60)))
|
||||
Pilot.FlightGroup:AddMission(capauftrag)
|
||||
|
||||
@@ -5904,6 +5947,8 @@ function AWACS:onafterStart(From, Event, To)
|
||||
-- set up the AWACS and let it orbit
|
||||
local AwacsAW = self.AirWing -- Ops.Airwing#AIRWING
|
||||
local mission = AUFTRAG:NewORBIT_RACETRACK(self.OrbitZone:GetCoordinate(),self.AwacsAngels*1000,self.Speed,self.Heading,self.Leg)
|
||||
mission:SetMissionRange(self.MaxMissionRange)
|
||||
mission:SetRequiredAttribute({ GROUP.Attribute.AIR_AWACS }) -- prefered plane type, thanks to Heart8reaker
|
||||
local timeonstation = (self.AwacsTimeOnStation + self.ShiftChangeTime) * 3600
|
||||
mission:SetTime(nil,timeonstation)
|
||||
self.CatchAllMissions[#self.CatchAllMissions+1] = mission
|
||||
@@ -6046,6 +6091,7 @@ function AWACS:_CheckAwacsStatus()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--------------------------------
|
||||
-- AWACS
|
||||
--------------------------------
|
||||
@@ -6194,12 +6240,13 @@ function AWACS:_CheckAwacsStatus()
|
||||
|
||||
report:Add("====================")
|
||||
|
||||
local RESMission
|
||||
-- Check for replacement mission - if any
|
||||
if self.ShiftChangeEscortsFlag and self.ShiftChangeEscortsRequested then -- Ops.Auftrag#AUFTRAG
|
||||
ESmission = self.EscortMissionReplacement[i]
|
||||
local esstatus = ESmission:GetState()
|
||||
local ESmissiontime = (timer.getTime() - self.EscortsTimeStamp)
|
||||
local ESTOSLeft = UTILS.Round((((self.EscortsTimeOnStation+self.ShiftChangeTime)*3600) - ESmissiontime),0) -- seconds
|
||||
RESMission = self.EscortMissionReplacement[i]
|
||||
local esstatus = RESMission:GetState()
|
||||
local RESMissiontime = (timer.getTime() - self.EscortsTimeStamp)
|
||||
local ESTOSLeft = UTILS.Round((((self.EscortsTimeOnStation+self.ShiftChangeTime)*3600) - RESMissiontime),0) -- seconds
|
||||
ESTOSLeft = UTILS.Round(ESTOSLeft/60,0) -- minutes
|
||||
local ChangeTime = UTILS.Round(((self.ShiftChangeTime * 3600)/60),0)
|
||||
|
||||
@@ -6207,7 +6254,7 @@ function AWACS:_CheckAwacsStatus()
|
||||
report:Add(string.format("Auftrag Status: %s",esstatus))
|
||||
report:Add(string.format("TOS Left: %d min",ESTOSLeft))
|
||||
|
||||
local OpsGroups = ESmission:GetOpsGroups()
|
||||
local OpsGroups = RESMission:GetOpsGroups()
|
||||
local OpsGroup = self:_GetAliveOpsGroupFromTable(OpsGroups) -- Ops.OpsGroup#OPSGROUP
|
||||
if OpsGroup then
|
||||
local OpsName = OpsGroup:GetName() or "Unknown"
|
||||
@@ -6219,13 +6266,13 @@ function AWACS:_CheckAwacsStatus()
|
||||
report:Add("***** Cannot obtain (yet) this missions OpsGroup!")
|
||||
end
|
||||
|
||||
if ESmission:IsExecuting() then
|
||||
if RESMission and RESMission:IsExecuting() then
|
||||
-- make the actual change in the queue
|
||||
self.ShiftChangeEscortsFlag = false
|
||||
self.ShiftChangeEscortsRequested = false
|
||||
-- cancel old mission
|
||||
if ESmission and ESmission:IsNotOver() then
|
||||
ESmission:Cancel()
|
||||
ESmission:__Cancel(1)
|
||||
end
|
||||
self.EscortMission[i] = self.EscortMissionReplacement[i]
|
||||
self.EscortMissionReplacement[i] = nil
|
||||
@@ -6477,6 +6524,7 @@ function AWACS:onafterAssignedAnchor(From, Event, To, GID, Anchor, AnchorStackNo
|
||||
if auftragtype == AUFTRAG.Type.ALERT5 then
|
||||
-- all correct
|
||||
local capauftrag = AUFTRAG:NewCAP(Anchor.StationZone,Angels*1000,AnchorSpeed,Anchor.StationZone:GetCoordinate(),0,15,{})
|
||||
capauftrag:SetMissionRange(self.MaxMissionRange)
|
||||
capauftrag:SetTime(nil,((self.CAPTimeOnStation*3600)+(15*60)))
|
||||
capauftrag:AddAsset(managedgroup.FlightGroup)
|
||||
self.CatchAllMissions[#self.CatchAllMissions+1] = capauftrag
|
||||
@@ -6840,7 +6888,8 @@ function AWACS:onafterAwacsShiftChange(From,Event,To)
|
||||
self.CatchAllMissions[#self.CatchAllMissions+1] = mission
|
||||
local timeonstation = (self.AwacsTimeOnStation + self.ShiftChangeTime) * 3600
|
||||
mission:SetTime(nil,timeonstation)
|
||||
|
||||
mission:SetMissionRange(self.MaxMissionRange)
|
||||
|
||||
AwacsAW:AddMission(mission)
|
||||
|
||||
self.AwacsMissionReplacement = mission
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
-- @image OPS_CSAR.jpg
|
||||
|
||||
---
|
||||
-- Last Update July 2024
|
||||
-- Last Update Jan 2025
|
||||
|
||||
-------------------------------------------------------------------------
|
||||
--- **CSAR** class, extends Core.Base#BASE, Core.Fsm#FSM
|
||||
@@ -41,6 +41,7 @@
|
||||
-- @field #string lid Class id string for output to DCS log file.
|
||||
-- @field #number coalition Coalition side number, e.g. `coalition.side.RED`.
|
||||
-- @field Core.Set#SET_GROUP allheligroupset Set of CSAR heli groups.
|
||||
-- @field Core.Set#SET_GROUP UserSetGroup Set of CSAR heli groups as designed by the mission designer (if any set).
|
||||
-- @extends Core.Fsm#FSM
|
||||
|
||||
--- *Combat search and rescue (CSAR) are search and rescue operations that are carried out during war that are within or near combat zones.* (Wikipedia)
|
||||
@@ -91,7 +92,7 @@
|
||||
-- mycsar.immortalcrew = true -- Set to true to make wounded crew immortal.
|
||||
-- mycsar.invisiblecrew = false -- Set to true to make wounded crew insvisible.
|
||||
-- mycsar.loadDistance = 75 -- configure distance for pilots to get into helicopter in meters.
|
||||
-- mycsar.mashprefix = {"MASH"} -- prefixes of #GROUP objects used as MASHes.
|
||||
-- mycsar.mashprefix = {"MASH"} -- prefixes of #GROUP objects used as MASHes. Will also try to add ZONE and STATIC objects with this prefix once at startup.
|
||||
-- mycsar.max_units = 6 -- max number of pilots that can be carried if #CSAR.AircraftType is undefined.
|
||||
-- mycsar.messageTime = 15 -- Time to show messages for in seconds. Doubled for long messages.
|
||||
-- mycsar.radioSound = "beacon.ogg" -- the name of the sound file to use for the pilots\' radio beacons.
|
||||
@@ -116,8 +117,17 @@
|
||||
-- mycsar.topmenuname = "CSAR" -- set the menu entry name
|
||||
-- mycsar.ADFRadioPwr = 1000 -- ADF Beacons sending with 1KW as default
|
||||
-- mycsar.PilotWeight = 80 -- Loaded pilots weigh 80kgs each
|
||||
-- mycsar.AllowIRStrobe = false -- Allow a menu item to request an IR strobe to find a downed pilot at night (requires NVGs to see it).
|
||||
-- mycsar.IRStrobeRuntime = 300 -- If an IR Strobe is activated, it runs for 300 seconds (5 mins).
|
||||
--
|
||||
-- ## 2.1 Create own SET_GROUP to manage CTLD Pilot groups
|
||||
--
|
||||
-- -- Parameter: Set The SET_GROUP object created by the mission designer/user to represent the CSAR pilot groups.
|
||||
-- -- Needs to be set before starting the CSAR instance.
|
||||
-- local myset = SET_GROUP:New():FilterPrefixes("Helikopter"):FilterCoalitions("red"):FilterStart()
|
||||
-- mycsar:SetOwnSetPilotGroups(myset)
|
||||
--
|
||||
-- ## 2.1 SRS Features and Other Features
|
||||
-- ## 2.2 SRS Features and Other Features
|
||||
--
|
||||
-- mycsar.useSRS = false -- Set true to use FF\'s SRS integration
|
||||
-- mycsar.SRSPath = "C:\\Progra~1\\DCS-SimpleRadio-Standalone\\" -- adjust your own path in your SRS installation -- server(!)
|
||||
@@ -136,6 +146,7 @@
|
||||
-- mycsar.csarUsePara = false -- If set to true, will use the LandingAfterEjection Event instead of Ejection. Requires mycsar.enableForAI to be set to true. --shagrat
|
||||
-- mycsar.wetfeettemplate = "man in floating thingy" -- if you use a mod to have a pilot in a rescue float, put the template name in here for wet feet spawns. Note: in conjunction with csarUsePara this might create dual ejected pilots in edge cases.
|
||||
-- mycsar.allowbronco = false -- set to true to use the Bronco mod as a CSAR plane
|
||||
-- mycsar.CreateRadioBeacons = true -- set to false to disallow creating ADF radio beacons.
|
||||
--
|
||||
-- ## 3. Results
|
||||
--
|
||||
@@ -256,6 +267,10 @@ CSAR = {
|
||||
topmenuname = "CSAR",
|
||||
ADFRadioPwr = 1000,
|
||||
PilotWeight = 80,
|
||||
CreateRadioBeacons = true,
|
||||
UserSetGroup = nil,
|
||||
AllowIRStrobe = false,
|
||||
IRStrobeRuntime = 300,
|
||||
}
|
||||
|
||||
--- Downed pilots info.
|
||||
@@ -272,6 +287,7 @@ CSAR = {
|
||||
-- @field #number timestamp Timestamp for approach process.
|
||||
-- @field #boolean alive Group is alive or dead/rescued.
|
||||
-- @field #boolean wetfeet Group is spawned over (deep) water.
|
||||
-- @field #string BeaconName Name of radio beacon - if any.
|
||||
|
||||
--- All slot / Limit settings
|
||||
-- @type CSAR.AircraftType
|
||||
@@ -292,12 +308,12 @@ CSAR.AircraftType["AH-64D_BLK_II"] = 2
|
||||
CSAR.AircraftType["Bronco-OV-10A"] = 2
|
||||
CSAR.AircraftType["MH-60R"] = 10
|
||||
CSAR.AircraftType["OH-6A"] = 2
|
||||
CSAR.AircraftType["OH-58D"] = 2
|
||||
CSAR.AircraftType["OH58D"] = 2
|
||||
CSAR.AircraftType["CH-47Fbl1"] = 31
|
||||
|
||||
--- CSAR class version.
|
||||
-- @field #string version
|
||||
CSAR.version="1.0.26"
|
||||
CSAR.version="1.0.30"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- ToDo list
|
||||
@@ -456,6 +472,9 @@ function CSAR:New(Coalition, Template, Alias)
|
||||
|
||||
-- added 1.0.16
|
||||
self.PilotWeight = 80
|
||||
|
||||
-- Own SET_GROUP if any
|
||||
self.UserSetGroup = nil
|
||||
|
||||
-- WARNING - here\'ll be dragons
|
||||
-- for this to work you need to de-sanitize your mission environment in <DCS root>\Scripts\MissionScripting.lua
|
||||
@@ -634,7 +653,7 @@ end
|
||||
-- @param #string Playername Name of Player (if applicable)
|
||||
-- @param #boolean Wetfeet Ejected over water
|
||||
-- @return #CSAR self.
|
||||
function CSAR:_CreateDownedPilotTrack(Group,Groupname,Side,OriginalUnit,Description,Typename,Frequency,Playername,Wetfeet)
|
||||
function CSAR:_CreateDownedPilotTrack(Group,Groupname,Side,OriginalUnit,Description,Typename,Frequency,Playername,Wetfeet,BeaconName)
|
||||
self:T({"_CreateDownedPilotTrack",Groupname,Side,OriginalUnit,Description,Typename,Frequency,Playername})
|
||||
|
||||
-- create new entry
|
||||
@@ -642,7 +661,7 @@ function CSAR:_CreateDownedPilotTrack(Group,Groupname,Side,OriginalUnit,Descript
|
||||
DownedPilot.desc = Description or ""
|
||||
DownedPilot.frequency = Frequency or 0
|
||||
DownedPilot.index = self.downedpilotcounter
|
||||
DownedPilot.name = Groupname or ""
|
||||
DownedPilot.name = Groupname or Playername or ""
|
||||
DownedPilot.originalUnit = OriginalUnit or ""
|
||||
DownedPilot.player = Playername or ""
|
||||
DownedPilot.side = Side or 0
|
||||
@@ -651,6 +670,7 @@ function CSAR:_CreateDownedPilotTrack(Group,Groupname,Side,OriginalUnit,Descript
|
||||
DownedPilot.timestamp = 0
|
||||
DownedPilot.alive = true
|
||||
DownedPilot.wetfeet = Wetfeet or false
|
||||
DownedPilot.BeaconName = BeaconName
|
||||
|
||||
-- Add Pilot
|
||||
local PilotTable = self.downedPilots
|
||||
@@ -737,7 +757,6 @@ function CSAR:_SpawnPilotInField(country,point,frequency,wetfeet)
|
||||
:NewWithAlias(template,alias)
|
||||
:InitCoalition(coalition)
|
||||
:InitCountry(country)
|
||||
--:InitAIOnOff(pilotcacontrol)
|
||||
:InitDelayOff()
|
||||
:SpawnFromCoordinate(point)
|
||||
|
||||
@@ -790,6 +809,8 @@ end
|
||||
-- @param #boolean noMessage
|
||||
-- @param #string _description Description
|
||||
-- @param #boolean forcedesc Use the description only for the pilot track entry
|
||||
-- @return Wrapper.Group#GROUP PilotInField Pilot GROUP object
|
||||
-- @return #string AliasName Alias display name
|
||||
function CSAR:_AddCsar(_coalition , _country, _point, _typeName, _unitName, _playerName, _freq, noMessage, _description, forcedesc )
|
||||
self:T(self.lid .. " _AddCsar")
|
||||
self:T({_coalition , _country, _point, _typeName, _unitName, _playerName, _freq, noMessage, _description})
|
||||
@@ -819,8 +840,18 @@ function CSAR:_AddCsar(_coalition , _country, _point, _typeName, _unitName, _pla
|
||||
end
|
||||
end
|
||||
|
||||
local BeaconName
|
||||
|
||||
if _playerName then
|
||||
BeaconName = _playerName..math.random(1,10000)
|
||||
elseif _unitName then
|
||||
BeaconName = _unitName..math.random(1,10000)
|
||||
else
|
||||
BeaconName = "Ghost-1-1"..math.random(1,10000)
|
||||
end
|
||||
|
||||
if (_freq and _freq ~= 0) then --shagrat only add beacon if _freq is NOT 0
|
||||
self:_AddBeaconToGroup(_spawnedGroup, _freq)
|
||||
self:_AddBeaconToGroup(_spawnedGroup, _freq, BeaconName)
|
||||
end
|
||||
|
||||
self:_AddSpecialOptions(_spawnedGroup)
|
||||
@@ -845,11 +876,11 @@ function CSAR:_AddCsar(_coalition , _country, _point, _typeName, _unitName, _pla
|
||||
|
||||
local _GroupName = _spawnedGroup:GetName() or _alias
|
||||
|
||||
self:_CreateDownedPilotTrack(_spawnedGroup,_GroupName,_coalition,_unitName,_text,_typeName,_freq,_playerName,wetfeet)
|
||||
self:_CreateDownedPilotTrack(_spawnedGroup,_GroupName,_coalition,_unitName,_text,_typeName,_freq,_playerName,wetfeet,BeaconName)
|
||||
|
||||
self:_InitSARForPilot(_spawnedGroup, _unitName, _freq, noMessage, _playerName) --shagrat use unitName to have the aircraft callsign / descriptive "name" etc.
|
||||
|
||||
return self
|
||||
return _spawnedGroup, _alias
|
||||
end
|
||||
|
||||
--- (Internal) Function to add a CSAR object into the scene at a zone coordinate. For mission designers wanting to add e.g. PoWs to the scene.
|
||||
@@ -963,7 +994,6 @@ end
|
||||
-- @param Core.Point#COORDINATE Point
|
||||
-- @param #number Coalition Coalition.
|
||||
-- @param #string Description (optional) Description.
|
||||
-- @param #boolean addBeacon (optional) yes or no.
|
||||
-- @param #boolean Nomessage (optional) If true, don\'t send a message to SAR.
|
||||
-- @param #string Unitname (optional) Name of the lost unit.
|
||||
-- @param #string Typename (optional) Type of plane.
|
||||
@@ -1793,9 +1823,6 @@ function CSAR:_DisplayMessageToSAR(_unit, _text, _time, _clear, _speak, _overrid
|
||||
end
|
||||
_text = string.gsub(_text,"km"," kilometer")
|
||||
_text = string.gsub(_text,"nm"," nautical miles")
|
||||
--self.msrs:SetVoice(self.SRSVoice)
|
||||
--self.SRSQueue:NewTransmission(_text,nil,self.msrs,nil,1)
|
||||
--self:I("Voice = "..self.SRSVoice)
|
||||
self.SRSQueue:NewTransmission(_text,duration,self.msrs,tstart,2,subgroups,subtitle,subduration,self.SRSchannel,self.SRSModulation,gender,culture,self.SRSVoice,volume,label,coord)
|
||||
end
|
||||
return self
|
||||
@@ -1804,8 +1831,9 @@ end
|
||||
--- (Internal) Function to get string of a group\'s position.
|
||||
-- @param #CSAR self
|
||||
-- @param Wrapper.Controllable#CONTROLLABLE _woundedGroup Group or Unit object.
|
||||
-- @param Wrapper.Unit#UNIT _Unit Requesting helo pilot unit
|
||||
-- @return #string Coordinates as Text
|
||||
function CSAR:_GetPositionOfWounded(_woundedGroup)
|
||||
function CSAR:_GetPositionOfWounded(_woundedGroup,_Unit)
|
||||
self:T(self.lid .. " _GetPositionOfWounded")
|
||||
local _coordinate = _woundedGroup:GetCoordinate()
|
||||
local _coordinatesText = "None"
|
||||
@@ -1820,6 +1848,26 @@ function CSAR:_GetPositionOfWounded(_woundedGroup)
|
||||
_coordinatesText = _coordinate:ToStringBULLS(self.coalition)
|
||||
end
|
||||
end
|
||||
if _Unit and _Unit:GetPlayerName() then
|
||||
local playername = _Unit:GetPlayerName()
|
||||
if playername then
|
||||
local settings = _DATABASE:GetPlayerSettings(playername) or _SETTINGS
|
||||
if settings then
|
||||
self:T("Get Settings ok!")
|
||||
if settings:IsA2G_MGRS() then
|
||||
_coordinatesText = _coordinate:ToStringMGRS(settings)
|
||||
elseif settings:IsA2G_LL_DMS() then
|
||||
_coordinatesText = _coordinate:ToStringLLDMS(settings)
|
||||
elseif settings:IsA2G_LL_DDM() then
|
||||
_coordinatesText = _coordinate:ToStringLLDDM(settings)
|
||||
elseif settings:IsA2G_BR() then
|
||||
-- attention this is the distance from the ASKING unit to target, not from RECCE to target!
|
||||
local startcoordinate = _Unit:GetCoordinate()
|
||||
_coordinatesText = _coordinate:ToStringBR(startcoordinate,settings)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return _coordinatesText
|
||||
end
|
||||
|
||||
@@ -1845,22 +1893,26 @@ function CSAR:_DisplayActiveSAR(_unitName)
|
||||
self:T({Table=_value})
|
||||
local _woundedGroup = _value.group
|
||||
if _woundedGroup and _value.alive then
|
||||
local _coordinatesText = self:_GetPositionOfWounded(_woundedGroup)
|
||||
local _coordinatesText = self:_GetPositionOfWounded(_woundedGroup,_heli)
|
||||
local _helicoord = _heli:GetCoordinate()
|
||||
local _woundcoord = _woundedGroup:GetCoordinate()
|
||||
local _distance = self:_GetDistance(_helicoord, _woundcoord)
|
||||
self:T({_distance = _distance})
|
||||
local distancetext = ""
|
||||
if _SETTINGS:IsImperial() then
|
||||
local settings = _SETTINGS
|
||||
if _heli:GetPlayerName() then
|
||||
settings = _DATABASE:GetPlayerSettings(_heli:GetPlayerName()) or _SETTINGS
|
||||
end
|
||||
if settings:IsImperial() then
|
||||
distancetext = string.format("%.1fnm",UTILS.MetersToNM(_distance))
|
||||
else
|
||||
distancetext = string.format("%.1fkm", _distance/1000.0)
|
||||
end
|
||||
if _value.frequency == 0 then--shagrat insert CASEVAC without Frequency
|
||||
table.insert(_csarList, { dist = _distance, msg = string.format("%s at %s - %s ", _value.desc, _coordinatesText, distancetext) })
|
||||
else
|
||||
table.insert(_csarList, { dist = _distance, msg = string.format("%s at %s - %.2f KHz ADF - %s ", _value.desc, _coordinatesText, _value.frequency / 1000, distancetext) })
|
||||
end
|
||||
if _value.frequency == 0 or self.CreateRadioBeacons == false then--shagrat insert CASEVAC without Frequency
|
||||
table.insert(_csarList, { dist = _distance, msg = string.format("%s at %s - %s ", _value.desc, _coordinatesText, distancetext) })
|
||||
else
|
||||
table.insert(_csarList, { dist = _distance, msg = string.format("%s at %s - %.2f KHz ADF - %s ", _value.desc, _coordinatesText, _value.frequency / 1000, distancetext) })
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1941,7 +1993,7 @@ function CSAR:_SignalFlare(_unitName)
|
||||
else
|
||||
_distance = string.format("%.1fkm",_closest.distance/1000)
|
||||
end
|
||||
local _msg = string.format("%s - Popping signal flare at your %s o\'clock. Distance %s", self:_GetCustomCallSign(_unitName), _clockDir, _distance)
|
||||
local _msg = string.format("%s - Firing signal flare at your %s o\'clock. Distance %s", self:_GetCustomCallSign(_unitName), _clockDir, _distance)
|
||||
self:_DisplayMessageToSAR(_heli, _msg, self.messageTime, false, true, true)
|
||||
|
||||
local _coord = _closest.pilot:GetCoordinate()
|
||||
@@ -1975,7 +2027,7 @@ function CSAR:_DisplayToAllSAR(_message, _side, _messagetime,ToSRS,ToScreen)
|
||||
if self.msrs:GetProvider() == MSRS.Provider.WINDOWS then
|
||||
voice = self.CSARVoiceMS or MSRS.Voices.Microsoft.Hedda
|
||||
end
|
||||
self:F("Voice = "..voice)
|
||||
--self:F("Voice = "..voice)
|
||||
self.SRSQueue:NewTransmission(_message,duration,self.msrs,tstart,2,subgroups,subtitle,subduration,self.SRSchannel,self.SRSModulation,gender,culture,voice,volume,label,self.coordinate)
|
||||
end
|
||||
if ToScreen == true or ToScreen == nil then
|
||||
@@ -1989,6 +2041,41 @@ function CSAR:_DisplayToAllSAR(_message, _side, _messagetime,ToSRS,ToScreen)
|
||||
return self
|
||||
end
|
||||
|
||||
---(Internal) Request IR Strobe at closest downed pilot.
|
||||
--@param #CSAR self
|
||||
--@param #string _unitName Name of the helicopter
|
||||
function CSAR:_ReqIRStrobe( _unitName )
|
||||
self:T(self.lid .. " _ReqIRStrobe")
|
||||
local _heli = self:_GetSARHeli(_unitName)
|
||||
if _heli == nil then
|
||||
return
|
||||
end
|
||||
local smokedist = 8000
|
||||
if smokedist < self.approachdist_far then smokedist = self.approachdist_far end
|
||||
local _closest = self:_GetClosestDownedPilot(_heli)
|
||||
if _closest ~= nil and _closest.pilot ~= nil and _closest.distance > 0 and _closest.distance < smokedist then
|
||||
local _clockDir = self:_GetClockDirection(_heli, _closest.pilot)
|
||||
local _distance = string.format("%.1fkm",_closest.distance/1000)
|
||||
if _SETTINGS:IsImperial() then
|
||||
_distance = string.format("%.1fnm",UTILS.MetersToNM(_closest.distance))
|
||||
else
|
||||
_distance = string.format("%.1fkm",_closest.distance/1000)
|
||||
end
|
||||
local _msg = string.format("%s - IR Strobe active at your %s o\'clock. Distance %s", self:_GetCustomCallSign(_unitName), _clockDir, _distance)
|
||||
self:_DisplayMessageToSAR(_heli, _msg, self.messageTime, false, true, true)
|
||||
_closest.pilot:NewIRMarker(true,self.IRStrobeRuntime or 300)
|
||||
else
|
||||
local _distance = string.format("%.1fkm",smokedist/1000)
|
||||
if _SETTINGS:IsImperial() then
|
||||
_distance = string.format("%.1fnm",UTILS.MetersToNM(smokedist))
|
||||
else
|
||||
_distance = string.format("%.1fkm",smokedist/1000)
|
||||
end
|
||||
self:_DisplayMessageToSAR(_heli, string.format("No Pilots within %s",_distance), self.messageTime, false, false, true)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
---(Internal) Request smoke at closest downed pilot.
|
||||
--@param #CSAR self
|
||||
--@param #string _unitName Name of the helicopter
|
||||
@@ -2140,7 +2227,12 @@ function CSAR:_AddMedevacMenuItem()
|
||||
local _rootMenu1 = MENU_GROUP_COMMAND:New(_group,"List Active CSAR",_rootPath, self._DisplayActiveSAR,self,_unitName)
|
||||
local _rootMenu2 = MENU_GROUP_COMMAND:New(_group,"Check Onboard",_rootPath, self._CheckOnboard,self,_unitName)
|
||||
local _rootMenu3 = MENU_GROUP_COMMAND:New(_group,"Request Signal Flare",_rootPath, self._SignalFlare,self,_unitName)
|
||||
local _rootMenu4 = MENU_GROUP_COMMAND:New(_group,"Request Smoke",_rootPath, self._Reqsmoke,self,_unitName):Refresh()
|
||||
local _rootMenu4 = MENU_GROUP_COMMAND:New(_group,"Request Smoke",_rootPath, self._Reqsmoke,self,_unitName)
|
||||
if self.AllowIRStrobe then
|
||||
local _rootMenu5 = MENU_GROUP_COMMAND:New(_group,"Request IR Strobe",_rootPath, self._ReqIRStrobe,self,_unitName):Refresh()
|
||||
else
|
||||
_rootMenu4:Refresh()
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -2231,9 +2323,13 @@ end
|
||||
-- @param #CSAR self
|
||||
-- @param Wrapper.Group#GROUP _group Group #GROUP object.
|
||||
-- @param #number _freq Frequency to use
|
||||
function CSAR:_AddBeaconToGroup(_group, _freq)
|
||||
-- @param #string _name Beacon Name to use
|
||||
-- @return #CSAR self
|
||||
function CSAR:_AddBeaconToGroup(_group, _freq, _name)
|
||||
self:T(self.lid .. " _AddBeaconToGroup")
|
||||
if self.CreateRadioBeacons == false then return end
|
||||
local _group = _group
|
||||
|
||||
if _group == nil then
|
||||
--return frequency to pool of available
|
||||
for _i, _current in ipairs(self.UsedVHFFrequencies) do
|
||||
@@ -2248,22 +2344,24 @@ function CSAR:_AddBeaconToGroup(_group, _freq)
|
||||
if _group:IsAlive() then
|
||||
local _radioUnit = _group:GetUnit(1)
|
||||
if _radioUnit then
|
||||
local name = _radioUnit:GetName()
|
||||
local name = _radioUnit:GetName()
|
||||
local Frequency = _freq -- Freq in Hertz
|
||||
local name = _radioUnit:GetName()
|
||||
local Sound = "l10n/DEFAULT/"..self.radioSound
|
||||
local vec3 = _radioUnit:GetVec3() or _radioUnit:GetPositionVec3() or {x=0,y=0,z=0}
|
||||
trigger.action.radioTransmission(Sound, vec3, 0, false, Frequency, self.ADFRadioPwr or 1000,name..math.random(1,10000)) -- Beacon in MP only runs for exactly 30secs straight
|
||||
trigger.action.radioTransmission(Sound, vec3, 0, false, Frequency, self.ADFRadioPwr or 1000,_name) -- Beacon in MP only runs for exactly 30secs straight
|
||||
end
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- (Internal) Helper function to (re-)add beacon to downed pilot.
|
||||
-- @param #CSAR self
|
||||
-- @param #table _args Arguments
|
||||
-- @return #CSAR self
|
||||
function CSAR:_RefreshRadioBeacons()
|
||||
self:T(self.lid .. " _RefreshRadioBeacons")
|
||||
if self.CreateRadioBeacons == false then return end
|
||||
if self:_CountActiveDownedPilots() > 0 then
|
||||
local PilotTable = self.downedPilots
|
||||
for _,_pilot in pairs (PilotTable) do
|
||||
@@ -2271,8 +2369,10 @@ function CSAR:_RefreshRadioBeacons()
|
||||
local pilot = _pilot -- #CSAR.DownedPilot
|
||||
local group = pilot.group
|
||||
local frequency = pilot.frequency or 0 -- thanks to @Thrud
|
||||
local bname = pilot.BeaconName or pilot.name..math.random(1,100000)
|
||||
trigger.action.stopRadioTransmission(bname)
|
||||
if group and group:IsAlive() and frequency > 0 then
|
||||
self:_AddBeaconToGroup(group,frequency)
|
||||
self:_AddBeaconToGroup(group,frequency,bname)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -2309,6 +2409,16 @@ function CSAR:_ReachedPilotLimit()
|
||||
end
|
||||
end
|
||||
|
||||
--- User - Function to add onw SET_GROUP Set-up for pilot filtering and assignment.
|
||||
-- Needs to be set before starting the CSAR instance.
|
||||
-- @param #CSAR self
|
||||
-- @param Core.Set#SET_GROUP Set The SET_GROUP object created by the mission designer/user to represent the CSAR pilot groups.
|
||||
-- @return #CSAR self
|
||||
function CSAR:SetOwnSetPilotGroups(Set)
|
||||
self.UserSetGroup = Set
|
||||
return self
|
||||
end
|
||||
|
||||
------------------------------
|
||||
--- FSM internal Functions ---
|
||||
------------------------------
|
||||
@@ -2330,7 +2440,9 @@ function CSAR:onafterStart(From, Event, To)
|
||||
self:HandleEvent(EVENTS.PlayerEnterUnit, self._EventHandler)
|
||||
self:HandleEvent(EVENTS.PilotDead, self._EventHandler)
|
||||
|
||||
if self.allowbronco then
|
||||
if self.UserSetGroup then
|
||||
self.allheligroupset = self.UserSetGroup
|
||||
elseif self.allowbronco then
|
||||
local prefixes = self.csarPrefix or {}
|
||||
self.allheligroupset = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(prefixes):FilterStart()
|
||||
elseif self.useprefix then
|
||||
@@ -2339,7 +2451,24 @@ function CSAR:onafterStart(From, Event, To)
|
||||
else
|
||||
self.allheligroupset = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterCategoryHelicopter():FilterStart()
|
||||
end
|
||||
self.mash = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(self.mashprefix):FilterStart() -- currently only GROUP objects, maybe support STATICs also?
|
||||
|
||||
self.mash = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(self.mashprefix):FilterStart()
|
||||
|
||||
local staticmashes = SET_STATIC:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(self.mashprefix):FilterOnce()
|
||||
local zonemashes = SET_ZONE:New():FilterPrefixes(self.mashprefix):FilterOnce()
|
||||
|
||||
if staticmashes:Count() > 0 then
|
||||
for _,_mash in pairs(staticmashes.Set) do
|
||||
self.mash:AddObject(_mash)
|
||||
end
|
||||
end
|
||||
|
||||
if zonemashes:Count() > 0 then
|
||||
for _,_mash in pairs(zonemashes.Set) do
|
||||
self.mash:AddObject(_mash)
|
||||
end
|
||||
end
|
||||
|
||||
if not self.coordinate then
|
||||
local csarhq = self.mash:GetRandom()
|
||||
if csarhq then
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -34,6 +34,7 @@
|
||||
-- @field Ops.Commander#COMMANDER commander Commander of assigned legions.
|
||||
-- @field #number Nsuccess Number of successful missions.
|
||||
-- @field #number Nfailure Number of failed mission.
|
||||
-- @field #table assetNumbers Asset numbers. Each entry is a table of data type `#CHIEF.AssetNumber`.
|
||||
-- @extends Ops.Intel#INTEL
|
||||
|
||||
--- *In preparing for battle I have always found that plans are useless, but planning is indispensable* -- Dwight D Eisenhower
|
||||
@@ -331,7 +332,7 @@ CHIEF.Strategy = {
|
||||
|
||||
--- CHIEF class version.
|
||||
-- @field #string version
|
||||
CHIEF.version="0.6.0"
|
||||
CHIEF.version="0.6.1"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- TODO list
|
||||
@@ -1284,8 +1285,10 @@ end
|
||||
--
|
||||
-- Empty:
|
||||
--
|
||||
-- * `AUFTRAG.Type.ONGURAD` with Nmin=1 and Nmax=1 assets, Attribute=`GROUP.Attribute.GROUND_TANK`.
|
||||
-- * `AUFTRAG.Type.ONGURAD` with Nmin=0 and Nmax=1 assets, Attribute=`GROUP.Attribute.GROUND_TANK`.
|
||||
-- * `AUFTRAG.Type.ONGURAD` with Nmin=0 and Nmax=1 assets, Attribute=`GROUP.Attribute.GROUND_IFV`.
|
||||
-- * `AUFTRAG.Type.ONGUARD` with Nmin=1 and Nmax=3 assets, Attribute=`GROUP.Attribute.GROUND_INFANTRY`.
|
||||
-- * `AUFTRAG.Type.OPSTRANSPORT` with Nmin=0 and Nmax=1 assets, Attribute=`GROUP.Attribute.AIR_TRANSPORTHELO` or `GROUP.Attribute.GROUND_APC`. This asset is used to transport the infantry groups.
|
||||
--
|
||||
-- Resources can be created with the @{#CHIEF.CreateResource} and @{#CHIEF.AddToResource} functions.
|
||||
--
|
||||
@@ -3033,10 +3036,13 @@ function CHIEF:RecruitAssetsForZone(StratZone, Resource)
|
||||
end
|
||||
|
||||
-- Recruite infantry assets.
|
||||
self:T(self.lid..string.format("Recruiting assets for zone %s", StratZone.opszone:GetName()))
|
||||
self:T(self.lid.."Missiontype="..MissionType)
|
||||
self:T({categories=Categories})
|
||||
self:T({attributes=Attributes})
|
||||
self:T({properties=Properties})
|
||||
|
||||
|
||||
local recruited, assets, legions=LEGION.RecruitCohortAssets(Cohorts, MissionType, nil, NassetsMin, NassetsMax, TargetVec2, nil, RangeMax, nil, nil, nil, nil, Categories, Attributes, Properties)
|
||||
|
||||
if recruited then
|
||||
@@ -3052,9 +3058,12 @@ function CHIEF:RecruitAssetsForZone(StratZone, Resource)
|
||||
local TargetCoord = TargetZone:GetCoordinate()
|
||||
|
||||
-- First check if we need a transportation.
|
||||
local transport=nil
|
||||
local transport=nil --Ops.OpsTransport#OPSTRANSPORT
|
||||
local Ntransports=0
|
||||
if Resource.carrierNmin and Resource.carrierNmax and Resource.carrierNmax>0 then
|
||||
|
||||
self:T(self.lid..string.format("Recruiting carrier assets: Nmin=%s, Nmax=%s", tostring(Resource.carrierNmin), tostring(Resource.carrierNmax)))
|
||||
|
||||
-- Filter only those assets that shall be transported.
|
||||
local cargoassets=CHIEF._FilterAssets(assets, Resource.Categories, Resource.Attributes, Resource.Properties)
|
||||
|
||||
@@ -3064,6 +3073,10 @@ function CHIEF:RecruitAssetsForZone(StratZone, Resource)
|
||||
recruited, transport=LEGION.AssignAssetsForTransport(self.commander, self.commander.legions, cargoassets,
|
||||
Resource.carrierNmin, Resource.carrierNmax, TargetZone, nil, Resource.carrierCategories, Resource.carrierAttributes, Resource.carrierProperties)
|
||||
|
||||
Ntransports=transport~=nil and #transport.assets or 0
|
||||
|
||||
self:T(self.lid..string.format("Recruited %d transport carrier assets success=%s", Ntransports, tostring(recruited)))
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
@@ -3076,7 +3089,7 @@ function CHIEF:RecruitAssetsForZone(StratZone, Resource)
|
||||
return false
|
||||
end
|
||||
|
||||
-- Debug messgage.
|
||||
-- Debug message
|
||||
self:T2(self.lid..string.format("Recruited %d assets for mission %s", #assets, MissionType))
|
||||
|
||||
|
||||
@@ -3224,10 +3237,13 @@ function CHIEF:RecruitAssetsForZone(StratZone, Resource)
|
||||
-- Attach mission to ops zone.
|
||||
StratZone.opszone:_AddMission(self.coalition, MissionType, mission)
|
||||
|
||||
mission:SetName(string.format("Stratzone %s-%d", StratZone.opszone:GetName(), mission.auftragsnummer))
|
||||
|
||||
-- Attach mission to resource.
|
||||
Resource.mission=mission
|
||||
|
||||
if transport then
|
||||
-- Check if transport assets could be allocated. If carrier Nmin=0 and 0 assets could be allocated, transport would still be created but not usefull obviously
|
||||
if transport and Ntransports>0 then
|
||||
-- Attach OPS transport to mission.
|
||||
mission.opstransport=transport
|
||||
-- Set ops zone to transport.
|
||||
|
||||
@@ -88,7 +88,10 @@ COHORT = {
|
||||
|
||||
--- COHORT class version.
|
||||
-- @field #string version
|
||||
COHORT.version="0.3.5"
|
||||
COHORT.version="0.3.6"
|
||||
|
||||
--- Global variable to store the unique(!) cohort names
|
||||
_COHORTNAMES={}
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- TODO list
|
||||
@@ -110,6 +113,17 @@ COHORT.version="0.3.5"
|
||||
-- @return #COHORT self
|
||||
function COHORT:New(TemplateGroupName, Ngroups, CohortName)
|
||||
|
||||
-- Name of the cohort.
|
||||
local name=tostring(CohortName or TemplateGroupName)
|
||||
|
||||
-- Cohort name has to be unique or we will get serious problems!
|
||||
if UTILS.IsAnyInTable(_COHORTNAMES, name) then
|
||||
env.error(string.format('ERROR: cannot create cohort "%s" because another cohort with that name already exists. Names must be unique!', name))
|
||||
return nil
|
||||
else
|
||||
table.insert(_COHORTNAMES, name)
|
||||
end
|
||||
|
||||
-- Inherit everything from FSM class.
|
||||
local self=BASE:Inherit(self, FSM:New()) -- #COHORT
|
||||
|
||||
@@ -117,7 +131,7 @@ function COHORT:New(TemplateGroupName, Ngroups, CohortName)
|
||||
self.templatename=TemplateGroupName
|
||||
|
||||
-- Cohort name.
|
||||
self.name=tostring(CohortName or TemplateGroupName)
|
||||
self.name=name
|
||||
|
||||
-- Set some string id for output to DCS.log file.
|
||||
self.lid=string.format("COHORT %s | ", self.name)
|
||||
|
||||
@@ -1774,8 +1774,10 @@ function COMMANDER:RecruitAssetsForMission(Mission)
|
||||
MaxWeight=cohort.cargobayLimit
|
||||
end
|
||||
end
|
||||
|
||||
self:T(self.lid..string.format("Largest cargo bay available=%.1f", MaxWeight))
|
||||
|
||||
if MaxWeight then
|
||||
self:T(self.lid..string.format("Largest cargo bay available=%.1f", MaxWeight))
|
||||
end
|
||||
end
|
||||
|
||||
local legions=self.legions
|
||||
@@ -2165,4 +2167,4 @@ end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -50,7 +50,7 @@
|
||||
-- @field #number capleg
|
||||
-- @field #number maxinterceptsize
|
||||
-- @field #number missionrange
|
||||
-- @field #number noaltert5
|
||||
-- @field #number noalert5
|
||||
-- @field #table ManagedAW
|
||||
-- @field #table ManagedSQ
|
||||
-- @field #table ManagedCP
|
||||
@@ -167,7 +167,7 @@
|
||||
-- * @{#EASYGCICAP.SetDefaultCAPLeg}: Set the length of the CAP leg, default is 15 NM.
|
||||
-- * @{#EASYGCICAP.SetDefaultCAPGrouping}: Set how many planes will be spawned per mission (CVAP/GCI), defaults to 2.
|
||||
-- * @{#EASYGCICAP.SetDefaultMissionRange}: Set how many NM the planes can go from the home base, defaults to 100.
|
||||
-- * @{#EASYGCICAP.SetDefaultNumberAlter5Standby}: Set how many planes will be spawned on cold standby (Alert5), default 2.
|
||||
-- * @{#EASYGCICAP.SetDefaultNumberAlert5Standby}: Set how many planes will be spawned on cold standby (Alert5), default 2.
|
||||
-- * @{#EASYGCICAP.SetDefaultEngageRange}: Set max engage range for CAP flights if they detect intruders, defaults to 50.
|
||||
-- * @{#EASYGCICAP.SetMaxAliveMissions}: Set max parallel missions can be done (CAP+GCI+Alert5+Tanker+AWACS), defaults to 8.
|
||||
-- * @{#EASYGCICAP.SetDefaultRepeatOnFailure}: Set max repeats on failure for intercepting/killing intruders, defaults to 3.
|
||||
@@ -197,7 +197,7 @@ EASYGCICAP = {
|
||||
capleg = 15,
|
||||
maxinterceptsize = 2,
|
||||
missionrange = 100,
|
||||
noaltert5 = 4,
|
||||
noalert5 = 4,
|
||||
ManagedAW = {},
|
||||
ManagedSQ = {},
|
||||
ManagedCP = {},
|
||||
@@ -252,7 +252,7 @@ EASYGCICAP = {
|
||||
|
||||
--- EASYGCICAP class version.
|
||||
-- @field #string version
|
||||
EASYGCICAP.version="0.1.13"
|
||||
EASYGCICAP.version="0.1.17"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- TODO list
|
||||
@@ -269,7 +269,7 @@ EASYGCICAP.version="0.1.13"
|
||||
-- @param #string Alias A Name for this GCICAP
|
||||
-- @param #string AirbaseName Name of the Home Airbase
|
||||
-- @param #string Coalition Coalition, e.g. "blue" or "red"
|
||||
-- @param #string EWRName (Partial) group name of the EWR system of the coalition, e.g. "Red EWR"
|
||||
-- @param #string EWRName (Partial) group name of the EWR system of the coalition, e.g. "Red EWR", can be handed in as table of names, e.g.{"EWR","Radar","SAM"}
|
||||
-- @return #EASYGCICAP self
|
||||
function EASYGCICAP:New(Alias, AirbaseName, Coalition, EWRName)
|
||||
-- Inherit everything from FSM class.
|
||||
@@ -278,9 +278,10 @@ function EASYGCICAP:New(Alias, AirbaseName, Coalition, EWRName)
|
||||
-- defaults
|
||||
self.alias = Alias or AirbaseName.." CAP Wing"
|
||||
self.coalitionname = string.lower(Coalition) or "blue"
|
||||
self.coalition = self.coaltitionname == "blue" and coalition.side.BLUE or coalition.side.RED
|
||||
self.coalition = self.coalitionname == "blue" and coalition.side.BLUE or coalition.side.RED
|
||||
self.wings = {}
|
||||
self.EWRName = EWRName or self.coalitionname.." EWR"
|
||||
if type(EWRName) == "string" then EWRName = {EWRName} end
|
||||
self.EWRName = EWRName --or self.coalitionname.." EWR"
|
||||
--self.CapZoneName = CapZoneName
|
||||
self.airbasename = AirbaseName
|
||||
self.airbase = AIRBASE:FindByName(self.airbasename)
|
||||
@@ -293,7 +294,7 @@ function EASYGCICAP:New(Alias, AirbaseName, Coalition, EWRName)
|
||||
self.capleg = 15
|
||||
self.capgrouping = 2
|
||||
self.missionrange = 100
|
||||
self.noaltert5 = 2
|
||||
self.noalert5 = 2
|
||||
self.MaxAliveMissions = 8
|
||||
self.engagerange = 50
|
||||
self.repeatsonfailure = 3
|
||||
@@ -347,9 +348,23 @@ function EASYGCICAP:SetTankerAndAWACSInvisible(Switch)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set Maximum of alive missions to stop airplanes spamming the map
|
||||
--- Count alive missions in our internal stack.
|
||||
-- @param #EASYGCICAP self
|
||||
-- @param #number Maxiumum Maxmimum number of parallel missions allowed. Count is Cap-Missions + Intercept-Missions + Alert5-Missionsm default is 6
|
||||
-- @return #number count
|
||||
function EASYGCICAP:_CountAliveAuftrags()
|
||||
local alive = 0
|
||||
for _,_auftrag in pairs(self.ListOfAuftrag) do
|
||||
local auftrag = _auftrag -- Ops.Auftrag#AUFTRAG
|
||||
if auftrag and (not (auftrag:IsCancelled() or auftrag:IsDone() or auftrag:IsOver())) then
|
||||
alive = alive + 1
|
||||
end
|
||||
end
|
||||
return alive
|
||||
end
|
||||
|
||||
--- Set Maximum of alive missions created by this instance to stop airplanes spamming the map
|
||||
-- @param #EASYGCICAP self
|
||||
-- @param #number Maxiumum Maxmimum number of parallel missions allowed. Count is Intercept-Missions + Alert5-Missions, default is 8
|
||||
-- @return #EASYGCICAP self
|
||||
function EASYGCICAP:SetMaxAliveMissions(Maxiumum)
|
||||
self:T(self.lid.."SetMaxAliveMissions")
|
||||
@@ -441,9 +456,9 @@ end
|
||||
-- @param #EASYGCICAP self
|
||||
-- @param #number Airframes defaults to 2
|
||||
-- @return #EASYGCICAP self
|
||||
function EASYGCICAP:SetDefaultNumberAlter5Standby(Airframes)
|
||||
self:T(self.lid.."SetDefaultNumberAlter5Standby")
|
||||
self.noaltert5 = math.abs(Airframes) or 2
|
||||
function EASYGCICAP:SetDefaultNumberAlert5Standby(Airframes)
|
||||
self:T(self.lid.."SetDefaultNumberAlert5Standby")
|
||||
self.noalert5 = math.abs(Airframes) or 2
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -452,7 +467,7 @@ end
|
||||
-- @param #number Range defaults to 50NM
|
||||
-- @return #EASYGCICAP self
|
||||
function EASYGCICAP:SetDefaultEngageRange(Range)
|
||||
self:T(self.lid.."SetDefaultNumberAlter5Standby")
|
||||
self:T(self.lid.."SetDefaultEngageRange")
|
||||
self.engagerange = Range or 50
|
||||
return self
|
||||
end
|
||||
@@ -584,7 +599,7 @@ function EASYGCICAP:_AddAirwing(Airbasename, Alias)
|
||||
|
||||
local TankerInvisible = self.TankerInvisible
|
||||
|
||||
function CAP_Wing:OnAfterFlightOnMission(From, Event, To, Flightgroup, Mission)
|
||||
function CAP_Wing:onbeforeFlightOnMission(From, Event, To, Flightgroup, Mission)
|
||||
local flightgroup = Flightgroup -- Ops.FlightGroup#FLIGHTGROUP
|
||||
if DespawnAfterLanding then
|
||||
flightgroup:SetDespawnAfterLanding()
|
||||
@@ -614,15 +629,15 @@ function EASYGCICAP:_AddAirwing(Airbasename, Alias)
|
||||
flightgroup:SetFuelLowRTB(true)
|
||||
Intel:AddAgent(flightgroup)
|
||||
if DespawnAfterHolding then
|
||||
function flightgroup:OnAfterHolding(From,Event,To)
|
||||
function flightgroup:onbeforeHolding(From,Event,To)
|
||||
self:Despawn(1,true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if self.noaltert5 > 0 then
|
||||
if self.noalert5 > 0 then
|
||||
local alert = AUFTRAG:NewALERT5(AUFTRAG.Type.INTERCEPT)
|
||||
alert:SetRequiredAssets(self.noaltert5)
|
||||
alert:SetRequiredAssets(self.noalert5)
|
||||
alert:SetRepeat(99)
|
||||
CAP_Wing:AddMission(alert)
|
||||
table.insert(self.ListOfAuftrag,alert)
|
||||
@@ -1176,7 +1191,7 @@ function EASYGCICAP:_AssignIntercept(Cluster)
|
||||
|
||||
local wings = self.wings
|
||||
local ctlpts = self.ManagedCP
|
||||
local MaxAliveMissions = self.MaxAliveMissions * self.capgrouping
|
||||
local MaxAliveMissions = self.MaxAliveMissions --* self.capgrouping
|
||||
local nogozoneset = self.NoGoZoneSet
|
||||
local ReadyFlightGroups = self.ReadyFlightGroups
|
||||
|
||||
@@ -1241,9 +1256,10 @@ function EASYGCICAP:_AssignIntercept(Cluster)
|
||||
-- Do we have a matching airwing?
|
||||
if targetairwing then
|
||||
local AssetCount = targetairwing:CountAssetsOnMission(MissionTypes,Cohort)
|
||||
local missioncount = self:_CountAliveAuftrags()
|
||||
-- Enough airframes on mission already?
|
||||
self:T(self.lid.." Assets on Mission "..AssetCount)
|
||||
if AssetCount <= MaxAliveMissions then
|
||||
if missioncount < MaxAliveMissions then
|
||||
local repeats = repeatsonfailure
|
||||
local InterceptAuftrag = AUFTRAG:NewINTERCEPT(contact.group)
|
||||
:SetMissionRange(150)
|
||||
@@ -1291,11 +1307,11 @@ function EASYGCICAP:_StartIntel()
|
||||
self:T(self.lid.."_StartIntel")
|
||||
-- Border GCI Detection
|
||||
local BlueAir_DetectionSetGroup = SET_GROUP:New()
|
||||
BlueAir_DetectionSetGroup:FilterPrefixes( { self.EWRName } )
|
||||
BlueAir_DetectionSetGroup:FilterPrefixes( self.EWRName )
|
||||
BlueAir_DetectionSetGroup:FilterStart()
|
||||
|
||||
-- Intel type detection
|
||||
local BlueIntel = INTEL:New(BlueAir_DetectionSetGroup,self.coalitionname, self.EWRName)
|
||||
local BlueIntel = INTEL:New(BlueAir_DetectionSetGroup,self.coalitionname, self.alias)
|
||||
BlueIntel:SetClusterAnalysis(true,false,false)
|
||||
BlueIntel:SetForgetTime(300)
|
||||
BlueIntel:SetAcceptZones(self.GoZoneSet)
|
||||
@@ -1311,7 +1327,7 @@ function EASYGCICAP:_StartIntel()
|
||||
self:_AssignIntercept(Cluster)
|
||||
end
|
||||
|
||||
function BlueIntel:OnAfterNewCluster(From,Event,To,Cluster)
|
||||
function BlueIntel:onbeforeNewCluster(From,Event,To,Cluster)
|
||||
AssignCluster(Cluster)
|
||||
end
|
||||
|
||||
@@ -1428,12 +1444,14 @@ function EASYGCICAP:onafterStatus(From,Event,To)
|
||||
local text = "GCICAP "..self.alias
|
||||
text = text.."\nWings: "..wings.."\nSquads: "..squads.."\nCapPoints: "..caps.."\nAssets on Mission: "..assets.."\nAssets in Stock: "..instock
|
||||
text = text.."\nThreats: "..threatcount
|
||||
text = text.."\nMissions: "..capmission+interceptmission
|
||||
text = text.."\nAirWing managed Missions: "..capmission+awacsmission+tankermission+reconmission
|
||||
text = text.."\n - CAP: "..capmission
|
||||
text = text.."\n - Intercept: "..interceptmission
|
||||
text = text.."\n - AWACS: "..awacsmission
|
||||
text = text.."\n - TANKER: "..tankermission
|
||||
text = text.."\n - Recon: "..reconmission
|
||||
text = text.."\nSelf managed Missions:"
|
||||
text = text.."\n - Mission Limit: "..self.MaxAliveMissions
|
||||
text = text.."\n - Alert5+Intercept "..self:_CountAliveAuftrags()
|
||||
MESSAGE:New(text,15,"GCICAP"):ToAll():ToLogIf(self.debug)
|
||||
end
|
||||
self:__Status(30)
|
||||
|
||||
@@ -217,7 +217,7 @@ FLIGHTGROUP.Players={}
|
||||
|
||||
--- FLIGHTGROUP class version.
|
||||
-- @field #string version
|
||||
FLIGHTGROUP.version="1.0.2"
|
||||
FLIGHTGROUP.version="1.0.3"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- TODO list
|
||||
@@ -1277,6 +1277,25 @@ function FLIGHTGROUP:Status()
|
||||
end
|
||||
end
|
||||
|
||||
--- check if we need to end holding
|
||||
--self:T(self.lid.."Checking if we are holding at a holding point...")
|
||||
if mission and mission.missionHoldingCoord and self.isHoldingAtHoldingPoint == true then
|
||||
self:T(self.lid.."...yes")
|
||||
if mission:IsReadyToPush() then
|
||||
--self:T(self.lid.."Ready to push -> YES")
|
||||
-- move flag to 1
|
||||
self.flaghold:Set(1)
|
||||
-- Not waiting any more.
|
||||
self.Twaiting=nil
|
||||
self.dTwait=nil
|
||||
self.isHoldingAtHoldingPoint = false
|
||||
--else
|
||||
--self:T(self.lid.."Ready to push -> NO!")
|
||||
end
|
||||
--else
|
||||
--self:T(self.lid.."...no")
|
||||
end
|
||||
|
||||
-- If mission, check if DCS task needs to be updated.
|
||||
if mission and mission.updateDCSTask then
|
||||
|
||||
@@ -3803,114 +3822,124 @@ end
|
||||
--- Initialize group parameters. Also initializes waypoints if self.waypoints is nil.
|
||||
-- @param #FLIGHTGROUP self
|
||||
-- @param #table Template Template used to init the group. Default is `self.template`.
|
||||
-- @param #number Delay Delay in seconds before group is initialized. Default `nil`, *i.e.* instantaneous.
|
||||
-- @return #FLIGHTGROUP self
|
||||
function FLIGHTGROUP:_InitGroup(Template)
|
||||
function FLIGHTGROUP:_InitGroup(Template, Delay)
|
||||
|
||||
-- First check if group was already initialized.
|
||||
if self.groupinitialized then
|
||||
self:T(self.lid.."WARNING: Group was already initialized! Will NOT do it again!")
|
||||
return
|
||||
end
|
||||
|
||||
-- Group object.
|
||||
local group=self.group --Wrapper.Group#GROUP
|
||||
|
||||
-- Helo group.
|
||||
self.isHelo=group:IsHelicopter()
|
||||
|
||||
-- Max speed in km/h.
|
||||
self.speedMax=group:GetSpeedMax()
|
||||
|
||||
-- Is group mobile?
|
||||
if self.speedMax and self.speedMax>3.6 then
|
||||
self.isMobile=true
|
||||
if Delay and Delay>0 then
|
||||
self:ScheduleOnce(Delay, FLIGHTGROUP._InitGroup, self, Template, 0)
|
||||
else
|
||||
self.isMobile=false
|
||||
self.speedMax = 0
|
||||
end
|
||||
|
||||
-- Cruise speed limit 380 kts for fixed and 110 knots for rotary wings.
|
||||
local speedCruiseLimit=self.isHelo and UTILS.KnotsToKmph(110) or UTILS.KnotsToKmph(380)
|
||||
|
||||
-- Cruise speed: 70% of max speed but within limit.
|
||||
self.speedCruise=math.min(self.speedMax*0.7, speedCruiseLimit)
|
||||
|
||||
-- Group ammo.
|
||||
self.ammo=self:GetAmmoTot()
|
||||
|
||||
-- Get template of group.
|
||||
local template=Template or self:_GetTemplate()
|
||||
|
||||
-- Is (template) group uncontrolled.
|
||||
self.isUncontrolled=template~=nil and template.uncontrolled or false
|
||||
|
||||
-- Is (template) group late activated.
|
||||
self.isLateActivated=template~=nil and template.lateActivation or false
|
||||
|
||||
if template then
|
||||
|
||||
-- Radio parameters from template. Default is set on spawn if not modified by user.
|
||||
self.radio.Freq=tonumber(template.frequency)
|
||||
self.radio.Modu=tonumber(template.modulation)
|
||||
self.radio.On=template.communication
|
||||
|
||||
-- Set callsign. Default is set on spawn if not modified by user.
|
||||
local callsign=template.units[1].callsign
|
||||
--self:I({callsign=callsign})
|
||||
if type(callsign)=="number" then -- Sometimes callsign is just "101".
|
||||
local cs=tostring(callsign)
|
||||
callsign={}
|
||||
callsign[1]=cs:sub(1,1)
|
||||
callsign[2]=cs:sub(2,2)
|
||||
callsign[3]=cs:sub(3,3)
|
||||
-- First check if group was already initialized.
|
||||
if self.groupinitialized then
|
||||
self:T(self.lid.."WARNING: Group was already initialized! Will NOT do it again!")
|
||||
return
|
||||
end
|
||||
self.callsign.NumberSquad=tonumber(callsign[1])
|
||||
self.callsign.NumberGroup=tonumber(callsign[2])
|
||||
self.callsign.NameSquad=UTILS.GetCallsignName(self.callsign.NumberSquad)
|
||||
|
||||
-- Group object.
|
||||
local group=self.group --Wrapper.Group#GROUP
|
||||
|
||||
-- Helo group.
|
||||
self.isHelo=group:IsHelicopter()
|
||||
|
||||
-- Max speed in km/h.
|
||||
self.speedMax=group:GetSpeedMax()
|
||||
|
||||
end
|
||||
|
||||
-- Set default formation.
|
||||
if self.isHelo then
|
||||
self.optionDefault.Formation=ENUMS.Formation.RotaryWing.EchelonLeft.D300
|
||||
else
|
||||
self.optionDefault.Formation=ENUMS.Formation.FixedWing.EchelonLeft.Group
|
||||
end
|
||||
|
||||
-- Default TACAN off.
|
||||
self:SetDefaultTACAN(nil, nil, nil, nil, true)
|
||||
self.tacan=UTILS.DeepCopy(self.tacanDefault)
|
||||
|
||||
-- Is this purely AI?
|
||||
self.isAI=not self:_IsHuman(group)
|
||||
|
||||
-- Create Menu.
|
||||
if not self.isAI then
|
||||
self.menu=self.menu or {}
|
||||
self.menu.atc=self.menu.atc or {} --#table
|
||||
self.menu.atc.root=self.menu.atc.root or MENU_GROUP:New(self.group, "ATC") --Core.Menu#MENU_GROUP
|
||||
self.menu.atc.help=self.menu.atc.help or MENU_GROUP:New(self.group, "Help", self.menu.atc.root) --Core.Menu#MENU_GROUP
|
||||
end
|
||||
|
||||
-- Units of the group.
|
||||
local units=self.group:GetUnits()
|
||||
-- Is group mobile?
|
||||
if self.speedMax and self.speedMax>3.6 then
|
||||
self.isMobile=true
|
||||
else
|
||||
self.isMobile=false
|
||||
self.speedMax = 0
|
||||
end
|
||||
|
||||
-- DCS group.
|
||||
local dcsgroup=Group.getByName(self.groupname)
|
||||
local size0=dcsgroup:getInitialSize()
|
||||
-- Cruise speed limit 380 kts for fixed and 110 knots for rotary wings.
|
||||
local speedCruiseLimit=self.isHelo and UTILS.KnotsToKmph(110) or UTILS.KnotsToKmph(380)
|
||||
|
||||
-- Quick check.
|
||||
if #units~=size0 then
|
||||
self:T(self.lid..string.format("ERROR: Got #units=%d but group consists of %d units!", #units, size0))
|
||||
end
|
||||
|
||||
-- Add elemets.
|
||||
for _,unit in pairs(units) do
|
||||
self:_AddElementByName(unit:GetName())
|
||||
-- Cruise speed: 70% of max speed but within limit.
|
||||
self.speedCruise=math.min(self.speedMax*0.7, speedCruiseLimit)
|
||||
|
||||
-- Group ammo.
|
||||
self.ammo=self:GetAmmoTot()
|
||||
|
||||
-- Get template of group.
|
||||
local template=Template or self:_GetTemplate()
|
||||
|
||||
-- Is (template) group uncontrolled.
|
||||
self.isUncontrolled=template~=nil and template.uncontrolled or false
|
||||
|
||||
-- Is (template) group late activated.
|
||||
self.isLateActivated=template~=nil and template.lateActivation or false
|
||||
|
||||
if template then
|
||||
|
||||
-- Radio parameters from template. Default is set on spawn if not modified by user.
|
||||
self.radio.Freq=tonumber(template.frequency)
|
||||
self.radio.Modu=tonumber(template.modulation)
|
||||
self.radio.On=template.communication
|
||||
|
||||
-- Set callsign. Default is set on spawn if not modified by user.
|
||||
local callsign=template.units[1].callsign
|
||||
--self:I({callsign=callsign})
|
||||
if type(callsign)=="number" then -- Sometimes callsign is just "101".
|
||||
local cs=tostring(callsign)
|
||||
callsign={}
|
||||
callsign[1]=cs:sub(1,1)
|
||||
callsign[2]=cs:sub(2,2)
|
||||
callsign[3]=cs:sub(3,3)
|
||||
end
|
||||
self.callsign.NumberSquad=tonumber(callsign[1])
|
||||
self.callsign.NumberGroup=tonumber(callsign[2])
|
||||
self.callsign.NameSquad=UTILS.GetCallsignName(self.callsign.NumberSquad)
|
||||
|
||||
end
|
||||
|
||||
-- Set default formation.
|
||||
if self.isHelo then
|
||||
self.optionDefault.Formation=ENUMS.Formation.RotaryWing.EchelonLeft.D300
|
||||
else
|
||||
self.optionDefault.Formation=ENUMS.Formation.FixedWing.EchelonLeft.Group
|
||||
end
|
||||
|
||||
-- Default TACAN off.
|
||||
if not self.tacanDefault then
|
||||
self:SetDefaultTACAN(nil, nil, nil, nil, true)
|
||||
end
|
||||
if not self.tacan then
|
||||
self.tacan=UTILS.DeepCopy(self.tacanDefault)
|
||||
end
|
||||
|
||||
-- Is this purely AI?
|
||||
self.isAI=not self:_IsHuman(group)
|
||||
|
||||
-- Create Menu.
|
||||
if not self.isAI then
|
||||
self.menu=self.menu or {}
|
||||
self.menu.atc=self.menu.atc or {} --#table
|
||||
self.menu.atc.root=self.menu.atc.root or MENU_GROUP:New(self.group, "ATC") --Core.Menu#MENU_GROUP
|
||||
self.menu.atc.help=self.menu.atc.help or MENU_GROUP:New(self.group, "Help", self.menu.atc.root) --Core.Menu#MENU_GROUP
|
||||
end
|
||||
|
||||
-- Units of the group.
|
||||
local units=self.group:GetUnits()
|
||||
|
||||
-- DCS group.
|
||||
local dcsgroup=Group.getByName(self.groupname)
|
||||
local size0=dcsgroup:getInitialSize()
|
||||
|
||||
-- Quick check.
|
||||
if #units~=size0 then
|
||||
self:T(self.lid..string.format("ERROR: Got #units=%d but group consists of %d units!", #units, size0))
|
||||
end
|
||||
|
||||
-- Add elemets.
|
||||
for _,unit in pairs(units) do
|
||||
self:_AddElementByName(unit:GetName())
|
||||
end
|
||||
|
||||
-- Init done.
|
||||
self.groupinitialized=true
|
||||
end
|
||||
|
||||
-- Init done.
|
||||
self.groupinitialized=true
|
||||
|
||||
return self
|
||||
end
|
||||
@@ -4179,51 +4208,6 @@ function FLIGHTGROUP:IsLandingAirbase(wp)
|
||||
return nil
|
||||
end
|
||||
|
||||
--- Initialize Mission Editor waypoints.
|
||||
-- @param #FLIGHTGROUP self
|
||||
-- @return #FLIGHTGROUP self
|
||||
function FLIGHTGROUP:InitWaypoints()
|
||||
|
||||
-- Template waypoints.
|
||||
self.waypoints0=self.group:GetTemplateRoutePoints()
|
||||
|
||||
-- Waypoints
|
||||
self.waypoints={}
|
||||
|
||||
for index,wp in pairs(self.waypoints0) do
|
||||
|
||||
local waypoint=self:_CreateWaypoint(wp)
|
||||
self:_AddWaypoint(waypoint)
|
||||
|
||||
end
|
||||
|
||||
-- Get home and destination airbases from waypoints.
|
||||
self.homebase=self.homebase or self:GetHomebaseFromWaypoints()
|
||||
self.destbase=self.destbase or self:GetDestinationFromWaypoints()
|
||||
self.currbase=self:GetHomebaseFromWaypoints()
|
||||
|
||||
-- Remove the landing waypoint. We use RTB for that. It makes adding new waypoints easier as we do not have to check if the last waypoint is the landing waypoint.
|
||||
if self.destbase and #self.waypoints>1 then
|
||||
table.remove(self.waypoints, #self.waypoints)
|
||||
else
|
||||
self.destbase=self.homebase
|
||||
end
|
||||
|
||||
-- Debug info.
|
||||
self:T(self.lid..string.format("Initializing %d waypoints. Homebase %s ==> %s Destination", #self.waypoints, self.homebase and self.homebase:GetName() or "unknown", self.destbase and self.destbase:GetName() or "uknown"))
|
||||
|
||||
-- Update route.
|
||||
if #self.waypoints>0 then
|
||||
|
||||
-- Check if only 1 wp?
|
||||
if #self.waypoints==1 then
|
||||
self:_PassedFinalWaypoint(true, "FLIGHTGROUP:InitWaypoints #self.waypoints==1")
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Add an AIR waypoint to the flight plan.
|
||||
-- @param #FLIGHTGROUP self
|
||||
|
||||
@@ -53,7 +53,7 @@ LEGION.RandomAssetScore=1
|
||||
|
||||
--- LEGION class version.
|
||||
-- @field #string version
|
||||
LEGION.version="0.5.0"
|
||||
LEGION.version="0.5.1"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- ToDo list
|
||||
@@ -445,6 +445,21 @@ function LEGION:DelCohort(Cohort)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Remove specific asset from legion.
|
||||
-- @param #LEGION self
|
||||
-- @param Functional.Warehouse#WAREHOUSE.Assetitem Asset The asset.
|
||||
-- @return #LEGION self
|
||||
function LEGION:DelAsset(Asset)
|
||||
|
||||
if Asset.cohort then
|
||||
Asset.cohort:DelAsset(Asset)
|
||||
else
|
||||
self:E(self.lid..string.format("ERROR: Asset has not cohort attached. Cannot remove it from legion!"))
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Relocate a cohort to another legion.
|
||||
-- Assets in stock are spawned and routed to the new legion.
|
||||
@@ -1643,6 +1658,9 @@ function LEGION:onafterAssetDead(From, Event, To, asset, request)
|
||||
if self.commander and self.commander.chief then
|
||||
self.commander.chief.detectionset:RemoveGroupsByName({asset.spawngroupname})
|
||||
end
|
||||
|
||||
-- Remove asset from cohort and legion.
|
||||
self:DelAsset(asset)
|
||||
|
||||
-- Remove asset from mission is done via Mission:AssetDead() call from flightgroup onafterFlightDead function
|
||||
-- Remove asset from squadron same
|
||||
@@ -1827,6 +1845,9 @@ function LEGION:_CreateFlightGroup(asset)
|
||||
-- Set home base.
|
||||
opsgroup.homebase=self.airbase
|
||||
|
||||
-- Set destination base
|
||||
opsgroup.destbase=self.airbase
|
||||
|
||||
-- Set home zone.
|
||||
opsgroup.homezone=self.spawnzone
|
||||
|
||||
@@ -2619,6 +2640,8 @@ function LEGION._CohortCan(Cohort, MissionType, Categories, Attributes, Properti
|
||||
local RangeMax = RangeMax or 0
|
||||
local InRange=(RangeMax and math.max(RangeMax, Rmax) or Rmax) >= TargetDistance
|
||||
|
||||
--env.info(string.format("Range TargetDist=%.1f Rmax=%.1f RangeMax=%.1f InRange=%s", TargetDistance, Rmax, RangeMax, tostring(InRange)))
|
||||
|
||||
return InRange
|
||||
end
|
||||
|
||||
@@ -2684,7 +2707,7 @@ function LEGION._CohortCan(Cohort, MissionType, Categories, Attributes, Properti
|
||||
else
|
||||
Cohort:T(Cohort.lid..string.format("Cohort %s cannot because of category", Cohort.name))
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
if can then
|
||||
can=CheckAttribute(Cohort)
|
||||
@@ -2740,7 +2763,7 @@ function LEGION._CohortCan(Cohort, MissionType, Categories, Attributes, Properti
|
||||
else
|
||||
Cohort:T(Cohort.lid..string.format("Cohort %s cannot because of max weight", Cohort.name))
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
@@ -2784,6 +2807,8 @@ function LEGION.RecruitCohortAssets(Cohorts, MissionTypeRecruit, MissionTypeOpt,
|
||||
|
||||
-- Check if cohort can do the mission.
|
||||
local can=LEGION._CohortCan(cohort, MissionTypeRecruit, Categories, Attributes, Properties, WeaponTypes, TargetVec2, RangeMax, RefuelSystem, CargoWeight, MaxWeight)
|
||||
|
||||
--env.info(string.format("RecruitCohortAssets %s Cohort=%s can=%s", MissionTypeRecruit, cohort:GetName(), tostring(can)))
|
||||
|
||||
-- Check OnDuty, capable, in range and refueling type (if TANKER).
|
||||
if can then
|
||||
@@ -2800,6 +2825,12 @@ function LEGION.RecruitCohortAssets(Cohorts, MissionTypeRecruit, MissionTypeOpt,
|
||||
|
||||
end
|
||||
|
||||
-- Break if no assets could be found
|
||||
if #Assets==0 then
|
||||
--env.info(string.format("LEGION.RecruitCohortAssets: No assets could be recruited for mission type %s [Nmin=%s, Nmax=%s]", MissionTypeRecruit, tostring(NreqMin), tostring(NreqMax)))
|
||||
return false, {}, {}
|
||||
end
|
||||
|
||||
-- Now we have a long list with assets.
|
||||
LEGION._OptimizeAssetSelection(Assets, MissionTypeOpt, TargetVec2, false, TotalWeight)
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@ NAVYGROUP = {
|
||||
|
||||
--- NavyGroup version.
|
||||
-- @field #string version
|
||||
NAVYGROUP.version="1.0.2"
|
||||
NAVYGROUP.version="1.0.3"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- TODO list
|
||||
@@ -395,7 +395,7 @@ function NAVYGROUP:New(group)
|
||||
self:HandleEvent(EVENTS.Birth, self.OnEventBirth)
|
||||
self:HandleEvent(EVENTS.Dead, self.OnEventDead)
|
||||
self:HandleEvent(EVENTS.RemoveUnit, self.OnEventRemoveUnit)
|
||||
self:HandleEvent(EVENTS.UnitLost, self.OnEventRemoveUnit)
|
||||
self:HandleEvent(EVENTS.UnitLost, self.OnEventRemoveUnit)
|
||||
|
||||
-- Start the status monitoring.
|
||||
self.timerStatus=TIMER:New(self.Status, self):Start(1, 30)
|
||||
@@ -775,7 +775,7 @@ end
|
||||
|
||||
--- Update status.
|
||||
-- @param #NAVYGROUP self
|
||||
function NAVYGROUP:Status(From, Event, To)
|
||||
function NAVYGROUP:Status()
|
||||
|
||||
-- FSM state.
|
||||
local fsmstate=self:GetState()
|
||||
@@ -978,6 +978,35 @@ function NAVYGROUP:Status(From, Event, To)
|
||||
|
||||
end
|
||||
|
||||
---
|
||||
-- Elements
|
||||
---
|
||||
|
||||
if self.verbose>=2 then
|
||||
local text="Elements:"
|
||||
for i,_element in pairs(self.elements) do
|
||||
local element=_element --Ops.OpsGroup#OPSGROUP.Element
|
||||
|
||||
local name=element.name
|
||||
local status=element.status
|
||||
local unit=element.unit
|
||||
local life,life0=self:GetLifePoints(element)
|
||||
|
||||
local life0=element.life0
|
||||
|
||||
-- Get ammo.
|
||||
local ammo=self:GetAmmoElement(element)
|
||||
|
||||
-- Output text for element.
|
||||
text=text..string.format("\n[%d] %s: status=%s, life=%.1f/%.1f, guns=%d, rockets=%d, bombs=%d, missiles=%d, cargo=%d/%d kg",
|
||||
i, name, status, life, life0, ammo.Guns, ammo.Rockets, ammo.Bombs, ammo.Missiles, element.weightCargo, element.weightMaxCargo)
|
||||
end
|
||||
if #self.elements==0 then
|
||||
text=text.." none!"
|
||||
end
|
||||
self:I(self.lid..text)
|
||||
end
|
||||
|
||||
---
|
||||
-- Engage Detected Targets
|
||||
---
|
||||
@@ -1041,7 +1070,7 @@ function NAVYGROUP:onafterSpawned(From, Event, To)
|
||||
|
||||
-- Debug info.
|
||||
if self.verbose>=1 then
|
||||
local text=string.format("Initialized Navy Group %s:\n", self.groupname)
|
||||
local text=string.format("Initialized Navy Group %s [GID=%d]:\n", self.groupname, self.group:GetID())
|
||||
text=text..string.format("Unit type = %s\n", self.actype)
|
||||
text=text..string.format("Speed max = %.1f Knots\n", UTILS.KmphToKnots(self.speedMax))
|
||||
text=text..string.format("Speed cruise = %.1f Knots\n", UTILS.KmphToKnots(self.speedCruise))
|
||||
@@ -1269,7 +1298,7 @@ function NAVYGROUP:onafterUpdateRoute(From, Event, To, n, N, Speed, Depth)
|
||||
if self.verbose>=10 then
|
||||
for i=1,#waypoints do
|
||||
local wp=waypoints[i] --Ops.OpsGroup#OPSGROUP.Waypoint
|
||||
local text=string.format("%s Waypoint [%d] UID=%d speed=%d", self.groupname, i-1, wp.uid or -1, wp.speed)
|
||||
local text=string.format("%s Waypoint [%d] UID=%d speed=%d m/s", self.groupname, i-1, wp.uid or -1, wp.speed)
|
||||
self:I(self.lid..text)
|
||||
COORDINATE:NewFromWaypoint(wp):MarkToAll(text)
|
||||
end
|
||||
@@ -1841,81 +1870,96 @@ end
|
||||
--- Initialize group parameters. Also initializes waypoints if self.waypoints is nil.
|
||||
-- @param #NAVYGROUP self
|
||||
-- @param #table Template Template used to init the group. Default is `self.template`.
|
||||
-- @param #number Delay Delay in seconds before group is initialized. Default `nil`, *i.e.* instantaneous.
|
||||
-- @return #NAVYGROUP self
|
||||
function NAVYGROUP:_InitGroup(Template)
|
||||
function NAVYGROUP:_InitGroup(Template, Delay)
|
||||
|
||||
-- First check if group was already initialized.
|
||||
if self.groupinitialized then
|
||||
self:T(self.lid.."WARNING: Group was already initialized! Will NOT do it again!")
|
||||
return
|
||||
end
|
||||
|
||||
-- Get template of group.
|
||||
local template=Template or self:_GetTemplate()
|
||||
|
||||
-- Ships are always AI.
|
||||
self.isAI=true
|
||||
|
||||
-- Is (template) group late activated.
|
||||
self.isLateActivated=template.lateActivation
|
||||
|
||||
-- Naval groups cannot be uncontrolled.
|
||||
self.isUncontrolled=false
|
||||
|
||||
-- Max speed in km/h.
|
||||
self.speedMax=self.group:GetSpeedMax()
|
||||
|
||||
-- Is group mobile?
|
||||
if self.speedMax and self.speedMax>3.6 then
|
||||
self.isMobile=true
|
||||
if Delay and Delay>0 then
|
||||
-- Delayed call
|
||||
self:ScheduleOnce(Delay, NAVYGROUP._InitGroup, self, Template, 0)
|
||||
else
|
||||
self.isMobile=false
|
||||
self.speedMax = 0
|
||||
end
|
||||
|
||||
-- Cruise speed: 70% of max speed.
|
||||
self.speedCruise=self.speedMax*0.7
|
||||
-- First check if group was already initialized.
|
||||
if self.groupinitialized then
|
||||
self:T(self.lid.."WARNING: Group was already initialized! Will NOT do it again!")
|
||||
return
|
||||
end
|
||||
|
||||
-- Group ammo.
|
||||
self.ammo=self:GetAmmoTot()
|
||||
-- Get template of group.
|
||||
local template=Template or self:_GetTemplate()
|
||||
|
||||
-- Radio parameters from template. Default is set on spawn if not modified by the user.
|
||||
self.radio.On=true -- Radio is always on for ships.
|
||||
self.radio.Freq=tonumber(template.units[1].frequency)/1000000
|
||||
self.radio.Modu=tonumber(template.units[1].modulation)
|
||||
-- Ships are always AI.
|
||||
self.isAI=true
|
||||
|
||||
-- Is (template) group late activated.
|
||||
self.isLateActivated=template.lateActivation
|
||||
|
||||
-- Naval groups cannot be uncontrolled.
|
||||
self.isUncontrolled=false
|
||||
|
||||
-- Max speed in km/h.
|
||||
self.speedMax=self.group:GetSpeedMax()
|
||||
|
||||
-- Is group mobile?
|
||||
if self.speedMax and self.speedMax>3.6 then
|
||||
self.isMobile=true
|
||||
else
|
||||
self.isMobile=false
|
||||
self.speedMax = 0
|
||||
end
|
||||
|
||||
-- Cruise speed: 70% of max speed.
|
||||
self.speedCruise=self.speedMax*0.7
|
||||
|
||||
-- Group ammo.
|
||||
self.ammo=self:GetAmmoTot()
|
||||
|
||||
-- Radio parameters from template. Default is set on spawn if not modified by the user.
|
||||
self.radio.On=true -- Radio is always on for ships.
|
||||
self.radio.Freq=tonumber(template.units[1].frequency)/1000000
|
||||
self.radio.Modu=tonumber(template.units[1].modulation)
|
||||
|
||||
-- Set default formation. No really applicable for ships.
|
||||
self.optionDefault.Formation="Off Road"
|
||||
self.option.Formation=self.optionDefault.Formation
|
||||
|
||||
-- Set default formation. No really applicable for ships.
|
||||
self.optionDefault.Formation="Off Road"
|
||||
self.option.Formation=self.optionDefault.Formation
|
||||
|
||||
-- Default TACAN off.
|
||||
self:SetDefaultTACAN(nil, nil, nil, nil, true)
|
||||
self.tacan=UTILS.DeepCopy(self.tacanDefault)
|
||||
-- Default TACAN off (we check if something is set already to keep those values in case of respawn)
|
||||
if not self.tacanDefault then
|
||||
self:SetDefaultTACAN(nil, nil, nil, nil, true)
|
||||
end
|
||||
if not self.tacan then
|
||||
self.tacan=UTILS.DeepCopy(self.tacanDefault)
|
||||
end
|
||||
|
||||
-- Default ICLS off.
|
||||
if not self.iclsDefault then
|
||||
self:SetDefaultICLS(nil, nil, nil, true)
|
||||
end
|
||||
if not self.icls then
|
||||
self.icls=UTILS.DeepCopy(self.iclsDefault)
|
||||
end
|
||||
|
||||
-- Get all units of the group.
|
||||
local units=self.group:GetUnits()
|
||||
|
||||
-- Default ICLS off.
|
||||
self:SetDefaultICLS(nil, nil, nil, true)
|
||||
self.icls=UTILS.DeepCopy(self.iclsDefault)
|
||||
|
||||
-- Get all units of the group.
|
||||
local units=self.group:GetUnits()
|
||||
|
||||
-- DCS group.
|
||||
local dcsgroup=Group.getByName(self.groupname)
|
||||
local size0=dcsgroup:getInitialSize()
|
||||
|
||||
-- Quick check.
|
||||
if #units~=size0 then
|
||||
self:E(self.lid..string.format("ERROR: Got #units=%d but group consists of %d units!", #units, size0))
|
||||
-- DCS group.
|
||||
local dcsgroup=Group.getByName(self.groupname)
|
||||
local size0=dcsgroup:getInitialSize()
|
||||
|
||||
-- Quick check.
|
||||
if #units~=size0 then
|
||||
self:E(self.lid..string.format("ERROR: Got #units=%d but group consists of %d units!", #units, size0))
|
||||
end
|
||||
|
||||
-- Add elemets.
|
||||
for _,unit in pairs(units) do
|
||||
self:_AddElementByName(unit:GetName())
|
||||
end
|
||||
|
||||
-- Init done.
|
||||
self.groupinitialized=true
|
||||
end
|
||||
|
||||
-- Add elemets.
|
||||
for _,unit in pairs(units) do
|
||||
self:_AddElementByName(unit:GetName())
|
||||
end
|
||||
|
||||
-- Init done.
|
||||
self.groupinitialized=true
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
@@ -512,7 +512,7 @@ OPSGROUP.CargoStatus={
|
||||
|
||||
--- OpsGroup version.
|
||||
-- @field #string version
|
||||
OPSGROUP.version="1.0.1"
|
||||
OPSGROUP.version="1.0.4"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- TODO list
|
||||
@@ -1041,7 +1041,7 @@ function OPSGROUP:SetReturnToLegion(Switch)
|
||||
else
|
||||
self.legionReturn=true
|
||||
end
|
||||
self:T(self.lid..string.format("Setting ReturnToLetion=%s", tostring(self.legionReturn)))
|
||||
self:T(self.lid..string.format("Setting ReturnToLegion=%s", tostring(self.legionReturn)))
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -1333,8 +1333,9 @@ end
|
||||
-- @param Core.Point#COORDINATE TargetCoord Coordinate of the target.
|
||||
-- @param #number WeaponBitType Weapon type.
|
||||
-- @param Core.Point#COORDINATE RefCoord Reference coordinate.
|
||||
-- @param #table SurfaceTypes Valid surfaces types of the coordinate. Default any (nil).
|
||||
-- @return Core.Point#COORDINATE Coordinate in weapon range
|
||||
function OPSGROUP:GetCoordinateInRange(TargetCoord, WeaponBitType, RefCoord)
|
||||
function OPSGROUP:GetCoordinateInRange(TargetCoord, WeaponBitType, RefCoord, SurfaceTypes)
|
||||
|
||||
local coordInRange=nil --Core.Point#COORDINATE
|
||||
|
||||
@@ -1343,35 +1344,58 @@ function OPSGROUP:GetCoordinateInRange(TargetCoord, WeaponBitType, RefCoord)
|
||||
-- Get weapon range.
|
||||
local weapondata=self:GetWeaponData(WeaponBitType)
|
||||
|
||||
-- Heading intervals to search for a possible new coordinate in range.
|
||||
local dh={0, -5, 5, -10, 10, -15, 15, -20, 20, -25, 25, -30, 30, -35, 35, -40, 40, -45, 45, -50, 50, -55, 55, -60, 60, -65, 65, -70, 70, -75, 75, -80, 80}
|
||||
|
||||
-- Function that checks if the given surface type is valid
|
||||
local function _checkSurface(point)
|
||||
if SurfaceTypes then
|
||||
local stype=point:GetSurfaceType()
|
||||
for _,sf in pairs(SurfaceTypes) do
|
||||
if sf==stype then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
else
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
if weapondata then
|
||||
|
||||
-- Heading to target.
|
||||
local heading=RefCoord:HeadingTo(TargetCoord)
|
||||
local heading=TargetCoord:HeadingTo(RefCoord)
|
||||
|
||||
-- Distance to target.
|
||||
local dist=RefCoord:Get2DDistance(TargetCoord)
|
||||
|
||||
local range=nil
|
||||
if dist>weapondata.RangeMax then
|
||||
range=weapondata.RangeMax
|
||||
self:T(self.lid..string.format("Out of max range = %.1f km by %.1f km for weapon %s", weapondata.RangeMax/1000, (weapondata.RangeMax-dist)/1000, tostring(WeaponBitType)))
|
||||
elseif dist<weapondata.RangeMin then
|
||||
range=weapondata.RangeMin
|
||||
self:T(self.lid..string.format("Out of min range = %.1f km by %.1f km for weapon %s", weapondata.RangeMin/1000, (weapondata.RangeMin-dist)/1000, tostring(WeaponBitType)))
|
||||
end
|
||||
|
||||
-- Check if we are within range.
|
||||
if dist>weapondata.RangeMax then
|
||||
|
||||
local d=(dist-weapondata.RangeMax)*1.05
|
||||
|
||||
-- New waypoint coord.
|
||||
coordInRange=RefCoord:Translate(d, heading)
|
||||
|
||||
-- Debug info.
|
||||
self:T(self.lid..string.format("Out of max range = %.1f km for weapon %s", weapondata.RangeMax/1000, tostring(WeaponBitType)))
|
||||
elseif dist<weapondata.RangeMin then
|
||||
|
||||
local d=(dist-weapondata.RangeMin)*1.05
|
||||
|
||||
-- New waypoint coord.
|
||||
coordInRange=RefCoord:Translate(d, heading)
|
||||
|
||||
-- Debug info.
|
||||
self:T(self.lid..string.format("Out of min range = %.1f km for weapon %s", weapondata.RangeMax/1000, tostring(WeaponBitType)))
|
||||
else
|
||||
if range then
|
||||
|
||||
for _,delta in pairs(dh) do
|
||||
|
||||
local h=heading+delta
|
||||
|
||||
-- New waypoint coord.
|
||||
coordInRange=TargetCoord:Translate(range, h)
|
||||
|
||||
if _checkSurface(coordInRange) then
|
||||
break
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
else
|
||||
-- Debug info.
|
||||
self:T(self.lid..string.format("Already in range for weapon %s", tostring(WeaponBitType)))
|
||||
end
|
||||
@@ -1450,11 +1474,14 @@ end
|
||||
-- @param #number RangeMin Minimum range in nautical miles. Default 0 NM.
|
||||
-- @param #number RangeMax Maximum range in nautical miles. Default 10 NM.
|
||||
-- @param #number BitType Bit mask of weapon type for which the given min/max ranges apply. Default is `ENUMS.WeaponFlag.Auto`, i.e. for all weapon types.
|
||||
-- @param #function ConversionToMeters Function that converts input units of ranges to meters. Defaul `UTILS.NMToMeters`.
|
||||
-- @return #OPSGROUP self
|
||||
function OPSGROUP:AddWeaponRange(RangeMin, RangeMax, BitType)
|
||||
function OPSGROUP:AddWeaponRange(RangeMin, RangeMax, BitType, ConversionToMeters)
|
||||
|
||||
RangeMin=UTILS.NMToMeters(RangeMin or 0)
|
||||
RangeMax=UTILS.NMToMeters(RangeMax or 10)
|
||||
ConversionToMeters=ConversionToMeters or UTILS.NMToMeters
|
||||
|
||||
RangeMin=ConversionToMeters(RangeMin or 0)
|
||||
RangeMax=ConversionToMeters(RangeMax or 10)
|
||||
|
||||
local weapon={} --#OPSGROUP.WeaponData
|
||||
|
||||
@@ -4169,7 +4196,7 @@ function OPSGROUP:onbeforeTaskExecute(From, Event, To, Task)
|
||||
if self:IsWaiting() then
|
||||
-- Group is already waiting
|
||||
else
|
||||
-- Wait indefinately.
|
||||
-- Wait indefinitely.
|
||||
local alt=Mission.missionAltitude and UTILS.MetersToFeet(Mission.missionAltitude) or nil
|
||||
self:Wait(nil, alt)
|
||||
end
|
||||
@@ -4480,7 +4507,7 @@ function OPSGROUP:_UpdateTask(Task, Mission)
|
||||
-- Set speed. Default max.
|
||||
local speed=self.speedMax and UTILS.KmphToKnots(self.speedMax) or nil
|
||||
if Task.dcstask.params.speed then
|
||||
speed=Task.dcstask.params.speed
|
||||
speed=UTILS.MpsToKnots(Task.dcstask.params.speed)
|
||||
end
|
||||
|
||||
if target then
|
||||
@@ -6073,17 +6100,16 @@ function OPSGROUP:RouteToMission(mission, delay)
|
||||
-- Target Coord.
|
||||
local targetcoord=mission:GetTargetCoordinate()
|
||||
|
||||
|
||||
-- In range already?
|
||||
local inRange=self:InWeaponRange(targetcoord, mission.engageWeaponType)
|
||||
local inRange=self:InWeaponRange(targetcoord, mission.engageWeaponType, waypointcoord)
|
||||
|
||||
if inRange then
|
||||
|
||||
waypointcoord=self:GetCoordinate(true)
|
||||
--waypointcoord=self:GetCoordinate(true)
|
||||
|
||||
else
|
||||
|
||||
local coordInRange=self:GetCoordinateInRange(targetcoord, mission.engageWeaponType, waypointcoord)
|
||||
local coordInRange=self:GetCoordinateInRange(targetcoord, mission.engageWeaponType, waypointcoord, surfacetypes)
|
||||
|
||||
if coordInRange then
|
||||
|
||||
@@ -6118,7 +6144,32 @@ function OPSGROUP:RouteToMission(mission, delay)
|
||||
-- Add mission execution (ingress) waypoint.
|
||||
local waypoint=nil --#OPSGROUP.Waypoint
|
||||
if self:IsFlightgroup() then
|
||||
|
||||
|
||||
|
||||
local ingresscoord = mission:GetMissionIngressCoord()
|
||||
local holdingcoord = mission:GetMissionHoldingCoord()
|
||||
|
||||
if holdingcoord then
|
||||
waypoint=FLIGHTGROUP.AddWaypoint(self, holdingcoord, mission.missionHoldingCoordSpeed or SpeedToMission, uid, UTILS.MetersToFeet(mission.missionHoldingCoordAlt or self.altitudeCruise), false)
|
||||
uid=waypoint.uid
|
||||
-- Orbit until flaghold=1 (true) but max 5 min
|
||||
self.flaghold:Set(0)
|
||||
local TaskOrbit = self.group:TaskOrbit(holdingcoord, mission.missionHoldingCoordAlt, UTILS.KnotsToMps(mission.missionHoldingCoordSpeed or SpeedToMission))
|
||||
local TaskStop = self.group:TaskCondition(nil, self.flaghold.UserFlagName, 1, nil, mission.missionHoldingDuration or 900)
|
||||
local TaskCntr = self.group:TaskControlled(TaskOrbit, TaskStop)
|
||||
local TaskOver = self.group:TaskFunction("FLIGHTGROUP._FinishedWaiting", self)
|
||||
local DCSTasks=self.group:TaskCombo({TaskCntr, TaskOver})
|
||||
-- Add waypoint task. UpdateRoute is called inside.
|
||||
local waypointtask=self:AddTaskWaypoint(DCSTasks, waypoint, "Holding")
|
||||
waypointtask.ismission=false
|
||||
self.isHoldingAtHoldingPoint = true
|
||||
end
|
||||
|
||||
if ingresscoord then
|
||||
waypoint=FLIGHTGROUP.AddWaypoint(self, ingresscoord, mission.missionIngressCoordSpeed or SpeedToMission, uid, UTILS.MetersToFeet(mission.missionIngressCoordAlt or self.altitudeCruise), false)
|
||||
uid=waypoint.uid
|
||||
end
|
||||
|
||||
waypoint=FLIGHTGROUP.AddWaypoint(self, waypointcoord, SpeedToMission, uid, UTILS.MetersToFeet(mission.missionAltitude or self.altitudeCruise), false)
|
||||
|
||||
elseif self:IsArmygroup() then
|
||||
@@ -6157,7 +6208,7 @@ function OPSGROUP:RouteToMission(mission, delay)
|
||||
if egresscoord then
|
||||
local Ewaypoint=nil --#OPSGROUP.Waypoint
|
||||
if self:IsFlightgroup() then
|
||||
Ewaypoint=FLIGHTGROUP.AddWaypoint(self, egresscoord, SpeedToMission, waypoint.uid, UTILS.MetersToFeet(mission.missionAltitude or self.altitudeCruise), false)
|
||||
Ewaypoint=FLIGHTGROUP.AddWaypoint(self, egresscoord, mission.missionEgressCoordSpeed or SpeedToMission, waypoint.uid, UTILS.MetersToFeet(mission.missionEgressCoordAlt or self.altitudeCruise), false)
|
||||
elseif self:IsArmygroup() then
|
||||
Ewaypoint=ARMYGROUP.AddWaypoint(self, egresscoord, SpeedToMission, waypoint.uid, mission.optionFormation, false)
|
||||
elseif self:IsNavygroup() then
|
||||
@@ -7675,6 +7726,7 @@ function OPSGROUP:Teleport(Coordinate, Delay, NoPauseMission)
|
||||
unit.heading=math.rad(heading)
|
||||
unit.psi=-unit.heading
|
||||
else
|
||||
-- Remove unit from spawn template because it is already dead
|
||||
table.remove(units, i)
|
||||
end
|
||||
end
|
||||
@@ -7762,25 +7814,41 @@ function OPSGROUP:_Respawn(Delay, Template, Reset)
|
||||
-- Despawn old group. Dont trigger any remove unit event since this is a respawn.
|
||||
self:Despawn(0, true)
|
||||
|
||||
else
|
||||
|
||||
---
|
||||
-- Group is NOT ALIVE
|
||||
---
|
||||
|
||||
-- Ensure elements in utero.
|
||||
for _,_element in pairs(self.elements) do
|
||||
local element=_element --#OPSGROUP.Element
|
||||
self:ElementInUtero(element)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-- Ensure elements in utero.
|
||||
for _,_element in pairs(self.elements) do
|
||||
local element=_element --#OPSGROUP.Element
|
||||
if element and element.status~=OPSGROUP.ElementStatus.DEAD then
|
||||
self:ElementInUtero(element)
|
||||
end
|
||||
end
|
||||
|
||||
-- Spawn with a little delay (especially Navy groups caused problems if they were instantly respawned)
|
||||
self:_Spawn(0.01, Template)
|
||||
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Spawn group from a given template.
|
||||
-- @param #OPSGROUP self
|
||||
-- @param #number Delay Delay in seconds before respawn happens. Default 0.
|
||||
-- @param DCS#Template Template (optional) The template of the Group retrieved with GROUP:GetTemplate(). If the template is not provided, the template will be retrieved of the group itself.
|
||||
-- @return #OPSGROUP self
|
||||
function OPSGROUP:_Spawn(Delay, Template)
|
||||
if Delay and Delay>0 then
|
||||
self:ScheduleOnce(Delay, OPSGROUP._Spawn, self, 0, Template)
|
||||
else
|
||||
-- Debug output.
|
||||
self:T({Template=Template})
|
||||
self:T2({Template=Template})
|
||||
|
||||
-- Spawn new group.
|
||||
self.group=_DATABASE:Spawn(Template)
|
||||
--local countryID=self.group:GetCountry()
|
||||
--local categoryID=self.group:GetCategory()
|
||||
--local dcsgroup=coalition.addGroup(countryID, categoryID, Template)
|
||||
|
||||
-- Set DCS group and controller.
|
||||
self.dcsgroup=self:GetDCSGroup()
|
||||
@@ -7794,7 +7862,6 @@ function OPSGROUP:_Respawn(Delay, Template, Reset)
|
||||
self.isDead=false
|
||||
self.isDestroyed=false
|
||||
|
||||
|
||||
self.groupinitialized=false
|
||||
self.wpcounter=1
|
||||
self.currentwp=1
|
||||
@@ -7802,15 +7869,12 @@ function OPSGROUP:_Respawn(Delay, Template, Reset)
|
||||
-- Init waypoints.
|
||||
self:_InitWaypoints()
|
||||
|
||||
-- Init Group.
|
||||
self:_InitGroup(Template)
|
||||
-- Init Group. This call is delayed because NAVY groups did not like to be initialized just yet (group did not contain any units).
|
||||
self:_InitGroup(Template, 0.001)
|
||||
|
||||
-- Reset events.
|
||||
--self:ResetEvents()
|
||||
|
||||
--self:ResetEvents()
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- On after "InUtero" event.
|
||||
@@ -7830,24 +7894,6 @@ end
|
||||
-- @param #string To To state.
|
||||
function OPSGROUP:onafterDamaged(From, Event, To)
|
||||
self:T(self.lid..string.format("Group damaged at t=%.3f", timer.getTime()))
|
||||
|
||||
--[[
|
||||
local lifemin=nil
|
||||
for _,_element in pairs(self.elements) do
|
||||
local element=_element --#OPSGROUP.Element
|
||||
if element.status~=OPSGROUP.ElementStatus.DEAD and element.status~=OPSGROUP.ElementStatus.INUTERO then
|
||||
local life, life0=self:GetLifePoints(element)
|
||||
if lifemin==nil or life<lifemin then
|
||||
lifemin=life
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if lifemin and lifemin/self.life<0.5 then
|
||||
self:RTB()
|
||||
end
|
||||
]]
|
||||
|
||||
end
|
||||
|
||||
--- On after "Destroyed" event.
|
||||
@@ -11444,10 +11490,10 @@ function OPSGROUP:_InitWaypoints(WpIndexMin, WpIndexMax)
|
||||
if self:IsFlightgroup() then
|
||||
|
||||
-- Get home and destination airbases from waypoints.
|
||||
self.homebase=self.homebase or self:GetHomebaseFromWaypoints()
|
||||
self.homebase=self.homebase or self:GetHomebaseFromWaypoints() -- GetHomebaseFromWaypoints() returns carriers or destroyers if no airbase is found.
|
||||
local destbase=self:GetDestinationFromWaypoints()
|
||||
self.destbase=self.destbase or destbase
|
||||
self.currbase=self:GetHomebaseFromWaypoints()
|
||||
self.currbase=self:GetHomebaseFromWaypoints() -- Skipped To fix RTB issue
|
||||
|
||||
--env.info("FF home base "..(self.homebase and self.homebase:GetName() or "unknown"))
|
||||
--env.info("FF dest base "..(self.destbase and self.destbase:GetName() or "unknown"))
|
||||
@@ -11458,7 +11504,7 @@ function OPSGROUP:_InitWaypoints(WpIndexMin, WpIndexMax)
|
||||
end
|
||||
|
||||
-- Set destination to homebase.
|
||||
if self.destbase==nil then
|
||||
if self.destbase==nil then -- Skipped To fix RTB issue
|
||||
self.destbase=self.homebase
|
||||
end
|
||||
|
||||
|
||||
@@ -490,6 +490,19 @@ function OPSZONE:SetDrawZone(Switch)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set if zone is drawn on the F10 map for the owner coalition only.
|
||||
-- @param #OPSZONE self
|
||||
-- @param #boolean Switch If `false` or `nil`, draw zone for all coalitions. If `true`, zone is drawn for the owning coalition only if drawZone is true.
|
||||
-- @return #OPSZONE self
|
||||
function OPSZONE:SetDrawZoneForCoalition(Switch)
|
||||
if Switch==true then
|
||||
self.drawZoneForCoalition=true
|
||||
else
|
||||
self.drawZoneForCoalition=false
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set if a marker on the F10 map shows the current zone status.
|
||||
-- @param #OPSZONE self
|
||||
-- @param #boolean Switch If `true`, zone is marked. If `false` or `nil`, zone is not marked.
|
||||
@@ -710,6 +723,7 @@ end
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @return #OPSZONE self
|
||||
function OPSZONE:onafterStart(From, Event, To)
|
||||
|
||||
-- Info.
|
||||
@@ -726,6 +740,7 @@ function OPSZONE:onafterStart(From, Event, To)
|
||||
self:HandleEvent(EVENTS.BaseCaptured)
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Stop OPSZONE FSM.
|
||||
@@ -837,8 +852,12 @@ function OPSZONE:onafterCaptured(From, Event, To, NewOwnerCoalition)
|
||||
self.zone:UndrawZone()
|
||||
|
||||
local color=self:_GetZoneColor()
|
||||
|
||||
self.zone:DrawZone(nil, color, 1.0, color, 0.5)
|
||||
|
||||
local coalition = nil
|
||||
if self.drawZoneForCoalition then
|
||||
coalition = self.ownerCurrent
|
||||
end
|
||||
self.zone:DrawZone(coalition, color, 1.0, color, 0.5)
|
||||
end
|
||||
|
||||
for _,_chief in pairs(self.chiefs) do
|
||||
@@ -913,8 +932,12 @@ function OPSZONE:onenterGuarded(From, Event, To)
|
||||
self.zone:UndrawZone()
|
||||
|
||||
local color=self:_GetZoneColor()
|
||||
|
||||
self.zone:DrawZone(nil, color, 1.0, color, 0.5)
|
||||
|
||||
local coalition = nil
|
||||
if self.drawZoneForCoalition then
|
||||
coalition = self.ownerCurrent
|
||||
end
|
||||
self.zone:DrawZone(coalition, color, 1.0, color, 0.5)
|
||||
end
|
||||
|
||||
end
|
||||
@@ -954,9 +977,13 @@ function OPSZONE:onenterAttacked(From, Event, To, AttackerCoalition)
|
||||
|
||||
-- Color.
|
||||
local color={1, 204/255, 204/255}
|
||||
|
||||
|
||||
local coalition = nil
|
||||
if self.drawZoneForCoalition then
|
||||
coalition = self.ownerCurrent
|
||||
end
|
||||
-- Draw zone.
|
||||
self.zone:DrawZone(nil, color, 1.0, color, 0.5)
|
||||
self.zone:DrawZone(coalition, color, 1.0, color, 0.5)
|
||||
end
|
||||
|
||||
self:_CleanMissionTable()
|
||||
@@ -987,8 +1014,12 @@ function OPSZONE:onenterEmpty(From, Event, To)
|
||||
self.zone:UndrawZone()
|
||||
|
||||
local color=self:_GetZoneColor()
|
||||
|
||||
self.zone:DrawZone(nil, color, 1.0, color, 0.2)
|
||||
|
||||
local coalition = nil
|
||||
if self.drawZoneForCoalition then
|
||||
coalition = self.ownerCurrent
|
||||
end
|
||||
self.zone:DrawZone(coalition, color, 1.0, color, 0.2)
|
||||
end
|
||||
|
||||
end
|
||||
@@ -1285,7 +1316,7 @@ function OPSZONE:EvaluateZone()
|
||||
|
||||
if Nblu>0 then
|
||||
|
||||
if not self:IsAttacked() and self.Tnut>=self.threatlevelCapture then
|
||||
if not self:IsAttacked() and self.Tblu>=self.threatlevelCapture then
|
||||
self:Attacked(coalition.side.BLUE)
|
||||
end
|
||||
|
||||
@@ -1337,7 +1368,7 @@ function OPSZONE:EvaluateZone()
|
||||
|
||||
if Nred>0 then
|
||||
|
||||
if not self:IsAttacked() and self.Tnut>=self.threatlevelCapture then
|
||||
if not self:IsAttacked() and self.Tred>=self.threatlevelCapture then
|
||||
-- Red is attacking blue zone.
|
||||
self:Attacked(coalition.side.RED)
|
||||
end
|
||||
|
||||
@@ -80,6 +80,7 @@
|
||||
-- @field #boolean smokeownposition
|
||||
-- @field #table SmokeOwn
|
||||
-- @field #boolean smokeaveragetargetpos
|
||||
-- @field #boolean reporttostringbullsonly
|
||||
-- @extends Core.Fsm#FSM
|
||||
|
||||
---
|
||||
@@ -105,7 +106,7 @@ PLAYERRECCE = {
|
||||
ClassName = "PLAYERRECCE",
|
||||
verbose = true,
|
||||
lid = nil,
|
||||
version = "0.1.23",
|
||||
version = "0.1.26",
|
||||
ViewZone = {},
|
||||
ViewZoneVisual = {},
|
||||
ViewZoneLaser = {},
|
||||
@@ -133,7 +134,8 @@ PLAYERRECCE = {
|
||||
TargetCache = nil,
|
||||
smokeownposition = false,
|
||||
SmokeOwn = {},
|
||||
smokeaveragetargetpos = false,
|
||||
smokeaveragetargetpos = true,
|
||||
reporttostringbullsonly = true,
|
||||
}
|
||||
|
||||
---
|
||||
@@ -152,7 +154,8 @@ PLAYERRECCE.LaserRelativePos = {
|
||||
["SA342Minigun"] = { x = 1.7, y = 1.2, z = 0 },
|
||||
["SA342L"] = { x = 1.7, y = 1.2, z = 0 },
|
||||
["Ka-50"] = { x = 6.1, y = -0.85 , z = 0 },
|
||||
["Ka-50_3"] = { x = 6.1, y = -0.85 , z = 0 }
|
||||
["Ka-50_3"] = { x = 6.1, y = -0.85 , z = 0 },
|
||||
["OH58D"] = {x = 0, y = 2.8, z = 0},
|
||||
}
|
||||
|
||||
---
|
||||
@@ -164,7 +167,8 @@ PLAYERRECCE.MaxViewDistance = {
|
||||
["SA342Minigun"] = 8000,
|
||||
["SA342L"] = 8000,
|
||||
["Ka-50"] = 8000,
|
||||
["Ka-50_3"] = 8000,
|
||||
["Ka-50_3"] = 8000,
|
||||
["OH58D"] = 8000,
|
||||
}
|
||||
|
||||
---
|
||||
@@ -176,7 +180,8 @@ PLAYERRECCE.Cameraheight = {
|
||||
["SA342Minigun"] = 2.85,
|
||||
["SA342L"] = 2.85,
|
||||
["Ka-50"] = 0.5,
|
||||
["Ka-50_3"] = 0.5,
|
||||
["Ka-50_3"] = 0.5,
|
||||
["OH58D"] = 4.25,
|
||||
}
|
||||
|
||||
---
|
||||
@@ -188,7 +193,8 @@ PLAYERRECCE.CanLase = {
|
||||
["SA342Minigun"] = false, -- no optics
|
||||
["SA342L"] = true,
|
||||
["Ka-50"] = true,
|
||||
["Ka-50_3"] = true,
|
||||
["Ka-50_3"] = true,
|
||||
["OH58D"] = false, -- has onboard and useable laser
|
||||
}
|
||||
|
||||
---
|
||||
@@ -236,6 +242,8 @@ function PLAYERRECCE:New(Name, Coalition, PlayerSet)
|
||||
|
||||
self.minthreatlevel = 0
|
||||
|
||||
self.reporttostringbullsonly = true
|
||||
|
||||
self.TForget = 600
|
||||
self.TargetCache = FIFO:New()
|
||||
|
||||
@@ -542,7 +550,7 @@ function PLAYERRECCE:SetAttackSet(AttackSet)
|
||||
return self
|
||||
end
|
||||
|
||||
---[Internal] Check Gazelle camera in on
|
||||
---[Internal] Check Helicopter camera in on
|
||||
-- @param #PLAYERRECCE self
|
||||
-- @param Wrapper.Client#CLIENT client
|
||||
-- @param #string playername
|
||||
@@ -558,6 +566,12 @@ function PLAYERRECCE:_CameraOn(client,playername)
|
||||
if vivihorizontal < -0.7 or vivihorizontal > 0.7 then
|
||||
camera = false
|
||||
end
|
||||
elseif string.find(typename,"OH58") then
|
||||
local dcsunit = Unit.getByName(client:GetName())
|
||||
local vivihorizontal = dcsunit:getDrawArgumentValue(528) or 0 -- Kiow
|
||||
if vivihorizontal < -0.527 or vivihorizontal > 0.527 then
|
||||
camera = false
|
||||
end
|
||||
elseif string.find(typename,"Ka-50") then
|
||||
camera = true
|
||||
end
|
||||
@@ -565,6 +579,52 @@ function PLAYERRECCE:_CameraOn(client,playername)
|
||||
return camera
|
||||
end
|
||||
|
||||
--- [Internal] Get the view parameters from a Kiowa MMS camera
|
||||
-- @param #PLAYERRECCE self
|
||||
-- @param Wrapper.Unit#UNIT Kiowa
|
||||
-- @return #number cameraheading in degrees.
|
||||
-- @return #number cameranodding in degrees.
|
||||
-- @return #number maxview in meters.
|
||||
-- @return #boolean cameraison If true, camera is on, else off.
|
||||
function PLAYERRECCE:_GetKiowaMMSSight(Kiowa)
|
||||
self:T(self.lid.."_GetKiowaMMSSight")
|
||||
local unit = Kiowa -- Wrapper.Unit#UNIT
|
||||
if unit and unit:IsAlive() then
|
||||
local dcsunit = Unit.getByName(Kiowa:GetName())
|
||||
--[[
|
||||
shagrat — 01/01/2025 23:13
|
||||
Found the necessary ARGS for the Kiowa MMS angle and rotation:
|
||||
Arg 527 vertical movement
|
||||
0 = neutral
|
||||
-1.0 = max depression (30° max depression angle)
|
||||
+1.0 = max elevation angle (30° max elevation angle)
|
||||
|
||||
Arg 528 horizontal movement
|
||||
0 = forward (0 degr)
|
||||
-0.25 = 90° left
|
||||
-0.5 = rear (180°) left (max 190° = -0.527
|
||||
+0.25 = 90° right
|
||||
+0.5 = 180° right (max 190° = 0.527)
|
||||
--]]
|
||||
local mmshorizontal = dcsunit:getDrawArgumentValue(528) or 0
|
||||
local mmsvertical = dcsunit:getDrawArgumentValue(527) or 0
|
||||
self:T(string.format("Kiowa MMS Arguments Read: H %.3f V %.3f",mmshorizontal,mmsvertical))
|
||||
local mmson = true
|
||||
if mmshorizontal < -0.527 or mmshorizontal > 0.527 then mmson = false end
|
||||
local horizontalview = mmshorizontal / 0.527 * 190
|
||||
local heading = unit:GetHeading()
|
||||
local mmsheading = (heading+horizontalview)%360
|
||||
--local mmsyaw = mmsvertical * 30
|
||||
local mmsyaw = math.atan(mmsvertical)*40
|
||||
local maxview = self:_GetActualMaxLOSight(unit,mmsheading, mmsyaw,not mmson)
|
||||
if maxview > 8000 then maxview = 8000 end
|
||||
self:T(string.format("Kiowa MMS Heading %d, Yaw %d, MaxView %dm MMS On %s",mmsheading,mmsyaw,maxview,tostring(mmson)))
|
||||
return mmsheading,mmsyaw,maxview,mmson
|
||||
end
|
||||
return 0,0,0,false
|
||||
end
|
||||
|
||||
|
||||
--- [Internal] Get the view parameters from a Gazelle camera
|
||||
-- @param #PLAYERRECCE self
|
||||
-- @param Wrapper.Unit#UNIT Gazelle
|
||||
@@ -593,40 +653,15 @@ function PLAYERRECCE:_GetGazelleVivianneSight(Gazelle)
|
||||
vivioff = true
|
||||
return 0,0,0,false
|
||||
end
|
||||
vivivertical = vivivertical / 1.10731 -- normalize
|
||||
|
||||
local horizontalview = vivihorizontal * -180
|
||||
local verticalview = vivivertical * 30 -- ca +/- 30°
|
||||
--self:I(string.format("vivihorizontal=%.5f | vivivertical=%.5f",vivihorizontal,vivivertical))
|
||||
--self:I(string.format("horizontal=%.5f | vertical=%.5f",horizontalview,verticalview))
|
||||
--local verticalview = vivivertical * 30 -- ca +/- 30°
|
||||
local verticalview = math.atan(vivivertical)
|
||||
|
||||
local heading = unit:GetHeading()
|
||||
local viviheading = (heading+horizontalview)%360
|
||||
local maxview = self:_GetActualMaxLOSight(unit,viviheading, verticalview,vivioff)
|
||||
--self:I(string.format("maxview=%.5f",maxview))
|
||||
-- visual skew
|
||||
local factor = 3.15
|
||||
self.GazelleViewFactors = {
|
||||
[1]=1.18,
|
||||
[2]=1.32,
|
||||
[3]=1.46,
|
||||
[4]=1.62,
|
||||
[5]=1.77,
|
||||
[6]=1.85,
|
||||
[7]=2.05,
|
||||
[8]=2.05,
|
||||
[9]=2.3,
|
||||
[10]=2.3,
|
||||
[11]=2.27,
|
||||
[12]=2.27,
|
||||
[13]=2.43,
|
||||
}
|
||||
local lfac = UTILS.Round(maxview,-2)
|
||||
if lfac <= 1300 then
|
||||
--factor = self.GazelleViewFactors[lfac/100]
|
||||
factor = 3.15
|
||||
maxview = math.ceil((maxview*factor)/100)*100
|
||||
end
|
||||
if maxview > 8000 then maxview = 8000 end
|
||||
--self:I(string.format("corrected maxview=%.5f",maxview))
|
||||
return viviheading, verticalview,maxview, not vivioff
|
||||
end
|
||||
return 0,0,0,false
|
||||
@@ -647,20 +682,20 @@ function PLAYERRECCE:_GetActualMaxLOSight(unit,vheading, vnod, vivoff)
|
||||
if unit and unit:IsAlive() then
|
||||
local typename = unit:GetTypeName()
|
||||
maxview = self.MaxViewDistance[typename] or 8000
|
||||
local CamHeight = self.Cameraheight[typename] or 0
|
||||
if vnod < 0 then
|
||||
local CamHeight = self.Cameraheight[typename] or 1
|
||||
if vnod < -2 then
|
||||
-- Looking down
|
||||
-- determine max distance we're looking at
|
||||
local beta = 90
|
||||
local gamma = math.floor(90-vnod)
|
||||
local alpha = math.floor(180-beta-gamma)
|
||||
local gamma = 90-math.abs(vnod)
|
||||
local alpha = 90-gamma
|
||||
local a = unit:GetHeight()-unit:GetCoordinate():GetLandHeight()+CamHeight
|
||||
local b = a / math.sin(math.rad(alpha))
|
||||
local c = b * math.sin(math.rad(gamma))
|
||||
maxview = c*1.2 -- +20%
|
||||
end
|
||||
end
|
||||
return math.abs(maxview)
|
||||
return math.ceil(math.abs(maxview))
|
||||
end
|
||||
|
||||
--- [User] Set callsign options for TTS output. See @{Wrapper.Group#GROUP.GetCustomCallSign}() on how to set customized callsigns.
|
||||
@@ -669,8 +704,10 @@ end
|
||||
-- @param #boolean Keepnumber If true, keep the **customized callsign** in the #GROUP name for players as-is, no amendments or numbers.
|
||||
-- @param #table CallsignTranslations (optional) Table to translate between DCS standard callsigns and bespoke ones. Does not apply if using customized
|
||||
-- callsigns from playername or group name.
|
||||
-- @param #func CallsignCustomFunc (Optional) For player names only(!). If given, this function will return the callsign. Needs to take the groupname and the playername as first two arguments.
|
||||
-- @param #arg ... (Optional) Comma separated arguments to add to the custom function call after groupname and playername.
|
||||
-- @return #PLAYERRECCE self
|
||||
function PLAYERRECCE:SetCallSignOptions(ShortCallsign,Keepnumber,CallsignTranslations)
|
||||
function PLAYERRECCE:SetCallSignOptions(ShortCallsign,Keepnumber,CallsignTranslations,CallsignCustomFunc,...)
|
||||
if not ShortCallsign or ShortCallsign == false then
|
||||
self.ShortCallsign = false
|
||||
else
|
||||
@@ -678,6 +715,8 @@ function PLAYERRECCE:SetCallSignOptions(ShortCallsign,Keepnumber,CallsignTransla
|
||||
end
|
||||
self.Keepnumber = Keepnumber or false
|
||||
self.CallsignTranslations = CallsignTranslations
|
||||
self.CallsignCustomFunc = CallsignCustomFunc
|
||||
self.CallsignCustomArgs = arg or {}
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -799,7 +838,7 @@ function PLAYERRECCE:_GetTargetSet(unit,camera,laser)
|
||||
local minview = 0
|
||||
local typename = unit:GetTypeName()
|
||||
local playername = unit:GetPlayerName()
|
||||
local maxview = self.MaxViewDistance[typename] or 5000
|
||||
local maxview = self.MaxViewDistance[typename] or 8000
|
||||
local heading,nod,maxview,angle = 0,30,8000,10
|
||||
local camon = false
|
||||
local name = unit:GetName()
|
||||
@@ -807,16 +846,25 @@ function PLAYERRECCE:_GetTargetSet(unit,camera,laser)
|
||||
heading,nod,maxview,camon = self:_GetGazelleVivianneSight(unit)
|
||||
angle=10
|
||||
-- Model nod and actual TV view don't compute
|
||||
maxview = self.MaxViewDistance[typename] or 5000
|
||||
maxview = self.MaxViewDistance[typename] or 8000
|
||||
elseif string.find(typename,"Ka-50") and camera then
|
||||
heading = unit:GetHeading()
|
||||
nod,maxview,camon = 10,1000,true
|
||||
angle = 10
|
||||
maxview = self.MaxViewDistance[typename] or 5000
|
||||
maxview = self.MaxViewDistance[typename] or 8000
|
||||
elseif string.find(typename,"OH58") and camera then
|
||||
--heading = unit:GetHeading()
|
||||
nod,maxview,camon = 0,8000,true
|
||||
heading,nod,maxview,camon = self:_GetKiowaMMSSight(unit)
|
||||
angle = 8
|
||||
if maxview == 0 then
|
||||
maxview = self.MaxViewDistance[typename] or 8000
|
||||
end
|
||||
else
|
||||
-- visual
|
||||
heading = unit:GetHeading()
|
||||
nod,maxview,camon = 10,1000,true
|
||||
nod,maxview,camon = 10,3000,true
|
||||
maxview = self.MaxViewDistance[typename] or 3000
|
||||
angle = 45
|
||||
end
|
||||
if laser then
|
||||
@@ -929,7 +977,8 @@ function PLAYERRECCE:_LaseTarget(client,targetset)
|
||||
if (not oldtarget) or targetset:IsNotInSet(oldtarget) or target:IsDead() or target:IsDestroyed() then
|
||||
-- lost LOS or dead
|
||||
laser:LaseOff()
|
||||
if target:IsDead() or target:IsDestroyed() or target:GetLife() < 2 then
|
||||
self:T(self.lid.."Target Life Points: "..target:GetLife() or "none")
|
||||
if target:IsDead() or target:IsDestroyed() or target:GetDamage() > 79 or target:GetLife() <= 1 then
|
||||
self:__Shack(-1,client,oldtarget)
|
||||
--self.LaserTarget[playername] = nil
|
||||
else
|
||||
@@ -1274,6 +1323,9 @@ self:T(self.lid.."_ReportLaserTargets")
|
||||
report:Add("Threat Level: "..ThreatGraph.." ("..ThreatLevelText..")")
|
||||
if not self.ReferencePoint then
|
||||
report:Add("Location: "..client:GetCoordinate():ToStringBULLS(self.Coalition,Settings))
|
||||
if self.reporttostringbullsonly ~= true then
|
||||
report:Add("Location: "..client:GetCoordinate():ToStringA2G(nil,Settings))
|
||||
end
|
||||
else
|
||||
report:Add("Location: "..client:GetCoordinate():ToStringFromRPShort(self.ReferencePoint,self.RPName,client,Settings))
|
||||
end
|
||||
@@ -1317,8 +1369,14 @@ function PLAYERRECCE:_ReportVisualTargets(client,group,playername)
|
||||
report:Add("Threat Level: "..ThreatGraph.." ("..ThreatLevelText..")")
|
||||
if not self.ReferencePoint then
|
||||
report:Add("Location: "..client:GetCoordinate():ToStringBULLS(self.Coalition,Settings))
|
||||
if self.reporttostringbullsonly ~= true then
|
||||
report:Add("Location: "..client:GetCoordinate():ToStringA2G(nil,Settings))
|
||||
end
|
||||
else
|
||||
report:Add("Location: "..client:GetCoordinate():ToStringFromRPShort(self.ReferencePoint,self.RPName,client,Settings))
|
||||
if self.reporttostringbullsonly ~= true then
|
||||
report:Add("Location: "..client:GetCoordinate():ToStringA2G(nil,Settings))
|
||||
end
|
||||
end
|
||||
report:Add(string.rep("-",15))
|
||||
local text = report:Text()
|
||||
@@ -1347,6 +1405,7 @@ function PLAYERRECCE:_BuildMenus(Client)
|
||||
local client = _client -- Wrapper.Client#CLIENT
|
||||
if client and client:IsAlive() then
|
||||
local playername = client:GetPlayerName()
|
||||
self:T("Menu for "..playername)
|
||||
if not self.UnitLaserCodes[playername] then
|
||||
self:_SetClientLaserCode(nil,nil,playername,1688)
|
||||
end
|
||||
@@ -1355,6 +1414,7 @@ function PLAYERRECCE:_BuildMenus(Client)
|
||||
end
|
||||
local group = client:GetGroup()
|
||||
if not self.ClientMenus[playername] then
|
||||
self:T("Start Menubuild for "..playername)
|
||||
local canlase = self.CanLase[client:GetTypeName()]
|
||||
self.ClientMenus[playername] = MENU_GROUP:New(group,self.MenuName or self.Name or "RECCE")
|
||||
local txtonstation = self.OnStation[playername] and "ON" or "OFF"
|
||||
@@ -1492,8 +1552,9 @@ end
|
||||
-- Note that this must be installed on your windows system. Can also be Google voice types, if you are using Google TTS.
|
||||
-- @param #number Volume (Optional) Volume - between 0.0 (silent) and 1.0 (loudest)
|
||||
-- @param #string PathToGoogleKey (Optional) Path to your google key if you want to use google TTS
|
||||
-- @param #string Backend (optional) Backend to be used, can be MSRS.Backend.SRSEXE or MSRS.Backend.GRPC
|
||||
-- @return #PLAYERRECCE self
|
||||
function PLAYERRECCE:SetSRS(Frequency,Modulation,PathToSRS,Gender,Culture,Port,Voice,Volume,PathToGoogleKey)
|
||||
function PLAYERRECCE:SetSRS(Frequency,Modulation,PathToSRS,Gender,Culture,Port,Voice,Volume,PathToGoogleKey,Backend)
|
||||
self:T(self.lid.."SetSRS")
|
||||
self.PathToSRS = PathToSRS or MSRS.path or "C:\\Program Files\\DCS-SimpleRadio-Standalone" --
|
||||
self.Gender = Gender or MSRS.gender or "male" --
|
||||
@@ -1515,6 +1576,9 @@ function PLAYERRECCE:SetSRS(Frequency,Modulation,PathToSRS,Gender,Culture,Port,V
|
||||
self.SRS:SetCulture(self.Culture)
|
||||
self.SRS:SetPort(self.Port)
|
||||
self.SRS:SetVolume(self.Volume)
|
||||
if Backend then
|
||||
self.SRS:SetBackend(Backend)
|
||||
end
|
||||
if self.PathToGoogleKey then
|
||||
self.SRS:SetProviderOptionsGoogle(self.PathToGoogleKey,self.PathToGoogleKey)
|
||||
self.SRS:SetProvider(MSRS.Provider.GOOGLE)
|
||||
@@ -1552,6 +1616,16 @@ function PLAYERRECCE:SetMenuName(Name)
|
||||
return self
|
||||
end
|
||||
|
||||
--- [User] Set reporting to be BULLS only or BULLS plus playersettings based coordinate.
|
||||
-- @param #PLAYERRECCE self
|
||||
-- @param #boolean OnOff
|
||||
-- @return #PLAYERRECCE self
|
||||
function PLAYERRECCE:SetReportBullsOnly(OnOff)
|
||||
self:T(self.lid.."SetReportBullsOnly: "..tostring(OnOff))
|
||||
self.reporttostringbullsonly = OnOff
|
||||
return self
|
||||
end
|
||||
|
||||
--- [User] Enable smoking of own position
|
||||
-- @param #PLAYERRECCE self
|
||||
-- @return #PLAYERRECCE self
|
||||
@@ -1561,6 +1635,15 @@ function PLAYERRECCE:EnableSmokeOwnPosition()
|
||||
return self
|
||||
end
|
||||
|
||||
--- [User] Enable auto lasing for the Kiowa OH-58D.
|
||||
-- @param #PLAYERRECCE self
|
||||
-- @return #PLAYERRECCE self
|
||||
function PLAYERRECCE:EnableKiowaAutolase()
|
||||
self:T(self.lid.."EnableKiowaAutolase")
|
||||
self.CanLase.OH58D = true
|
||||
return self
|
||||
end
|
||||
|
||||
--- [User] Disable smoking of own position
|
||||
-- @param #PLAYERRECCE self
|
||||
-- @return #PLAYERRECCE
|
||||
@@ -1718,7 +1801,7 @@ end
|
||||
-- @return #PLAYERRECCE self
|
||||
function PLAYERRECCE:onafterRecceOnStation(From, Event, To, Client, Playername)
|
||||
self:T({From, Event, To})
|
||||
local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations)
|
||||
local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs)
|
||||
local coord = Client:GetCoordinate()
|
||||
local coordtext = coord:ToStringBULLS(self.Coalition)
|
||||
if self.ReferencePoint then
|
||||
@@ -1727,7 +1810,7 @@ function PLAYERRECCE:onafterRecceOnStation(From, Event, To, Client, Playername)
|
||||
end
|
||||
local text1 = "Party time!"
|
||||
local text2 = string.format("All stations, FACA %s on station\nat %s!",callsign, coordtext)
|
||||
local text2tts = string.format("All stations, FACA %s on station at %s!",callsign, coordtext)
|
||||
local text2tts = string.format(" All stations, FACA %s on station at %s!",callsign, coordtext)
|
||||
text2tts = self:_GetTextForSpeech(text2tts)
|
||||
if self.debug then
|
||||
self:T(text2.."\n"..text2tts)
|
||||
@@ -1758,7 +1841,7 @@ end
|
||||
-- @return #PLAYERRECCE self
|
||||
function PLAYERRECCE:onafterRecceOffStation(From, Event, To, Client, Playername)
|
||||
self:T({From, Event, To})
|
||||
local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations)
|
||||
local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs)
|
||||
local coord = Client:GetCoordinate()
|
||||
local coordtext = coord:ToStringBULLS(self.Coalition)
|
||||
if self.ReferencePoint then
|
||||
@@ -1898,7 +1981,7 @@ end
|
||||
-- @return #PLAYERRECCE self
|
||||
function PLAYERRECCE:onafterIllumination(From, Event, To, Client, Playername, TargetSet)
|
||||
self:T({From, Event, To})
|
||||
local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations)
|
||||
local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs)
|
||||
local coord = Client:GetCoordinate()
|
||||
local coordtext = coord:ToStringBULLS(self.Coalition)
|
||||
if self.AttackSet then
|
||||
@@ -1941,7 +2024,7 @@ end
|
||||
-- @return #PLAYERRECCE self
|
||||
function PLAYERRECCE:onafterTargetsSmoked(From, Event, To, Client, Playername, TargetSet)
|
||||
self:T({From, Event, To})
|
||||
local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations)
|
||||
local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs)
|
||||
local coord = Client:GetCoordinate()
|
||||
local coordtext = coord:ToStringBULLS(self.Coalition)
|
||||
if self.AttackSet then
|
||||
@@ -1984,7 +2067,7 @@ end
|
||||
-- @return #PLAYERRECCE self
|
||||
function PLAYERRECCE:onafterTargetsFlared(From, Event, To, Client, Playername, TargetSet)
|
||||
self:T({From, Event, To})
|
||||
local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations)
|
||||
local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs)
|
||||
local coord = Client:GetCoordinate()
|
||||
local coordtext = coord:ToStringBULLS(self.Coalition)
|
||||
if self.AttackSet then
|
||||
@@ -2028,7 +2111,7 @@ end
|
||||
-- @return #PLAYERRECCE self
|
||||
function PLAYERRECCE:onafterTargetLasing(From, Event, To, Client, Target, Lasercode, Lasingtime)
|
||||
self:T({From, Event, To})
|
||||
local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations)
|
||||
local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs)
|
||||
local Settings = ( Client and _DATABASE:GetPlayerSettings( Client:GetPlayerName() ) ) or _SETTINGS
|
||||
local coord = Client:GetCoordinate()
|
||||
local coordtext = coord:ToStringBULLS(self.Coalition,Settings)
|
||||
@@ -2075,7 +2158,7 @@ end
|
||||
-- @return #PLAYERRECCE self
|
||||
function PLAYERRECCE:onafterShack(From, Event, To, Client, Target)
|
||||
self:T({From, Event, To})
|
||||
local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations)
|
||||
local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs)
|
||||
local Settings = ( Client and _DATABASE:GetPlayerSettings( Client:GetPlayerName() ) ) or _SETTINGS
|
||||
local coord = Client:GetCoordinate()
|
||||
local coordtext = coord:ToStringBULLS(self.Coalition,Settings)
|
||||
@@ -2122,7 +2205,7 @@ end
|
||||
-- @return #PLAYERRECCE self
|
||||
function PLAYERRECCE:onafterTargetLOSLost(From, Event, To, Client, Target)
|
||||
self:T({From, Event, To})
|
||||
local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations)
|
||||
local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs)
|
||||
local Settings = ( Client and _DATABASE:GetPlayerSettings( Client:GetPlayerName() ) ) or _SETTINGS
|
||||
local coord = Client:GetCoordinate()
|
||||
local coordtext = coord:ToStringBULLS(self.Coalition,Settings)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -997,6 +997,8 @@ function RECOVERYTANKER:onafterStart(From, Event, To)
|
||||
|
||||
-- Init status updates in 10 seconds.
|
||||
self:__Status(10)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
|
||||
@@ -963,6 +963,8 @@ function RESCUEHELO:onafterStart(From, Event, To)
|
||||
|
||||
-- Init status check
|
||||
self:__Status(1)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- On after Status event. Checks player status.
|
||||
|
||||
@@ -153,13 +153,13 @@ _TARGETID=0
|
||||
|
||||
--- TARGET class version.
|
||||
-- @field #string version
|
||||
TARGET.version="0.6.0"
|
||||
TARGET.version="0.7.1"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- TODO list
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
-- TODO: Had cases where target life was 0 but target was not dead. Need to figure out why!
|
||||
-- DONE: Had cases where target life was 0 but target was not dead. Need to figure out why! <== This is due to delayed dead event.
|
||||
-- DONE: Add pseudo functions.
|
||||
-- DONE: Initial object can be nil.
|
||||
|
||||
@@ -243,6 +243,36 @@ function TARGET:New(TargetObject)
|
||||
-- @function [parent=#TARGET] __Status
|
||||
-- @param #TARGET self
|
||||
-- @param #number delay Delay in seconds.
|
||||
|
||||
|
||||
--- Triggers the FSM event "ObjectDamaged".
|
||||
-- @function [parent=#TARGET] ObjectDamaged
|
||||
-- @param #TARGET self
|
||||
-- @param #TARGET.Object Target Target object.
|
||||
|
||||
--- Triggers the FSM event "ObjectDestroyed".
|
||||
-- @function [parent=#TARGET] ObjectDestroyed
|
||||
-- @param #TARGET self
|
||||
-- @param #TARGET.Object Target Target object.
|
||||
|
||||
--- Triggers the FSM event "ObjectDead".
|
||||
-- @function [parent=#TARGET] ObjectDead
|
||||
-- @param #TARGET self
|
||||
-- @param #TARGET.Object Target Target object.
|
||||
|
||||
|
||||
--- Triggers the FSM event "Damaged".
|
||||
-- @function [parent=#TARGET] Damaged
|
||||
-- @param #TARGET self
|
||||
|
||||
--- Triggers the FSM event "Destroyed".
|
||||
-- @function [parent=#TARGET] Destroyed
|
||||
-- @param #TARGET self
|
||||
|
||||
--- Triggers the FSM event "Dead".
|
||||
-- @function [parent=#TARGET] Dead
|
||||
-- @param #TARGET self
|
||||
|
||||
|
||||
--- On After "ObjectDamaged" event. A (sub-) target object has been damaged, e.g. a UNIT of a GROUP, or an object of a SET
|
||||
-- @function [parent=#TARGET] OnAfterObjectDamaged
|
||||
@@ -267,22 +297,23 @@ function TARGET:New(TargetObject)
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param #TARGET.Object Target Target object.
|
||||
|
||||
|
||||
--- On After "Damaged" event. The (whole) target object has been damaged.
|
||||
--- On After "Damaged" event. Any of the target objects has been damaged.
|
||||
-- @function [parent=#TARGET] OnAfterDamaged
|
||||
-- @param #TARGET self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
|
||||
--- On After "ObjectDestroyed" event. The (whole) target object has been destroyed.
|
||||
--- On After "Destroyed" event. All target objects have been destroyed.
|
||||
-- @function [parent=#TARGET] OnAfterDestroyed
|
||||
-- @param #TARGET self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
|
||||
--- On After "ObjectDead" event. The (whole) target object is dead.
|
||||
--- On After "Dead" event. All target objects are dead.
|
||||
-- @function [parent=#TARGET] OnAfterDead
|
||||
-- @param #TARGET self
|
||||
-- @param #string From From state.
|
||||
@@ -290,7 +321,7 @@ function TARGET:New(TargetObject)
|
||||
-- @param #string To To state.
|
||||
|
||||
-- Start.
|
||||
self:__Start(-1)
|
||||
self:__Start(-0.1)
|
||||
|
||||
return self
|
||||
end
|
||||
@@ -356,6 +387,8 @@ function TARGET:AddObject(Object)
|
||||
|
||||
if Object:IsInstanceOf("OPSGROUP") then
|
||||
self:_AddObject(Object:GetGroup()) -- We add the MOOSE GROUP object not the OPSGROUP object.
|
||||
--elseif Object:IsInstanceOf("OPSZONE") then
|
||||
--self:_AddObject(Object:GetZone())
|
||||
else
|
||||
self:_AddObject(Object)
|
||||
end
|
||||
@@ -527,6 +560,11 @@ function TARGET:IsAlive()
|
||||
for _,_target in pairs(self.targets) do
|
||||
local target=_target --Ops.Target#TARGET.Object
|
||||
if target.Status~=TARGET.ObjectStatus.DEAD then
|
||||
if self.isDestroyed then
|
||||
self:E(self.lid..string.format("ERROR: target is DESTROYED but target object status is not DEAD but %s for object %s", target.Status, target.Name))
|
||||
elseif self:IsDead() then
|
||||
self:E(self.lid..string.format("ERROR: target is DEAD but target object status is not DEAD but %s for object %s", target.Status, target.Name))
|
||||
end
|
||||
return true
|
||||
end
|
||||
end
|
||||
@@ -550,6 +588,25 @@ function TARGET:IsDead()
|
||||
return is
|
||||
end
|
||||
|
||||
--- Check if target object is dead.
|
||||
-- @param #TARGET self
|
||||
-- @param #TARGET.Object TargetObject The target object.
|
||||
-- @return #boolean If true, target is dead.
|
||||
function TARGET:IsTargetDead(TargetObject)
|
||||
local isDead=TargetObject.Status==TARGET.ObjectStatus.DEAD
|
||||
return isDead
|
||||
end
|
||||
|
||||
--- Check if target object is alive.
|
||||
-- @param #TARGET self
|
||||
-- @param #TARGET.Object TargetObject The target object.
|
||||
-- @return #boolean If true, target is dead.
|
||||
function TARGET:IsTargetAlive(TargetObject)
|
||||
local isAlive=TargetObject.Status==TARGET.ObjectStatus.ALIVE
|
||||
return isAlive
|
||||
end
|
||||
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Start & Status
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
@@ -581,7 +638,8 @@ end
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
function TARGET:onafterStatus(From, Event, To)
|
||||
self:T({From, Event, To})
|
||||
--self:T({From, Event, To})
|
||||
|
||||
-- FSM state.
|
||||
local fsmstate=self:GetState()
|
||||
|
||||
@@ -592,6 +650,7 @@ function TARGET:onafterStatus(From, Event, To)
|
||||
|
||||
-- old life
|
||||
local life=target.Life
|
||||
|
||||
-- curr life
|
||||
target.Life=self:GetTargetLife(target)
|
||||
|
||||
@@ -603,13 +662,14 @@ function TARGET:onafterStatus(From, Event, To)
|
||||
self.life0 = self.life0+delta
|
||||
end
|
||||
|
||||
-- Check if life decreased ==> damaged
|
||||
if target.Life<life then
|
||||
target.Status = TARGET.ObjectStatus.DAMAGED
|
||||
self:ObjectDamaged(target)
|
||||
--target.Status = TARGET.ObjectStatus.DAMAGED
|
||||
self:ObjectDamaged(target)
|
||||
damaged=true
|
||||
end
|
||||
|
||||
if life < 1 and (not target.Status == TARGET.ObjectStatus.DEAD) then
|
||||
if target.Life<1 and target.Status~=TARGET.ObjectStatus.DEAD then
|
||||
self:E(self.lid..string.format("FF life is zero but no object dead event fired ==> object dead now for target object %s!", tostring(target.Name)))
|
||||
self:ObjectDead(target)
|
||||
damaged = true
|
||||
@@ -624,11 +684,12 @@ function TARGET:onafterStatus(From, Event, To)
|
||||
|
||||
-- Log output verbose=1.
|
||||
if self.verbose>=1 then
|
||||
local text=string.format("%s: Targets=%d/%d Life=%.1f/%.1f Damage=%.1f", fsmstate, self:CountTargets(), self.N0, self:GetLife(), self:GetLife0(), self:GetDamage())
|
||||
local text=string.format("%s: Targets=%d/%d [%d, %d], Life=%.1f/%.1f, Damage=%.1f",
|
||||
fsmstate, self:CountTargets(), self.N0, self.Ndestroyed, self.Ndead, self:GetLife(), self:GetLife0(), self:GetDamage())
|
||||
if self:CountTargets() == 0 or self:GetDamage() >= 100 then
|
||||
text=text.." Dead!"
|
||||
text=text.." - Dead!"
|
||||
elseif damaged then
|
||||
text=text.." Damaged!"
|
||||
text=text.." - Damaged!"
|
||||
end
|
||||
self:I(self.lid..text)
|
||||
end
|
||||
@@ -639,19 +700,35 @@ function TARGET:onafterStatus(From, Event, To)
|
||||
for i,_target in pairs(self.targets) do
|
||||
local target=_target --#TARGET.Object
|
||||
local damage=(1-target.Life/target.Life0)*100
|
||||
text=text..string.format("\n[%d] %s %s %s: Life=%.1f/%.1f, Damage=%.1f", i, target.Type, target.Name, target.Status, target.Life, target.Life0, damage)
|
||||
text=text..string.format("\n[%d] %s %s %s: Life=%.1f/%.1f, Damage=%.1f, N0=%d, Ndestroyed=%d, Ndead=%d",
|
||||
i, target.Type, target.Name, target.Status, target.Life, target.Life0, damage, target.N0, target.Ndestroyed, target.Ndead)
|
||||
end
|
||||
self:I(self.lid..text)
|
||||
end
|
||||
|
||||
if self:CountTargets() == 0 or self:GetDamage() >= 100 then
|
||||
-- Consitency check if target is still alive but all target objects are dead
|
||||
if self:IsAlive() and (self:CountTargets()==0 or self:GetDamage()>=100) then
|
||||
self:Dead()
|
||||
end
|
||||
|
||||
-- Quick sanity check
|
||||
for i,_target in pairs(self.targets) do
|
||||
local target=_target --#TARGET.Object
|
||||
if target.Ndestroyed>target.N0 then
|
||||
self:E(self.lid..string.format("ERROR: Number of destroyed target objects greater than number of initial target objects: %d>%d!", target.Ndestroyed, target.N0))
|
||||
end
|
||||
if target.Ndestroyed>target.N0 then
|
||||
self:E(self.lid..string.format("ERROR: Number of dead target objects greater than number of initial target objects: %d>%d!", target.Ndead, target.N0))
|
||||
end
|
||||
end
|
||||
|
||||
-- Update status again in 30 sec.
|
||||
if self:IsAlive() then
|
||||
self:__Status(-self.TStatus)
|
||||
else
|
||||
self:I(self.lid..string.format("Target is not alive any more ==> no further status updates are carried out"))
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -687,6 +764,10 @@ function TARGET:onafterObjectDestroyed(From, Event, To, Target)
|
||||
-- Increase destroyed counter.
|
||||
self.Ndestroyed=self.Ndestroyed+1
|
||||
|
||||
Target.Ndestroyed=Target.Ndestroyed+1
|
||||
|
||||
Target.Life=0
|
||||
|
||||
-- Call object dead event.
|
||||
self:ObjectDead(Target)
|
||||
|
||||
@@ -707,6 +788,12 @@ function TARGET:onafterObjectDead(From, Event, To, Target)
|
||||
-- Set target status.
|
||||
Target.Status=TARGET.ObjectStatus.DEAD
|
||||
|
||||
-- Increase dead object counter
|
||||
Target.Ndead=Target.Ndead+1
|
||||
|
||||
-- Set target object life to 0.
|
||||
Target.Life=0
|
||||
|
||||
-- Increase dead counter.
|
||||
self.Ndead=self.Ndead+1
|
||||
|
||||
@@ -716,6 +803,7 @@ function TARGET:onafterObjectDead(From, Event, To, Target)
|
||||
local target=_target --#TARGET.Object
|
||||
if target.Status==TARGET.ObjectStatus.ALIVE then
|
||||
dead=false
|
||||
break -- break the loop because we now we are not dead
|
||||
end
|
||||
end
|
||||
|
||||
@@ -759,7 +847,6 @@ end
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
function TARGET:onafterDestroyed(From, Event, To)
|
||||
|
||||
self:T({From, Event, To})
|
||||
|
||||
self:T(self.lid..string.format("TARGET destroyed"))
|
||||
@@ -813,23 +900,27 @@ function TARGET:OnEventUnitDeadOrLost(EventData)
|
||||
-- Check if we could find a target object.
|
||||
if target then
|
||||
|
||||
local Ndead=target.Ndead
|
||||
local Ndestroyed=target.Ndestroyed
|
||||
if EventData.id==EVENTS.RemoveUnit then
|
||||
target.Ndead=target.Ndead+1
|
||||
Ndead=Ndead+1
|
||||
else
|
||||
target.Ndestroyed=target.Ndestroyed+1
|
||||
target.Ndead=target.Ndead+1
|
||||
Ndestroyed=Ndestroyed+1
|
||||
Ndead=Ndead+1
|
||||
end
|
||||
|
||||
if target.Ndead==target.N0 then
|
||||
|
||||
if target.Ndestroyed>=target.N0 then
|
||||
-- Check if ALL objects are dead
|
||||
if Ndead==target.N0 then
|
||||
|
||||
if Ndestroyed>=target.N0 then
|
||||
|
||||
-- Debug message.
|
||||
self:T2(self.lid..string.format("EVENT ID=%d: target %s dead/lost ==> destroyed", EventData.id, tostring(target.Name)))
|
||||
|
||||
target.Life = 0
|
||||
|
||||
-- Trigger object destroyed event.
|
||||
-- Trigger object destroyed event. This sets the Life to zero and increases Ndestroyed
|
||||
self:ObjectDestroyed(target)
|
||||
|
||||
else
|
||||
@@ -839,7 +930,7 @@ function TARGET:OnEventUnitDeadOrLost(EventData)
|
||||
|
||||
target.Life = 0
|
||||
|
||||
-- Trigger object dead event.
|
||||
-- Trigger object dead event. This sets the Life to zero and increases Ndead counter
|
||||
self:ObjectDead(target)
|
||||
|
||||
end
|
||||
@@ -979,6 +1070,8 @@ function TARGET:_AddObject(Object)
|
||||
|
||||
target.Life0=1
|
||||
target.Life=1
|
||||
|
||||
target.N0=target.N0+1
|
||||
|
||||
elseif Object:IsInstanceOf("ZONE_BASE") then
|
||||
|
||||
@@ -992,6 +1085,8 @@ function TARGET:_AddObject(Object)
|
||||
|
||||
target.Life0=1
|
||||
target.Life=1
|
||||
|
||||
target.N0=target.N0+1
|
||||
|
||||
elseif Object:IsInstanceOf("OPSZONE") then
|
||||
|
||||
@@ -1203,11 +1298,27 @@ function TARGET:GetTargetThreatLevelMax(Target)
|
||||
return 0
|
||||
|
||||
elseif Target.Type==TARGET.ObjectType.ZONE then
|
||||
|
||||
local zone = Target.Object -- Core.Zone#ZONE_RADIUS
|
||||
local foundunits = {}
|
||||
if zone:IsInstanceOf("ZONE_RADIUS") or zone:IsInstanceOf("ZONE_POLYGON") then
|
||||
zone:Scan({Object.Category.UNIT},{Unit.Category.GROUND_UNIT,Unit.Category.SHIP})
|
||||
foundunits = zone:GetScannedSetUnit()
|
||||
else
|
||||
foundunits = SET_UNIT:New():FilterZones({zone}):FilterOnce()
|
||||
end
|
||||
local ThreatMax = foundunits:GetThreatLevelMax() or 0
|
||||
return ThreatMax
|
||||
|
||||
return 0
|
||||
elseif Target.Type==TARGET.ObjectType.OPSZONE then
|
||||
|
||||
local unitset = Target.Object:GetScannedUnitSet() -- Core.Set#SET_UNIT
|
||||
local ThreatMax = unitset:GetThreatLevelMax()
|
||||
return ThreatMax
|
||||
|
||||
else
|
||||
self:E("ERROR: unknown target object type in GetTargetThreatLevel!")
|
||||
return 0
|
||||
end
|
||||
|
||||
return self
|
||||
@@ -1919,12 +2030,16 @@ function TARGET:CountObjectives(Target, Coalitions)
|
||||
|
||||
elseif Target.Type==TARGET.ObjectType.COORDINATE then
|
||||
|
||||
-- No target we can check!
|
||||
-- No target, where we can check the alive status, so we assume it is alive. Changed this because otherwise target count is 0 if we pass a coordinate.
|
||||
-- This is also more consitent with the life and is alive status.
|
||||
N=N+1
|
||||
|
||||
elseif Target.Type==TARGET.ObjectType.ZONE then
|
||||
|
||||
-- No target we can check!
|
||||
|
||||
-- No target, where we can check the alive status, so we assume it is alive. Changed this because otherwise target count is 0 if we pass a coordinate.
|
||||
-- This is also more consitent with the life and is alive status.
|
||||
N=N+1
|
||||
|
||||
elseif Target.Type==TARGET.ObjectType.OPSZONE then
|
||||
|
||||
local target=Target.Object --Ops.OpsZone#OPSZONE
|
||||
|
||||
@@ -52,6 +52,7 @@
|
||||
-- @field #table poptions Provider options. Each element is a data structure of type `MSRS.ProvierOptions`.
|
||||
-- @field #string provider Provider of TTS (win, gcloud, azure, amazon).
|
||||
-- @field #string backend Backend used as interface to SRS (MSRS.Backend.SRSEXE or MSRS.Backend.GRPC).
|
||||
-- @field #boolean UsePowerShell Use PowerShell to execute the command and not cmd.exe
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
--- *It is a very sad thing that nowadays there is so little useless information.* - Oscar Wilde
|
||||
@@ -256,26 +257,156 @@ MSRS = {
|
||||
ConfigFilePath = "Config\\",
|
||||
ConfigLoaded = false,
|
||||
poptions = {},
|
||||
UsePowerShell = false,
|
||||
}
|
||||
|
||||
--- MSRS class version.
|
||||
-- @field #string version
|
||||
MSRS.version="0.3.0"
|
||||
MSRS.version="0.3.3"
|
||||
|
||||
--- Voices
|
||||
-- @type MSRS.Voices
|
||||
MSRS.Voices = {
|
||||
Amazon = {
|
||||
Generative = {
|
||||
en_AU = {
|
||||
Olivia = "Olivia",
|
||||
},
|
||||
en_GB = {
|
||||
Amy = "Amy",
|
||||
},
|
||||
en_US = {
|
||||
Danielle = "Danielle",
|
||||
Joanna = "Joanna",
|
||||
Ruth = "Ruth",
|
||||
Stephen = "Stephen",
|
||||
},
|
||||
fr_FR = {
|
||||
["Léa"] = "Léa",
|
||||
["Rémi"] = "Rémi",
|
||||
},
|
||||
de_DE = {
|
||||
Vicki = "Vicki",
|
||||
Daniel = "Daniel",
|
||||
},
|
||||
it_IT = {
|
||||
Bianca = "Bianca",
|
||||
Adriano = "Adriano",
|
||||
},
|
||||
es_ES = {
|
||||
Lucia = "Lucia",
|
||||
Sergio = "Sergio",
|
||||
},
|
||||
},
|
||||
LongForm = {
|
||||
en_US = {
|
||||
Danielle = "Danielle",
|
||||
Gregory = "Gregory",
|
||||
Ivy = "Ivy",
|
||||
Ruth = "Ruth",
|
||||
Patrick = "Patrick",
|
||||
},
|
||||
es_ES = {
|
||||
Alba = "Alba",
|
||||
["Raúl"] = "Raúl",
|
||||
},
|
||||
},
|
||||
Neural = {
|
||||
en_AU = {
|
||||
Olivia = "Olivia",
|
||||
},
|
||||
en_GB = {
|
||||
Amy = "Amy",
|
||||
Emma = "Emma",
|
||||
Brian = "Brian",
|
||||
Arthur = "Arthur",
|
||||
},
|
||||
en_US = {
|
||||
Danielle = "Danielle",
|
||||
Gregory = "Gregory",
|
||||
Ivy = "Ivy",
|
||||
Joanna = "Joanna",
|
||||
Kendra = "Kendra",
|
||||
Kimberly = "Kimberly",
|
||||
Salli = "Salli",
|
||||
Joey = "Joey",
|
||||
Kevin = "Kevin",
|
||||
Ruth = "Ruth",
|
||||
Stephen = "Stephen",
|
||||
},
|
||||
fr_FR = {
|
||||
["Léa"] = "Léa",
|
||||
["Rémi"] = "Rémi",
|
||||
},
|
||||
de_DE = {
|
||||
Vicki = "Vicki",
|
||||
Daniel = "Daniel",
|
||||
},
|
||||
it_IT = {
|
||||
Bianca = "Bianca",
|
||||
Adriano = "Adriano",
|
||||
},
|
||||
es_ES = {
|
||||
Lucia = "Lucia",
|
||||
Sergio = "Sergio",
|
||||
},
|
||||
},
|
||||
Standard = {
|
||||
en_AU = {
|
||||
Nicole = "Nicole",
|
||||
Russel = "Russel",
|
||||
},
|
||||
en_GB = {
|
||||
Amy = "Amy",
|
||||
Emma = "Emma",
|
||||
Brian = "Brian",
|
||||
},
|
||||
en_IN = {
|
||||
Aditi = "Aditi",
|
||||
Raveena = "Raveena",
|
||||
},
|
||||
en_US = {
|
||||
Ivy = "Ivy",
|
||||
Joanna = "Joanna",
|
||||
Kendra = "Kendra",
|
||||
Kimberly = "Kimberly",
|
||||
Salli = "Salli",
|
||||
Joey = "Joey",
|
||||
Kevin = "Kevin",
|
||||
},
|
||||
fr_FR = {
|
||||
Celine = "Celine",
|
||||
["Léa"] = "Léa",
|
||||
Mathieu = "Mathieu",
|
||||
},
|
||||
de_DE = {
|
||||
Marlene = "Marlene",
|
||||
Vicki = "Vicki",
|
||||
Hans = "Hans",
|
||||
},
|
||||
it_IT = {
|
||||
Carla = "Carla",
|
||||
Bianca = "Bianca",
|
||||
Giorgio = "Giorgio",
|
||||
},
|
||||
es_ES = {
|
||||
Conchita = "Conchita",
|
||||
Lucia = "Lucia",
|
||||
Enrique = "Enrique",
|
||||
},
|
||||
},
|
||||
},
|
||||
Microsoft = { -- working ones if not using gRPC and MS
|
||||
["Hedda"] = "Microsoft Hedda Desktop", -- de-DE
|
||||
["Hazel"] = "Microsoft Hazel Desktop", -- en-GB
|
||||
["David"] = "Microsoft David Desktop", -- en-US
|
||||
["Zira"] = "Microsoft Zira Desktop", -- en-US
|
||||
["Hortense"] = "Microsoft Hortense Desktop", --fr-FR
|
||||
["de-DE-Hedda"] = "Microsoft Hedda Desktop", -- de-DE
|
||||
["en-GB-Hazel"] = "Microsoft Hazel Desktop", -- en-GB
|
||||
["en-US-David"] = "Microsoft David Desktop", -- en-US
|
||||
["en-US-Zira"] = "Microsoft Zira Desktop", -- en-US
|
||||
["fr-FR-Hortense"] = "Microsoft Hortense Desktop", --fr-FR
|
||||
["de_DE_Hedda"] = "Microsoft Hedda Desktop", -- de-DE
|
||||
["en_GB_Hazel"] = "Microsoft Hazel Desktop", -- en-GB
|
||||
["en_US_David"] = "Microsoft David Desktop", -- en-US
|
||||
["en_US_Zira"] = "Microsoft Zira Desktop", -- en-US
|
||||
["fr_FR_Hortense"] = "Microsoft Hortense Desktop", --fr-FR
|
||||
},
|
||||
MicrosoftGRPC = { -- en-US/GB voices only as of Jan 2024, working ones if using gRPC and MS, if voice packs are installed
|
||||
--["Hedda"] = "Hedda", -- de-DE
|
||||
@@ -304,8 +435,7 @@ MSRS.Voices = {
|
||||
["en_CA_Linda"] = "Linda", --en-CA
|
||||
["en_IN_Ravi"] = "Ravi", --en-IN
|
||||
["en_IN_Heera"] = "Heera", --en-IN
|
||||
["en_IR_Sean"] = "Sean", --en-IR
|
||||
--]]
|
||||
["en_IR_Sean"] = "Sean", --en-IR
|
||||
},
|
||||
Google = {
|
||||
Standard = {
|
||||
@@ -317,11 +447,14 @@ MSRS.Voices = {
|
||||
["en_IN_Standard_B"] = 'en-IN-Standard-B', -- [6] MALE
|
||||
["en_IN_Standard_C"] = 'en-IN-Standard-C', -- [7] MALE
|
||||
["en_IN_Standard_D"] = 'en-IN-Standard-D', -- [8] FEMALE
|
||||
["en_GB_Standard_A"] = 'en-GB-Standard-A', -- [9] FEMALE
|
||||
["en_GB_Standard_B"] = 'en-GB-Standard-B', -- [10] MALE
|
||||
["en_GB_Standard_C"] = 'en-GB-Standard-C', -- [11] FEMALE
|
||||
["en_GB_Standard_D"] = 'en-GB-Standard-D', -- [12] MALE
|
||||
["en_GB_Standard_F"] = 'en-GB-Standard-F', -- [13] FEMALE
|
||||
-- 2025 changes
|
||||
["en_GB_Standard_A"] = 'en-GB-Standard-N', -- [9] FEMALE
|
||||
["en_GB_Standard_B"] = 'en-GB-Standard-O', -- [10] MALE
|
||||
["en_GB_Standard_C"] = 'en-GB-Standard-N', -- [11] FEMALE
|
||||
["en_GB_Standard_D"] = 'en-GB-Standard-O', -- [12] MALE
|
||||
["en_GB_Standard_F"] = 'en-GB-Standard-N', -- [13] FEMALE
|
||||
["en_GB_Standard_O"] = 'en-GB-Standard-O', -- [12] MALE
|
||||
["en_GB_Standard_N"] = 'en-GB-Standard-N', -- [13] FEMALE
|
||||
["en_US_Standard_A"] = 'en-US-Standard-A', -- [14] MALE
|
||||
["en_US_Standard_B"] = 'en-US-Standard-B', -- [15] MALE
|
||||
["en_US_Standard_C"] = 'en-US-Standard-C', -- [16] FEMALE
|
||||
@@ -332,25 +465,36 @@ MSRS.Voices = {
|
||||
["en_US_Standard_H"] = 'en-US-Standard-H', -- [21] FEMALE
|
||||
["en_US_Standard_I"] = 'en-US-Standard-I', -- [22] MALE
|
||||
["en_US_Standard_J"] = 'en-US-Standard-J', -- [23] MALE
|
||||
["fr_FR_Standard_A"] = "fr-FR-Standard-A", -- Female
|
||||
["fr_FR_Standard_B"] = "fr-FR-Standard-B", -- Male
|
||||
["fr_FR_Standard_C"] = "fr-FR-Standard-C", -- Female
|
||||
["fr_FR_Standard_D"] = "fr-FR-Standard-D", -- Male
|
||||
["fr_FR_Standard_E"] = "fr-FR-Standard-E", -- Female
|
||||
["de_DE_Standard_A"] = "de-DE-Standard-A", -- Female
|
||||
["de_DE_Standard_B"] = "de-DE-Standard-B", -- Male
|
||||
["de_DE_Standard_C"] = "de-DE-Standard-C", -- Female
|
||||
["de_DE_Standard_D"] = "de-DE-Standard-D", -- Male
|
||||
["de_DE_Standard_E"] = "de-DE-Standard-E", -- Male
|
||||
["de_DE_Standard_F"] = "de-DE-Standard-F", -- Female
|
||||
["es_ES_Standard_A"] = "es-ES-Standard-A", -- Female
|
||||
["es_ES_Standard_B"] = "es-ES-Standard-B", -- Male
|
||||
["es_ES_Standard_C"] = "es-ES-Standard-C", -- Female
|
||||
["es_ES_Standard_D"] = "es-ES-Standard-D", -- Female
|
||||
["it_IT_Standard_A"] = "it-IT-Standard-A", -- Female
|
||||
["it_IT_Standard_B"] = "it-IT-Standard-B", -- Female
|
||||
["it_IT_Standard_C"] = "it-IT-Standard-C", -- Male
|
||||
["it_IT_Standard_D"] = "it-IT-Standard-D", -- Male
|
||||
-- 2025 catalog changes
|
||||
["fr_FR_Standard_A"] = "fr-FR-Standard-F", -- Female
|
||||
["fr_FR_Standard_B"] = "fr-FR-Standard-G", -- Male
|
||||
["fr_FR_Standard_C"] = "fr-FR-Standard-F", -- Female
|
||||
["fr_FR_Standard_D"] = "fr-FR-Standard-G", -- Male
|
||||
["fr_FR_Standard_E"] = "fr-FR-Standard-F", -- Female
|
||||
["fr_FR_Standard_G"] = "fr-FR-Standard-G", -- Male
|
||||
["fr_FR_Standard_F"] = "fr-FR-Standard-F", -- Female
|
||||
-- 2025 catalog changes
|
||||
["de_DE_Standard_A"] = "de-DE-Standard-G", -- Female
|
||||
["de_DE_Standard_B"] = "de-DE-Standard-H", -- Male
|
||||
["de_DE_Standard_C"] = "de-DE-Standard-G", -- Female
|
||||
["de_DE_Standard_D"] = "de-DE-Standard-H", -- Male
|
||||
["de_DE_Standard_E"] = "de-DE-Standard-H", -- Male
|
||||
["de_DE_Standard_F"] = "de-DE-Standard-G", -- Female
|
||||
["de_DE_Standard_H"] = "de-DE-Standard-H", -- Male
|
||||
["de_DE_Standard_G"] = "de-DE-Standard-G", -- Female
|
||||
["es_ES_Standard_A"] = "es-ES-Standard-E", -- Female
|
||||
["es_ES_Standard_B"] = "es-ES-Standard-F", -- Male
|
||||
["es_ES_Standard_C"] = "es-ES-Standard-E", -- Female
|
||||
["es_ES_Standard_D"] = "es-ES-Standard-F", -- Male
|
||||
["es_ES_Standard_E"] = "es-ES-Standard-E", -- Female
|
||||
["es_ES_Standard_F"] = "es-ES-Standard-F", -- Male
|
||||
-- 2025 catalog changes
|
||||
["it_IT_Standard_A"] = "it-IT-Standard-E", -- Female
|
||||
["it_IT_Standard_B"] = "it-IT-Standard-E", -- Female
|
||||
["it_IT_Standard_C"] = "it-IT-Standard-F", -- Male
|
||||
["it_IT_Standard_D"] = "it-IT-Standard-F", -- Male
|
||||
["it_IT_Standard_E"] = "it-IT-Standard-E", -- Female
|
||||
["it_IT_Standard_F"] = "it-IT-Standard-F", -- Male
|
||||
},
|
||||
Wavenet = {
|
||||
["en_AU_Wavenet_A"] = 'en-AU-Wavenet-A', -- [1] FEMALE
|
||||
@@ -361,12 +505,15 @@ MSRS.Voices = {
|
||||
["en_IN_Wavenet_B"] = 'en-IN-Wavenet-B', -- [6] MALE
|
||||
["en_IN_Wavenet_C"] = 'en-IN-Wavenet-C', -- [7] MALE
|
||||
["en_IN_Wavenet_D"] = 'en-IN-Wavenet-D', -- [8] FEMALE
|
||||
["en_GB_Wavenet_A"] = 'en-GB-Wavenet-A', -- [9] FEMALE
|
||||
["en_GB_Wavenet_B"] = 'en-GB-Wavenet-B', -- [10] MALE
|
||||
["en_GB_Wavenet_C"] = 'en-GB-Wavenet-C', -- [11] FEMALE
|
||||
["en_GB_Wavenet_D"] = 'en-GB-Wavenet-D', -- [12] MALE
|
||||
["en_GB_Wavenet_F"] = 'en-GB-Wavenet-F', -- [13] FEMALE
|
||||
["en_US_Wavenet_A"] = 'en-US-Wavenet-A', -- [14] MALE
|
||||
-- 2025 changes
|
||||
["en_GB_Wavenet_A"] = 'en-GB-Wavenet-N', -- [9] FEMALE
|
||||
["en_GB_Wavenet_B"] = 'en-GB-Wavenet-O', -- [10] MALE
|
||||
["en_GB_Wavenet_C"] = 'en-GB-Wavenet-N', -- [11] FEMALE
|
||||
["en_GB_Wavenet_D"] = 'en-GB-Wavenet-O', -- [12] MALE
|
||||
["en_GB_Wavenet_F"] = 'en-GB-Wavenet-N', -- [13] FEMALE
|
||||
["en_GB_Wavenet_O"] = 'en-GB-Wavenet-O', -- [12] MALE
|
||||
["en_GB_Wavenet_N"] = 'en-GB-Wavenet-N', -- [13] FEMALE
|
||||
["en_US_Wavenet_A"] = 'en-US-Wavenet-N', -- [14] MALE
|
||||
["en_US_Wavenet_B"] = 'en-US-Wavenet-B', -- [15] MALE
|
||||
["en_US_Wavenet_C"] = 'en-US-Wavenet-C', -- [16] FEMALE
|
||||
["en_US_Wavenet_D"] = 'en-US-Wavenet-D', -- [17] MALE
|
||||
@@ -376,24 +523,35 @@ MSRS.Voices = {
|
||||
["en_US_Wavenet_H"] = 'en-US-Wavenet-H', -- [21] FEMALE
|
||||
["en_US_Wavenet_I"] = 'en-US-Wavenet-I', -- [22] MALE
|
||||
["en_US_Wavenet_J"] = 'en-US-Wavenet-J', -- [23] MALE
|
||||
["fr_FR_Wavenet_A"] = "fr-FR-Wavenet-A", -- Female
|
||||
["fr_FR_Wavenet_B"] = "fr-FR-Wavenet-B", -- Male
|
||||
["fr_FR_Wavenet_C"] = "fr-FR-Wavenet-C", -- Female
|
||||
["fr_FR_Wavenet_D"] = "fr-FR-Wavenet-D", -- Male
|
||||
["fr_FR_Wavenet_E"] = "fr-FR-Wavenet-E", -- Female
|
||||
["de_DE_Wavenet_A"] = "de-DE-Wavenet-A", -- Female
|
||||
["de_DE_Wavenet_B"] = "de-DE-Wavenet-B", -- Male
|
||||
["de_DE_Wavenet_C"] = "de-DE-Wavenet-C", -- Female
|
||||
["de_DE_Wavenet_D"] = "de-DE-Wavenet-D", -- Male
|
||||
["de_DE_Wavenet_E"] = "de-DE-Wavenet-E", -- Male
|
||||
["de_DE_Wavenet_F"] = "de-DE-Wavenet-F", -- Female
|
||||
["es_ES_Wavenet_B"] = "es-ES-Wavenet-B", -- Male
|
||||
["es_ES_Wavenet_C"] = "es-ES-Wavenet-C", -- Female
|
||||
["es_ES_Wavenet_D"] = "es-ES-Wavenet-D", -- Female
|
||||
["it_IT_Wavenet_A"] = "it-IT-Wavenet-A", -- Female
|
||||
["it_IT_Wavenet_B"] = "it-IT-Wavenet-B", -- Female
|
||||
["it_IT_Wavenet_C"] = "it-IT-Wavenet-C", -- Male
|
||||
["it_IT_Wavenet_D"] = "it-IT-Wavenet-D", -- Male
|
||||
-- 2025 catalog changes
|
||||
["fr_FR_Wavenet_A"] = "fr-FR-Wavenet-F", -- Female
|
||||
["fr_FR_Wavenet_B"] = "fr-FR-Wavenet-G", -- Male
|
||||
["fr_FR_Wavenet_C"] = "fr-FR-Wavenet-F", -- Female
|
||||
["fr_FR_Wavenet_D"] = "fr-FR-Wavenet-G", -- Male
|
||||
["fr_FR_Wavenet_E"] = "fr-FR-Wavenet-F", -- Female
|
||||
["fr_FR_Wavenet_G"] = "fr-FR-Wavenet-G", -- Male
|
||||
["fr_FR_Wavenet_F"] = "fr-FR-Wavenet-F", -- Female
|
||||
-- 2025 catalog changes
|
||||
["de_DE_Wavenet_A"] = "de-DE-Wavenet-G", -- Female
|
||||
["de_DE_Wavenet_B"] = "de-DE-Wavenet-H", -- Male
|
||||
["de_DE_Wavenet_C"] = "de-DE-Wavenet-G", -- Female
|
||||
["de_DE_Wavenet_D"] = "de-DE-Wavenet-H", -- Male
|
||||
["de_DE_Wavenet_E"] = "de-DE-Wavenet-H", -- Male
|
||||
["de_DE_Wavenet_F"] = "de-DE-Wavenet-G", -- Female
|
||||
["de_DE_Wavenet_H"] = "de-DE-Wavenet-H", -- Male
|
||||
["de_DE_Wavenet_G"] = "de-DE-Wavenet-G", -- Female
|
||||
["es_ES_Wavenet_B"] = "es-ES-Wavenet-E", -- Male
|
||||
["es_ES_Wavenet_C"] = "es-ES-Wavenet-F", -- Female
|
||||
["es_ES_Wavenet_D"] = "es-ES-Wavenet-E", -- Female
|
||||
["es_ES_Wavenet_E"] = "es-ES-Wavenet-E", -- Male
|
||||
["es_ES_Wavenet_F"] = "es-ES-Wavenet-F", -- Female
|
||||
-- 2025 catalog changes
|
||||
["it_IT_Wavenet_A"] = "it-IT-Wavenet-E", -- Female
|
||||
["it_IT_Wavenet_B"] = "it-IT-Wavenet-E", -- Female
|
||||
["it_IT_Wavenet_C"] = "it-IT-Wavenet-F", -- Male
|
||||
["it_IT_Wavenet_D"] = "it-IT-Wavenet-F", -- Male
|
||||
["it_IT_Wavenet_E"] = "it-IT-Wavenet-E", -- Female
|
||||
["it_IT_Wavenet_F"] = "it-IT-Wavenet-F", -- Male
|
||||
} ,
|
||||
},
|
||||
}
|
||||
@@ -589,7 +747,7 @@ function MSRS:SetBackendSRSEXE()
|
||||
end
|
||||
|
||||
--- Set the default backend.
|
||||
-- @param #MSRS self
|
||||
-- @param #string Backend
|
||||
function MSRS.SetDefaultBackend(Backend)
|
||||
MSRS.backend=Backend or MSRS.Backend.SRSEXE
|
||||
end
|
||||
@@ -945,7 +1103,7 @@ end
|
||||
-- - `MSRS.Provider.WINDOWS`: Microsoft Windows (default)
|
||||
-- - `MSRS.Provider.GOOGLE`: Google Cloud
|
||||
-- - `MSRS.Provider.AZURE`: Microsoft Azure (only with DCS-gRPC backend)
|
||||
-- - `MSRS.Provier.AMAZON`: Amazone Web Service (only with DCS-gRPC backend)
|
||||
-- - `MSRS.Provier.AMAZON`: Amazon Web Service (only with DCS-gRPC backend)
|
||||
--
|
||||
-- Note that all providers except Microsoft Windows need as additonal information the credentials of your account.
|
||||
--
|
||||
@@ -1155,7 +1313,8 @@ function MSRS:PlaySoundFile(Soundfile, Delay)
|
||||
|
||||
-- Append file.
|
||||
command=command..' --file="'..tostring(soundfile)..'"'
|
||||
|
||||
command=string.gsub(command,"--ssml","-h")
|
||||
|
||||
-- Execute command.
|
||||
self:_ExecCommand(command)
|
||||
|
||||
@@ -1376,20 +1535,25 @@ function MSRS:_GetCommand(freqs, modus, coal, gender, voice, culture, volume, sp
|
||||
modus=modus:gsub("1", "FM")
|
||||
|
||||
-- Command.
|
||||
local pwsh = string.format('Start-Process -WindowStyle Hidden -WorkingDirectory \"%s\" -FilePath \"%s\" -ArgumentList \'-f "%s" -m "%s" -c %s -p %s -n "%s" -v "%.1f"', path, exe, freqs, modus, coal, port, label,volume )
|
||||
|
||||
local command=string.format('"%s\\%s" -f "%s" -m "%s" -c %s -p %s -n "%s" -v "%.1f"', path, exe, freqs, modus, coal, port, label,volume)
|
||||
|
||||
-- Set voice or gender/culture.
|
||||
if voice then
|
||||
if voice and self.UsePowerShell ~= true then
|
||||
-- Use a specific voice (no need for gender and/or culture.
|
||||
command=command..string.format(" --voice=\"%s\"", tostring(voice))
|
||||
pwsh=pwsh..string.format(" --voice=\"%s\"", tostring(voice))
|
||||
else
|
||||
-- Add gender.
|
||||
if gender and gender~="female" then
|
||||
command=command..string.format(" -g %s", tostring(gender))
|
||||
pwsh=pwsh..string.format(" -g %s", tostring(gender))
|
||||
end
|
||||
-- Add culture.
|
||||
if culture and culture~="en-GB" then
|
||||
command=command..string.format(" -l %s", tostring(culture))
|
||||
pwsh=pwsh..string.format(" -l %s", tostring(culture))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1397,16 +1561,18 @@ function MSRS:_GetCommand(freqs, modus, coal, gender, voice, culture, volume, sp
|
||||
if coordinate then
|
||||
local lat,lon,alt=self:_GetLatLongAlt(coordinate)
|
||||
command=command..string.format(" -L %.4f -O %.4f -A %d", lat, lon, alt)
|
||||
pwsh=pwsh..string.format(" -L %.4f -O %.4f -A %d", lat, lon, alt)
|
||||
end
|
||||
|
||||
-- Set provider options
|
||||
if self.provider==MSRS.Provider.GOOGLE then
|
||||
local pops=self:GetProviderOptions()
|
||||
command=command..string.format(' --ssml -G "%s"', pops.credentials)
|
||||
pwsh=pwsh..string.format(' --ssml -G "%s"', pops.credentials)
|
||||
elseif self.provider==MSRS.Provider.WINDOWS then
|
||||
-- Nothing to do.
|
||||
else
|
||||
self:E("ERROR: SRS only supports WINWOWS and GOOGLE as TTS providers! Use DCS-gRPC backend for other providers such as ")
|
||||
self:E("ERROR: SRS only supports WINDOWS and GOOGLE as TTS providers! Use DCS-gRPC backend for other providers such as AWS and Azure.")
|
||||
end
|
||||
|
||||
if not UTILS.FileExists(fullPath) then
|
||||
@@ -1416,8 +1582,12 @@ function MSRS:_GetCommand(freqs, modus, coal, gender, voice, culture, volume, sp
|
||||
|
||||
-- Debug output.
|
||||
self:T("MSRS command from _GetCommand="..command)
|
||||
|
||||
return command
|
||||
|
||||
if self.UsePowerShell == true then
|
||||
return pwsh
|
||||
else
|
||||
return command
|
||||
end
|
||||
end
|
||||
|
||||
--- Execute SRS command to play sound using the `DCS-SR-ExternalAudio.exe`.
|
||||
@@ -1425,7 +1595,7 @@ end
|
||||
-- @param #string command Command to executer
|
||||
-- @return #number Return value of os.execute() command.
|
||||
function MSRS:_ExecCommand(command)
|
||||
self:F( {command=command} )
|
||||
self:T2( {command=command} )
|
||||
|
||||
-- Skip this function if _GetCommand was not able to find the executable
|
||||
if string.find(command, "CommandNotFound") then return 0 end
|
||||
@@ -1433,7 +1603,13 @@ function MSRS:_ExecCommand(command)
|
||||
local batContent = command.." && exit"
|
||||
-- Create a tmp file.
|
||||
local filename=os.getenv('TMP').."\\MSRS-"..MSRS.uuid()..".bat"
|
||||
|
||||
|
||||
if self.UsePowerShell == true then
|
||||
filename=os.getenv('TMP').."\\MSRS-"..MSRS.uuid()..".ps1"
|
||||
batContent = command .. "\'"
|
||||
self:T({batContent=batContent})
|
||||
end
|
||||
|
||||
local script=io.open(filename, "w+")
|
||||
script:write(batContent)
|
||||
script:close()
|
||||
@@ -1442,7 +1618,7 @@ function MSRS:_ExecCommand(command)
|
||||
self:T("MSRS batch content: "..batContent)
|
||||
|
||||
local res=nil
|
||||
if true then
|
||||
if self.UsePowerShell ~= true then
|
||||
|
||||
-- Create a tmp file.
|
||||
local filenvbs = os.getenv('TMP') .. "\\MSRS-"..MSRS.uuid()..".vbs"
|
||||
@@ -1470,23 +1646,20 @@ function MSRS:_ExecCommand(command)
|
||||
timer.scheduleFunction(os.remove, filenvbs, timer.getTime()+1)
|
||||
self:T("MSRS vbs and batch file removed")
|
||||
|
||||
elseif false then
|
||||
|
||||
-- Create a tmp file.
|
||||
local filenvbs = os.getenv('TMP') .. "\\MSRS-"..MSRS.uuid()..".vbs"
|
||||
|
||||
-- VBS script
|
||||
local script = io.open(filenvbs, "w+")
|
||||
script:write(string.format('Set oShell = CreateObject ("Wscript.Shell")\n'))
|
||||
script:write(string.format('Dim strArgs\n'))
|
||||
script:write(string.format('strArgs = "cmd /c %s"\n', filename))
|
||||
script:write(string.format('oShell.Run strArgs, 0, false'))
|
||||
script:close()
|
||||
|
||||
local runvbs=string.format('cscript.exe //Nologo //B "%s"', filenvbs)
|
||||
elseif self.UsePowerShell == true then
|
||||
|
||||
local pwsh = string.format('start /min "" powershell.exe -ExecutionPolicy Unrestricted -WindowStyle Hidden -Command "%s"',filename)
|
||||
--env.info("[MSRS] TextToSpeech Command :\n" .. pwsh.."\n")
|
||||
|
||||
if string.len(pwsh) > 255 then
|
||||
self:E("[MSRS] - pwsh string too long")
|
||||
end
|
||||
|
||||
-- Play file in 0.01 seconds
|
||||
res=os.execute(runvbs)
|
||||
res=os.execute(pwsh)
|
||||
|
||||
-- Remove file in 1 second.
|
||||
timer.scheduleFunction(os.remove, filename, timer.getTime()+1)
|
||||
|
||||
else
|
||||
-- Play command.
|
||||
@@ -1617,11 +1790,11 @@ function MSRS:_DCSgRPCtts(Text, Frequencies, Gender, Culture, Voice, Volume, Lab
|
||||
ssml=string.format("<voice%s%s>%s</voice>", gender, language, Text)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
for _,freq in pairs(Frequencies) do
|
||||
self:F("Calling GRPC.tts with the following parameter:")
|
||||
self:F({ssml=ssml, freq=freq, options=options})
|
||||
self:F(options.provider[provider])
|
||||
self:T("Calling GRPC.tts with the following parameter:")
|
||||
self:T({ssml=ssml, freq=freq, options=options})
|
||||
self:T(options.provider[provider])
|
||||
GRPC.tts(ssml, freq*1e6, options)
|
||||
end
|
||||
|
||||
@@ -1943,7 +2116,7 @@ end
|
||||
-- @param Core.Point#COORDINATE coordinate Coordinate to be used
|
||||
-- @return #MSRSQUEUE.Transmission Radio transmission table.
|
||||
function MSRSQUEUE:NewTransmission(text, duration, msrs, tstart, interval, subgroups, subtitle, subduration, frequency, modulation, gender, culture, voice, volume, label,coordinate)
|
||||
|
||||
self:T({Text=text, Dur=duration, start=tstart, int=interval, sub=subgroups, subt=subtitle, sudb=subduration, F=frequency, M=modulation, G=gender, C=culture, V=voice, Vol=volume, L=label})
|
||||
if self.TransmitOnlyWithPlayers then
|
||||
if self.PlayerSet and self.PlayerSet:CountAlive() == 0 then
|
||||
return self
|
||||
@@ -1983,7 +2156,7 @@ function MSRSQUEUE:NewTransmission(text, duration, msrs, tstart, interval, subgr
|
||||
transmission.volume = volume or msrs.volume
|
||||
transmission.label = label or msrs.Label
|
||||
transmission.coordinate = coordinate or msrs.coordinate
|
||||
|
||||
|
||||
-- Add transmission to queue.
|
||||
self:AddTransmission(transmission)
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
-- ## 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)
|
||||
-- * Create a SOUNDTEXT object for text-to-speech output vis SRS Simple-Text-To-Speech (MSRS)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
|
||||
@@ -580,6 +580,18 @@ ENUMS.Link16Power = {
|
||||
--- Enums for the STORAGE class for stores - which need to be in ""
|
||||
-- @type ENUMS.Storage
|
||||
-- @type ENUMS.Storage.weapons
|
||||
-- @type ENUMS.Storage.weapons.missiles
|
||||
-- @type ENUMS.Storage.weapons.bombs
|
||||
-- @type ENUMS.Storage.weapons.nurs
|
||||
-- @type ENUMS.Storage.weapons.containers
|
||||
-- @type ENUMS.Storage.weapons.droptanks
|
||||
-- @type ENUMS.Storage.weapons.adapters
|
||||
-- @type ENUMS.Storage.weapons.torpedoes
|
||||
-- @type ENUMS.Storage.weapons.Gazelle
|
||||
-- @type ENUMS.Storage.weapons.CH47
|
||||
-- @type ENUMS.Storage.weapons.OH58
|
||||
-- @type ENUMS.Storage.weapons.UH1H
|
||||
-- @type ENUMS.Storage.weapons.AH64D
|
||||
ENUMS.Storage = {
|
||||
weapons = {
|
||||
missiles = {}, -- Missiles
|
||||
@@ -589,6 +601,11 @@ ENUMS.Storage = {
|
||||
droptanks = {}, -- Droptanks
|
||||
adapters = {}, -- Adapter
|
||||
torpedoes = {}, -- Torpedoes
|
||||
Gazelle = {}, -- Gazelle specifics
|
||||
CH47 = {}, -- Chinook specifics
|
||||
OH58 = {}, -- Kiowa specifics
|
||||
UH1H = {}, -- Huey specifics
|
||||
AH64D = {}, -- Huey specifics
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1148,4 +1165,197 @@ ENUMS.Storage.weapons.bombs.BDU_50LD = "weapons.bombs.BDU_50LD"
|
||||
ENUMS.Storage.weapons.bombs.AGM_62 = "weapons.bombs.AGM_62"
|
||||
ENUMS.Storage.weapons.containers.US_M10_SMOKE_TANK_WHITE = "weapons.containers.{US_M10_SMOKE_TANK_WHITE}"
|
||||
ENUMS.Storage.weapons.missiles.MICA_T = "weapons.missiles.MICA_T"
|
||||
ENUMS.Storage.weapons.containers.HVAR_rocket = "weapons.containers.HVAR_rocket"
|
||||
ENUMS.Storage.weapons.containers.HVAR_rocket = "weapons.containers.HVAR_rocket"
|
||||
-- 2025
|
||||
ENUMS.Storage.weapons.containers.LANTIRN = "weapons.containers.LANTIRN"
|
||||
ENUMS.Storage.weapons.missiles.AGM_78B = "weapons.missiles.AGM_78B"
|
||||
ENUMS.Storage.weapons.containers.uh_60l_pilot = "weapons.containers.uh-60l_pilot"
|
||||
ENUMS.Storage.weapons.missiles.AIM_92E = "weapons.missiles.AIM-92E"
|
||||
ENUMS.Storage.weapons.missiles.KD_63B = "weapons.missiles.KD_63B"
|
||||
ENUMS.Storage.weapons.bombs.Type_200A = "weapons.bombs.Type_200A"
|
||||
ENUMS.Storage.weapons.missiles.HB_AIM_7E_2 = "weapons.missiles.HB-AIM-7E-2"
|
||||
ENUMS.Storage.weapons.containers.Spear = "weapons.containers.Spear"
|
||||
ENUMS.Storage.weapons.missiles.LS_6 = "weapons.missiles.LS_6"
|
||||
ENUMS.Storage.weapons.containers.HB_ALE_40_0_120 = "weapons.containers.HB_ALE_40_0_120"
|
||||
ENUMS.Storage.weapons.containers.Fantasm = "weapons.containers.Fantasm"
|
||||
ENUMS.Storage.weapons.nurs.FFAR_Mk61 = "weapons.nurs.FFAR_Mk61"
|
||||
ENUMS.Storage.weapons.bombs.HB_F4E_GBU15V1 = "weapons.bombs.HB_F4E_GBU15V1"
|
||||
ENUMS.Storage.weapons.containers.HB_F14_EXT_AN_APQ_167 = "weapons.containers.HB_F14_EXT_AN_APQ-167"
|
||||
ENUMS.Storage.weapons.nurs.LWL_RP = "weapons.nurs.LWL_RP"
|
||||
ENUMS.Storage.weapons.bombs.AGM_62_I = "weapons.bombs.AGM_62_I"
|
||||
ENUMS.Storage.weapons.containers.ETHER = "weapons.containers.ETHER"
|
||||
ENUMS.Storage.weapons.containers.TANGAZH = "weapons.containers.TANGAZH"
|
||||
ENUMS.Storage.weapons.bombs.LYSBOMB_11086 = "weapons.bombs.LYSBOMB 11086"
|
||||
ENUMS.Storage.weapons.containers.Stub_Wing = "weapons.containers.Stub_Wing"
|
||||
ENUMS.Storage.weapons.missiles.AIM_9E = "weapons.missiles.AIM-9E"
|
||||
ENUMS.Storage.weapons.missiles.C_701T = "weapons.missiles.C_701T"
|
||||
ENUMS.Storage.weapons.bombs.BAP_100 = "weapons.bombs.BAP_100"
|
||||
ENUMS.Storage.weapons.missiles.CM_802AKG = "weapons.missiles.CM-802AKG"
|
||||
ENUMS.Storage.weapons.missiles.CM_400AKG = "weapons.missiles.CM-400AKG"
|
||||
ENUMS.Storage.weapons.missiles.C_802AK = "weapons.missiles.C_802AK"
|
||||
ENUMS.Storage.weapons.missiles.KD_63 = "weapons.missiles.KD_63"
|
||||
ENUMS.Storage.weapons.containers.HB_ORD_Pave_Spike_Fast = "weapons.containers.HB_ORD_Pave_Spike_Fast"
|
||||
ENUMS.Storage.weapons.missiles.SPIKE_ER2 = "weapons.missiles.SPIKE_ER2"
|
||||
ENUMS.Storage.weapons.containers.KINGAL = "weapons.containers.KINGAL"
|
||||
ENUMS.Storage.weapons.containers.LANTIRN_F14_TARGET = "weapons.containers.LANTIRN-F14-TARGET"
|
||||
ENUMS.Storage.weapons.containers.SPS_141 = "weapons.containers.SPS-141"
|
||||
ENUMS.Storage.weapons.bombs.BLU_3B_GROUP = "weapons.bombs.BLU-3B_GROUP"
|
||||
ENUMS.Storage.weapons.containers.HB_ALE_40_30_0 = "weapons.containers.HB_ALE_40_30_0"
|
||||
ENUMS.Storage.weapons.droptanks.HB_HIGH_PERFORMANCE_CENTERLINE_600_GAL = "weapons.droptanks.HB_HIGH_PERFORMANCE_CENTERLINE_600_GAL"
|
||||
ENUMS.Storage.weapons.containers.ALQ_184 = "weapons.containers.ALQ-184"
|
||||
ENUMS.Storage.weapons.missiles.AGM_45B = "weapons.missiles.AGM_45B"
|
||||
ENUMS.Storage.weapons.bombs.BLU_3_GROUP = "weapons.bombs.BLU-3_GROUP"
|
||||
ENUMS.Storage.weapons.missiles.SPIKE_ER = "weapons.missiles.SPIKE_ER"
|
||||
ENUMS.Storage.weapons.nurs.ARAKM70BAPPX = "weapons.nurs.ARAKM70BAPPX"
|
||||
ENUMS.Storage.weapons.bombs.LYSBOMB_11088 = "weapons.bombs.LYSBOMB 11088"
|
||||
ENUMS.Storage.weapons.bombs.LYSBOMB_11087 = "weapons.bombs.LYSBOMB 11087"
|
||||
ENUMS.Storage.weapons.missiles.KD_20 = "weapons.missiles.KD_20"
|
||||
ENUMS.Storage.weapons.droptanks.HB_F_4E_EXT_WingTank = "weapons.droptanks.HB_F-4E_EXT_WingTank"
|
||||
ENUMS.Storage.weapons.missiles.Rb_04 = "weapons.missiles.Rb_04"
|
||||
ENUMS.Storage.weapons.containers.AAQ_33 = "weapons.containers.AAQ-33"
|
||||
ENUMS.Storage.weapons.droptanks.HB_F_4E_EXT_Center_Fuel_Tank_EMPTY = "weapons.droptanks.HB_F-4E_EXT_Center_Fuel_Tank_EMPTY"
|
||||
ENUMS.Storage.weapons.droptanks.HB_F_4E_EXT_WingTank_R_EMPTY = "weapons.droptanks.HB_F-4E_EXT_WingTank_R_EMPTY"
|
||||
ENUMS.Storage.weapons.droptanks.HB_F_4E_EXT_WingTank_EMPTY = "weapons.droptanks.HB_F-4E_EXT_WingTank_EMPTY"
|
||||
ENUMS.Storage.weapons.containers.uh_60l_copilot = "weapons.containers.uh-60l_copilot"
|
||||
ENUMS.Storage.weapons.droptanks.JAYHAWK_80gal_Fuel_Tankv2 = "weapons.droptanks.JAYHAWK_80gal_Fuel_Tankv2"
|
||||
ENUMS.Storage.weapons.containers.supply_m134 = "weapons.containers.supply_m134"
|
||||
ENUMS.Storage.weapons.containers.Seahawk_Pylon = "weapons.containers.Seahawk_Pylon"
|
||||
ENUMS.Storage.weapons.nurs.LWL_MPP = "weapons.nurs.LWL_MPP"
|
||||
ENUMS.Storage.weapons.nurs.S_5KP = "weapons.nurs.S_5KP"
|
||||
ENUMS.Storage.weapons.missiles.AIM_92J = "weapons.missiles.AIM-92J"
|
||||
ENUMS.Storage.weapons.missiles.HB_AIM_7E = "weapons.missiles.HB-AIM-7E"
|
||||
ENUMS.Storage.weapons.containers.ALQ_131 = "weapons.containers.ALQ-131"
|
||||
ENUMS.Storage.weapons.containers.HB_F14_EXT_TARPS = "weapons.containers.HB_F14_EXT_TARPS"
|
||||
ENUMS.Storage.weapons.containers.MH60_SOAR = "weapons.containers.MH60_SOAR"
|
||||
ENUMS.Storage.weapons.missiles.YJ_83 = "weapons.missiles.YJ-83"
|
||||
ENUMS.Storage.weapons.bombs.GBU_8_B = "weapons.bombs.GBU_8_B"
|
||||
ENUMS.Storage.weapons.containers.HB_F14_EXT_ECA = "weapons.containers.HB_F14_EXT_ECA"
|
||||
ENUMS.Storage.weapons.bombs.BAP_100 = "weapons.bombs.BAP-100"
|
||||
ENUMS.Storage.weapons.nurs.M261_MPSM_Rocket = "weapons.nurs.M261_MPSM_Rocket"
|
||||
ENUMS.Storage.weapons.droptanks.SEAHAWK_120_Fuel_Tank = "weapons.droptanks.SEAHAWK_120_Fuel_Tank"
|
||||
ENUMS.Storage.weapons.containers.SHPIL = "weapons.containers.SHPIL"
|
||||
ENUMS.Storage.weapons.bombs.GBU_39 = "weapons.bombs.GBU_39"
|
||||
ENUMS.Storage.weapons.nurs.S_5M = "weapons.nurs.S_5M"
|
||||
ENUMS.Storage.weapons.containers.HB_ALE_40_15_90 = "weapons.containers.HB_ALE_40_15_90"
|
||||
ENUMS.Storage.weapons.missiles.AIM_7E = "weapons.missiles.AIM-7E"
|
||||
ENUMS.Storage.weapons.missiles.AIM_9P3 = "weapons.missiles.AIM-9P3"
|
||||
ENUMS.Storage.weapons.missiles.AGM_12B = "weapons.missiles.AGM_12B"
|
||||
ENUMS.Storage.weapons.missiles.CM_802AKG = "weapons.missiles.CM_802AKG"
|
||||
ENUMS.Storage.weapons.droptanks.JAYHAWK_120_Fuel_Dual_Tank = "weapons.droptanks.JAYHAWK_120_Fuel_Dual_Tank"
|
||||
ENUMS.Storage.weapons.droptanks.HB_F_4E_EXT_Center_Fuel_Tank = "weapons.droptanks.HB_F-4E_EXT_Center_Fuel_Tank"
|
||||
ENUMS.Storage.weapons.containers.PAVETACK = "weapons.containers.PAVETACK"
|
||||
ENUMS.Storage.weapons.missiles.LS_6_500 = "weapons.missiles.LS_6_500"
|
||||
ENUMS.Storage.weapons.bombs.LYSBOMB_11089 = "weapons.bombs.LYSBOMB 11089"
|
||||
ENUMS.Storage.weapons.bombs.BLU_4B_GROUP = "weapons.bombs.BLU-4B_GROUP"
|
||||
ENUMS.Storage.weapons.containers.ah_64d_radar = "weapons.containers.ah-64d_radar"
|
||||
ENUMS.Storage.weapons.containers.F_18_LDT_POD = "weapons.containers.F-18-LDT-POD"
|
||||
ENUMS.Storage.weapons.containers.HB_ALE_40_30_60 = "weapons.containers.HB_ALE_40_30_60"
|
||||
ENUMS.Storage.weapons.bombs.LS_6_100 = "weapons.bombs.LS_6_100"
|
||||
ENUMS.Storage.weapons.droptanks.HB_F_4E_EXT_WingTank_R = "weapons.droptanks.HB_F-4E_EXT_WingTank_R"
|
||||
ENUMS.Storage.weapons.containers.SORBCIJA_R = "weapons.containers.SORBCIJA_R"
|
||||
ENUMS.Storage.weapons.missiles.CATM_65K = "weapons.missiles.CATM_65K"
|
||||
ENUMS.Storage.weapons.containers.HB_ORD_Pave_Spike = "weapons.containers.HB_ORD_Pave_Spike"
|
||||
ENUMS.Storage.weapons.containers.RobbieTank1 = "weapons.containers.RobbieTank1"
|
||||
ENUMS.Storage.weapons.containers.SKY_SHADOW = "weapons.containers.SKY_SHADOW"
|
||||
ENUMS.Storage.weapons.containers.SORBCIJA_L = "weapons.containers.SORBCIJA_L"
|
||||
ENUMS.Storage.weapons.containers.Pavehawk = "weapons.containers.Pavehawk"
|
||||
ENUMS.Storage.weapons.bombs.BLG66_EG = "weapons.bombs.BLG66_EG"
|
||||
ENUMS.Storage.weapons.missiles.AGM_12C_ED = "weapons.missiles.AGM_12C_ED"
|
||||
ENUMS.Storage.weapons.missiles.AIM_92C = "weapons.missiles.AIM-92C"
|
||||
ENUMS.Storage.weapons.containers.MPS_410 = "weapons.containers.MPS-410"
|
||||
ENUMS.Storage.weapons.missiles.HJ_12 = "weapons.missiles.HJ-12"
|
||||
ENUMS.Storage.weapons.containers.AAQ_28_LITENING = "weapons.containers.AAQ-28_LITENING"
|
||||
ENUMS.Storage.weapons.containers.F_18_FLIR_POD = "weapons.containers.F-18-FLIR-POD"
|
||||
ENUMS.Storage.weapons.bombs.BLU_3B_GROUP = "weapons.bombs.BLU_3B_GROUP"
|
||||
ENUMS.Storage.weapons.containers.UH60L_Jayhawk = "weapons.containers.UH60L_Jayhawk"
|
||||
ENUMS.Storage.weapons.containers.BOZ_100 = "weapons.containers.BOZ-100"
|
||||
ENUMS.Storage.weapons.missiles.AGM_78A = "weapons.missiles.AGM_78A"
|
||||
ENUMS.Storage.weapons.missiles.LAU_61_APKWS_M282 = "weapons.missiles.LAU_61_APKWS_M282"
|
||||
ENUMS.Storage.weapons.bombs.BAP_100 = "weapons.bombs.BAP-100"
|
||||
ENUMS.Storage.weapons.missiles.CM_802AKG = "weapons.missiles.CM-802AKG"
|
||||
ENUMS.Storage.weapons.bombs.BLU_3B_GROUP = "weapons.bombs.BLU_3B_GROUP"
|
||||
ENUMS.Storage.weapons.bombs.BLU_4B_GROUP = "weapons.bombs.BLU-4B_GROUP"
|
||||
ENUMS.Storage.weapons.nurs.S_5M = "weapons.nurs.S_5M"
|
||||
ENUMS.Storage.weapons.missiles.AGM_12A = "weapons.missiles.AGM_12A"
|
||||
ENUMS.Storage.weapons.droptanks.JAYHAWK_120_Fuel_Tank = "weapons.droptanks.JAYHAWK_120_Fuel_Tank"
|
||||
ENUMS.Storage.weapons.bombs.GBU_15_V_1_B = "weapons.bombs.GBU_15_V_1_B"
|
||||
ENUMS.Storage.weapons.missiles.HYDRA_70_M151_APKWS = {4,4,8,292}
|
||||
ENUMS.Storage.weapons.missiles.HYDRA_70_M282_APKWS = {4,4,8,293}
|
||||
-- dupes with typos
|
||||
ENUMS.Storage.weapons.bombs.BAP100 = "weapons.bombs.BAP_100"
|
||||
ENUMS.Storage.weapons.bombs.BLU3B_GROUP = "weapons.bombs.BLU-3B_GROUP"
|
||||
ENUMS.Storage.weapons.missiles.CM_802AKG = "weapons.missiles.CM_802AKG"
|
||||
ENUMS.Storage.weapons.bombs.BLU_4B_GROUP = "weapons.bombs.BLU_4B_GROUP"
|
||||
ENUMS.Storage.weapons.nurs.S5M = "weapons.nurs.S-5M"
|
||||
-- Gazelle
|
||||
ENUMS.Storage.weapons.Gazelle.HMP400_100RDS = {4,15,46,1771}
|
||||
ENUMS.Storage.weapons.Gazelle.HMP400_200RDS = {4,15,46,1770}
|
||||
ENUMS.Storage.weapons.Gazelle.HMP400_400RDS = {4,15,46,1769}
|
||||
ENUMS.Storage.weapons.Gazelle.GIAT_M261_AP = {4,15,46,1768}
|
||||
ENUMS.Storage.weapons.Gazelle.GIAT_M261_SAPHEI = {4,15,46,1767}
|
||||
ENUMS.Storage.weapons.Gazelle.GIAT_M261_HE = {4,15,46,1766}
|
||||
ENUMS.Storage.weapons.Gazelle.GIAT_M261_HEAP = {4,15,46,1765}
|
||||
ENUMS.Storage.weapons.Gazelle.GIAT_M261_APHE = {4,15,46,1764}
|
||||
ENUMS.Storage.weapons.Gazelle.GAZELLE_IR_DEFLECTOR = {4,15,47,680}
|
||||
ENUMS.Storage.weapons.Gazelle.GAZELLE_FAS_SANDFILTER = {4,15,47,679}
|
||||
-- Chinook (changed)
|
||||
ENUMS.Storage.weapons.CH47.CH47_PORT_M60D = {4,15,46,2489}
|
||||
ENUMS.Storage.weapons.CH47.CH47_STBD_M60D = {4,15,46,2488}
|
||||
ENUMS.Storage.weapons.CH47.CH47_AFT_M60D = {4,15,46,2490}
|
||||
ENUMS.Storage.weapons.CH47.CH47_PORT_M134D = {4,15,46,2494}
|
||||
ENUMS.Storage.weapons.CH47.CH47_STBD_M134D = {4,15,46,2495}
|
||||
ENUMS.Storage.weapons.CH47.CH47_AFT_M3M = {4,15,46,2496} --
|
||||
ENUMS.Storage.weapons.CH47.CH47_PORT_M240H = {4,15,46,2492}
|
||||
ENUMS.Storage.weapons.CH47.CH47_STBD_M240H = {4,15,46,2491}
|
||||
ENUMS.Storage.weapons.CH47.CH47_AFT_M240H = {4,15,46,2493}
|
||||
-- Huey
|
||||
ENUMS.Storage.weapons.UH1H.M134_MiniGun_Right = {4,15,46,161}
|
||||
ENUMS.Storage.weapons.UH1H.M134_MiniGun_Left = {4,15,46,160}
|
||||
ENUMS.Storage.weapons.UH1H.M134_MiniGun_Right_Door = {4,15,46,175}
|
||||
ENUMS.Storage.weapons.UH1H.M60_MG_Right_Door = {4,15,46,177}
|
||||
ENUMS.Storage.weapons.UH1H.M134_MiniGun_Left_Door = {4,15,46,174}
|
||||
ENUMS.Storage.weapons.UH1H.M60_MG_Left_Door = {4,15,46,176}
|
||||
-- Kiowa
|
||||
ENUMS.Storage.weapons.OH58.FIM92 = {4,4,7,449}
|
||||
ENUMS.Storage.weapons.OH58.MG_M3P100 = {4,15,46,2611}
|
||||
ENUMS.Storage.weapons.OH58.MG_M3P200 = {4,15,46,2610}
|
||||
ENUMS.Storage.weapons.OH58.MG_M3P300 = {4,15,46,2609}
|
||||
ENUMS.Storage.weapons.OH58.MG_M3P400 = {4,15,46,2608}
|
||||
ENUMS.Storage.weapons.OH58.MG_M3P500 = {4,15,46,2607}
|
||||
ENUMS.Storage.weapons.OH58.Smk_Grenade_Blue = {4,5,9,488}
|
||||
ENUMS.Storage.weapons.OH58.Smk_Grenade_Green = {4,5,9,489}
|
||||
ENUMS.Storage.weapons.OH58.Smk_Grenade_Red = {4,5,9,487}
|
||||
ENUMS.Storage.weapons.OH58.Smk_Grenade_Violet = {4,5,9,490}
|
||||
ENUMS.Storage.weapons.OH58.Smk_Grenade_White = {4,5,9,492}
|
||||
ENUMS.Storage.weapons.OH58.Smk_Grenade_Yellow = {4,5,9,491}
|
||||
-- Apache
|
||||
ENUMS.Storage.weapons.AH64D.AN_APG78 = {4,15,44,2114}
|
||||
ENUMS.Storage.weapons.AH64D.Internal_Aux_FuelTank = {1,3,43,1700}
|
||||
|
||||
---
|
||||
-- @type ENUMS.FARPType
|
||||
-- @field #string FARP
|
||||
-- @field #string INVISIBLE
|
||||
-- @field #string HELIPADSINGLE
|
||||
-- @field #string PADSINGLE
|
||||
ENUMS.FARPType = {
|
||||
FARP = "FARP",
|
||||
INVISIBLE = "INVISIBLE",
|
||||
HELIPADSINGLE = "HELIPADSINGLE",
|
||||
PADSINGLE = "PADSINGLE",
|
||||
}
|
||||
|
||||
|
||||
---
|
||||
-- @type ENUMS.FARPObjectTypeNamesAndShape
|
||||
-- @field #string FARP
|
||||
-- @field #string INVISIBLE
|
||||
-- @field #string HELIPADSINGLE
|
||||
-- @field #string PADSINGLE
|
||||
ENUMS.FARPObjectTypeNamesAndShape ={
|
||||
[ENUMS.FARPType.FARP] = { TypeName="FARP", ShapeName="FARPS"},
|
||||
[ENUMS.FARPType.INVISIBLE] = { TypeName="Invisible FARP", ShapeName="invisiblefarp"},
|
||||
[ENUMS.FARPType.HELIPADSINGLE] = { TypeName="SINGLE_HELIPAD", ShapeName="FARP"},
|
||||
[ENUMS.FARPType.PADSINGLE] = { TypeName="FARP_SINGLE_01", ShapeName="FARP_SINGLE_01"},
|
||||
}
|
||||
|
||||
|
||||
@@ -1,259 +0,0 @@
|
||||
--- **Utilities** - DCS Simple Text-To-Speech (STTS).
|
||||
--
|
||||
--
|
||||
-- @module Utilities.STTS
|
||||
-- @image MOOSE.JPG
|
||||
|
||||
--- [DCS Enum world](https://wiki.hoggitworld.com/view/DCS_enum_world)
|
||||
-- @type STTS
|
||||
-- @field #string DIRECTORY Path of the SRS directory.
|
||||
|
||||
--- Simple Text-To-Speech
|
||||
--
|
||||
-- Version 0.4 - Compatible with SRS version 1.9.6.0+
|
||||
--
|
||||
-- # DCS Modification Required
|
||||
--
|
||||
-- You will need to edit MissionScripting.lua in DCS World/Scripts/MissionScripting.lua and remove the sanitization.
|
||||
-- To do this remove all the code below the comment - the line starts "local function sanitizeModule(name)"
|
||||
-- Do this without DCS running to allow mission scripts to use os functions.
|
||||
--
|
||||
-- *You WILL HAVE TO REAPPLY AFTER EVERY DCS UPDATE*
|
||||
--
|
||||
-- # USAGE:
|
||||
--
|
||||
-- Add this script into the mission as a DO SCRIPT or DO SCRIPT FROM FILE to initialize it
|
||||
-- Make sure to edit the STTS.SRS_PORT and STTS.DIRECTORY to the correct values before adding to the mission.
|
||||
-- Then its as simple as calling the correct function in LUA as a DO SCRIPT or in your own scripts.
|
||||
--
|
||||
-- Example calls:
|
||||
--
|
||||
-- STTS.TextToSpeech("Hello DCS WORLD","251","AM","1.0","SRS",2)
|
||||
--
|
||||
-- Arguments in order are:
|
||||
--
|
||||
-- * Message to say, make sure not to use a newline (\n) !
|
||||
-- * Frequency in MHz
|
||||
-- * Modulation - AM/FM
|
||||
-- * Volume - 1.0 max, 0.5 half
|
||||
-- * Name of the transmitter - ATC, RockFM etc
|
||||
-- * Coalition - 0 spectator, 1 red 2 blue
|
||||
-- * OPTIONAL - Vec3 Point i.e Unit.getByName("A UNIT"):getPoint() - needs Vec3 for Height! OR null if not needed
|
||||
-- * OPTIONAL - Speed -10 to +10
|
||||
-- * OPTIONAL - Gender male, female or neuter
|
||||
-- * OPTIONAL - Culture - en-US, en-GB etc
|
||||
-- * OPTIONAL - Voice - a specific voice by name. Run DCS-SR-ExternalAudio.exe with --help to get the ones you can use on the command line
|
||||
-- * OPTIONAL - Google TTS - Switch to Google Text To Speech - Requires STTS.GOOGLE_CREDENTIALS path and Google project setup correctly
|
||||
--
|
||||
--
|
||||
-- ## Example
|
||||
--
|
||||
-- This example will say the words "Hello DCS WORLD" on 251 MHz AM at maximum volume with a client called SRS and to the Blue coalition only
|
||||
--
|
||||
-- STTS.TextToSpeech("Hello DCS WORLD","251","AM","1.0","SRS",2,null,-5,"male","en-GB")
|
||||
--
|
||||
-- ## Example
|
||||
--
|
||||
-- This example will say the words "Hello DCS WORLD" on 251 MHz AM at maximum volume with a client called SRS and to the Blue coalition only centered on the position of the Unit called "A UNIT"
|
||||
--
|
||||
-- STTS.TextToSpeech("Hello DCS WORLD","251","AM","1.0","SRS",2,Unit.getByName("A UNIT"):getPoint(),-5,"male","en-GB")
|
||||
--
|
||||
-- Arguments in order are:
|
||||
--
|
||||
-- * FULL path to the MP3 OR OGG to play
|
||||
-- * Frequency in MHz - to use multiple separate with a comma - Number of frequencies MUST match number of Modulations
|
||||
-- * Modulation - AM/FM - to use multiple
|
||||
-- * Volume - 1.0 max, 0.5 half
|
||||
-- * Name of the transmitter - ATC, RockFM etc
|
||||
-- * Coalition - 0 spectator, 1 red 2 blue
|
||||
--
|
||||
-- ## Example
|
||||
--
|
||||
-- This will play that MP3 on 255MHz AM & 31 FM at half volume with a client called "Multiple" and to Spectators only
|
||||
--
|
||||
-- STTS.PlayMP3("C:\\Users\\Ciaran\\Downloads\\PR-Music.mp3","255,31","AM,FM","0.5","Multiple",0)
|
||||
--
|
||||
-- @field #STTS
|
||||
STTS = {
|
||||
ClassName = "STTS",
|
||||
DIRECTORY = "",
|
||||
SRS_PORT = 5002,
|
||||
GOOGLE_CREDENTIALS = "C:\\Users\\Ciaran\\Downloads\\googletts.json",
|
||||
EXECUTABLE = "DCS-SR-ExternalAudio.exe"
|
||||
}
|
||||
|
||||
--- FULL Path to the FOLDER containing DCS-SR-ExternalAudio.exe - EDIT TO CORRECT FOLDER
|
||||
STTS.DIRECTORY = "D:/DCS/_SRS"
|
||||
|
||||
--- LOCAL SRS PORT - DEFAULT IS 5002
|
||||
STTS.SRS_PORT = 5002
|
||||
|
||||
--- Google credentials file
|
||||
STTS.GOOGLE_CREDENTIALS = "C:\\Users\\Ciaran\\Downloads\\googletts.json"
|
||||
|
||||
--- DON'T CHANGE THIS UNLESS YOU KNOW WHAT YOU'RE DOING
|
||||
STTS.EXECUTABLE = "DCS-SR-ExternalAudio.exe"
|
||||
|
||||
--- Function for UUID.
|
||||
function STTS.uuid()
|
||||
local random = math.random
|
||||
local template = 'yxxx-xxxxxxxxxxxx'
|
||||
return string.gsub( template, '[xy]', function( c )
|
||||
local v = (c == 'x') and random( 0, 0xf ) or random( 8, 0xb )
|
||||
return string.format( '%x', v )
|
||||
end )
|
||||
end
|
||||
|
||||
--- Round a number.
|
||||
-- @param #number x Number.
|
||||
-- @param #number n Precision.
|
||||
function STTS.round( x, n )
|
||||
n = math.pow( 10, n or 0 )
|
||||
x = x * n
|
||||
if x >= 0 then
|
||||
x = math.floor( x + 0.5 )
|
||||
else
|
||||
x = math.ceil( x - 0.5 )
|
||||
end
|
||||
return x / n
|
||||
end
|
||||
|
||||
--- Function returns estimated speech time in seconds.
|
||||
-- Assumptions for time calc: 100 Words per min, average of 5 letters for english word so
|
||||
--
|
||||
-- * 5 chars * 100wpm = 500 characters per min = 8.3 chars per second
|
||||
--
|
||||
-- So length 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 #number length can also be passed as #string
|
||||
-- @param #number speed Defaults to 1.0
|
||||
-- @param #boolean isGoogle We're using Google TTS
|
||||
function STTS.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
|
||||
|
||||
local wpm = math.ceil( 100 * speedFactor )
|
||||
local cps = math.floor( (wpm * 5) / 60 )
|
||||
|
||||
if type( length ) == "string" then
|
||||
length = string.len( length )
|
||||
end
|
||||
|
||||
return length/cps --math.ceil(length/cps)
|
||||
end
|
||||
|
||||
--- Text to speech function.
|
||||
function STTS.TextToSpeech( message, freqs, modulations, volume, name, coalition, point, speed, gender, culture, voice, googleTTS )
|
||||
if os == nil or io == nil then
|
||||
env.info( "[DCS-STTS] LUA modules os or io are sanitized. skipping. " )
|
||||
return
|
||||
end
|
||||
|
||||
speed = speed or 1
|
||||
gender = gender or "female"
|
||||
culture = culture or ""
|
||||
voice = voice or ""
|
||||
coalition = coalition or "0"
|
||||
name = name or "ROBOT"
|
||||
volume = 1
|
||||
speed = 1
|
||||
|
||||
message = message:gsub( "\"", "\\\"" )
|
||||
|
||||
local cmd = string.format( "start /min \"\" /d \"%s\" /b \"%s\" -f %s -m %s -c %s -p %s -n \"%s\" -h", STTS.DIRECTORY, STTS.EXECUTABLE, freqs or "305", modulations or "AM", coalition, STTS.SRS_PORT, name )
|
||||
|
||||
if voice ~= "" then
|
||||
cmd = cmd .. string.format( " -V \"%s\"", voice )
|
||||
else
|
||||
|
||||
if culture ~= "" then
|
||||
cmd = cmd .. string.format( " -l %s", culture )
|
||||
end
|
||||
|
||||
if gender ~= "" then
|
||||
cmd = cmd .. string.format( " -g %s", gender )
|
||||
end
|
||||
end
|
||||
|
||||
if googleTTS == true then
|
||||
cmd = cmd .. string.format( " -G \"%s\"", STTS.GOOGLE_CREDENTIALS )
|
||||
end
|
||||
|
||||
if speed ~= 1 then
|
||||
cmd = cmd .. string.format( " -s %s", speed )
|
||||
end
|
||||
|
||||
if volume ~= 1.0 then
|
||||
cmd = cmd .. string.format( " -v %s", volume )
|
||||
end
|
||||
|
||||
if point and type( point ) == "table" and point.x then
|
||||
local lat, lon, alt = coord.LOtoLL( point )
|
||||
|
||||
lat = STTS.round( lat, 4 )
|
||||
lon = STTS.round( lon, 4 )
|
||||
alt = math.floor( alt )
|
||||
|
||||
cmd = cmd .. string.format( " -L %s -O %s -A %s", lat, lon, alt )
|
||||
end
|
||||
|
||||
cmd = cmd .. string.format( " -t \"%s\"", message )
|
||||
|
||||
if string.len( cmd ) > 255 then
|
||||
local filename = os.getenv( 'TMP' ) .. "\\DCS_STTS-" .. STTS.uuid() .. ".bat"
|
||||
local script = io.open( filename, "w+" )
|
||||
script:write( cmd .. " && exit" )
|
||||
script:close()
|
||||
cmd = string.format( "\"%s\"", filename )
|
||||
timer.scheduleFunction( os.remove, filename, timer.getTime() + 1 )
|
||||
end
|
||||
|
||||
if string.len( cmd ) > 255 then
|
||||
env.info( "[DCS-STTS] - cmd string too long" )
|
||||
env.info( "[DCS-STTS] TextToSpeech Command :\n" .. cmd .. "\n" )
|
||||
end
|
||||
os.execute( cmd )
|
||||
|
||||
return STTS.getSpeechTime( message, speed, googleTTS )
|
||||
end
|
||||
|
||||
--- Play mp3 function.
|
||||
-- @param #string pathToMP3 Path to the sound file.
|
||||
-- @param #string freqs Frequencies, e.g. "305, 256".
|
||||
-- @param #string modulations Modulations, e.g. "AM, FM".
|
||||
-- @param #string volume Volume, e.g. "0.5".
|
||||
function STTS.PlayMP3( pathToMP3, freqs, modulations, volume, name, coalition, point )
|
||||
|
||||
local cmd = string.format( "start \"\" /d \"%s\" /b /min \"%s\" -i \"%s\" -f %s -m %s -c %s -p %s -n \"%s\" -v %s -h", STTS.DIRECTORY, STTS.EXECUTABLE, pathToMP3, freqs or "305", modulations or "AM", coalition or "0", STTS.SRS_PORT, name or "ROBOT", volume or "1" )
|
||||
|
||||
if point and type( point ) == "table" and point.x then
|
||||
local lat, lon, alt = coord.LOtoLL( point )
|
||||
|
||||
lat = STTS.round( lat, 4 )
|
||||
lon = STTS.round( lon, 4 )
|
||||
alt = math.floor( alt )
|
||||
|
||||
cmd = cmd .. string.format( " -L %s -O %s -A %s", lat, lon, alt )
|
||||
end
|
||||
|
||||
env.info( "[DCS-STTS] MP3/OGG Command :\n" .. cmd .. "\n" )
|
||||
os.execute( cmd )
|
||||
|
||||
end
|
||||
@@ -1,612 +0,0 @@
|
||||
--- **Utilities** - Templates.
|
||||
--
|
||||
-- DCS unit templates
|
||||
--
|
||||
-- @module Utilities.Templates
|
||||
-- @image MOOSE.JPG
|
||||
|
||||
--- TEMPLATE class.
|
||||
-- @type TEMPLATE
|
||||
-- @field #string ClassName Name of the class.
|
||||
|
||||
--- *Templates*
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- 
|
||||
--
|
||||
-- Get DCS templates from thin air.
|
||||
--
|
||||
-- # Ground Units
|
||||
--
|
||||
-- Ground units.
|
||||
--
|
||||
-- # Naval Units
|
||||
--
|
||||
-- Ships are not implemented yet.
|
||||
--
|
||||
-- # Aircraft
|
||||
--
|
||||
-- ## Airplanes
|
||||
--
|
||||
-- Airplanes are not implemented yet.
|
||||
--
|
||||
-- ## Helicopters
|
||||
--
|
||||
-- Helicopters are not implemented yet.
|
||||
--
|
||||
-- @field #TEMPLATE
|
||||
TEMPLATE = {
|
||||
ClassName = "TEMPLATE",
|
||||
Ground = {},
|
||||
Naval = {},
|
||||
Airplane = {},
|
||||
Helicopter = {},
|
||||
}
|
||||
|
||||
--- Ground unit type names.
|
||||
-- @type TEMPLATE.TypeGround
|
||||
-- @param #string InfantryAK
|
||||
TEMPLATE.TypeGround={
|
||||
InfantryAK="Infantry AK",
|
||||
ParatrooperAKS74="Paratrooper AKS-74",
|
||||
ParatrooperRPG16="Paratrooper RPG-16",
|
||||
SoldierWWIIUS="soldier_wwii_us",
|
||||
InfantryM248="Infantry M249",
|
||||
SoldierM4="Soldier M4",
|
||||
}
|
||||
|
||||
--- Naval unit type names.
|
||||
-- @type TEMPLATE.TypeNaval
|
||||
-- @param #string Ticonderoga
|
||||
TEMPLATE.TypeNaval={
|
||||
Ticonderoga="TICONDEROG",
|
||||
}
|
||||
|
||||
--- Rotary wing unit type names.
|
||||
-- @type TEMPLATE.TypeAirplane
|
||||
-- @param #string A10C
|
||||
TEMPLATE.TypeAirplane={
|
||||
A10C="A-10C",
|
||||
}
|
||||
|
||||
--- Rotary wing unit type names.
|
||||
-- @type TEMPLATE.TypeHelicopter
|
||||
-- @param #string AH1W
|
||||
TEMPLATE.TypeHelicopter={
|
||||
AH1W="AH-1W",
|
||||
}
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Ground Template
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Get template for ground units.
|
||||
-- @param #string TypeName Type name of the unit(s) in the groups. See `TEMPLATE.Ground`.
|
||||
-- @param #string GroupName Name of the spawned group. **Must be unique!**
|
||||
-- @param #number CountryID Country ID. Default `country.id.USA`. Coalition is automatically determined by the one the country belongs to.
|
||||
-- @param DCS#Vec3 Vec3 Position of the group and the first unit.
|
||||
-- @param #number Nunits Number of units. Default 1.
|
||||
-- @param #number Radius Spawn radius for additonal units in meters. Default 50 m.
|
||||
-- @return #table Template Template table.
|
||||
function TEMPLATE.GetGround(TypeName, GroupName, CountryID, Vec3, Nunits, Radius)
|
||||
|
||||
-- Defaults.
|
||||
TypeName=TypeName or TEMPLATE.TypeGround.SoldierM4
|
||||
GroupName=GroupName or "Ground-1"
|
||||
CountryID=CountryID or country.id.USA
|
||||
Vec3=Vec3 or {x=0, y=0, z=0}
|
||||
Nunits=Nunits or 1
|
||||
Radius=Radius or 50
|
||||
|
||||
|
||||
-- Get generic template.
|
||||
local template=UTILS.DeepCopy(TEMPLATE.GenericGround)
|
||||
|
||||
-- Set group name.
|
||||
template.name=GroupName
|
||||
|
||||
-- These are additional entries required by the MOOSE _DATABASE:Spawn() function.
|
||||
template.CountryID=CountryID
|
||||
template.CoalitionID=coalition.getCountryCoalition(template.CountryID)
|
||||
template.CategoryID=Unit.Category.GROUND_UNIT
|
||||
|
||||
-- Set first unit.
|
||||
template.units[1].type=TypeName
|
||||
template.units[1].name=GroupName.."-1"
|
||||
|
||||
if Vec3 then
|
||||
TEMPLATE.SetPositionFromVec3(template, Vec3)
|
||||
end
|
||||
|
||||
TEMPLATE.SetUnits(template, Nunits, COORDINATE:NewFromVec3(Vec3), Radius)
|
||||
|
||||
return template
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Naval Template
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Get template for ground units.
|
||||
-- @param #string TypeName Type name of the unit(s) in the groups. See `TEMPLATE.Ground`.
|
||||
-- @param #string GroupName Name of the spawned group. **Must be unique!**
|
||||
-- @param #number CountryID Country ID. Default `country.id.USA`. Coalition is automatically determined by the one the country belongs to.
|
||||
-- @param DCS#Vec3 Vec3 Position of the group and the first unit.
|
||||
-- @param #number Nunits Number of units. Default 1.
|
||||
-- @param #number Radius Spawn radius for additonal units in meters. Default 500 m.
|
||||
-- @return #table Template Template table.
|
||||
function TEMPLATE.GetNaval(TypeName, GroupName, CountryID, Vec3, Nunits, Radius)
|
||||
|
||||
-- Defaults.
|
||||
TypeName=TypeName or TEMPLATE.TypeNaval.Ticonderoga
|
||||
GroupName=GroupName or "Naval-1"
|
||||
CountryID=CountryID or country.id.USA
|
||||
Vec3=Vec3 or {x=0, y=0, z=0}
|
||||
Nunits=Nunits or 1
|
||||
Radius=Radius or 500
|
||||
|
||||
|
||||
-- Get generic template.
|
||||
local template=UTILS.DeepCopy(TEMPLATE.GenericNaval)
|
||||
|
||||
-- Set group name.
|
||||
template.name=GroupName
|
||||
|
||||
-- These are additional entries required by the MOOSE _DATABASE:Spawn() function.
|
||||
template.CountryID=CountryID
|
||||
template.CoalitionID=coalition.getCountryCoalition(template.CountryID)
|
||||
template.CategoryID=Unit.Category.SHIP
|
||||
|
||||
-- Set first unit.
|
||||
template.units[1].type=TypeName
|
||||
template.units[1].name=GroupName.."-1"
|
||||
|
||||
if Vec3 then
|
||||
TEMPLATE.SetPositionFromVec3(template, Vec3)
|
||||
end
|
||||
|
||||
TEMPLATE.SetUnits(template, Nunits, COORDINATE:NewFromVec3(Vec3), Radius)
|
||||
|
||||
return template
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Aircraft Template
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Get template for fixed wing units.
|
||||
-- @param #string TypeName Type name of the unit(s) in the groups. See `TEMPLATE.Ground`.
|
||||
-- @param #string GroupName Name of the spawned group. **Must be unique!**
|
||||
-- @param #number CountryID Country ID. Default `country.id.USA`. Coalition is automatically determined by the one the country belongs to.
|
||||
-- @param DCS#Vec3 Vec3 Position of the group and the first unit.
|
||||
-- @param #number Nunits Number of units. Default 1.
|
||||
-- @param #number Radius Spawn radius for additonal units in meters. Default 500 m.
|
||||
-- @return #table Template Template table.
|
||||
function TEMPLATE.GetAirplane(TypeName, GroupName, CountryID, Vec3, Nunits, Radius)
|
||||
|
||||
-- Defaults.
|
||||
TypeName=TypeName or TEMPLATE.TypeAirplane.A10C
|
||||
GroupName=GroupName or "Airplane-1"
|
||||
CountryID=CountryID or country.id.USA
|
||||
Vec3=Vec3 or {x=0, y=1000, z=0}
|
||||
Nunits=Nunits or 1
|
||||
Radius=Radius or 100
|
||||
|
||||
local template=TEMPLATE._GetAircraft(true, TypeName, GroupName, CountryID, Vec3, Nunits, Radius)
|
||||
|
||||
return template
|
||||
end
|
||||
|
||||
--- Get template for fixed wing units.
|
||||
-- @param #string TypeName Type name of the unit(s) in the groups. See `TEMPLATE.Ground`.
|
||||
-- @param #string GroupName Name of the spawned group. **Must be unique!**
|
||||
-- @param #number CountryID Country ID. Default `country.id.USA`. Coalition is automatically determined by the one the country belongs to.
|
||||
-- @param DCS#Vec3 Vec3 Position of the group and the first unit.
|
||||
-- @param #number Nunits Number of units. Default 1.
|
||||
-- @param #number Radius Spawn radius for additonal units in meters. Default 500 m.
|
||||
-- @return #table Template Template table.
|
||||
function TEMPLATE.GetHelicopter(TypeName, GroupName, CountryID, Vec3, Nunits, Radius)
|
||||
|
||||
-- Defaults.
|
||||
TypeName=TypeName or TEMPLATE.TypeHelicopter.AH1W
|
||||
GroupName=GroupName or "Helicopter-1"
|
||||
CountryID=CountryID or country.id.USA
|
||||
Vec3=Vec3 or {x=0, y=500, z=0}
|
||||
Nunits=Nunits or 1
|
||||
Radius=Radius or 100
|
||||
|
||||
-- Limit unis to 4.
|
||||
Nunits=math.min(Nunits, 4)
|
||||
|
||||
local template=TEMPLATE._GetAircraft(false, TypeName, GroupName, CountryID, Vec3, Nunits, Radius)
|
||||
|
||||
return template
|
||||
end
|
||||
|
||||
|
||||
--- Get template for aircraft units.
|
||||
-- @param #boolean Airplane If true, this is a fixed wing. Else, rotary wing.
|
||||
-- @param #string TypeName Type name of the unit(s) in the groups. See `TEMPLATE.Ground`.
|
||||
-- @param #string GroupName Name of the spawned group. **Must be unique!**
|
||||
-- @param #number CountryID Country ID. Default `country.id.USA`. Coalition is automatically determined by the one the country belongs to.
|
||||
-- @param DCS#Vec3 Vec3 Position of the group and the first unit.
|
||||
-- @param #number Nunits Number of units. Default 1.
|
||||
-- @param #number Radius Spawn radius for additonal units in meters. Default 500 m.
|
||||
-- @return #table Template Template table.
|
||||
function TEMPLATE._GetAircraft(Airplane, TypeName, GroupName, CountryID, Vec3, Nunits, Radius)
|
||||
|
||||
-- Defaults.
|
||||
TypeName=TypeName
|
||||
GroupName=GroupName or "Aircraft-1"
|
||||
CountryID=CountryID or country.id.USA
|
||||
Vec3=Vec3 or {x=0, y=0, z=0}
|
||||
Nunits=Nunits or 1
|
||||
Radius=Radius or 100
|
||||
|
||||
-- Get generic template.
|
||||
local template=UTILS.DeepCopy(TEMPLATE.GenericAircraft)
|
||||
|
||||
-- Set group name.
|
||||
template.name=GroupName
|
||||
|
||||
-- These are additional entries required by the MOOSE _DATABASE:Spawn() function.
|
||||
template.CountryID=CountryID
|
||||
template.CoalitionID=coalition.getCountryCoalition(template.CountryID)
|
||||
if Airplane then
|
||||
template.CategoryID=Unit.Category.AIRPLANE
|
||||
else
|
||||
template.CategoryID=Unit.Category.HELICOPTER
|
||||
end
|
||||
|
||||
-- Set first unit.
|
||||
template.units[1].type=TypeName
|
||||
template.units[1].name=GroupName.."-1"
|
||||
|
||||
-- Set position.
|
||||
if Vec3 then
|
||||
TEMPLATE.SetPositionFromVec3(template, Vec3)
|
||||
end
|
||||
|
||||
-- Set number of units.
|
||||
TEMPLATE.SetUnits(template, Nunits, COORDINATE:NewFromVec3(Vec3), Radius)
|
||||
|
||||
return template
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Misc Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Set the position of the template.
|
||||
-- @param #table Template The template to be modified.
|
||||
-- @param DCS#Vec2 Vec2 2D Position vector with x and y components of the group.
|
||||
function TEMPLATE.SetPositionFromVec2(Template, Vec2)
|
||||
|
||||
Template.x=Vec2.x
|
||||
Template.y=Vec2.y
|
||||
|
||||
for _,unit in pairs(Template.units) do
|
||||
unit.x=Vec2.x
|
||||
unit.y=Vec2.y
|
||||
end
|
||||
|
||||
Template.route.points[1].x=Vec2.x
|
||||
Template.route.points[1].y=Vec2.y
|
||||
Template.route.points[1].alt=0 --TODO: Use land height.
|
||||
|
||||
end
|
||||
|
||||
--- Set the position of the template.
|
||||
-- @param #table Template The template to be modified.
|
||||
-- @param DCS#Vec3 Vec3 Position vector of the group.
|
||||
function TEMPLATE.SetPositionFromVec3(Template, Vec3)
|
||||
|
||||
local Vec2={x=Vec3.x, y=Vec3.z}
|
||||
|
||||
TEMPLATE.SetPositionFromVec2(Template, Vec2)
|
||||
|
||||
end
|
||||
|
||||
--- Set the position of the template.
|
||||
-- @param #table Template The template to be modified.
|
||||
-- @param #number N Total number of units in the group.
|
||||
-- @param Core.Point#COORDINATE Coordinate Position of the first unit.
|
||||
-- @param #number Radius Radius in meters to randomly place the additional units.
|
||||
function TEMPLATE.SetUnits(Template, N, Coordinate, Radius)
|
||||
|
||||
local units=Template.units
|
||||
|
||||
local unit1=units[1]
|
||||
|
||||
local Vec3=Coordinate:GetVec3()
|
||||
|
||||
unit1.x=Vec3.x
|
||||
unit1.y=Vec3.z
|
||||
unit1.alt=Vec3.y
|
||||
|
||||
for i=2,N do
|
||||
units[i]=UTILS.DeepCopy(unit1)
|
||||
end
|
||||
|
||||
for i=1,N do
|
||||
local unit=units[i]
|
||||
unit.name=string.format("%s-%d", Template.name, i)
|
||||
if i>1 then
|
||||
local vec2=Coordinate:GetRandomCoordinateInRadius(Radius, 5):GetVec2()
|
||||
unit.x=vec2.x
|
||||
unit.y=vec2.y
|
||||
unit.alt=unit1.alt
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Set the position of the template.
|
||||
-- @param #table Template The template to be modified.
|
||||
-- @param Wrapper.Airbase#AIRBASE AirBase The airbase where the aircraft are spawned.
|
||||
-- @param #table ParkingSpots List of parking spot IDs. Every unit needs one!
|
||||
-- @param #boolean EngineOn If true, aircraft are spawned hot.
|
||||
function TEMPLATE.SetAirbase(Template, AirBase, ParkingSpots, EngineOn)
|
||||
|
||||
-- Airbase ID.
|
||||
local AirbaseID=AirBase:GetID()
|
||||
|
||||
-- Spawn point.
|
||||
local point=Template.route.points[1]
|
||||
|
||||
-- Set ID.
|
||||
if AirBase:IsAirdrome() then
|
||||
point.airdromeId=AirbaseID
|
||||
else
|
||||
point.helipadId=AirbaseID
|
||||
point.linkUnit=AirbaseID
|
||||
end
|
||||
|
||||
if EngineOn then
|
||||
point.action=COORDINATE.WaypointAction.FromParkingAreaHot
|
||||
point.type=COORDINATE.WaypointType.TakeOffParkingHot
|
||||
else
|
||||
point.action=COORDINATE.WaypointAction.FromParkingArea
|
||||
point.type=COORDINATE.WaypointType.TakeOffParking
|
||||
end
|
||||
|
||||
for i,unit in ipairs(Template.units) do
|
||||
unit.parking_id=ParkingSpots[i]
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Add a waypoint.
|
||||
-- @param #table Template The template to be modified.
|
||||
-- @param #table Waypoint Waypoint table.
|
||||
function TEMPLATE.AddWaypoint(Template, Waypoint)
|
||||
|
||||
table.insert(Template.route.points, Waypoint)
|
||||
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Generic Ground Template
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
TEMPLATE.GenericGround=
|
||||
{
|
||||
["visible"] = false,
|
||||
["tasks"] = {}, -- end of ["tasks"]
|
||||
["uncontrollable"] = false,
|
||||
["task"] = "Ground Nothing",
|
||||
["route"] =
|
||||
{
|
||||
["spans"] = {}, -- end of ["spans"]
|
||||
["points"] =
|
||||
{
|
||||
[1] =
|
||||
{
|
||||
["alt"] = 0,
|
||||
["type"] = "Turning Point",
|
||||
["ETA"] = 0,
|
||||
["alt_type"] = "BARO",
|
||||
["formation_template"] = "",
|
||||
["y"] = 0,
|
||||
["x"] = 0,
|
||||
["ETA_locked"] = true,
|
||||
["speed"] = 0,
|
||||
["action"] = "Off Road",
|
||||
["task"] =
|
||||
{
|
||||
["id"] = "ComboTask",
|
||||
["params"] =
|
||||
{
|
||||
["tasks"] =
|
||||
{
|
||||
}, -- end of ["tasks"]
|
||||
}, -- end of ["params"]
|
||||
}, -- end of ["task"]
|
||||
["speed_locked"] = true,
|
||||
}, -- end of [1]
|
||||
}, -- end of ["points"]
|
||||
}, -- end of ["route"]
|
||||
["groupId"] = nil,
|
||||
["hidden"] = false,
|
||||
["units"] =
|
||||
{
|
||||
[1] =
|
||||
{
|
||||
["transportable"] =
|
||||
{
|
||||
["randomTransportable"] = false,
|
||||
}, -- end of ["transportable"]
|
||||
["skill"] = "Average",
|
||||
["type"] = "Infantry AK",
|
||||
["unitId"] = nil,
|
||||
["y"] = 0,
|
||||
["x"] = 0,
|
||||
["name"] = "Infantry AK-47 Rus",
|
||||
["heading"] = 0,
|
||||
["playerCanDrive"] = false,
|
||||
}, -- end of [1]
|
||||
}, -- end of ["units"]
|
||||
["y"] = 0,
|
||||
["x"] = 0,
|
||||
["name"] = "Infantry AK-47 Rus",
|
||||
["start_time"] = 0,
|
||||
}
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Generic Ship Template
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
TEMPLATE.GenericNaval=
|
||||
{
|
||||
["visible"] = false,
|
||||
["tasks"] = {}, -- end of ["tasks"]
|
||||
["uncontrollable"] = false,
|
||||
["route"] =
|
||||
{
|
||||
["points"] =
|
||||
{
|
||||
[1] =
|
||||
{
|
||||
["alt"] = 0,
|
||||
["type"] = "Turning Point",
|
||||
["ETA"] = 0,
|
||||
["alt_type"] = "BARO",
|
||||
["formation_template"] = "",
|
||||
["y"] = 0,
|
||||
["x"] = 0,
|
||||
["ETA_locked"] = true,
|
||||
["speed"] = 0,
|
||||
["action"] = "Turning Point",
|
||||
["task"] =
|
||||
{
|
||||
["id"] = "ComboTask",
|
||||
["params"] =
|
||||
{
|
||||
["tasks"] =
|
||||
{
|
||||
}, -- end of ["tasks"]
|
||||
}, -- end of ["params"]
|
||||
}, -- end of ["task"]
|
||||
["speed_locked"] = true,
|
||||
}, -- end of [1]
|
||||
}, -- end of ["points"]
|
||||
}, -- end of ["route"]
|
||||
["groupId"] = nil,
|
||||
["hidden"] = false,
|
||||
["units"] =
|
||||
{
|
||||
[1] =
|
||||
{
|
||||
["transportable"] =
|
||||
{
|
||||
["randomTransportable"] = false,
|
||||
}, -- end of ["transportable"]
|
||||
["skill"] = "Average",
|
||||
["type"] = "TICONDEROG",
|
||||
["unitId"] = nil,
|
||||
["y"] = 0,
|
||||
["x"] = 0,
|
||||
["name"] = "Naval-1-1",
|
||||
["heading"] = 0,
|
||||
["modulation"] = 0,
|
||||
["frequency"] = 127500000,
|
||||
}, -- end of [1]
|
||||
}, -- end of ["units"]
|
||||
["y"] = 0,
|
||||
["x"] = 0,
|
||||
["name"] = "Naval-1",
|
||||
["start_time"] = 0,
|
||||
}
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Generic Aircraft Template
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
TEMPLATE.GenericAircraft=
|
||||
{
|
||||
["groupId"] = nil,
|
||||
["name"] = "Rotary-1",
|
||||
["uncontrolled"] = false,
|
||||
["hidden"] = false,
|
||||
["task"] = "Nothing",
|
||||
["y"] = 0,
|
||||
["x"] = 0,
|
||||
["start_time"] = 0,
|
||||
["communication"] = true,
|
||||
["radioSet"] = false,
|
||||
["frequency"] = 127.5,
|
||||
["modulation"] = 0,
|
||||
["taskSelected"] = true,
|
||||
["tasks"] = {}, -- end of ["tasks"]
|
||||
["route"] =
|
||||
{
|
||||
["points"] =
|
||||
{
|
||||
[1] =
|
||||
{
|
||||
["y"] = 0,
|
||||
["x"] = 0,
|
||||
["alt"] = 1000,
|
||||
["alt_type"] = "BARO",
|
||||
["action"] = "Turning Point",
|
||||
["type"] = "Turning Point",
|
||||
["airdromeId"] = nil,
|
||||
["task"] =
|
||||
{
|
||||
["id"] = "ComboTask",
|
||||
["params"] =
|
||||
{
|
||||
["tasks"] = {}, -- end of ["tasks"]
|
||||
}, -- end of ["params"]
|
||||
}, -- end of ["task"]
|
||||
["ETA"] = 0,
|
||||
["ETA_locked"] = true,
|
||||
["speed"] = 100,
|
||||
["speed_locked"] = true,
|
||||
["formation_template"] = "",
|
||||
}, -- end of [1]
|
||||
}, -- end of ["points"]
|
||||
}, -- end of ["route"]
|
||||
["units"] =
|
||||
{
|
||||
[1] =
|
||||
{
|
||||
["name"] = "Rotary-1-1",
|
||||
["unitId"] = nil,
|
||||
["type"] = "AH-1W",
|
||||
["onboard_num"] = "050",
|
||||
["livery_id"] = "USA X Black",
|
||||
["skill"] = "High",
|
||||
["ropeLength"] = 15,
|
||||
["speed"] = 0,
|
||||
["x"] = 0,
|
||||
["y"] = 0,
|
||||
["alt"] = 10,
|
||||
["alt_type"] = "BARO",
|
||||
["heading"] = 0,
|
||||
["psi"] = 0,
|
||||
["parking"] = nil,
|
||||
["parking_id"] = nil,
|
||||
["payload"] =
|
||||
{
|
||||
["pylons"] = {}, -- end of ["pylons"]
|
||||
["fuel"] = "1250.0",
|
||||
["flare"] = 30,
|
||||
["chaff"] = 30,
|
||||
["gun"] = 100,
|
||||
}, -- end of ["payload"]
|
||||
["callsign"] =
|
||||
{
|
||||
[1] = 2,
|
||||
[2] = 1,
|
||||
[3] = 1,
|
||||
["name"] = "Springfield11",
|
||||
}, -- end of ["callsign"]
|
||||
}, -- end of [1]
|
||||
}, -- end of ["units"]
|
||||
}
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
@@ -56,6 +56,8 @@ BIGSMOKEPRESET = {
|
||||
-- @field #string Falklands South Atlantic map.
|
||||
-- @field #string Sinai Sinai map.
|
||||
-- @field #string Kola Kola map.
|
||||
-- @field #string Afghanistan Afghanistan map
|
||||
-- @field #string Iraq Iraq map
|
||||
DCSMAP = {
|
||||
Caucasus="Caucasus",
|
||||
NTTR="Nevada",
|
||||
@@ -68,6 +70,7 @@ DCSMAP = {
|
||||
Sinai="SinaiMap",
|
||||
Kola="Kola",
|
||||
Afghanistan="Afghanistan",
|
||||
Iraq="Iraq"
|
||||
}
|
||||
|
||||
|
||||
@@ -511,7 +514,7 @@ function UTILS.PrintTableToLog(table, indent, noprint)
|
||||
env.info(string.rep(" ", indent) .. tostring(k) .. " = {")
|
||||
end
|
||||
text = text ..string.rep(" ", indent) .. tostring(k) .. " = {\n"
|
||||
text = text .. tostring(UTILS.PrintTableToLog(v, indent + 1)).."\n"
|
||||
text = text .. tostring(UTILS.PrintTableToLog(v, indent + 1), noprint).."\n"
|
||||
if not noprint then
|
||||
env.info(string.rep(" ", indent) .. "},")
|
||||
end
|
||||
@@ -1221,7 +1224,7 @@ function UTILS.SecondsToClock(seconds, short)
|
||||
end
|
||||
|
||||
-- Seconds
|
||||
local seconds = tonumber(seconds)
|
||||
local seconds = tonumber(seconds) or 0
|
||||
|
||||
-- Seconds of this day.
|
||||
local _seconds=seconds%(60*60*24)
|
||||
@@ -1786,6 +1789,8 @@ function UTILS.GetMagneticDeclination(map)
|
||||
declination=15
|
||||
elseif map==DCSMAP.Afghanistan then
|
||||
declination=3
|
||||
elseif map==DCSMAP.Iraq then
|
||||
declination=4.4
|
||||
else
|
||||
declination=0
|
||||
end
|
||||
@@ -2122,9 +2127,9 @@ function UTILS.GetSunRiseAndSet(DayOfYear, Latitude, Longitude, Rising, Tlocal)
|
||||
local cosH = (cos(zenith) - (sinDec * sin(latitude))) / (cosDec * cos(latitude))
|
||||
|
||||
if rising and cosH > 1 then
|
||||
return "N/R" -- The sun never rises on this location on the specified date
|
||||
return "N/S" -- The sun never rises on this location on the specified date
|
||||
elseif cosH < -1 then
|
||||
return "N/S" -- The sun never sets on this location on the specified date
|
||||
return "N/R" -- The sun never sets on this location on the specified date
|
||||
end
|
||||
|
||||
-- Finish calculating H and convert into hours
|
||||
@@ -2315,9 +2320,9 @@ function UTILS.IsLoadingDoorOpen( unit_name )
|
||||
return true
|
||||
end
|
||||
|
||||
if type_name == "OH-58D" and (unit:getDrawArgumentValue(35) > 0 or unit:getDrawArgumentValue(421) == -1) then
|
||||
BASE:T(unit_name .. " cargo door is open")
|
||||
return true
|
||||
if type_name == "OH58D" then
|
||||
BASE:T(unit_name .. " front door(s) are open")
|
||||
return true -- no doors on this one ;)
|
||||
end
|
||||
|
||||
if type_name == "CH-47Fbl1" and (unit:getDrawArgumentValue(86) > 0.5) then
|
||||
@@ -2350,17 +2355,19 @@ end
|
||||
--- Function to generate valid VHF frequencies in kHz for radio beacons (FM).
|
||||
-- @return #table VHFrequencies
|
||||
function UTILS.GenerateVHFrequencies()
|
||||
|
||||
|
||||
-- known and sorted map-wise NDBs in kHz
|
||||
local _skipFrequencies = {
|
||||
214,274,291.5,295,297.5,
|
||||
300.5,304,305,307,309.5,311,312,312.5,316,
|
||||
320,324,328,329,330,332,336,337,
|
||||
342,343,348,351,352,353,358,
|
||||
363,365,368,372.5,374,
|
||||
380,381,384,385,389,395,396,
|
||||
414,420,430,432,435,440,450,455,462,470,485,
|
||||
507,515,520,525,528,540,550,560,570,577,580,
|
||||
214,243,264,273,274,288,291.5,295,297.5,
|
||||
300.5,304,305,307,309.5,310,311,312,312.5,316,317,
|
||||
320,323,324,325,326,328,329,330,332,335,336,337,
|
||||
340,342,343,346,348,351,352,353,358,
|
||||
360,363,364,365,368,372.5,373,374,
|
||||
380,381,384,385,387,389,391,395,396,399,
|
||||
403,404,410,412,414,418,420,423,
|
||||
430,432,435,440,445,
|
||||
450,455,462,470,485,490,
|
||||
507,515,520,525,528,540,550,560,563,570,577,580,595,
|
||||
602,625,641,662,670,680,682,690,
|
||||
705,720,722,730,735,740,745,750,770,795,
|
||||
822,830,862,866,
|
||||
@@ -2521,7 +2528,7 @@ end
|
||||
--- Function to save an object to a file
|
||||
-- @param #string Path The path to use. Use double backslashes \\\\ on Windows filesystems.
|
||||
-- @param #string Filename The name of the file. Existing file will be overwritten.
|
||||
-- @param #table Data The LUA data structure to save. This will be e.g. a table of text lines with an \\n at the end of each line.
|
||||
-- @param #string Data The data structure to save. This will be e.g. a string of text lines with an \\n at the end of each line.
|
||||
-- @return #boolean outcome True if saving is possible, else false.
|
||||
function UTILS.SaveToFile(Path,Filename,Data)
|
||||
-- Thanks to @FunkyFranky
|
||||
@@ -4090,3 +4097,305 @@ function UTILS.LCGRandom()
|
||||
UTILS.lcg.seed = (UTILS.lcg.a * UTILS.lcg.seed + UTILS.lcg.c) % UTILS.lcg.m
|
||||
return UTILS.lcg.seed / UTILS.lcg.m
|
||||
end
|
||||
|
||||
--- Spawns a new FARP of a defined type and coalition and functional statics (fuel depot, ammo storage, tent, windsock) around that FARP to make it operational.
|
||||
-- Adds vehicles from template if given. Fills the FARP warehouse with liquids and known materiels.
|
||||
-- References: [DCS Forum Topic](https://forum.dcs.world/topic/282989-farp-equipment-to-run-it)
|
||||
-- @param #string Name Name of this FARP installation. Must be unique.
|
||||
-- @param Core.Point#COORDINATE Coordinate Where to spawn the FARP.
|
||||
-- @param #string FARPType Type of FARP, can be one of the known types ENUMS.FARPType.FARP, ENUMS.FARPType.INVISIBLE, ENUMS.FARPType.HELIPADSINGLE, ENUMS.FARPType.PADSINGLE. Defaults to ENUMS.FARPType.FARP.
|
||||
-- @param #number Coalition Coalition of this FARP, i.e. coalition.side.BLUE or coalition.side.RED, defaults to coalition.side.BLUE.
|
||||
-- @param #number Country Country of this FARP, defaults to country.id.USA (blue) or country.id.RUSSIA (red).
|
||||
-- @param #number CallSign Callsign of the FARP ATC, defaults to CALLSIGN.FARP.Berlin.
|
||||
-- @param #number Frequency Frequency of the FARP ATC Radio, defaults to 127.5 (MHz).
|
||||
-- @param #number Modulation Modulation of the FARP ATC Radio, defaults to radio.modulation.AM.
|
||||
-- @param #number ADF ADF Beacon (FM) Frequency in KHz, e.g. 428. If not nil, creates an VHF/FM ADF Beacon for this FARP. Requires a sound called "beacon.ogg" to be in the mission (trigger "sound to" ...)
|
||||
-- @param #number SpawnRadius Radius of the FARP, i.e. where the FARP objects will be placed in meters, not more than 150m away. Defaults to 100.
|
||||
-- @param #string VehicleTemplate, template name for additional vehicles. Can be nil for no additional vehicles.
|
||||
-- @param #number Liquids Tons of fuel to be added initially to the FARP. Defaults to 10 (tons). Set to 0 for no fill.
|
||||
-- @param #number Equipment Number of equipment items per known item to be added initially to the FARP. Defaults to 10 (items). Set to 0 for no fill.
|
||||
-- @return #list<Wrapper.Static#STATIC> Table of spawned objects and vehicle object (if given).
|
||||
-- @return #string ADFBeaconName Name of the ADF beacon, to be able to remove/stop it later.
|
||||
function UTILS.SpawnFARPAndFunctionalStatics(Name,Coordinate,FARPType,Coalition,Country,CallSign,Frequency,Modulation,ADF,SpawnRadius,VehicleTemplate,Liquids,Equipment)
|
||||
|
||||
-- Set Defaults
|
||||
local farplocation = Coordinate
|
||||
local farptype = FARPType or ENUMS.FARPType.FARP
|
||||
local Coalition = Coalition or coalition.side.BLUE
|
||||
local callsign = CallSign or CALLSIGN.FARP.Berlin
|
||||
local freq = Frequency or 127.5
|
||||
local mod = Modulation or radio.modulation.AM
|
||||
local radius = SpawnRadius or 100
|
||||
if radius < 0 or radius > 150 then radius = 100 end
|
||||
local liquids = Liquids or 10
|
||||
liquids = liquids * 1000 -- tons to kg
|
||||
local equip = Equipment or 10
|
||||
local statictypes = ENUMS.FARPObjectTypeNamesAndShape[farptype] or {TypeName="FARP", ShapeName="FARPS"}
|
||||
local STypeName = statictypes.TypeName
|
||||
local SShapeName = statictypes.ShapeName
|
||||
local Country = Country or (Coalition == coalition.side.BLUE and country.id.USA or country.id.RUSSIA)
|
||||
local ReturnObjects = {}
|
||||
|
||||
-- Spawn FARP
|
||||
local newfarp = SPAWNSTATIC:NewFromType(STypeName,"Heliports",Country) -- "Invisible FARP" "FARP"
|
||||
newfarp:InitShape(SShapeName) -- "invisiblefarp" "FARPS"
|
||||
newfarp:InitFARP(callsign,freq,mod)
|
||||
local spawnedfarp = newfarp:SpawnFromCoordinate(farplocation,0,Name)
|
||||
table.insert(ReturnObjects,spawnedfarp)
|
||||
-- Spawn Objects
|
||||
local FARPStaticObjectsNato = {
|
||||
["FUEL"] = { TypeName = "FARP Fuel Depot", ShapeName = "GSM Rus", Category = "Fortifications"},
|
||||
["AMMO"] = { TypeName = "FARP Ammo Dump Coating", ShapeName = "SetkaKP", Category = "Fortifications"},
|
||||
["TENT"] = { TypeName = "FARP Tent", ShapeName = "PalatkaB", Category = "Fortifications"},
|
||||
["WINDSOCK"] = { TypeName = "Windsock", ShapeName = "H-Windsock_RW", Category = "Fortifications"},
|
||||
}
|
||||
|
||||
local farpobcount = 0
|
||||
for _name,_object in pairs(FARPStaticObjectsNato) do
|
||||
local objloc = farplocation:Translate(radius,farpobcount*30)
|
||||
local heading = objloc:HeadingTo(farplocation)
|
||||
local newobject = SPAWNSTATIC:NewFromType(_object.TypeName,_object.Category,Country)
|
||||
newobject:InitShape(_object.ShapeName)
|
||||
newobject:InitHeading(heading)
|
||||
newobject:SpawnFromCoordinate(objloc,farpobcount*30,_name.." - "..Name)
|
||||
table.insert(ReturnObjects,newobject)
|
||||
farpobcount = farpobcount + 1
|
||||
end
|
||||
|
||||
-- Vehicle if any
|
||||
if VehicleTemplate and type(VehicleTemplate) == "string" then
|
||||
local vcoordinate = farplocation:Translate(radius,farpobcount*30)
|
||||
local heading = vcoordinate:HeadingTo(farplocation)
|
||||
local vehicles = SPAWN:NewWithAlias(VehicleTemplate,"FARP Vehicles - "..Name)
|
||||
vehicles:InitGroupHeading(heading)
|
||||
vehicles:InitCountry(Country)
|
||||
vehicles:InitCoalition(Coalition)
|
||||
vehicles:InitDelayOff()
|
||||
local spawnedvehicle = vehicles:SpawnFromCoordinate(vcoordinate)
|
||||
table.insert(ReturnObjects,spawnedvehicle)
|
||||
end
|
||||
|
||||
local newWH = STORAGE:New(Name)
|
||||
if liquids and liquids > 0 then
|
||||
-- Storage fill-up
|
||||
newWH:SetLiquid(STORAGE.Liquid.DIESEL,liquids) -- kgs to tons
|
||||
newWH:SetLiquid(STORAGE.Liquid.GASOLINE,liquids)
|
||||
newWH:SetLiquid(STORAGE.Liquid.JETFUEL,liquids)
|
||||
newWH:SetLiquid(STORAGE.Liquid.MW50,liquids)
|
||||
end
|
||||
|
||||
if equip and equip > 0 then
|
||||
for cat,nitem in pairs(ENUMS.Storage.weapons) do
|
||||
for name,item in pairs(nitem) do
|
||||
newWH:SetItem(item,equip)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local ADFName
|
||||
if ADF and type(ADF) == "number" then
|
||||
local ADFFreq = ADF*1000 -- KHz to Hz
|
||||
local Sound = "l10n/DEFAULT/beacon.ogg"
|
||||
local vec3 = farplocation:GetVec3()
|
||||
ADFName = Name .. " ADF "..tostring(ADF).."KHz"
|
||||
--BASE:I(string.format("Adding FARP Beacon %d KHz Name %s",ADF,ADFName))
|
||||
trigger.action.radioTransmission(Sound, vec3, 0, true, ADFFreq, 250, ADFName)
|
||||
end
|
||||
|
||||
return ReturnObjects, ADFName
|
||||
end
|
||||
|
||||
--- Converts a Vec2 to a Vec3.
|
||||
-- @param vec the 2D vector
|
||||
-- @param y optional new y axis (altitude) value. If omitted it's 0.
|
||||
function UTILS.Vec2toVec3(vec,y)
|
||||
if not vec.z then
|
||||
if vec.alt and not y then
|
||||
y = vec.alt
|
||||
elseif not y then
|
||||
y = 0
|
||||
end
|
||||
return {x = vec.x, y = y, z = vec.y}
|
||||
else
|
||||
return {x = vec.x, y = vec.y, z = vec.z} -- it was already Vec3, actually.
|
||||
end
|
||||
end
|
||||
|
||||
--- Get the correction needed for true north in radians
|
||||
-- @param gPoint The map point vec2 or vec3
|
||||
-- @return number correction
|
||||
function UTILS.GetNorthCorrection(gPoint)
|
||||
local point = UTILS.DeepCopy(gPoint)
|
||||
if not point.z then --Vec2; convert to Vec3
|
||||
point.z = point.y
|
||||
point.y = 0
|
||||
end
|
||||
local lat, lon = coord.LOtoLL(point)
|
||||
local north_posit = coord.LLtoLO(lat + 1, lon)
|
||||
return math.atan2(north_posit.z - point.z, north_posit.x - point.x)
|
||||
end
|
||||
|
||||
--- Convert time in seconds to a DHMS table `{d = days, h = hours, m = minutes, s = seconds}`
|
||||
-- @param timeInSec Time in Seconds
|
||||
-- @return #table Table with DHMS data
|
||||
function UTILS.GetDHMS(timeInSec)
|
||||
if timeInSec and type(timeInSec) == 'number' then
|
||||
local tbl = {d = 0, h = 0, m = 0, s = 0}
|
||||
if timeInSec > 86400 then
|
||||
while timeInSec > 86400 do
|
||||
tbl.d = tbl.d + 1
|
||||
timeInSec = timeInSec - 86400
|
||||
end
|
||||
end
|
||||
if timeInSec > 3600 then
|
||||
while timeInSec > 3600 do
|
||||
tbl.h = tbl.h + 1
|
||||
timeInSec = timeInSec - 3600
|
||||
end
|
||||
end
|
||||
if timeInSec > 60 then
|
||||
while timeInSec > 60 do
|
||||
tbl.m = tbl.m + 1
|
||||
timeInSec = timeInSec - 60
|
||||
end
|
||||
end
|
||||
tbl.s = timeInSec
|
||||
return tbl
|
||||
else
|
||||
BASE:E("No number handed!")
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
--- Returns heading-error corrected direction in radians.
|
||||
-- True-north corrected direction from point along vector vec.
|
||||
-- @param vec Vec3 Starting point
|
||||
-- @param point Vec2 Direction
|
||||
-- @return direction corrected direction from point.
|
||||
function UTILS.GetDirectionRadians(vec, point)
|
||||
local dir = math.atan2(vec.z, vec.x)
|
||||
if point then
|
||||
dir = dir + UTILS.GetNorthCorrection(point)
|
||||
end
|
||||
if dir < 0 then
|
||||
dir = dir + 2 * math.pi -- put dir in range of 0 to 2*pi
|
||||
end
|
||||
return dir
|
||||
end
|
||||
|
||||
--- Raycasting a point in polygon. Code from http://softsurfer.com/Archive/algorithm_0103/algorithm_0103.htm
|
||||
-- @param point Vec2 or Vec3 to test
|
||||
-- @param #table poly Polygon Table of Vec2/3 point forming the Polygon
|
||||
-- @param #number maxalt Altitude limit (optional)
|
||||
-- @param #boolean outcome
|
||||
function UTILS.IsPointInPolygon(point, poly, maxalt)
|
||||
point = UTILS.Vec2toVec3(point)
|
||||
local px = point.x
|
||||
local pz = point.z
|
||||
local cn = 0
|
||||
local newpoly = UTILS.DeepCopy(poly)
|
||||
|
||||
if not maxalt or (point.y <= maxalt) then
|
||||
local polysize = #newpoly
|
||||
newpoly[#newpoly + 1] = newpoly[1]
|
||||
|
||||
newpoly[1] = UTILS.Vec2toVec3(newpoly[1])
|
||||
|
||||
for k = 1, polysize do
|
||||
newpoly[k+1] = UTILS.Vec2toVec3(newpoly[k+1])
|
||||
if ((newpoly[k].z <= pz) and (newpoly[k+1].z > pz)) or ((newpoly[k].z > pz) and (newpoly[k+1].z <= pz)) then
|
||||
local vt = (pz - newpoly[k].z) / (newpoly[k+1].z - newpoly[k].z)
|
||||
if (px < newpoly[k].x + vt*(newpoly[k+1].x - newpoly[k].x)) then
|
||||
cn = cn + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return cn%2 == 1
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
--- Vector scalar multiplication.
|
||||
-- @param vec Vec3 vector to multiply
|
||||
-- @param #number mult scalar multiplicator
|
||||
-- @return Vec3 new vector multiplied with the given scalar
|
||||
function UTILS.ScalarMult(vec, mult)
|
||||
return {x = vec.x*mult, y = vec.y*mult, z = vec.z*mult}
|
||||
end
|
||||
|
||||
--- Utilities weather class for fog mainly.
|
||||
-- @type UTILS.Weather
|
||||
UTILS.Weather = {}
|
||||
|
||||
--- Returns the current fog thickness in meters. Returns zero if fog is not present.
|
||||
function UTILS.Weather.GetFogThickness()
|
||||
return world.weather.getFogThickness()
|
||||
end
|
||||
|
||||
--- Sets the fog to the desired thickness in meters at sea level.
|
||||
-- @param #number Thickness Thickness in meters.
|
||||
-- Any fog animation will be discarded.
|
||||
-- Valid range : 100 to 5000 meters
|
||||
function UTILS.Weather.SetFogThickness(Thickness)
|
||||
local value = Thickness
|
||||
if value < 100 then value = 100
|
||||
elseif value > 5000 then value = 5000 end
|
||||
return world.weather.setFogThickness(value)
|
||||
end
|
||||
|
||||
--- Removes the fog.
|
||||
function UTILS.Weather.RemoveFog()
|
||||
return world.weather.setFogThickness(0)
|
||||
end
|
||||
|
||||
--- Gets the maximum visibility distance of the current fog setting.
|
||||
-- Returns 0 if no fog is present.
|
||||
function UTILS.Weather.GetFogVisibilityDistanceMax()
|
||||
return world.weather.getFogVisibilityDistance()
|
||||
end
|
||||
|
||||
--- Sets the maximum visibility at sea level in meters.
|
||||
-- @param #number Thickness Thickness in meters.
|
||||
-- Limit: 100 to 100000
|
||||
function UTILS.Weather.SetFogVisibilityDistance(Thickness)
|
||||
local value = Thickness
|
||||
if value < 100 then value = 100
|
||||
elseif value > 100000 then value = 100000 end
|
||||
return world.weather.setFogVisibilityDistance(value)
|
||||
end
|
||||
|
||||
--- Uses data from the passed table to change the fog visibility and thickness over a desired timeframe. This allows for a gradual increase/decrease of fog values rather than abruptly applying the values.
|
||||
-- Animation Key Format: {time, visibility, thickness}
|
||||
-- @param #table AnimationKeys Table of AnimationKey tables
|
||||
-- @usage
|
||||
-- Time: in seconds 0 to infinity
|
||||
-- Time is relative to when the function was called. Time value for each key must be larger than the previous key. If time is set to 0 then the fog will be applied to the corresponding visibility and thickness values at that key. Any time value greater than 0 will result in the current fog being inherited and changed to the first key.
|
||||
-- Visibility: in meters 100 to 100000
|
||||
-- Thickness: in meters 100 to 5000
|
||||
-- The speed at which the visibility and thickness changes is based on the time between keys and the values that visibility and thickness are being set to.
|
||||
--
|
||||
-- When the function is passed an empty table {} or nil the fog animation will be discarded and whatever the current thickness and visibility are set to will remain.
|
||||
--
|
||||
-- The following will set the fog in the mission to disappear in 1 minute.
|
||||
--
|
||||
-- UTILS.Weather.SetFogAnimation({ {60, 0, 0} })
|
||||
--
|
||||
-- The following will take 1 hour to get to the first fog setting, it will maintain that fog setting for another hour, then lightly removes the fog over the 2nd and 3rd hour, the completely removes the fog after 3 hours and 3 minutes from when the function was called.
|
||||
--
|
||||
-- UTILS.Weather.SetFogAnimation({
|
||||
-- {3600, 10000, 3000}, -- one hour to get to that fog setting
|
||||
-- {7200, 10000, 3000}, -- will maintain for 2 hours
|
||||
-- {10800, 20000, 2000}, -- at 3 hours visibility will have been increased while thickness decreases slightly
|
||||
-- {12600, 0, 0}, -- at 3:30 after the function was called the fog will be completely removed.
|
||||
-- })
|
||||
--
|
||||
function UTILS.Weather.SetFogAnimation(AnimationKeys)
|
||||
return world.weather.setFogAnimation(AnimationKeys)
|
||||
end
|
||||
|
||||
--- The fog animation will be discarded and whatever the current thickness and visibility are set to will remain
|
||||
function UTILS.Weather.StopFogAnimation()
|
||||
return world.weather.setFogAnimation({})
|
||||
end
|
||||
|
||||
@@ -63,6 +63,11 @@
|
||||
-- To be able to distinguish easily in your code the difference between a AIRBASE API call and a DCS Airbase API call,
|
||||
-- the first letter of the method is also capitalized. So, by example, the DCS Airbase method DCSWrapper.Airbase#Airbase.getName()
|
||||
-- is implemented in the AIRBASE class as @{#AIRBASE.GetName}().
|
||||
--
|
||||
-- ## Note on the "H" heli pads in the Syria map:
|
||||
--
|
||||
-- As of the time of writing (Oct 2024, DCS DCS 2.9.8.1107), these 143 objects have the **same name and object ID**, which makes them unusable in Moose, e.g. you cannot find a specific one for spawning etc.
|
||||
-- Waiting for Ugra and ED to fix this issue.
|
||||
--
|
||||
-- @field #AIRBASE AIRBASE
|
||||
AIRBASE = {
|
||||
@@ -248,6 +253,9 @@ AIRBASE.Nevada = {
|
||||
-- * AIRBASE.Normandy.Villacoublay
|
||||
-- * AIRBASE.Normandy.Vrigny
|
||||
-- * AIRBASE.Normandy.West_Malling
|
||||
-- * AIRBASE.Normandy.Eastchurch
|
||||
-- * AIRBASE.Normandy.Headcorn
|
||||
-- * AIRBASE.Normandy.Hawkinge
|
||||
--
|
||||
-- @field Normandy
|
||||
AIRBASE.Normandy = {
|
||||
@@ -330,6 +338,9 @@ AIRBASE.Normandy = {
|
||||
["Villacoublay"] = "Villacoublay",
|
||||
["Vrigny"] = "Vrigny",
|
||||
["West_Malling"] = "West Malling",
|
||||
["Eastchurch"] = "Eastchurch",
|
||||
["Headcorn"] = "Headcorn",
|
||||
["Hawkinge"] = "Hawkinge",
|
||||
}
|
||||
|
||||
--- Airbases of the Persion Gulf Map:
|
||||
@@ -450,6 +461,7 @@ AIRBASE.TheChannel = {
|
||||
-- * AIRBASE.Syria.Gaziantep
|
||||
-- * AIRBASE.Syria.Gazipasa
|
||||
-- * AIRBASE.Syria.Gecitkale
|
||||
-- * AIRBASE.Syria.H
|
||||
-- * AIRBASE.Syria.H3
|
||||
-- * AIRBASE.Syria.H3_Northwest
|
||||
-- * AIRBASE.Syria.H3_Southwest
|
||||
@@ -497,6 +509,10 @@ AIRBASE.TheChannel = {
|
||||
-- * AIRBASE.Syria.Tha_lah
|
||||
-- * AIRBASE.Syria.Tiyas
|
||||
-- * AIRBASE.Syria.Wujah_Al_Hajar
|
||||
-- * AIRBASE.Syria.Ben_Gurion
|
||||
-- * AIRBASE.Syria.Hatzor
|
||||
-- * AIRBASE.Syria.Palmashim
|
||||
-- * AIRBASE.Syria.Tel_Nof
|
||||
--
|
||||
--@field Syria
|
||||
AIRBASE.Syria={
|
||||
@@ -518,6 +534,7 @@ AIRBASE.Syria={
|
||||
["Gaziantep"] = "Gaziantep",
|
||||
["Gazipasa"] = "Gazipasa",
|
||||
["Gecitkale"] = "Gecitkale",
|
||||
["H"] = "H",
|
||||
["H3"] = "H3",
|
||||
["H3_Northwest"] = "H3 Northwest",
|
||||
["H3_Southwest"] = "H3 Southwest",
|
||||
@@ -565,6 +582,10 @@ AIRBASE.Syria={
|
||||
["Tha_lah"] = "Tha'lah",
|
||||
["Tiyas"] = "Tiyas",
|
||||
["Wujah_Al_Hajar"] = "Wujah Al Hajar",
|
||||
["Ben_Gurion"] = "Ben Gurion",
|
||||
["Hatzor"] = "Hatzor",
|
||||
["Palmashim"] = "Palmashim",
|
||||
["Tel_Nof"] = "Tel Nof",
|
||||
}
|
||||
|
||||
--- Airbases of the Mariana Islands map:
|
||||
@@ -752,12 +773,14 @@ AIRBASE.Sinai = {
|
||||
--
|
||||
-- * AIRBASE.Kola.Banak
|
||||
-- * AIRBASE.Kola.Bodo
|
||||
-- * AIRBASE.Kola.Ivalo
|
||||
-- * AIRBASE.Kola.Jokkmokk
|
||||
-- * AIRBASE.Kola.Kalixfors
|
||||
-- * AIRBASE.Kola.Kallax
|
||||
-- * AIRBASE.Kola.Kemi_Tornio
|
||||
-- * AIRBASE.Kola.Kirkenes
|
||||
-- * AIRBASE.Kola.Kiruna
|
||||
-- * AIRBASE.Kola.Kuusamo
|
||||
-- * AIRBASE.Kola.Monchegorsk
|
||||
-- * AIRBASE.Kola.Murmansk_International
|
||||
-- * AIRBASE.Kola.Olenya
|
||||
@@ -766,62 +789,124 @@ AIRBASE.Sinai = {
|
||||
-- * AIRBASE.Kola.Severomorsk_3
|
||||
-- * AIRBASE.Kola.Vidsel
|
||||
-- * AIRBASE.Kola.Vuojarvi
|
||||
--
|
||||
-- * AIRBASE.Kola.Andoya
|
||||
-- * AIRBASE.Kola.Alakourtti
|
||||
-- * AIRBASE.Kola.Kittila
|
||||
-- * AIRBASE.Kola.Bardufoss
|
||||
--
|
||||
-- @field Kola
|
||||
AIRBASE.Kola = {
|
||||
["Banak"] = "Banak",
|
||||
["Bodo"] = "Bodo",
|
||||
["Ivalo"] = "Ivalo",
|
||||
["Jokkmokk"] = "Jokkmokk",
|
||||
["Kalixfors"] = "Kalixfors",
|
||||
["Kallax"] = "Kallax",
|
||||
["Kemi_Tornio"] = "Kemi Tornio",
|
||||
["Kirkenes"] = "Kirkenes",
|
||||
["Kiruna"] = "Kiruna",
|
||||
["Kuusamo"] = "Kuusamo",
|
||||
["Monchegorsk"] = "Monchegorsk",
|
||||
["Murmansk_International"] = "Murmansk International",
|
||||
["Olenya"] = "Olenya",
|
||||
["Rovaniemi"] = "Rovaniemi",
|
||||
["Severomorsk_1"] = "Severomorsk-1",
|
||||
["Severomorsk_3"] = "Severomorsk-3",
|
||||
["Vuojarvi"] = "Vuojarvi",
|
||||
["Kirkenes"] = "Kirkenes",
|
||||
["Kallax"] = "Kallax",
|
||||
["Vidsel"] = "Vidsel",
|
||||
["Vuojarvi"] = "Vuojarvi",
|
||||
["Andoya"] = "Andoya",
|
||||
["Alakourtti"] = "Alakourtti",
|
||||
["Kittila"] = "Kittila",
|
||||
["Bardufoss"] = "Bardufoss",
|
||||
}
|
||||
|
||||
--- Airbases of the Afghanistan map
|
||||
--
|
||||
-- * AIRBASE.Afghanistan.Bost
|
||||
-- * AIRBASE.Afghanistan.Bagram
|
||||
-- * AIRBASE.Afghanistan.Bamyan
|
||||
-- * AIRBASE.Afghanistan.Camp_Bastion
|
||||
-- * AIRBASE.Afghanistan.Camp_Bastion_Heliport
|
||||
-- * AIRBASE.Afghanistan.Chaghcharan
|
||||
-- * AIRBASE.Afghanistan.Dwyer
|
||||
-- * AIRBASE.Afghanistan.Farah
|
||||
-- * AIRBASE.Afghanistan.Herat
|
||||
-- * AIRBASE.Afghanistan.Gardez
|
||||
-- * AIRBASE.Afghanistan.Ghazni_Heliport
|
||||
-- * AIRBASE.Afghanistan.Jalalabad
|
||||
-- * AIRBASE.Afghanistan.Kabul
|
||||
-- * AIRBASE.Afghanistan.Kandahar
|
||||
-- * AIRBASE.Afghanistan.Kandahar_Heliport
|
||||
-- * AIRBASE.Afghanistan.Khost
|
||||
-- * AIRBASE.Afghanistan.Khost_Heliport
|
||||
-- * AIRBASE.Afghanistan.Maymana_Zahiraddin_Faryabi
|
||||
-- * AIRBASE.Afghanistan.Nimroz
|
||||
-- * AIRBASE.Afghanistan.Qala_i_Naw
|
||||
-- * AIRBASE.Afghanistan.Shindand
|
||||
-- * AIRBASE.Afghanistan.Shindand_Heliport
|
||||
-- * AIRBASE.Afghanistan.Tarinkot
|
||||
-- * AIRBASE.Afghanistan.Urgoon_Heliport
|
||||
--
|
||||
-- @field Afghanistan
|
||||
AIRBASE.Afghanistan = {
|
||||
["Bagram"] = "Bagram",
|
||||
["Bamyan"] = "Bamyan",
|
||||
["Bost"] = "Bost",
|
||||
["Camp_Bastion"] = "Camp Bastion",
|
||||
["Camp_Bastion_Heliport"] = "Camp Bastion Heliport",
|
||||
["Chaghcharan"] = "Chaghcharan",
|
||||
["Dwyer"] = "Dwyer",
|
||||
["Farah"] = "Farah",
|
||||
["Gardez"] = "Gardez",
|
||||
["Ghazni_Heliport"] = "Ghazni Heliport",
|
||||
["Herat"] = "Herat",
|
||||
["Jalalabad"] = "Jalalabad",
|
||||
["Kabul"] = "Kabul",
|
||||
["Kandahar"] = "Kandahar",
|
||||
["Kandahar_Heliport"] = "Kandahar Heliport",
|
||||
["Khost"] = "Khost",
|
||||
["Khost_Heliport"] = "Khost Heliport",
|
||||
["Maymana_Zahiraddin_Faryabi"] = "Maymana Zahiraddin Faryabi",
|
||||
["Nimroz"] = "Nimroz",
|
||||
["Qala_i_Naw"] = "Qala i Naw",
|
||||
["Sharana"] = "Sharana",
|
||||
["Shindand"] = "Shindand",
|
||||
["Shindand_Heliport"] = "Shindand Heliport",
|
||||
["Tarinkot"] = "Tarinkot",
|
||||
["Urgoon_Heliport"] = "Urgoon Heliport",
|
||||
}
|
||||
|
||||
--- Airbases of the Iraq map
|
||||
--
|
||||
-- * AIRBASE.Iraq.Baghdad_International_Airport
|
||||
-- * AIRBASE.Iraq.Sulaimaniyah_International_Airport
|
||||
-- * AIRBASE.Iraq.Al_Sahra_Airport
|
||||
-- * AIRBASE.Iraq.Erbil_International_Airpor
|
||||
-- * AIRBASE.Iraq.Al_Taji_Airport
|
||||
-- * AIRBASE.Iraq.Al_Asad_Airbase
|
||||
-- * AIRBASE.Iraq.Al_Salam_Airbase
|
||||
-- * AIRBASE.Iraq.Balad_Airbase
|
||||
-- * AIRBASE.Iraq.Kirkuk_International_Airport
|
||||
-- * AIRBASE.Iraq.Bashur_Airport
|
||||
-- * AIRBASE.Iraq.Al_Taquddum_Airport
|
||||
-- * AIRBASE.Iraq.Qayyarah_Airfield_West
|
||||
-- * AIRBASE.Iraq.K1_Base
|
||||
--
|
||||
-- @field Iraq
|
||||
AIRBASE.Iraq = {
|
||||
["Baghdad_International_Airport"] = "Baghdad International Airport",
|
||||
["Sulaimaniyah_International_Airport"] = "Sulaimaniyah International Airport",
|
||||
["Al_Sahra_Airport"] = "Al-Sahra Airport",
|
||||
["Erbil_International_Airport"] = "Erbil International Airport",
|
||||
["Al_Taji_Airport"] = "Al-Taji Airport",
|
||||
["Al_Asad_Airbase"] = "Al-Asad Airbase",
|
||||
["Al_Salam_Airbase"] = "Al-Salam Airbase",
|
||||
["Balad_Airbase"] = "Balad Airbase",
|
||||
["Kirkuk_International_Airport"] = "Kirkuk International Airport",
|
||||
["Bashur_Airport"] = "Bashur Airport",
|
||||
["Al_Taquddum_Airport"] = "Al-Taquddum Airport",
|
||||
["Qayyarah_Airfield_West"] = "Qayyarah Airfield West",
|
||||
["K1_Base"] = "K1 Base",
|
||||
}
|
||||
|
||||
--- AIRBASE.ParkingSpot ".Coordinate, ".TerminalID", ".TerminalType", ".TOAC", ".Free", ".TerminalID0", ".DistToRwy".
|
||||
@@ -860,11 +945,12 @@ AIRBASE.Afghanistan = {
|
||||
-- @field #number HelicopterOnly 40: Special spots for Helicopers.
|
||||
-- @field #number Shelter 68: Hardened Air Shelter. Currently only on Caucaus map.
|
||||
-- @field #number OpenMed 72: Open/Shelter air airplane only.
|
||||
-- @field #number SmallSizeFigher 100: Tight spots for smaller type fixed wing aircraft, like the F-16. Example of these spots: 04, 05, 06 on Muwaffaq_Salti. A Viper sized plane can spawn here, but an A-10 or Strike Eagle can't
|
||||
-- @field #number OpenBig 104: Open air spawn points. Generally larger but does not guarantee large aircraft are capable of spawning there.
|
||||
-- @field #number OpenMedOrBig 176: Combines OpenMed and OpenBig spots.
|
||||
-- @field #number HelicopterUsable 216: Combines HelicopterOnly, OpenMed and OpenBig.
|
||||
-- @field #number FighterAircraft 244: Combines Shelter. OpenMed and OpenBig spots. So effectively all spots usable by fixed wing aircraft.
|
||||
-- @field #number SmallSizeFigher 100: Tight spots for smaller type fixed wing aircraft, like the F-16. Example of these spots: 04, 05, 06 on Muwaffaq_Salti. A Viper sized plane can spawn here, but an A-10 or Strike Eagle can't
|
||||
-- @field #number FighterAircraft 244: Combines Shelter, OpenMed and OpenBig spots. So effectively all spots usable by fixed wing aircraft.
|
||||
-- @field #number FighterAircraftSmall 344: Combines Shelter, SmallsizeFighter, OpenMed and OpenBig spots. So effectively all spots usable by small fixed wing aircraft.
|
||||
AIRBASE.TerminalType = {
|
||||
Runway=16,
|
||||
HelicopterOnly=40,
|
||||
@@ -875,6 +961,7 @@ AIRBASE.TerminalType = {
|
||||
OpenMedOrBig=176,
|
||||
HelicopterUsable=216,
|
||||
FighterAircraft=244,
|
||||
FighterAircraftSmall=344,
|
||||
}
|
||||
|
||||
--- Status of a parking spot.
|
||||
@@ -937,40 +1024,55 @@ function AIRBASE:Register(AirbaseName)
|
||||
--end
|
||||
|
||||
-- Set category.
|
||||
if self.category==Airbase.Category.AIRDROME then
|
||||
self.isAirdrome=true
|
||||
elseif self.category==Airbase.Category.HELIPAD then
|
||||
if self.category==Airbase.Category.AIRDROME then
|
||||
self.isAirdrome=true
|
||||
elseif self.category==Airbase.Category.HELIPAD or self.descriptors.typeName=="FARP_SINGLE_01" then
|
||||
self.isHelipad=true
|
||||
self.category=Airbase.Category.HELIPAD
|
||||
elseif self.category==Airbase.Category.SHIP then
|
||||
self.isShip=true
|
||||
-- DCS bug: Oil rigs and gas platforms have category=2 (ship). Also they cannot be retrieved by coalition.getStaticObjects()
|
||||
if self.descriptors.typeName=="Oil rig" or self.descriptors.typeName=="Ga" then
|
||||
self.isHelipad=true
|
||||
elseif self.category==Airbase.Category.SHIP then
|
||||
self.isShip=true
|
||||
-- DCS bug: Oil rigs and gas platforms have category=2 (ship). Also they cannot be retrieved by coalition.getStaticObjects()
|
||||
if self.descriptors.typeName=="Oil rig" or self.descriptors.typeName=="Ga" then
|
||||
self.isHelipad=true
|
||||
self.isShip=false
|
||||
self.category=Airbase.Category.HELIPAD
|
||||
_DATABASE:AddStatic(AirbaseName)
|
||||
end
|
||||
else
|
||||
self:E("ERROR: Unknown airbase category!")
|
||||
self.isShip=false
|
||||
self.category=Airbase.Category.HELIPAD
|
||||
_DATABASE:AddStatic(AirbaseName)
|
||||
end
|
||||
else
|
||||
self:E("ERROR: Unknown airbase category!")
|
||||
end
|
||||
|
||||
-- Init Runways.
|
||||
self:_InitRunways()
|
||||
|
||||
|
||||
-- Number of runways
|
||||
local Nrunways=#self.runways
|
||||
|
||||
-- Set the active runways based on wind direction.
|
||||
if self.isAirdrome then
|
||||
if Nrunways>0 then
|
||||
self:SetActiveRunway()
|
||||
end
|
||||
|
||||
-- Init parking spots.
|
||||
self:_InitParkingSpots()
|
||||
|
||||
-- Some heliports identify as airdromes in the airbase category. This is buggy in the descriptors category but also in the getCategory() and getCategoryEx() functions.
|
||||
-- Well, thinking about it, this is actually not that "buggy" since these are really helicopter airdromes, which do not have an automatic parking spot routine.
|
||||
-- I am still changing the category but marking it as airdrome and heliport at the same time via isAirdrome=true and isHelipad=true (important in SPAWN.SpawnAtAirbase).
|
||||
-- The main reason for changing the category is to be able to filter airdromes from helipads, e.g. in SET_AIRBASE.
|
||||
if self.category==Airbase.Category.AIRDROME and (Nrunways==0 or self.NparkingTotal==self.NparkingTerminal[AIRBASE.TerminalType.HelicopterOnly]) then
|
||||
--self:E(string.format("WARNING: %s identifies as airdrome (category=0) but has no runways or just helo parking ==> will change to helipad (category=1)", self.AirbaseName))
|
||||
self.category=Airbase.Category.HELIPAD
|
||||
self.isAirdrome=true
|
||||
self.isHelipad=true
|
||||
end
|
||||
|
||||
-- Get 2D position vector.
|
||||
local vec2=self:GetVec2()
|
||||
|
||||
-- Init coordinate.
|
||||
self:GetCoordinate()
|
||||
|
||||
|
||||
-- Storage.
|
||||
self.storage=_DATABASE:AddStorage(AirbaseName)
|
||||
|
||||
@@ -993,6 +1095,46 @@ function AIRBASE:Register(AirbaseName)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get the category of this airbase. This is only a debug function because DCS 2.9 incorrectly returns heliports as airdromes.
|
||||
-- @param #AIRBASE self
|
||||
function AIRBASE:_GetCategory()
|
||||
|
||||
local name=self.AirbaseName
|
||||
|
||||
local static=StaticObject.getByName(name)
|
||||
local airbase=Airbase.getByName(name)
|
||||
local unit=Unit.getByName(name)
|
||||
|
||||
local text=string.format("\n=====================================================")
|
||||
text=text..string.format("\nAirbase %s:", name)
|
||||
if static then
|
||||
local oc, uc=static:getCategory()
|
||||
local ex=static:getCategoryEx()
|
||||
text=text..string.format("\nSTATIC: oc=%d, uc=%d, ex=%d", oc, uc, ex)
|
||||
--text=text..UTILS.PrintTableToLog(static:getDesc(), nil, true)
|
||||
text=text..string.format("\n--------------------------------------------------")
|
||||
end
|
||||
if unit then
|
||||
local oc, uc=unit:getCategory()
|
||||
local ex=unit:getCategoryEx()
|
||||
text=text..string.format("\nUNIT: oc=%d, uc=%d, ex=%d", oc, uc, ex)
|
||||
--text=text..UTILS.PrintTableToLog(unit:getDesc(), nil, true)
|
||||
text=text..string.format("\n--------------------------------------------------")
|
||||
end
|
||||
if airbase then
|
||||
local oc, uc=airbase:getCategory()
|
||||
local ex=airbase:getCategoryEx()
|
||||
text=text..string.format("\nAIRBASE: oc=%d, uc=%d, ex=%d", oc, uc, ex)
|
||||
text=text..string.format("\n--------------------------------------------------")
|
||||
text=text..UTILS.PrintTableToLog(airbase:getDesc(), nil, true)
|
||||
end
|
||||
|
||||
text=text..string.format("\n=====================================================")
|
||||
|
||||
|
||||
env.info(text)
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Reference methods
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
@@ -1516,7 +1658,7 @@ function AIRBASE:_InitParkingSpots()
|
||||
self.NparkingTotal=self.NparkingTotal+1
|
||||
|
||||
for _,terminalType in pairs(AIRBASE.TerminalType) do
|
||||
if self._CheckTerminalType(terminalType, park.TerminalType) then
|
||||
if self._CheckTerminalType(park.TerminalType, terminalType) then
|
||||
self.NparkingTerminal[terminalType]=self.NparkingTerminal[terminalType]+1
|
||||
end
|
||||
end
|
||||
@@ -1524,6 +1666,9 @@ function AIRBASE:_InitParkingSpots()
|
||||
self.parkingByID[park.TerminalID]=park
|
||||
table.insert(self.parking, park)
|
||||
end
|
||||
|
||||
-- Runways are not included in total number of parking spots
|
||||
self.NparkingTotal=self.NparkingTotal-self.NparkingTerminal[AIRBASE.TerminalType.Runway]
|
||||
|
||||
return self
|
||||
end
|
||||
@@ -1947,9 +2092,13 @@ function AIRBASE._CheckTerminalType(Term_Type, termtype)
|
||||
match=true
|
||||
end
|
||||
elseif termtype==AIRBASE.TerminalType.FighterAircraft then
|
||||
if Term_Type==AIRBASE.TerminalType.OpenMed or Term_Type==AIRBASE.TerminalType.OpenBig or Term_Type==AIRBASE.TerminalType.Shelter or Term_Type==AIRBASE.TerminalType.SmallSizeFighter then
|
||||
if Term_Type==AIRBASE.TerminalType.OpenMed or Term_Type==AIRBASE.TerminalType.OpenBig or Term_Type==AIRBASE.TerminalType.Shelter then
|
||||
match=true
|
||||
end
|
||||
elseif termtype==AIRBASE.TerminalType.FighterAircraftSmall then
|
||||
if Term_Type==AIRBASE.TerminalType.OpenMed or Term_Type==AIRBASE.TerminalType.OpenBig or Term_Type==AIRBASE.TerminalType.Shelter or Term_Type==AIRBASE.TerminalType.SmallSizeFighter then
|
||||
match=true
|
||||
end
|
||||
end
|
||||
|
||||
return match
|
||||
@@ -2007,11 +2156,6 @@ function AIRBASE:_InitRunways(IncludeInverse)
|
||||
-- Runway table.
|
||||
local Runways={}
|
||||
|
||||
if self:GetAirbaseCategory()~=Airbase.Category.AIRDROME then
|
||||
self.runways={}
|
||||
return {}
|
||||
end
|
||||
|
||||
--- Function to create a runway data table.
|
||||
local function _createRunway(name, course, width, length, center)
|
||||
|
||||
@@ -2097,7 +2241,7 @@ function AIRBASE:_InitRunways(IncludeInverse)
|
||||
-- Debug info.
|
||||
self:T2(runways)
|
||||
|
||||
if runways then
|
||||
if runways and #runways>0 then
|
||||
|
||||
-- Loop over runways.
|
||||
for _,rwy in pairs(runways) do
|
||||
@@ -2130,6 +2274,12 @@ function AIRBASE:_InitRunways(IncludeInverse)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
else
|
||||
|
||||
-- No runways
|
||||
self.runways={}
|
||||
return {}
|
||||
|
||||
end
|
||||
|
||||
|
||||
@@ -174,7 +174,10 @@
|
||||
-- * @{#CONTROLLABLE.OptionKeepWeaponsOnThreat}
|
||||
--
|
||||
-- ## 5.5) Air-2-Air missile attack range:
|
||||
-- * @{#CONTROLLABLE.OptionAAAttackRange}(): Defines the usage of A2A missiles against possible targets .
|
||||
-- * @{#CONTROLLABLE.OptionAAAttackRange}(): Defines the usage of A2A missiles against possible targets.
|
||||
--
|
||||
-- # 6) [GROUND] IR Maker Beacons for GROUPs and UNITs
|
||||
-- * @{#CONTROLLABLE:NewIRMarker}(): Create a blinking IR Marker on a GROUP or UNIT.
|
||||
--
|
||||
-- @field #CONTROLLABLE
|
||||
CONTROLLABLE = {
|
||||
@@ -899,7 +902,11 @@ function CONTROLLABLE:CommandEPLRS( SwitchOnOff, Delay )
|
||||
groupId = self:GetID(),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
--if self:IsGround() then
|
||||
--CommandEPLRS.params.groupId = self:GetID()
|
||||
--end
|
||||
|
||||
if Delay and Delay > 0 then
|
||||
SCHEDULER:New( nil, self.CommandEPLRS, { self, SwitchOnOff }, Delay )
|
||||
else
|
||||
@@ -934,7 +941,7 @@ function CONTROLLABLE:CommandSetUnlimitedFuel(OnOff, Delay)
|
||||
end
|
||||
|
||||
|
||||
--- Set radio frequency. See [DCS command EPLRS](https://wiki.hoggitworld.com/view/DCS_command_setFrequency)
|
||||
--- Set radio frequency. See [DCS command SetFrequency](https://wiki.hoggitworld.com/view/DCS_command_setFrequency)
|
||||
-- @param #CONTROLLABLE self
|
||||
-- @param #number Frequency Radio frequency in MHz.
|
||||
-- @param #number Modulation Radio modulation. Default `radio.modulation.AM`.
|
||||
@@ -953,7 +960,7 @@ function CONTROLLABLE:CommandSetFrequency( Frequency, Modulation, Power, Delay )
|
||||
}
|
||||
|
||||
if Delay and Delay > 0 then
|
||||
SCHEDULER:New( nil, self.CommandSetFrequency, { self, Frequency, Modulation, Power } )
|
||||
SCHEDULER:New( nil, self.CommandSetFrequency, { self, Frequency, Modulation, Power },Delay )
|
||||
else
|
||||
self:SetCommand( CommandSetFrequency )
|
||||
end
|
||||
@@ -961,12 +968,12 @@ function CONTROLLABLE:CommandSetFrequency( Frequency, Modulation, Power, Delay )
|
||||
return self
|
||||
end
|
||||
|
||||
--- [AIR] Set radio frequency. See [DCS command EPLRS](https://wiki.hoggitworld.com/view/DCS_command_setFrequencyForUnit)
|
||||
--- [AIR] Set radio frequency. See [DCS command SetFrequencyForUnit](https://wiki.hoggitworld.com/view/DCS_command_setFrequencyForUnit)
|
||||
-- @param #CONTROLLABLE self
|
||||
-- @param #number Frequency Radio frequency in MHz.
|
||||
-- @param #number Modulation Radio modulation. Default `radio.modulation.AM`.
|
||||
-- @param #number Power (Optional) Power of the Radio in Watts. Defaults to 10.
|
||||
-- @param #UnitID UnitID (Optional, if your object is a UNIT) The UNIT ID this is for.
|
||||
-- @param #number UnitID (Optional, if your object is a UNIT) The UNIT ID this is for.
|
||||
-- @param #number Delay (Optional) Delay in seconds before the frequency is set. Default is immediately.
|
||||
-- @return #CONTROLLABLE self
|
||||
function CONTROLLABLE:CommandSetFrequencyForUnit(Frequency,Modulation,Power,UnitID,Delay)
|
||||
@@ -980,7 +987,7 @@ function CONTROLLABLE:CommandSetFrequencyForUnit(Frequency,Modulation,Power,Unit
|
||||
},
|
||||
}
|
||||
if Delay and Delay>0 then
|
||||
SCHEDULER:New(nil,self.CommandSetFrequencyForUnit,{self,Frequency,Modulation,Power,UnitID})
|
||||
SCHEDULER:New(nil,self.CommandSetFrequencyForUnit,{self,Frequency,Modulation,Power,UnitID},Delay)
|
||||
else
|
||||
self:SetCommand(CommandSetFrequencyForUnit)
|
||||
end
|
||||
@@ -1006,7 +1013,11 @@ function CONTROLLABLE:TaskEPLRS( SwitchOnOff, idx )
|
||||
groupId = self:GetID(),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
--if self:IsGround() then
|
||||
--CommandEPLRS.params.groupId = self:GetID()
|
||||
--end
|
||||
|
||||
return self:TaskWrappedAction( CommandEPLRS, idx or 1 )
|
||||
end
|
||||
|
||||
@@ -1766,8 +1777,6 @@ function CONTROLLABLE:TaskFAC_AttackGroup( AttackGroup, WeaponType, Designation,
|
||||
return DCSTask
|
||||
end
|
||||
|
||||
-- EN-ACT_ROUTE TASKS FOR AIRBORNE CONTROLLABLES
|
||||
|
||||
--- (AIR) Engaging targets of defined types.
|
||||
-- @param #CONTROLLABLE self
|
||||
-- @param DCS#Distance Distance Maximal distance from the target to a route leg. If the target is on a greater distance it will be ignored.
|
||||
@@ -2994,7 +3003,7 @@ function CONTROLLABLE:GetDetectedTargets( DetectVisual, DetectOptical, DetectRad
|
||||
if DCSControllable then
|
||||
|
||||
local DetectionVisual = (DetectVisual and DetectVisual == true) and Controller.Detection.VISUAL or nil
|
||||
local DetectionOptical = (DetectOptical and DetectOptical == true) and Controller.Detection.OPTICAL or nil
|
||||
local DetectionOptical = (DetectOptical and DetectOptical == true) and Controller.Detection.OPTIC or nil
|
||||
local DetectionRadar = (DetectRadar and DetectRadar == true) and Controller.Detection.RADAR or nil
|
||||
local DetectionIRST = (DetectIRST and DetectIRST == true) and Controller.Detection.IRST or nil
|
||||
local DetectionRWR = (DetectRWR and DetectRWR == true) and Controller.Detection.RWR or nil
|
||||
@@ -3028,26 +3037,27 @@ function CONTROLLABLE:GetDetectedTargets( DetectVisual, DetectOptical, DetectRad
|
||||
return nil
|
||||
end
|
||||
|
||||
--- Check if a target is detected.
|
||||
--- Check if a DCS object (unit or static) is detected by the controllable.
|
||||
-- Note that after a target is detected it remains "detected" for a certain amount of time, even if the controllable cannot "see" the target any more with it's sensors.
|
||||
-- The optional parametes specify the detection methods that can be applied.
|
||||
--
|
||||
-- If **no** detection method is given, the detection will use **all** the available methods by default.
|
||||
-- If **at least one** detection method is specified, only the methods set to *true* will be used.
|
||||
-- @param #CONTROLLABLE self
|
||||
-- @param DCS#Object DCSObject The DCS object that is checked.
|
||||
-- @param #CONTROLLABLE self
|
||||
-- @param #boolean DetectVisual (Optional) If *false*, do not include visually detected targets.
|
||||
-- @param #boolean DetectOptical (Optional) If *false*, do not include optically detected targets.
|
||||
-- @param #boolean DetectRadar (Optional) If *false*, do not include targets detected by radar.
|
||||
-- @param #boolean DetectIRST (Optional) If *false*, do not include targets detected by IRST.
|
||||
-- @param #boolean DetectRWR (Optional) If *false*, do not include targets detected by RWR.
|
||||
-- @param #boolean DetectDLINK (Optional) If *false*, do not include targets detected by data link.
|
||||
-- @return #boolean True if target is detected.
|
||||
-- @return #boolean True if target is visible by line of sight.
|
||||
-- @return #number Mission time when target was detected.
|
||||
-- @return #boolean True if target type is known.
|
||||
-- @return #boolean True if distance to target is known.
|
||||
-- @return DCS#Vec3 Last known position vector of the target.
|
||||
-- @return DCS#Vec3 Last known velocity vector of the target.
|
||||
-- @return #boolean `true` if target is detected.
|
||||
-- @return #boolean `true` if target is *currently* visible by line of sight. Target must be detected (first parameter returns `true`).
|
||||
-- @return #boolean `true` if target type is known. Target must be detected (first parameter returns `true`).
|
||||
-- @return #boolean `true` if distance to target is known. Target must be detected (first parameter returns `true`).
|
||||
-- @return #number Mission time in seconds when target was last detected. Only present if the target is currently not visible (second parameter returns `false`) otherwise `nil` is returned.
|
||||
-- @return DCS#Vec3 Last known position vector of the target. Only present if the target is currently not visible (second parameter returns `false`) otherwise `nil` is returned.
|
||||
-- @return DCS#Vec3 Last known velocity vector of the target. Only present if the target is currently not visible (second parameter returns `false`) otherwise `nil` is returned.
|
||||
function CONTROLLABLE:IsTargetDetected( DCSObject, DetectVisual, DetectOptical, DetectRadar, DetectIRST, DetectRWR, DetectDLINK )
|
||||
self:F2( self.ControllableName )
|
||||
|
||||
@@ -3056,7 +3066,7 @@ function CONTROLLABLE:IsTargetDetected( DCSObject, DetectVisual, DetectOptical,
|
||||
if DCSControllable then
|
||||
|
||||
local DetectionVisual = (DetectVisual and DetectVisual == true) and Controller.Detection.VISUAL or nil
|
||||
local DetectionOptical = (DetectOptical and DetectOptical == true) and Controller.Detection.OPTICAL or nil
|
||||
local DetectionOptical = (DetectOptical and DetectOptical == true) and Controller.Detection.OPTIC or nil
|
||||
local DetectionRadar = (DetectRadar and DetectRadar == true) and Controller.Detection.RADAR or nil
|
||||
local DetectionIRST = (DetectIRST and DetectIRST == true) and Controller.Detection.IRST or nil
|
||||
local DetectionRWR = (DetectRWR and DetectRWR == true) and Controller.Detection.RWR or nil
|
||||
@@ -3064,10 +3074,10 @@ function CONTROLLABLE:IsTargetDetected( DCSObject, DetectVisual, DetectOptical,
|
||||
|
||||
local Controller = self:_GetController()
|
||||
|
||||
local TargetIsDetected, TargetIsVisible, TargetLastTime, TargetKnowType, TargetKnowDistance, TargetLastPos, TargetLastVelocity
|
||||
local TargetIsDetected, TargetIsVisible, TargetKnowType, TargetKnowDistance, TargetLastTime, TargetLastPos, TargetLastVelocity
|
||||
= Controller:isTargetDetected( DCSObject, DetectionVisual, DetectionOptical, DetectionRadar, DetectionIRST, DetectionRWR, DetectionDLINK )
|
||||
|
||||
return TargetIsDetected, TargetIsVisible, TargetLastTime, TargetKnowType, TargetKnowDistance, TargetLastPos, TargetLastVelocity
|
||||
return TargetIsDetected, TargetIsVisible, TargetKnowType, TargetKnowDistance, TargetLastTime, TargetLastPos, TargetLastVelocity
|
||||
end
|
||||
|
||||
return nil
|
||||
@@ -3075,6 +3085,7 @@ end
|
||||
|
||||
--- Check if a certain UNIT is detected by the controllable.
|
||||
-- The optional parametes specify the detection methods that can be applied.
|
||||
--
|
||||
-- If **no** detection method is given, the detection will use **all** the available methods by default.
|
||||
-- If **at least one** detection method is specified, only the methods set to *true* will be used.
|
||||
-- @param #CONTROLLABLE self
|
||||
@@ -3085,13 +3096,13 @@ end
|
||||
-- @param #boolean DetectIRST (Optional) If *false*, do not include targets detected by IRST.
|
||||
-- @param #boolean DetectRWR (Optional) If *false*, do not include targets detected by RWR.
|
||||
-- @param #boolean DetectDLINK (Optional) If *false*, do not include targets detected by data link.
|
||||
-- @return #boolean True if target is detected.
|
||||
-- @return #boolean True if target is visible by line of sight.
|
||||
-- @return #number Mission time when target was detected.
|
||||
-- @return #boolean True if target type is known.
|
||||
-- @return #boolean True if distance to target is known.
|
||||
-- @return DCS#Vec3 Last known position vector of the target.
|
||||
-- @return DCS#Vec3 Last known velocity vector of the target.
|
||||
-- @return #boolean `true` if target is detected.
|
||||
-- @return #boolean `true` if target is *currently* visible by line of sight. Target must be detected (first parameter returns `true`).
|
||||
-- @return #boolean `true` if target type is known. Target must be detected (first parameter returns `true`).
|
||||
-- @return #boolean `true` if distance to target is known. Target must be detected (first parameter returns `true`).
|
||||
-- @return #number Mission time in seconds when target was last detected. Only present if the target is currently not visible (second parameter returns `false`) otherwise `nil` is returned.
|
||||
-- @return DCS#Vec3 Last known position vector of the target. Only present if the target is currently not visible (second parameter returns `false`) otherwise `nil` is returned.
|
||||
-- @return DCS#Vec3 Last known velocity vector of the target. Only present if the target is currently not visible (second parameter returns `false`) otherwise `nil` is returned.
|
||||
function CONTROLLABLE:IsUnitDetected( Unit, DetectVisual, DetectOptical, DetectRadar, DetectIRST, DetectRWR, DetectDLINK )
|
||||
self:F2( self.ControllableName )
|
||||
|
||||
@@ -4250,6 +4261,9 @@ function CONTROLLABLE:RelocateGroundRandomInRadius( speed, radius, onroad, short
|
||||
self:F2( { self.ControllableName } )
|
||||
|
||||
local _coord = self:GetCoordinate()
|
||||
if not _coord then
|
||||
return self
|
||||
end
|
||||
local _radius = radius or 500
|
||||
local _speed = speed or 20
|
||||
local _tocoord = _coord:GetRandomCoordinateInRadius( _radius, 100 )
|
||||
@@ -4299,7 +4313,7 @@ function CONTROLLABLE:OptionDisperseOnAttack( Seconds )
|
||||
end
|
||||
|
||||
--- Returns if the unit is a submarine.
|
||||
-- @param #POSITIONABLE self
|
||||
-- @param #CONTROLLABLE self
|
||||
-- @return #boolean Submarines attributes result.
|
||||
function CONTROLLABLE:IsSubmarine()
|
||||
self:F2()
|
||||
@@ -5619,3 +5633,166 @@ function CONTROLLABLE:PatrolRaceTrack(Point1, Point2, Altitude, Speed, Formation
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- IR Marker courtesy Florian Brinker (fbrinker)
|
||||
|
||||
--- [GROUND] Create and enable a new IR Marker for the given controllable UNIT or GROUP.
|
||||
-- @param #CONTROLLABLE self
|
||||
-- @param #boolean EnableImmediately (Optionally) If true start up the IR Marker immediately. Else you need to call `myobject:EnableIRMarker()` later on.
|
||||
-- @param #number Runtime (Optionally) Run this IR Marker for the given number of seconds, then stop. Use in conjunction with EnableImmediately. Defaults to 60 seconds.
|
||||
-- @return #CONTROLLABLE self
|
||||
function CONTROLLABLE:NewIRMarker(EnableImmediately, Runtime)
|
||||
self:T2("NewIRMarker")
|
||||
if self:IsInstanceOf("GROUP") then
|
||||
if self.IRMarkerGroup == true then return end
|
||||
self.IRMarkerGroup = true
|
||||
self.IRMarkerUnit = false
|
||||
elseif self:IsInstanceOf("UNIT") then
|
||||
if self.IRMarkerUnit == true then return end
|
||||
self.IRMarkerGroup = false
|
||||
self.IRMarkerUnit = true
|
||||
end
|
||||
|
||||
self.Runtime = Runtime or 60
|
||||
if EnableImmediately and EnableImmediately == true then
|
||||
self:EnableIRMarker(Runtime)
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- [GROUND] Enable the IR marker.
|
||||
-- @param #CONTROLLABLE self
|
||||
-- @param #number Runtime (Optionally) Run this IR Marker for the given number of seconds, then stop. Else run until you call `myobject:DisableIRMarker()`.
|
||||
-- @return #CONTROLLABLE self
|
||||
function CONTROLLABLE:EnableIRMarker(Runtime)
|
||||
self:T2("EnableIRMarker")
|
||||
if self.IRMarkerGroup == nil then
|
||||
self:NewIRMarker(true,Runtime)
|
||||
return
|
||||
end
|
||||
|
||||
if self:IsInstanceOf("GROUP") then
|
||||
self:EnableIRMarkerForGroup(Runtime)
|
||||
return
|
||||
end
|
||||
|
||||
if self.timer and self.timer:IsRunning() then return self end
|
||||
|
||||
local Runtime = Runtime or self.Runtime
|
||||
self.timer = TIMER:New(CONTROLLABLE._MarkerBlink, self)
|
||||
self.timer:Start(nil, 1 - math.random(1, 5) / 10 / 2, Runtime) -- start randomized
|
||||
self.IRMarkerUnit = true
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- [GROUND] Disable the IR marker.
|
||||
-- @param #CONTROLLABLE self
|
||||
-- @return #CONTROLLABLE self
|
||||
function CONTROLLABLE:DisableIRMarker()
|
||||
self:T2("DisableIRMarker")
|
||||
if self:IsInstanceOf("GROUP") then
|
||||
self:DisableIRMarkerForGroup()
|
||||
return
|
||||
end
|
||||
|
||||
if self.spot then
|
||||
self.spot = nil
|
||||
end
|
||||
if self.timer and self.timer:IsRunning() then
|
||||
self.timer:Stop()
|
||||
self.timer = nil
|
||||
end
|
||||
|
||||
if self:IsInstanceOf("GROUP") then
|
||||
self.IRMarkerGroup = nil
|
||||
elseif self:IsInstanceOf("UNIT") then
|
||||
self.IRMarkerUnit = nil
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- [GROUND] Enable the IR markers for a whole group.
|
||||
-- @param #CONTROLLABLE self
|
||||
-- @param #number Runtime Runtime of the marker in seconds
|
||||
-- @return #CONTROLLABLE self
|
||||
function CONTROLLABLE:EnableIRMarkerForGroup(Runtime)
|
||||
self:T2("EnableIRMarkerForGroup")
|
||||
if self:IsInstanceOf("GROUP")
|
||||
then
|
||||
local units = self:GetUnits() or {}
|
||||
for _,_unit in pairs(units) do
|
||||
_unit:EnableIRMarker(Runtime)
|
||||
end
|
||||
self.IRMarkerGroup = true
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- [GROUND] Disable the IR markers for a whole group.
|
||||
-- @param #CONTROLLABLE self
|
||||
-- @return #CONTROLLABLE self
|
||||
function CONTROLLABLE:DisableIRMarkerForGroup()
|
||||
self:T2("DisableIRMarkerForGroup")
|
||||
if self:IsInstanceOf("GROUP") then
|
||||
local units = self:GetUnits() or {}
|
||||
for _,_unit in pairs(units) do
|
||||
_unit:DisableIRMarker()
|
||||
end
|
||||
self.IRMarkerGroup = nil
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- [GROUND] Check if an IR Spot exists.
|
||||
-- @param #CONTROLLABLE self
|
||||
-- @return #boolean outcome
|
||||
function CONTROLLABLE:HasIRMarker()
|
||||
self:T2("HasIRMarker")
|
||||
if self:IsInstanceOf("GROUP") then
|
||||
local units = self:GetUnits() or {}
|
||||
for _,_unit in pairs(units) do
|
||||
if _unit.timer and _unit.timer:IsRunning() then return true end
|
||||
end
|
||||
elseif self.timer and self.timer:IsRunning() then return true end
|
||||
return false
|
||||
end
|
||||
|
||||
--- [Internal] This method is called by the scheduler to blink the IR marker.
|
||||
function CONTROLLABLE._StopSpot(spot)
|
||||
if spot then
|
||||
spot:destroy()
|
||||
end
|
||||
end
|
||||
|
||||
--- [Internal] This method is called by the scheduler after enabling the IR marker.
|
||||
-- @param #CONTROLLABLE self
|
||||
-- @return #CONTROLLABLE self
|
||||
function CONTROLLABLE:_MarkerBlink()
|
||||
self:T2("_MarkerBlink")
|
||||
if self:IsAlive() ~= true then
|
||||
self:DisableIRMarker()
|
||||
return
|
||||
end
|
||||
|
||||
self.timer.dT = 1 - (math.random(1, 2) / 10 / 2) -- randomize the blinking by a small amount
|
||||
|
||||
local _, _, unitBBHeight, _ = self:GetObjectSize()
|
||||
local unitPos = self:GetPositionVec3()
|
||||
|
||||
if self.timer:IsRunning() then
|
||||
self:T2("Create Spot")
|
||||
local spot = Spot.createInfraRed(
|
||||
self.DCSUnit,
|
||||
{ x = 0, y = (unitBBHeight + 1), z = 0 },
|
||||
{ x = unitPos.x, y = (unitPos.y + unitBBHeight), z = unitPos.z }
|
||||
)
|
||||
self.spot = spot
|
||||
local offTimer = nil
|
||||
local offTimer = TIMER:New(CONTROLLABLE._StopSpot, spot)
|
||||
offTimer:Start(0.5)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
506
Moose Development/Moose/Wrapper/DynamicCargo.lua
Normal file
506
Moose Development/Moose/Wrapper/DynamicCargo.lua
Normal file
@@ -0,0 +1,506 @@
|
||||
--- **Wrapper** - Dynamic Cargo create from the F8 menu.
|
||||
--
|
||||
-- ## Main Features:
|
||||
--
|
||||
-- * Convenient access to DCS API functions
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ## Example Missions:
|
||||
--
|
||||
-- Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_Demos/tree/master/Wrapper/Storage).
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **Applevangelist**
|
||||
--
|
||||
-- ===
|
||||
-- @module Wrapper.DynamicCargo
|
||||
-- @image Wrapper_Storage.png
|
||||
|
||||
|
||||
--- DYNAMICCARGO class.
|
||||
-- @type DYNAMICCARGO
|
||||
-- @field #string ClassName Name of the class.
|
||||
-- @field #number verbose Verbosity level.
|
||||
-- @field #string lid Class id string for output to DCS log file.
|
||||
-- @field Wrapper.Storage#STORAGE warehouse The STORAGE object.
|
||||
-- @field #string version.
|
||||
-- @field #string CargoState.
|
||||
-- @field #table DCS#Vec3 LastPosition.
|
||||
-- @field #number Interval Check Interval. 20 secs default.
|
||||
-- @field #boolean testing
|
||||
-- @field Core.Timer#TIMER timer Timmer to run intervals
|
||||
-- @field #string Owner The playername who has created, loaded or unloaded this cargo. Depends on state.
|
||||
-- @extends Wrapper.Positionable#POSITIONABLE
|
||||
|
||||
--- *The capitalist cannot store labour-power in warehouses after he has bought it, as he may do with the raw material.* -- Karl Marx
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- # The DYNAMICCARGO Concept
|
||||
--
|
||||
-- The DYNAMICCARGO class offers an easy-to-use wrapper interface to all DCS API functions of DCS dynamically spawned cargo crates.
|
||||
-- We named the class DYNAMICCARGO, because the name WAREHOUSE is already taken by another MOOSE class..
|
||||
--
|
||||
-- # Constructor
|
||||
--
|
||||
-- @field #DYNAMICCARGO
|
||||
DYNAMICCARGO = {
|
||||
ClassName = "DYNAMICCARGO",
|
||||
verbose = 0,
|
||||
testing = false,
|
||||
Interval = 10,
|
||||
|
||||
}
|
||||
|
||||
--- Liquid types.
|
||||
-- @type DYNAMICCARGO.Liquid
|
||||
-- @field #number JETFUEL Jet fuel (0).
|
||||
-- @field #number GASOLINE Aviation gasoline (1).
|
||||
-- @field #number MW50 MW50 (2).
|
||||
-- @field #number DIESEL Diesel (3).
|
||||
DYNAMICCARGO.Liquid = {
|
||||
JETFUEL = 0,
|
||||
GASOLINE = 1,
|
||||
MW50 = 2,
|
||||
DIESEL = 3,
|
||||
}
|
||||
|
||||
--- Liquid Names for the static cargo resource table.
|
||||
-- @type DYNAMICCARGO.LiquidName
|
||||
-- @field #number JETFUEL "jet_fuel".
|
||||
-- @field #number GASOLINE "gasoline".
|
||||
-- @field #number MW50 "methanol_mixture".
|
||||
-- @field #number DIESEL "diesel".
|
||||
DYNAMICCARGO.LiquidName = {
|
||||
GASOLINE = "gasoline",
|
||||
DIESEL = "diesel",
|
||||
MW50 = "methanol_mixture",
|
||||
JETFUEL = "jet_fuel",
|
||||
}
|
||||
|
||||
--- Storage types.
|
||||
-- @type DYNAMICCARGO.Type
|
||||
-- @field #number WEAPONS weapons.
|
||||
-- @field #number LIQUIDS liquids. Also see #list<#DYNAMICCARGO.Liquid> for types of liquids.
|
||||
-- @field #number AIRCRAFT aircraft.
|
||||
DYNAMICCARGO.Type = {
|
||||
WEAPONS = "weapons",
|
||||
LIQUIDS = "liquids",
|
||||
AIRCRAFT = "aircrafts",
|
||||
}
|
||||
|
||||
--- State types
|
||||
-- @type DYNAMICCARGO.State
|
||||
-- @field #string NEW
|
||||
-- @field #string LOADED
|
||||
-- @field #string UNLOADED
|
||||
-- @field #string REMOVED
|
||||
DYNAMICCARGO.State = {
|
||||
NEW = "NEW",
|
||||
LOADED = "LOADED",
|
||||
UNLOADED = "UNLOADED",
|
||||
REMOVED = "REMOVED",
|
||||
}
|
||||
|
||||
--- Helo types possible.
|
||||
-- @type DYNAMICCARGO.AircraftTypes
|
||||
DYNAMICCARGO.AircraftTypes = {
|
||||
["CH-47Fbl1"] = "CH-47Fbl1",
|
||||
}
|
||||
|
||||
--- Helo types possible.
|
||||
-- @type DYNAMICCARGO.AircraftDimensions
|
||||
DYNAMICCARGO.AircraftDimensions = {
|
||||
-- CH-47 model start coordinate is quite exactly in the middle of the model, so half values here
|
||||
["CH-47Fbl1"] = {
|
||||
["width"] = 4,
|
||||
["height"] = 6,
|
||||
["length"] = 11,
|
||||
["ropelength"] = 30,
|
||||
},
|
||||
}
|
||||
|
||||
--- DYNAMICCARGO class version.
|
||||
-- @field #string version
|
||||
DYNAMICCARGO.version="0.0.5"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- TODO list
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
-- TODO: A lot...
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Constructor
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Create a new DYNAMICCARGO object from the DCS static cargo object.
|
||||
-- @param #DYNAMICCARGO self
|
||||
-- @param #string CargoName Name of the Cargo.
|
||||
-- @return #DYNAMICCARGO self
|
||||
function DYNAMICCARGO:Register(CargoName)
|
||||
|
||||
-- Inherit everything from a BASE class.
|
||||
local self=BASE:Inherit(self, POSITIONABLE:New(CargoName)) -- #DYNAMICCARGO
|
||||
|
||||
self.StaticName = CargoName
|
||||
|
||||
self.LastPosition = self:GetCoordinate()
|
||||
|
||||
self.CargoState = DYNAMICCARGO.State.NEW
|
||||
|
||||
self.Interval = DYNAMICCARGO.Interval or 10
|
||||
|
||||
local DCSObject = self:GetDCSObject()
|
||||
|
||||
if DCSObject then
|
||||
local warehouse = STORAGE:NewFromDynamicCargo(CargoName)
|
||||
self.warehouse = warehouse
|
||||
end
|
||||
|
||||
self.lid = string.format("DYNAMICCARGO %s", CargoName)
|
||||
|
||||
self.Owner = string.match(CargoName,"^(.+)|%d%d:%d%d|PKG%d+") or "None"
|
||||
|
||||
self.timer = TIMER:New(DYNAMICCARGO._UpdatePosition,self)
|
||||
self.timer:Start(self.Interval,self.Interval)
|
||||
|
||||
if not _DYNAMICCARGO_HELOS then
|
||||
_DYNAMICCARGO_HELOS = SET_CLIENT:New():FilterAlive():FilterFunction(DYNAMICCARGO._FilterHeloTypes):FilterStart()
|
||||
end
|
||||
|
||||
if self.testing then
|
||||
BASE:TraceOn()
|
||||
BASE:TraceClass("DYNAMICCARGO")
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Get DCS object.
|
||||
-- @param #DYNAMICCARGO self
|
||||
-- @return DCS static object
|
||||
function DYNAMICCARGO:GetDCSObject()
|
||||
local DCSStatic = Unit.getByName( self.StaticName )
|
||||
if DCSStatic then
|
||||
return DCSStatic
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- User API Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- Get last known owner name of this DYNAMICCARGO
|
||||
-- @param #DYNAMICCARGO self
|
||||
-- @return #string Owner
|
||||
function DYNAMICCARGO:GetLastOwner()
|
||||
return self.Owner
|
||||
end
|
||||
|
||||
--- Returns true if the cargo is new and has never been loaded into a Helo.
|
||||
-- @param #DYNAMICCARGO self
|
||||
-- @return #boolean Outcome
|
||||
function DYNAMICCARGO:IsNew()
|
||||
if self.CargoState and self.CargoState == DYNAMICCARGO.State.NEW then
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
--- Returns true if the cargo been loaded into a Helo.
|
||||
-- @param #DYNAMICCARGO self
|
||||
-- @return #boolean Outcome
|
||||
function DYNAMICCARGO:IsLoaded()
|
||||
if self.CargoState and self.CargoState == DYNAMICCARGO.State.LOADED then
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
--- Returns true if the cargo has been unloaded from a Helo.
|
||||
-- @param #DYNAMICCARGO self
|
||||
-- @return #boolean Outcome
|
||||
function DYNAMICCARGO:IsUnloaded()
|
||||
if self.CargoState and self.CargoState == DYNAMICCARGO.State.REMOVED then
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
--- Returns true if the cargo has been removed.
|
||||
-- @param #DYNAMICCARGO self
|
||||
-- @return #boolean Outcome
|
||||
function DYNAMICCARGO:IsRemoved()
|
||||
if self.CargoState and self.CargoState == DYNAMICCARGO.State.UNLOADED then
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
--- [CTLD] Get number of crates this DYNAMICCARGO consists of. Always one.
|
||||
-- @param #DYNAMICCARGO self
|
||||
-- @return #number crate number, always one
|
||||
function DYNAMICCARGO:GetCratesNeeded()
|
||||
return 1
|
||||
end
|
||||
|
||||
--- [CTLD] Get this DYNAMICCARGO drop state. True if DYNAMICCARGO.State.UNLOADED
|
||||
-- @param #DYNAMICCARGO self
|
||||
-- @return #boolean Dropped
|
||||
function DYNAMICCARGO:WasDropped()
|
||||
return self.CargoState == DYNAMICCARGO.State.UNLOADED and true or false
|
||||
end
|
||||
|
||||
--- [CTLD] Get CTLD_CARGO.Enum type of this DYNAMICCARGO
|
||||
-- @param #DYNAMICCARGO self
|
||||
-- @return #string Type, only one at the moment is CTLD_CARGO.Enum.GCLOADABLE
|
||||
function DYNAMICCARGO:GetType()
|
||||
return CTLD_CARGO.Enum.GCLOADABLE
|
||||
end
|
||||
|
||||
|
||||
--- Find last known position of this DYNAMICCARGO
|
||||
-- @param #DYNAMICCARGO self
|
||||
-- @return DCS#Vec3 Position in 3D space
|
||||
function DYNAMICCARGO:GetLastPosition()
|
||||
return self.LastPosition
|
||||
end
|
||||
|
||||
--- Find current state of this DYNAMICCARGO
|
||||
-- @param #DYNAMICCARGO self
|
||||
-- @return string The current state
|
||||
function DYNAMICCARGO:GetState()
|
||||
return self.CargoState
|
||||
end
|
||||
|
||||
--- Find a DYNAMICCARGO in the **_DATABASE** using the name associated with it.
|
||||
-- @param #DYNAMICCARGO self
|
||||
-- @param #string Name The dynamic cargo name
|
||||
-- @return #DYNAMICCARGO self
|
||||
function DYNAMICCARGO:FindByName( Name )
|
||||
local storage = _DATABASE:FindDynamicCargo( Name )
|
||||
return storage
|
||||
end
|
||||
|
||||
--- Find the first(!) DYNAMICCARGO matching using patterns. Note that this is **a lot** slower than `:FindByName()`!
|
||||
-- @param #DYNAMICCARGO self
|
||||
-- @param #string Pattern The pattern to look for. Refer to [LUA patterns](http://www.easyuo.com/openeuo/wiki/index.php/Lua_Patterns_and_Captures_\(Regular_Expressions\)) for regular expressions in LUA.
|
||||
-- @return #DYNAMICCARGO The DYNAMICCARGO.
|
||||
-- @usage
|
||||
-- -- Find a dynamic cargo with a partial dynamic cargo name
|
||||
-- local grp = DYNAMICCARGO:FindByMatching( "Apple" )
|
||||
-- -- will return e.g. a dynamic cargo named "Apple|08:00|PKG08"
|
||||
--
|
||||
-- -- using a pattern
|
||||
-- local grp = DYNAMICCARGO:FindByMatching( ".%d.%d$" )
|
||||
-- -- will return the first dynamic cargo found ending in "-1-1" to "-9-9", but not e.g. "-10-1"
|
||||
function DYNAMICCARGO:FindByMatching( Pattern )
|
||||
local GroupFound = nil
|
||||
|
||||
for name,static in pairs(_DATABASE.DYNAMICCARGO) do
|
||||
if string.match(name, Pattern ) then
|
||||
GroupFound = static
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
return GroupFound
|
||||
end
|
||||
|
||||
--- Find all DYNAMICCARGO objects matching using patterns. Note that this is **a lot** slower than `:FindByName()`!
|
||||
-- @param #DYNAMICCARGO self
|
||||
-- @param #string Pattern The pattern to look for. Refer to [LUA patterns](http://www.easyuo.com/openeuo/wiki/index.php/Lua_Patterns_and_Captures_\(Regular_Expressions\)) for regular expressions in LUA.
|
||||
-- @return #table Groups Table of matching #DYNAMICCARGO objects found
|
||||
-- @usage
|
||||
-- -- Find all dynamic cargo with a partial dynamic cargo name
|
||||
-- local grptable = DYNAMICCARGO:FindAllByMatching( "Apple" )
|
||||
-- -- will return all dynamic cargos with "Apple" in the name
|
||||
--
|
||||
-- -- using a pattern
|
||||
-- local grp = DYNAMICCARGO:FindAllByMatching( ".%d.%d$" )
|
||||
-- -- will return the all dynamic cargos found ending in "-1-1" to "-9-9", but not e.g. "-10-1" or "-1-10"
|
||||
function DYNAMICCARGO:FindAllByMatching( Pattern )
|
||||
local GroupsFound = {}
|
||||
|
||||
for name,static in pairs(_DATABASE.DYNAMICCARGO) do
|
||||
if string.match(name, Pattern ) then
|
||||
GroupsFound[#GroupsFound+1] = static
|
||||
end
|
||||
end
|
||||
|
||||
return GroupsFound
|
||||
end
|
||||
|
||||
--- Get the #STORAGE object from this dynamic cargo.
|
||||
-- @param #DYNAMICCARGO self
|
||||
-- @return Wrapper.Storage#STORAGE Storage The #STORAGE object
|
||||
function DYNAMICCARGO:GetStorageObject()
|
||||
return self.warehouse
|
||||
end
|
||||
|
||||
--- Get the weight in kgs from this dynamic cargo.
|
||||
-- @param #DYNAMICCARGO self
|
||||
-- @return #number Weight in kgs.
|
||||
function DYNAMICCARGO:GetCargoWeight()
|
||||
local DCSObject = self:GetDCSObject()
|
||||
if DCSObject then
|
||||
local weight = DCSObject:getCargoWeight()
|
||||
return weight
|
||||
else
|
||||
return 0
|
||||
end
|
||||
end
|
||||
|
||||
--- Get the cargo display name from this dynamic cargo.
|
||||
-- @param #DYNAMICCARGO self
|
||||
-- @return #string The display name
|
||||
function DYNAMICCARGO:GetCargoDisplayName()
|
||||
local DCSObject = self:GetDCSObject()
|
||||
if DCSObject then
|
||||
local weight = DCSObject:getCargoDisplayName()
|
||||
return weight
|
||||
else
|
||||
return self.StaticName
|
||||
end
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Private Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- [Internal] _Get Possible Player Helo Nearby
|
||||
-- @param #DYNAMICCARGO self
|
||||
-- @param Core.Point#COORDINATE pos
|
||||
-- @param #boolean loading If true measure distance for loading else for unloading
|
||||
-- @return #boolean Success
|
||||
-- @return Wrapper.Client#CLIENT Helo
|
||||
-- @return #string PlayerName
|
||||
function DYNAMICCARGO:_GetPossibleHeloNearby(pos,loading)
|
||||
local set = _DYNAMICCARGO_HELOS:GetAliveSet()
|
||||
local success = false
|
||||
local Helo = nil
|
||||
local Playername = nil
|
||||
for _,_helo in pairs (set or {}) do
|
||||
local helo = _helo -- Wrapper.Client#CLIENT
|
||||
local name = helo:GetPlayerName() or _DATABASE:_FindPlayerNameByUnitName(helo:GetName()) or "None"
|
||||
self:T(self.lid.." Checking: "..name)
|
||||
local hpos = helo:GetCoordinate()
|
||||
-- TODO Unloading via sling load?
|
||||
--local inair = hpos.y-hpos:GetLandHeight() > 4.5 and true or false -- Standard FARP is 4.5m
|
||||
local inair = helo:InAir()
|
||||
self:T(self.lid.." InAir: AGL/InAir: "..hpos.y-hpos:GetLandHeight().."/"..tostring(inair))
|
||||
local typename = helo:GetTypeName()
|
||||
if hpos and typename and inair == false then
|
||||
local dimensions = DYNAMICCARGO.AircraftDimensions[typename]
|
||||
if dimensions then
|
||||
local delta2D = hpos:Get2DDistance(pos)
|
||||
local delta3D = hpos:Get3DDistance(pos)
|
||||
if self.testing then
|
||||
self:T(string.format("Cargo relative position: 2D %dm | 3D %dm",delta2D,delta3D))
|
||||
self:T(string.format("Helo dimension: length %dm | width %dm | rope %dm",dimensions.length,dimensions.width,dimensions.ropelength))
|
||||
end
|
||||
if loading~=true and delta2D > dimensions.length or delta2D > dimensions.width or delta3D > dimensions.ropelength then
|
||||
success = true
|
||||
Helo = helo
|
||||
Playername = name
|
||||
end
|
||||
if loading == true and delta2D < dimensions.length or delta2D < dimensions.width or delta3D < dimensions.ropelength then
|
||||
success = true
|
||||
Helo = helo
|
||||
Playername = name
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return success,Helo,Playername
|
||||
end
|
||||
|
||||
--- [Internal] Update internal states.
|
||||
-- @param #DYNAMICCARGO self
|
||||
-- @return #DYNAMICCARGO self
|
||||
function DYNAMICCARGO:_UpdatePosition()
|
||||
self:T(self.lid.." _UpdatePositionAndState")
|
||||
if self:IsAlive() then
|
||||
local pos = self:GetCoordinate()
|
||||
if self.testing then
|
||||
self:T(string.format("Cargo position: x=%d, y=%d, z=%d",pos.x,pos.y,pos.z))
|
||||
self:T(string.format("Last position: x=%d, y=%d, z=%d",self.LastPosition.x,self.LastPosition.y,self.LastPosition.z))
|
||||
end
|
||||
if UTILS.Round(UTILS.VecDist3D(pos,self.LastPosition),2) > 0.5 then
|
||||
---------------
|
||||
-- LOAD Cargo
|
||||
---------------
|
||||
if self.CargoState == DYNAMICCARGO.State.NEW then
|
||||
local isloaded, client, playername = self:_GetPossibleHeloNearby(pos,true)
|
||||
self:T(self.lid.." moved! NEW -> LOADED by "..tostring(playername))
|
||||
self.CargoState = DYNAMICCARGO.State.LOADED
|
||||
self.Owner = playername
|
||||
_DATABASE:CreateEventDynamicCargoLoaded(self)
|
||||
---------------
|
||||
-- UNLOAD Cargo
|
||||
---------------
|
||||
elseif self.CargoState == DYNAMICCARGO.State.LOADED then
|
||||
-- TODO add checker if we are in flight somehow
|
||||
-- ensure not just the helo is moving
|
||||
local count = _DYNAMICCARGO_HELOS:CountAlive()
|
||||
-- Testing
|
||||
local landheight = pos:GetLandHeight()
|
||||
local agl = pos.y-landheight
|
||||
agl = UTILS.Round(agl,2)
|
||||
self:T(self.lid.." AGL: "..agl or -1)
|
||||
local isunloaded = true
|
||||
local client
|
||||
local playername = self.Owner
|
||||
if count > 0 and (agl > 0 or self.testing) then
|
||||
self:T(self.lid.." Possible alive helos: "..count or -1)
|
||||
if agl ~= 0 or self.testing then
|
||||
isunloaded, client, playername = self:_GetPossibleHeloNearby(pos,false)
|
||||
end
|
||||
if isunloaded then
|
||||
self:T(self.lid.." moved! LOADED -> UNLOADED by "..tostring(playername))
|
||||
self.CargoState = DYNAMICCARGO.State.UNLOADED
|
||||
self.Owner = playername
|
||||
_DATABASE:CreateEventDynamicCargoUnloaded(self)
|
||||
end
|
||||
elseif count > 0 and agl == 0 then
|
||||
self:T(self.lid.." moved! LOADED -> UNLOADED by "..tostring(playername))
|
||||
self.CargoState = DYNAMICCARGO.State.UNLOADED
|
||||
self.Owner = playername
|
||||
_DATABASE:CreateEventDynamicCargoUnloaded(self)
|
||||
end
|
||||
end
|
||||
self.LastPosition = pos
|
||||
end
|
||||
else
|
||||
---------------
|
||||
-- REMOVED Cargo
|
||||
---------------
|
||||
if self.timer and self.timer:IsRunning() then self.timer:Stop() end
|
||||
self:T(self.lid.." dead! " ..self.CargoState.."-> REMOVED")
|
||||
self.CargoState = DYNAMICCARGO.State.REMOVED
|
||||
_DATABASE:CreateEventDynamicCargoRemoved(self)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- [Internal] Track helos for loaded/unloaded decision making.
|
||||
-- @param Wrapper.Client#CLIENT client
|
||||
-- @return #boolean IsIn
|
||||
function DYNAMICCARGO._FilterHeloTypes(client)
|
||||
if not client then return false end
|
||||
local typename = client:GetTypeName()
|
||||
local isinclude = DYNAMICCARGO.AircraftTypes[typename] ~= nil and true or false
|
||||
return isinclude
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
File diff suppressed because it is too large
Load Diff
@@ -43,7 +43,7 @@ do
|
||||
-- @field #NET
|
||||
NET = {
|
||||
ClassName = "NET",
|
||||
Version = "0.1.3",
|
||||
Version = "0.1.4",
|
||||
BlockTime = 600,
|
||||
BlockedPilots = {},
|
||||
BlockedUCIDs = {},
|
||||
@@ -67,6 +67,9 @@ function NET:New()
|
||||
self.KnownPilots = {}
|
||||
self:SetBlockMessage()
|
||||
self:SetUnblockMessage()
|
||||
self.BlockedSides = {}
|
||||
self.BlockedSides[1] = false
|
||||
self.BlockedSides[2] = false
|
||||
|
||||
-- Start State.
|
||||
self:SetStartState("Stopped")
|
||||
@@ -160,11 +163,12 @@ end
|
||||
-- @param #string PlayerSlot
|
||||
-- @return #boolean IsBlocked
|
||||
function NET:IsAnyBlocked(UCID,Name,PlayerID,PlayerSide,PlayerSlot)
|
||||
self:T({UCID,Name,PlayerID,PlayerSide,PlayerSlot})
|
||||
local blocked = false
|
||||
local TNow = timer.getTime()
|
||||
-- UCID
|
||||
if UCID and self.BlockedUCIDs[UCID] and TNow < self.BlockedUCIDs[UCID] then
|
||||
return true
|
||||
blocked = true
|
||||
end
|
||||
-- ID/Name
|
||||
if PlayerID and not Name then
|
||||
@@ -172,16 +176,18 @@ function NET:IsAnyBlocked(UCID,Name,PlayerID,PlayerSide,PlayerSlot)
|
||||
end
|
||||
-- Name
|
||||
if Name and self.BlockedPilots[Name] and TNow < self.BlockedPilots[Name] then
|
||||
return true
|
||||
blocked = true
|
||||
end
|
||||
-- Side
|
||||
if PlayerSide and self.BlockedSides[PlayerSide] and TNow < self.BlockedSides[PlayerSide] then
|
||||
return true
|
||||
self:T({time = self.BlockedSides[PlayerSide]})
|
||||
if PlayerSide and type(self.BlockedSides[PlayerSide]) == "number" and TNow < self.BlockedSides[PlayerSide] then
|
||||
blocked = true
|
||||
end
|
||||
-- Slot
|
||||
if PlayerSlot and self.BlockedSlots[PlayerSlot] and TNow < self.BlockedSlots[PlayerSlot] then
|
||||
return true
|
||||
blocked = true
|
||||
end
|
||||
self:T("IsAnyBlocked: "..tostring(blocked))
|
||||
return blocked
|
||||
end
|
||||
|
||||
@@ -200,19 +206,27 @@ function NET:_EventHandler(EventData)
|
||||
local ucid = self:GetPlayerUCID(nil,name) or "none"
|
||||
local PlayerID = self:GetPlayerIDByName(name) or "none"
|
||||
local PlayerSide, PlayerSlot = self:GetSlot(data.IniUnit)
|
||||
if not PlayerSide then PlayerSide = EventData.IniCoalition end
|
||||
if not PlayerSlot then PlayerSlot = EventData.IniUnit:GetID() or -1 end
|
||||
local TNow = timer.getTime()
|
||||
|
||||
self:T(self.lid.."Event for: "..name.." | UCID: "..ucid)
|
||||
--self:T(self.lid.."Event for: "..name.." | UCID: "..ucid .. " | ID/SIDE/SLOT "..PlayerID.."/"..PlayerSide.."/"..PlayerSlot)
|
||||
|
||||
-- Joining
|
||||
if data.id == EVENTS.PlayerEnterUnit or data.id == EVENTS.PlayerEnterAircraft then
|
||||
self:T(self.lid.."Pilot Joining: "..name.." | UCID: "..ucid.." | Event ID: "..data.id)
|
||||
-- Check for blockages
|
||||
local blocked = self:IsAnyBlocked(ucid,name,PlayerID,PlayerSide,PlayerSlot)
|
||||
|
||||
if blocked and PlayerID and tonumber(PlayerID) ~= 1 then
|
||||
if blocked and PlayerID then -- and tonumber(PlayerID) ~= 1 then
|
||||
self:T("Player blocked")
|
||||
-- block pilot
|
||||
local outcome = net.force_player_slot(tonumber(PlayerID), 0, '' )
|
||||
local outcome = net.force_player_slot(tonumber(PlayerID), PlayerSide, data.IniUnit:GetID() )
|
||||
self:T({Blocked_worked=outcome})
|
||||
if outcome == false then
|
||||
local unit = data.IniUnit
|
||||
local sched = TIMER:New(unit.Destroy,unit,3):Start(3)
|
||||
self:__PlayerBlocked(5,unit,name,1)
|
||||
end
|
||||
else
|
||||
local client = CLIENT:FindByPlayerName(name) or data.IniUnit
|
||||
if not self.KnownPilots[name] or (self.KnownPilots[name] and TNow-self.KnownPilots[name].timestamp > 3) then
|
||||
@@ -225,6 +239,7 @@ function NET:_EventHandler(EventData)
|
||||
slot = PlayerSlot,
|
||||
timestamp = TNow,
|
||||
}
|
||||
--UTILS.PrintTableToLog(self.KnownPilots[name])
|
||||
end
|
||||
return self
|
||||
end
|
||||
@@ -350,11 +365,10 @@ end
|
||||
|
||||
--- Block a specific coalition side, does NOT automatically kick all players of that side or kick out joined players
|
||||
-- @param #NET self
|
||||
-- @param #number side The side to block - 1 : Red, 2 : Blue
|
||||
-- @param #number Side The side to block - 1 : Red, 2 : Blue
|
||||
-- @param #number Seconds Seconds (optional) Number of seconds the player has to wait before rejoining.
|
||||
-- @return #NET self
|
||||
function NET:BlockSide(Side,Seconds)
|
||||
self:T({Side,Seconds})
|
||||
local addon = Seconds or self.BlockTime
|
||||
if Side == 1 or Side == 2 then
|
||||
self.BlockedSides[Side] = timer.getTime()+addon
|
||||
@@ -367,10 +381,9 @@ end
|
||||
-- @param #number Seconds Seconds (optional) Number of seconds the player has to wait before rejoining.
|
||||
-- @return #NET self
|
||||
function NET:UnblockSide(Side,Seconds)
|
||||
self:T({Side,Seconds})
|
||||
local addon = Seconds or self.BlockTime
|
||||
if Side == 1 or Side == 2 then
|
||||
self.BlockedSides[Side] = nil
|
||||
self.BlockedSides[Side] = false
|
||||
end
|
||||
return self
|
||||
end
|
||||
@@ -485,8 +498,11 @@ end
|
||||
-- @param Wrapper.Client#CLIENT Client The client
|
||||
-- @return #number PlayerID or nil
|
||||
function NET:GetPlayerIDFromClient(Client)
|
||||
self:T("GetPlayerIDFromClient")
|
||||
self:T({Client=Client})
|
||||
if Client then
|
||||
local name = Client:GetPlayerName()
|
||||
self:T({name=name})
|
||||
local id = self:GetPlayerIDByName(name)
|
||||
return id
|
||||
else
|
||||
@@ -528,6 +544,7 @@ function NET:SendChatToPlayer(Message, ToPlayer, FromPlayer)
|
||||
return self
|
||||
end
|
||||
|
||||
--[[ not in 2.97 MSE any longer
|
||||
--- Load a specific mission.
|
||||
-- @param #NET self
|
||||
-- @param #string Path and Mission
|
||||
@@ -550,6 +567,7 @@ function NET:LoadNextMission()
|
||||
outcome = net.load_next_mission()
|
||||
return outcome
|
||||
end
|
||||
--]]
|
||||
|
||||
--- Return a table of players currently connected to the server.
|
||||
-- @param #NET self
|
||||
@@ -680,16 +698,19 @@ end
|
||||
-- @return #number SideID i.e. 0 : spectators, 1 : Red, 2 : Blue
|
||||
-- @return #number SlotID
|
||||
function NET:GetSlot(Client)
|
||||
self:T("NET.GetSlot")
|
||||
local PlayerID = self:GetPlayerIDFromClient(Client)
|
||||
self:T("NET.GetSlot PlayerID = "..tostring(PlayerID))
|
||||
if PlayerID then
|
||||
local side,slot = net.get_slot(tonumber(PlayerID))
|
||||
self:T("NET.GetSlot side, slot = "..tostring(side)..","..tostring(slot))
|
||||
return side,slot
|
||||
else
|
||||
return nil,nil
|
||||
end
|
||||
end
|
||||
|
||||
--- Force the slot for a specific client.
|
||||
--- Force the slot for a specific client. If this returns false, it didn't work via `net` (which is ALWAYS the case as of Nov 2024)!
|
||||
-- @param #NET self
|
||||
-- @param Wrapper.Client#CLIENT Client The client
|
||||
-- @param #number SideID i.e. 0 : spectators, 1 : Red, 2 : Blue
|
||||
@@ -697,19 +718,22 @@ end
|
||||
-- @return #boolean Success
|
||||
function NET:ForceSlot(Client,SideID,SlotID)
|
||||
local PlayerID = self:GetPlayerIDFromClient(Client)
|
||||
if PlayerID and tonumber(PlayerID) ~= 1 then
|
||||
return net.force_player_slot(tonumber(PlayerID), SideID, SlotID or '' )
|
||||
local SlotID = SlotID or Client:GetID()
|
||||
if PlayerID then -- and tonumber(PlayerID) ~= 1 then
|
||||
return net.force_player_slot(tonumber(PlayerID), SideID, SlotID )
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
--- Force a client back to spectators.
|
||||
--- Force a client back to spectators. If this returns false, it didn't work via `net` (which is ALWAYS the case as of Nov 2024)!
|
||||
-- @param #NET self
|
||||
-- @param Wrapper.Client#CLIENT Client The client
|
||||
-- @return #boolean Succes
|
||||
function NET:ReturnToSpectators(Client)
|
||||
local outcome = self:ForceSlot(Client,0)
|
||||
-- workaround
|
||||
local sched = TIMER:New(Client.Destroy,Client,1):Start(1)
|
||||
return outcome
|
||||
end
|
||||
|
||||
@@ -779,7 +803,7 @@ function NET:onafterStatus(From,Event,To)
|
||||
local function HouseHold(tavolo)
|
||||
local TNow = timer.getTime()
|
||||
for _,entry in pairs (tavolo) do
|
||||
if entry >= TNow then entry = nil end
|
||||
if type(entry) == "number" and entry >= TNow then entry = false end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -110,14 +110,17 @@ function POSITIONABLE:Destroy( GenerateEvent )
|
||||
|
||||
if GenerateEvent and GenerateEvent == true then
|
||||
if self:IsAir() then
|
||||
--self:ScheduleOnce(1,self.CreateEventCrash,self,timer.getTime(),DCSObject)
|
||||
self:CreateEventCrash( timer.getTime(), DCSObject )
|
||||
else
|
||||
--self:ScheduleOnce(1,self.CreateEventDead,self,timer.getTime(),DCSObject)
|
||||
self:CreateEventDead( timer.getTime(), DCSObject )
|
||||
end
|
||||
elseif GenerateEvent == false then
|
||||
-- Do nothing!
|
||||
else
|
||||
self:CreateEventRemoveUnit( timer.getTime(), DCSObject )
|
||||
--self:ScheduleOnce(1,self.CreateEventRemoveUnit,self,timer.getTime(),DCSObject)
|
||||
end
|
||||
|
||||
USERFLAG:New( UnitGroupName ):Set( 100 )
|
||||
@@ -142,7 +145,11 @@ function POSITIONABLE:GetPosition()
|
||||
self:F2( self.PositionableName )
|
||||
|
||||
local DCSPositionable = self:GetDCSObject()
|
||||
|
||||
|
||||
if self:IsInstanceOf("GROUP") then
|
||||
DCSPositionable = self:GetFirstUnitAlive():GetDCSObject()
|
||||
end
|
||||
|
||||
if DCSPositionable then
|
||||
local PositionablePosition = DCSPositionable:getPosition()
|
||||
self:T3( PositionablePosition )
|
||||
@@ -1853,6 +1860,7 @@ do -- Cargo
|
||||
["HL_KORD"] = 6*POSITIONABLE.DefaultInfantryWeight,
|
||||
["HL_DSHK"] = 6*POSITIONABLE.DefaultInfantryWeight,
|
||||
["CCKW_353"] = 16*POSITIONABLE.DefaultInfantryWeight, --GMC CCKW 2½-ton 6×6 truck, estimating 16 soldiers,
|
||||
["MaxxPro_MRAP"] = 7*POSITIONABLE.DefaultInfantryWeight,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -48,8 +48,8 @@ function SCENERY:Register( SceneryName, SceneryObject )
|
||||
|
||||
self.SceneryObject = SceneryObject
|
||||
|
||||
if self.SceneryObject then
|
||||
self.Life0 = self.SceneryObject:getLife()
|
||||
if self.SceneryObject and self.SceneryObject.getLife then -- fix some objects do not have all functions
|
||||
self.Life0 = self.SceneryObject:getLife() or 0
|
||||
else
|
||||
self.Life0 = 0
|
||||
end
|
||||
@@ -59,7 +59,7 @@ function SCENERY:Register( SceneryName, SceneryObject )
|
||||
return self
|
||||
end
|
||||
|
||||
--- Returns the Value of the zone with the given PropertyName, or nil if no matching property exists.
|
||||
--- Returns the value of the scenery with the given PropertyName, or nil if no matching property exists.
|
||||
-- @param #SCENERY self
|
||||
-- @param #string PropertyName The name of a the QuadZone Property from the scenery assignment to be retrieved.
|
||||
-- @return #string The Value of the QuadZone Property from the scenery assignment with the given PropertyName, or nil if absent.
|
||||
@@ -67,6 +67,14 @@ function SCENERY:GetProperty(PropertyName)
|
||||
return self.Properties[PropertyName]
|
||||
end
|
||||
|
||||
--- Checks if the value of the scenery with the given PropertyName exists.
|
||||
-- @param #SCENERY self
|
||||
-- @param #string PropertyName The name of a the QuadZone Property from the scenery assignment to be retrieved.
|
||||
-- @return #boolean Outcome True if it exists, else false.
|
||||
function SCENERY:HasProperty(PropertyName)
|
||||
return self.Properties[PropertyName] ~= nil and true or false
|
||||
end
|
||||
|
||||
--- Returns the scenery Properties table.
|
||||
-- @param #SCENERY self
|
||||
-- @return #table The Key:Value table of QuadZone properties of the zone from the scenery assignment .
|
||||
@@ -83,6 +91,7 @@ function SCENERY:SetProperty(PropertyName, PropertyValue)
|
||||
self.Properties[PropertyName] = PropertyValue
|
||||
return self
|
||||
end
|
||||
|
||||
--- Obtain object name.
|
||||
--@param #SCENERY self
|
||||
--@return #string Name
|
||||
@@ -97,7 +106,7 @@ function SCENERY:GetDCSObject()
|
||||
return self.SceneryObject
|
||||
end
|
||||
|
||||
--- Get current life points from the SCENERY Object.
|
||||
--- Get current life points from the SCENERY Object. Note - Some scenery objects always have 0 life points.
|
||||
-- **CAVEAT**: Some objects change their life value or "hitpoints" **after** the first hit. Hence we will adjust the life0 value to 120%
|
||||
-- of the last life value if life exceeds life0 (initial life) at any point. Thus will will get a smooth percentage decrease, if you use this e.g. as success
|
||||
-- criteria for a bombing task.
|
||||
@@ -105,7 +114,7 @@ end
|
||||
--@return #number life
|
||||
function SCENERY:GetLife()
|
||||
local life = 0
|
||||
if self.SceneryObject then
|
||||
if self.SceneryObject and self.SceneryObject.getLife then
|
||||
life = self.SceneryObject:getLife()
|
||||
if life > self.Life0 then
|
||||
self.Life0 = math.floor(life * 1.2)
|
||||
@@ -121,7 +130,7 @@ function SCENERY:GetLife0()
|
||||
return self.Life0 or 0
|
||||
end
|
||||
|
||||
--- Check if SCENERY Object is alive.
|
||||
--- Check if SCENERY Object is alive. Note - Some scenery objects always have 0 life points.
|
||||
--@param #SCENERY self
|
||||
--@param #number Threshold (Optional) If given, SCENERY counts as alive above this relative life in percent (1..100).
|
||||
--@return #number life
|
||||
@@ -133,7 +142,7 @@ function SCENERY:IsAlive(Threshold)
|
||||
end
|
||||
end
|
||||
|
||||
--- Check if SCENERY Object is dead.
|
||||
--- Check if SCENERY Object is dead. Note - Some scenery objects always have 0 life points.
|
||||
--@param #SCENERY self
|
||||
--@param #number Threshold (Optional) If given, SCENERY counts as dead below this relative life in percent (1..100).
|
||||
--@return #number life
|
||||
@@ -145,12 +154,13 @@ function SCENERY:IsDead(Threshold)
|
||||
end
|
||||
end
|
||||
|
||||
--- Get SCENERY relative life in percent, e.g. 75.
|
||||
--- Get SCENERY relative life in percent, e.g. 75. Note - Some scenery objects always have 0 life points.
|
||||
--@param #SCENERY self
|
||||
--@return #number rlife
|
||||
function SCENERY:GetRelativeLife()
|
||||
local life = self:GetLife()
|
||||
local life0 = self:GetLife0()
|
||||
if life == 0 or life0 == 0 then return 0 end
|
||||
local rlife = math.floor((life/life0)*100)
|
||||
return rlife
|
||||
end
|
||||
|
||||
@@ -61,6 +61,8 @@ function STATIC:Register( StaticName )
|
||||
if DCSStatic then
|
||||
local Life0 = DCSStatic:getLife() or 1
|
||||
self.Life0 = Life0
|
||||
else
|
||||
self:E(string.format("Static object %s does not exist!", tostring(self.StaticName)))
|
||||
end
|
||||
|
||||
return self
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
-- @field #string lid Class id string for output to DCS log file.
|
||||
-- @field DCS#Warehouse warehouse The DCS warehouse object.
|
||||
-- @field DCS#Airbase airbase The DCS airbase object.
|
||||
-- @field Core.Timer#TIMER SaverTimer The TIMER for autosave.
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
--- *The capitalist cannot store labour-power in warehouses after he has bought it, as he may do with the raw material.* -- Karl Marx
|
||||
@@ -127,6 +128,35 @@
|
||||
-- # Weapons Helper Enumerater
|
||||
--
|
||||
-- The currently available weapon items are available in the `ENUMS.Storage.weapons`, e.g. `ENUMS.Storage.weapons.bombs.Mk_82Y`.
|
||||
--
|
||||
-- # Persistence
|
||||
--
|
||||
-- The contents of the storage can be saved to and read from disk. For this to function, `io` and `lfs` need to be desanitized in `MissionScripting.lua`.
|
||||
--
|
||||
-- ## Save once
|
||||
--
|
||||
-- ### To save once, e.g. this is sufficient:
|
||||
--
|
||||
-- -- Filenames created are the Filename given amended by "_Liquids", "_Aircraft" and "_Weapons" followed by a ".csv". Only Storage NOT set to unlimited will be saved.
|
||||
-- local Path = "C:\\Users\\UserName\\Saved Games\\DCS\\Missions\\"
|
||||
-- local Filename = "Batumi"
|
||||
-- storage:SaveToFile(Path,Filename)
|
||||
--
|
||||
-- ### Autosave
|
||||
--
|
||||
-- storage:StartAutoSave(Path,Filename,300,true) -- save every 300 secs/5 mins starting in 5 mins, load the existing storage - if any - first if the last parameter is **not** `false`.
|
||||
--
|
||||
-- ### Stop Autosave
|
||||
--
|
||||
-- storage:StopAutoSave() -- stop the scheduler.
|
||||
--
|
||||
-- ### Load back with e.g.
|
||||
--
|
||||
-- -- Filenames searched for the Filename given amended by "_Liquids", "_Aircraft" and "_Weapons" followed by a ".csv". Only Storage NOT set to unlimited will be loaded.
|
||||
-- local Path = "C:\\Users\\UserName\\Saved Games\\DCS\\Missions\\"
|
||||
-- local Filename = "Batumi"
|
||||
-- storage:LoadFromFile(Path,Filename)
|
||||
--
|
||||
--
|
||||
-- @field #STORAGE
|
||||
STORAGE = {
|
||||
@@ -173,14 +203,14 @@ STORAGE.Type = {
|
||||
|
||||
--- STORAGE class version.
|
||||
-- @field #string version
|
||||
STORAGE.version="0.0.3"
|
||||
STORAGE.version="0.1.5"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- TODO list
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
-- TODO: A lot...
|
||||
-- TODO: Persistence
|
||||
-- DONE: Persistence
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Constructor
|
||||
@@ -197,11 +227,11 @@ function STORAGE:New(AirbaseName)
|
||||
|
||||
self.airbase=Airbase.getByName(AirbaseName)
|
||||
|
||||
if Airbase.getWarehouse then
|
||||
if Airbase.getWarehouse and self.airbase then
|
||||
self.warehouse=self.airbase:getWarehouse()
|
||||
end
|
||||
|
||||
self.lid = string.format("STORAGE %s", AirbaseName)
|
||||
self.lid = string.format("STORAGE %s | ", AirbaseName)
|
||||
|
||||
return self
|
||||
end
|
||||
@@ -221,7 +251,27 @@ function STORAGE:NewFromStaticCargo(StaticCargoName)
|
||||
self.warehouse=Warehouse.getCargoAsWarehouse(self.airbase)
|
||||
end
|
||||
|
||||
self.lid = string.format("STORAGE %s", StaticCargoName)
|
||||
self.lid = string.format("STORAGE %s | ", StaticCargoName)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Create a new STORAGE object from a Wrapper.DynamicCargo#DYNAMICCARGO object.
|
||||
-- @param #STORAGE self
|
||||
-- @param #string DynamicCargoName Unit name of the dynamic cargo.
|
||||
-- @return #STORAGE self
|
||||
function STORAGE:NewFromDynamicCargo(DynamicCargoName)
|
||||
|
||||
-- Inherit everything from BASE class.
|
||||
local self=BASE:Inherit(self, BASE:New()) -- #STORAGE
|
||||
|
||||
self.airbase=Unit.getByName(DynamicCargoName) or StaticObject.getByName(DynamicCargoName)
|
||||
|
||||
if Airbase.getWarehouse then
|
||||
self.warehouse=Warehouse.getCargoAsWarehouse(self.airbase)
|
||||
end
|
||||
|
||||
self.lid = string.format("STORAGE %s | ", DynamicCargoName)
|
||||
|
||||
return self
|
||||
end
|
||||
@@ -246,6 +296,10 @@ end
|
||||
-- @return #STORAGE self
|
||||
function STORAGE:SetVerbosity(VerbosityLevel)
|
||||
self.verbose=VerbosityLevel or 0
|
||||
if self.verbose > 1 then
|
||||
BASE:TraceOn()
|
||||
BASE:TraceClass("STORAGE")
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -479,7 +533,7 @@ function STORAGE:IsUnlimited(Type)
|
||||
end
|
||||
|
||||
-- Debug info.
|
||||
self:I(self.lid..string.format("Type=%s: unlimited=%s (N=%d n=%d)", tostring(Type), tostring(unlimited), N, n))
|
||||
self:T(self.lid..string.format("Type=%s: unlimited=%s (N=%d n=%d)", tostring(Type), tostring(unlimited), N, n))
|
||||
end
|
||||
|
||||
return unlimited
|
||||
@@ -575,6 +629,247 @@ function STORAGE:GetInventory(Item)
|
||||
return inventory.aircraft, inventory.liquids, inventory.weapon
|
||||
end
|
||||
|
||||
--- Save the contents of a STORAGE to files in CSV format. Filenames created are the Filename given amended by "_Liquids", "_Aircraft" and "_Weapons" followed by a ".csv". Requires io and lfs to be desanitized to be working.
|
||||
-- @param #STORAGE self
|
||||
-- @param #string Path The path to use. Use double backslashes \\\\ on Windows filesystems.
|
||||
-- @param #string Filename The base name of the files. Existing files will be overwritten.
|
||||
-- @return #STORAGE self
|
||||
function STORAGE:SaveToFile(Path,Filename)
|
||||
|
||||
if not io then
|
||||
BASE:E("ERROR: io not desanitized. Can't save the files.")
|
||||
return false
|
||||
end
|
||||
|
||||
-- Check default path.
|
||||
if Path==nil and not lfs then
|
||||
BASE:E("WARNING: lfs not desanitized. File will be saved in DCS installation root directory rather than your given path.")
|
||||
end
|
||||
|
||||
local ac, lq, wp = self:GetInventory()
|
||||
local DataAircraft = ""
|
||||
local DataLiquids = ""
|
||||
local DataWeapons = ""
|
||||
|
||||
if #lq > 0 then
|
||||
DataLiquids = DataLiquids .."Liquids in Storage:\n"
|
||||
for key,amount in pairs(lq) do
|
||||
DataLiquids = DataLiquids..tostring(key).."="..tostring(amount).."\n"
|
||||
end
|
||||
UTILS.SaveToFile(Path,Filename.."_Liquids.csv",DataLiquids)
|
||||
if self.verbose and self.verbose > 0 then
|
||||
self:I(self.lid.."Saving Liquids to "..tostring(Path).."\\"..tostring(Filename).."_Liquids.csv")
|
||||
end
|
||||
end
|
||||
|
||||
if UTILS.TableLength(ac) > 0 then
|
||||
DataAircraft = DataAircraft .."Aircraft in Storage:\n"
|
||||
for key,amount in pairs(ac) do
|
||||
DataAircraft = DataAircraft..tostring(key).."="..tostring(amount).."\n"
|
||||
end
|
||||
UTILS.SaveToFile(Path,Filename.."_Aircraft.csv",DataAircraft)
|
||||
if self.verbose and self.verbose > 0 then
|
||||
self:I(self.lid.."Saving Aircraft to "..tostring(Path).."\\"..tostring(Filename).."_Aircraft.csv")
|
||||
end
|
||||
end
|
||||
|
||||
if UTILS.TableLength(wp) > 0 then
|
||||
DataWeapons = DataWeapons .."Weapons and Materiel in Storage:\n"
|
||||
|
||||
for _,_category in pairs(ENUMS.Storage.weapons) do
|
||||
for _,_key in pairs(_category) do
|
||||
local amount = self:GetAmount(_key)
|
||||
if type(_key) == "table" then
|
||||
_key = "{"..table.concat(_key,",").."}"
|
||||
end
|
||||
DataWeapons = DataWeapons..tostring(_key).."="..tostring(amount).."\n"
|
||||
end
|
||||
end
|
||||
|
||||
-- Gazelle table keys
|
||||
for key,amount in pairs(ENUMS.Storage.weapons.Gazelle) do
|
||||
amount = self:GetItemAmount(ENUMS.Storage.weapons.Gazelle[key])
|
||||
DataWeapons = DataWeapons.."ENUMS.Storage.weapons.Gazelle."..tostring(key).."="..tostring(amount).."\n"
|
||||
end
|
||||
-- CH47
|
||||
for key,amount in pairs(ENUMS.Storage.weapons.CH47) do
|
||||
amount = self:GetItemAmount(ENUMS.Storage.weapons.CH47[key])
|
||||
DataWeapons = DataWeapons.."ENUMS.Storage.weapons.CH47."..tostring(key).."="..tostring(amount).."\n"
|
||||
end
|
||||
-- UH1H
|
||||
for key,amount in pairs(ENUMS.Storage.weapons.UH1H) do
|
||||
amount = self:GetItemAmount(ENUMS.Storage.weapons.UH1H[key])
|
||||
DataWeapons = DataWeapons.."ENUMS.Storage.weapons.UH1H."..tostring(key).."="..tostring(amount).."\n"
|
||||
end
|
||||
-- OH58D
|
||||
for key,amount in pairs(ENUMS.Storage.weapons.OH58) do
|
||||
amount = self:GetItemAmount(ENUMS.Storage.weapons.OH58[key])
|
||||
DataWeapons = DataWeapons.."ENUMS.Storage.weapons.OH58."..tostring(key).."="..tostring(amount).."\n"
|
||||
end
|
||||
-- AH64D
|
||||
for key,amount in pairs(ENUMS.Storage.weapons.AH64D) do
|
||||
amount = self:GetItemAmount(ENUMS.Storage.weapons.AH64D[key])
|
||||
DataWeapons = DataWeapons.."ENUMS.Storage.weapons.AH64D."..tostring(key).."="..tostring(amount).."\n"
|
||||
end
|
||||
UTILS.SaveToFile(Path,Filename.."_Weapons.csv",DataWeapons)
|
||||
if self.verbose and self.verbose > 0 then
|
||||
self:I(self.lid.."Saving Weapons to "..tostring(Path).."\\"..tostring(Filename).."_Weapons.csv")
|
||||
end
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Load the contents of a STORAGE from files. Filenames searched for are the Filename given amended by "_Liquids", "_Aircraft" and "_Weapons" followed by a ".csv". Requires io and lfs to be desanitized to be working.
|
||||
-- @param #STORAGE self
|
||||
-- @param #string Path The path to use. Use double backslashes \\\\ on Windows filesystems.
|
||||
-- @param #string Filename The name of the file.
|
||||
-- @return #STORAGE self
|
||||
function STORAGE:LoadFromFile(Path,Filename)
|
||||
|
||||
if not io then
|
||||
BASE:E("ERROR: io not desanitized. Can't read the files.")
|
||||
return false
|
||||
end
|
||||
|
||||
-- Check default path.
|
||||
if Path==nil and not lfs then
|
||||
BASE:E("WARNING: lfs not desanitized. File will be read from DCS installation root directory rather than your give path.")
|
||||
end
|
||||
|
||||
--Liquids
|
||||
if self:IsLimitedLiquids() then
|
||||
local Ok,Liquids = UTILS.LoadFromFile(Path,Filename.."_Liquids.csv")
|
||||
if Ok then
|
||||
if self.verbose and self.verbose > 0 then
|
||||
self:I(self.lid.."Loading Liquids from "..tostring(Path).."\\"..tostring(Filename).."_Liquids.csv")
|
||||
end
|
||||
for _id,_line in pairs(Liquids) do
|
||||
if string.find(_line,"Storage") == nil then
|
||||
local tbl=UTILS.Split(_line,"=")
|
||||
local lqno = tonumber(tbl[1])
|
||||
local lqam = tonumber(tbl[2])
|
||||
self:SetLiquid(lqno,lqam)
|
||||
end
|
||||
end
|
||||
else
|
||||
self:E("File for Liquids could not be found: "..tostring(Path).."\\"..tostring(Filename"_Liquids.csv"))
|
||||
end
|
||||
end
|
||||
|
||||
--Aircraft
|
||||
if self:IsLimitedAircraft() then
|
||||
local Ok,Aircraft = UTILS.LoadFromFile(Path,Filename.."_Aircraft.csv")
|
||||
if Ok then
|
||||
if self.verbose and self.verbose > 0 then
|
||||
self:I(self.lid.."Loading Aircraft from "..tostring(Path).."\\"..tostring(Filename).."_Aircraft.csv")
|
||||
end
|
||||
for _id,_line in pairs(Aircraft) do
|
||||
if string.find(_line,"Storage") == nil then
|
||||
local tbl=UTILS.Split(_line,"=")
|
||||
local acname = tbl[1]
|
||||
local acnumber = tonumber(tbl[2])
|
||||
self:SetAmount(acname,acnumber)
|
||||
end
|
||||
end
|
||||
else
|
||||
self:E("File for Aircraft could not be found: "..tostring(Path).."\\"..tostring(Filename"_Aircraft.csv"))
|
||||
end
|
||||
end
|
||||
|
||||
--Weapons
|
||||
if self:IsLimitedWeapons() then
|
||||
local Ok,Weapons = UTILS.LoadFromFile(Path,Filename.."_Weapons.csv")
|
||||
if Ok then
|
||||
if self.verbose and self.verbose > 0 then
|
||||
self:I(self.lid.."Loading Weapons from "..tostring(Path).."\\"..tostring(Filename).."_Weapons.csv")
|
||||
end
|
||||
for _id,_line in pairs(Weapons) do
|
||||
if string.find(_line,"Storage") == nil then
|
||||
local tbl=UTILS.Split(_line,"=")
|
||||
local wpname = tbl[1]
|
||||
local wpnumber = tonumber(tbl[2])
|
||||
if string.find(wpname,"{") == 1 then
|
||||
--self:I("Found a table: "..wpname)
|
||||
wpname = string.gsub(wpname,"{","")
|
||||
wpname = string.gsub(wpname,"}","")
|
||||
local tbl = UTILS.Split(wpname,",")
|
||||
local wptbl = {}
|
||||
for _id,_key in ipairs(tbl) do
|
||||
table.insert(wptbl,_id,_key)
|
||||
end
|
||||
self:SetAmount(wptbl,wpnumber)
|
||||
else
|
||||
self:SetAmount(wpname,wpnumber)
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
self:E("File for Weapons could not be found: "..tostring(Path).."\\"..tostring(Filename"_Weapons.csv"))
|
||||
end
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Start a STORAGE autosave process.
|
||||
-- @param #STORAGE self
|
||||
-- @param #string Path The path to use. Use double backslashes \\\\ on Windows filesystems.
|
||||
-- @param #string Filename The name of the file.
|
||||
-- @param #number Interval The interval, start after this many seconds and repeat every interval seconds. Defaults to 300.
|
||||
-- @param #boolean LoadOnce If LoadOnce is true or nil, we try to load saved storage first.
|
||||
-- @return #STORAGE self
|
||||
function STORAGE:StartAutoSave(Path,Filename,Interval,LoadOnce)
|
||||
if LoadOnce ~= false then
|
||||
self:LoadFromFile(Path,Filename)
|
||||
end
|
||||
local interval = Interval or 300
|
||||
self.SaverTimer = TIMER:New(STORAGE.SaveToFile,self,Path,Filename)
|
||||
self.SaverTimer:Start(interval,interval)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Stop a running STORAGE autosave process.
|
||||
-- @param #STORAGE self
|
||||
-- @return #STORAGE self
|
||||
function STORAGE:StopAutoSave()
|
||||
if self.SaverTimer and self.SaverTimer:IsRunning() then
|
||||
self.SaverTimer:Stop()
|
||||
self.SaverTimer = nil
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Try to find the #STORAGE object of one of the many "H"-Helipads in Syria. You need to put a (small, round) zone on top of it, because the name is not unique(!).
|
||||
-- @param #STORAGE self
|
||||
-- @param #string ZoneName The name of the zone where to find the helipad.
|
||||
-- @return #STORAGE self or nil if not found.
|
||||
function STORAGE:FindSyriaHHelipadWarehouse(ZoneName)
|
||||
local findzone = ZONE:New(ZoneName)
|
||||
local base = world.getAirbases()
|
||||
for i = 1, #base do
|
||||
local info = {}
|
||||
--info.desc = Airbase.getDesc(base[i])
|
||||
info.callsign = Airbase.getCallsign(base[i])
|
||||
info.id = Airbase.getID(base[i])
|
||||
--info.cat = Airbase.getCategory(base[i])
|
||||
info.point = Airbase.getPoint(base[i])
|
||||
info.coordinate = COORDINATE:NewFromVec3(info.point)
|
||||
info.DCSObject = base[i]
|
||||
--if Airbase.getUnit(base[i]) then
|
||||
--info.unitId = Airbase.getUnit(base[i]):getID()
|
||||
--end
|
||||
if info.callsign == "H" and findzone:IsCoordinateInZone(info.coordinate) then
|
||||
info.warehouse = info.DCSObject:getWarehouse()
|
||||
info.Storage = STORAGE:New(info.callsign..info.id)
|
||||
info.Storage.airbase = info.DCSObject
|
||||
info.Storage.warehouse = info.warehouse
|
||||
return info.Storage
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Private Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -385,24 +385,26 @@ function WEAPON:GetTarget()
|
||||
|
||||
--Target name
|
||||
local name=object:getName()
|
||||
|
||||
-- Debug info.
|
||||
self:T(self.lid..string.format("Got Target Object %s, category=%d", object:getName(), category))
|
||||
|
||||
if category==Object.Category.UNIT then
|
||||
|
||||
target=UNIT:FindByName(name)
|
||||
|
||||
elseif category==Object.Category.STATIC then
|
||||
|
||||
target=STATIC:FindByName(name, false)
|
||||
|
||||
elseif category==Object.Category.SCENERY then
|
||||
self:E(self.lid..string.format("ERROR: Scenery target not implemented yet!"))
|
||||
else
|
||||
self:E(self.lid..string.format("ERROR: Object category=%d is not implemented yet!", category))
|
||||
|
||||
if name then
|
||||
|
||||
-- Debug info.
|
||||
self:T(self.lid..string.format("Got Target Object %s, category=%d", name, category))
|
||||
|
||||
if category==Object.Category.UNIT then
|
||||
|
||||
target=UNIT:FindByName(name)
|
||||
|
||||
elseif category==Object.Category.STATIC then
|
||||
|
||||
target=STATIC:FindByName(name, false)
|
||||
|
||||
elseif category==Object.Category.SCENERY then
|
||||
self:E(self.lid..string.format("ERROR: Scenery target not implemented yet!"))
|
||||
else
|
||||
self:E(self.lid..string.format("ERROR: Object category=%d is not implemented yet!", category))
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ Utilities/Enums.lua
|
||||
Utilities/Utils.lua
|
||||
Utilities/Enums.lua
|
||||
Utilities/Profiler.lua
|
||||
Utilities/STTS.lua
|
||||
Utilities/Templates.lua
|
||||
Utilities/FiFo.lua
|
||||
Utilities/Socket.lua
|
||||
|
||||
@@ -47,6 +47,7 @@ Wrapper/Marker.lua
|
||||
Wrapper/Weapon.lua
|
||||
Wrapper/Net.lua
|
||||
Wrapper/Storage.lua
|
||||
Wrapper/DynamicCargo.lua
|
||||
|
||||
Functional/Scoring.lua
|
||||
Functional/CleanUp.lua
|
||||
|
||||
Reference in New Issue
Block a user